Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add reference library that supports React Server and uses React Compiler #74923

Merged
merged 1 commit into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions test/e2e/react-compiler/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
5 changes: 5 additions & 0 deletions test/e2e/react-compiler/app/library-client/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Container } from 'reference-library/client'

export default function Page() {
return <Container>Client</Container>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Container } from 'reference-library/missing-react-server'

export const dynamic = 'force-dynamic'

export default function Page() {
return <Container>Library missing react-server</Container>
}
5 changes: 5 additions & 0 deletions test/e2e/react-compiler/app/library-react-server/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Container } from 'reference-library'

export default function Page() {
return <Container>Library react-server</Container>
}
7 changes: 7 additions & 0 deletions test/e2e/react-compiler/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"private": true,
"name": "test-e2e-react-compiler",
"dependencies": {
"reference-library": "link:./reference-library"
}
}
69 changes: 67 additions & 2 deletions test/e2e/react-compiler/react-compiler.test.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,47 @@
import { nextTestSetup, FileRef } from 'e2e-utils'
import { retry } from 'next-test-utils'
import { assertHasRedbox, retry } from 'next-test-utils'
import { join } from 'path'
import stripAnsi from 'strip-ansi'

function normalizeCodeLocInfo(str) {
return (
str &&
str.replace(/^ +(?:at|in) ([\S]+)[^\n]*/gm, function (m, name) {
const dot = name.lastIndexOf('.')
if (dot !== -1) {
name = name.slice(dot + 1)
}
return ' at ' + name + (/\d/.test(m) ? ' (**)' : '')
})
)
}

describe.each(
['default', process.env.TURBOPACK ? undefined : 'babelrc'].filter(Boolean)
)('react-compiler %s', (variant) => {
const { next } = nextTestSetup({
const dependencies = (global as any).isNextDeploy
? // `link` is incompatible with the npm version used when this test is deployed
{
'reference-library': 'file:./reference-library',
}
: {
'reference-library': 'link:./reference-library',
}
const { next, isNextDev } = nextTestSetup({
files:
variant === 'babelrc'
? __dirname
: {
app: new FileRef(join(__dirname, 'app')),
'next.config.js': new FileRef(join(__dirname, 'next.config.js')),
'reference-library': new FileRef(
join(__dirname, 'reference-library')
),
},

dependencies: {
'babel-plugin-react-compiler': '0.0.0-experimental-4690415-20240515',
...dependencies,
},
})

Expand All @@ -37,4 +62,44 @@ describe.each(
expect(text).toMatch(/React compiler is enabled with \d+ memo slots/)
})
})

it('should work with a library that uses the react-server condition', async () => {
const outputIndex = next.cliOutput.length
await next.render('/library-react-server')

const cliOutput = stripAnsi(next.cliOutput.slice(outputIndex))
expect(cliOutput).not.toMatch(/error/)
})

it('should work with a library using use client', async () => {
const outputIndex = next.cliOutput.length
await next.render('/library-client')

const cliOutput = stripAnsi(next.cliOutput.slice(outputIndex))
expect(cliOutput).not.toMatch(/error/)
})

it('throws if the React Compiler is used in a React Server environment', async () => {
const outputIndex = next.cliOutput.length
const browser = await next.browser('/library-missing-react-server')

const cliOutput = normalizeCodeLocInfo(
stripAnsi(next.cliOutput.slice(outputIndex))
)
if (isNextDev) {
// TODO(NDX-663): Unhelpful error message.
// Should say that the library should have a react-server entrypoint that doesn't use the React Compiler.
expect(cliOutput).toContain(
'' +
"\n ⨯ TypeError: Cannot read properties of undefined (reading 'H')" +
// location not important. Just that this is the only frame.
// TODO: Stack should start at product code. Possible React limitation.
'\n at Container (**)' +
// Will just point to original file location
'\n 2 |'
)

await assertHasRedbox(browser)
}
})
})
8 changes: 8 additions & 0 deletions test/e2e/react-compiler/reference-library/compiled/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
These are manually compiled versions from `src/index`.js with [React Compiler Playground](https://playground.react.dev/) and [Babel.js Repl](https://babeljs.io/repl) (only to compile JSX runtime).

| module | import condition | resolved | React Compiler | Server-Client boundary | Valid environment |
|------------------------------------------|------------------|--------------------------------------|----------------|------------------------|-------------------|
| `reference-library/client` | any | `./compiled/client.js` | Yes | Yes | client-only |
| `reference-library` | `react-server` | `./compiled/index.react-server.js` | No | No | any |
| `reference-library` | default | `./compiled/index.js` | Yes | No | client-only |
| `reference-library/missing-react-server` | any | `./compiled/missing-react-server.js` | Yes | No | client-only |
Copy link
Member Author

@eps1lon eps1lon Jan 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the part most libraries make a mistake because we don't have docs/good error message (error message will be improved with facebook/react#32056). You'd just think you can throw the React Compiler at your library and call it a day. But once this library is supposed to run in a React Server, React will throw because there's nothing to memoize on the React Server.

It's important to understand that the React Compiler currently only targets the browser. It doesn't necessarily produce output optimized for SSR or RSCs.

If you want your library to support a React Server environment, you need to add a separate react-server entrypoint. If you just want to integrate with frameworks using a React Server environment, add a use client at the top of the entrypoint.
If you don't want that your library is used in a React Server environment, add the client-only package as a dependency and add import 'client-only' at the top of your entrypoint.

19 changes: 19 additions & 0 deletions test/e2e/react-compiler/reference-library/compiled/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use client'

import { c as _c } from 'react/compiler-runtime'
import { jsx as _jsx } from 'react/jsx-runtime'
export function Container(t0) {
const $ = _c(2)
const { children } = t0
let t1
if ($[0] !== children) {
t1 = /*#__PURE__*/ _jsx('p', {
children: children,
})
$[0] = children
$[1] = t1
} else {
t1 = $[1]
}
return t1
}
17 changes: 17 additions & 0 deletions test/e2e/react-compiler/reference-library/compiled/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { c as _c } from 'react/compiler-runtime'
import { jsx as _jsx } from 'react/jsx-runtime'
export function Container(t0) {
const $ = _c(2)
const { children } = t0
let t1
if ($[0] !== children) {
t1 = /*#__PURE__*/ _jsx('button', {
children: children,
})
$[0] = children
$[1] = t1
} else {
t1 = $[1]
}
return t1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { jsx as _jsx } from 'react/jsx-runtime'
export function Container({ children }) {
return /*#__PURE__*/ _jsx('p', {
children: children,
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { c as _c } from 'react/compiler-runtime'
import { jsx as _jsx } from 'react/jsx-runtime'
export function Container(t0) {
const $ = _c(2)
const { children } = t0
let t1
if ($[0] !== children) {
t1 = /*#__PURE__*/ _jsx('button', {
children: children,
})
$[0] = children
$[1] = t1
} else {
t1 = $[1]
}
return t1
}
23 changes: 23 additions & 0 deletions test/e2e/react-compiler/reference-library/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
declare module 'reference-library' {
import type * as React from 'react'

export function Container(props: {
children?: React.ReactNode
}): React.ReactNode
}

declare module 'reference-library/client' {
import type * as React from 'react'

export function Container(props: {
children?: React.ReactNode
}): React.ReactNode
}

declare module 'reference-library/missing-react-server' {
import type * as React from 'react'

export function Container(props: {
children?: React.ReactNode
}): React.ReactNode
}
20 changes: 20 additions & 0 deletions test/e2e/react-compiler/reference-library/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "reference-library",
"version": "1.0.0",
"types": "./index.d.ts",
"exports": {
"./client": {
"types": "./index.d.ts",
"default": "./compiled/client.js"
},
"./missing-react-server": {
"types": "./index.d.ts",
"default": "./compiled/missing-react-server.js"
},
".": {
"types": "./index.d.ts",
"react-server": "./compiled/index.react-server.js",
"default": "./compiled/index.js"
}
}
}
3 changes: 3 additions & 0 deletions test/e2e/react-compiler/reference-library/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function Container({ children }) {
return <p children={children} />
}
Loading