From 9011a554d74cca8114303f9597a8e3e6705d2aee Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Wed, 15 Jan 2025 13:21:42 +0100 Subject: [PATCH] Add reference library that supports React Server and uses React Compiler --- test/e2e/react-compiler/.gitignore | 1 + .../app/library-client/page.tsx | 5 ++ .../app/library-missing-react-server/page.tsx | 7 ++ .../app/library-react-server/page.tsx | 5 ++ test/e2e/react-compiler/package.json | 7 ++ .../e2e/react-compiler/react-compiler.test.ts | 69 ++++++++++++++++++- .../reference-library/compiled/README.md | 8 +++ .../reference-library/compiled/client.js | 19 +++++ .../reference-library/compiled/index.js | 17 +++++ .../compiled/index.react-server.js | 6 ++ .../compiled/missing-react-server.js | 17 +++++ .../reference-library/index.d.ts | 23 +++++++ .../reference-library/package.json | 20 ++++++ .../reference-library/src/index.js | 3 + 14 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 test/e2e/react-compiler/.gitignore create mode 100644 test/e2e/react-compiler/app/library-client/page.tsx create mode 100644 test/e2e/react-compiler/app/library-missing-react-server/page.tsx create mode 100644 test/e2e/react-compiler/app/library-react-server/page.tsx create mode 100644 test/e2e/react-compiler/package.json create mode 100644 test/e2e/react-compiler/reference-library/compiled/README.md create mode 100644 test/e2e/react-compiler/reference-library/compiled/client.js create mode 100644 test/e2e/react-compiler/reference-library/compiled/index.js create mode 100644 test/e2e/react-compiler/reference-library/compiled/index.react-server.js create mode 100644 test/e2e/react-compiler/reference-library/compiled/missing-react-server.js create mode 100644 test/e2e/react-compiler/reference-library/index.d.ts create mode 100644 test/e2e/react-compiler/reference-library/package.json create mode 100644 test/e2e/react-compiler/reference-library/src/index.js diff --git a/test/e2e/react-compiler/.gitignore b/test/e2e/react-compiler/.gitignore new file mode 100644 index 0000000000000..40b878db5b1c9 --- /dev/null +++ b/test/e2e/react-compiler/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/test/e2e/react-compiler/app/library-client/page.tsx b/test/e2e/react-compiler/app/library-client/page.tsx new file mode 100644 index 0000000000000..fcc770dc89019 --- /dev/null +++ b/test/e2e/react-compiler/app/library-client/page.tsx @@ -0,0 +1,5 @@ +import { Container } from 'reference-library/client' + +export default function Page() { + return Client +} diff --git a/test/e2e/react-compiler/app/library-missing-react-server/page.tsx b/test/e2e/react-compiler/app/library-missing-react-server/page.tsx new file mode 100644 index 0000000000000..2f593216be963 --- /dev/null +++ b/test/e2e/react-compiler/app/library-missing-react-server/page.tsx @@ -0,0 +1,7 @@ +import { Container } from 'reference-library/missing-react-server' + +export const dynamic = 'force-dynamic' + +export default function Page() { + return Library missing react-server +} diff --git a/test/e2e/react-compiler/app/library-react-server/page.tsx b/test/e2e/react-compiler/app/library-react-server/page.tsx new file mode 100644 index 0000000000000..648cc7e63aae9 --- /dev/null +++ b/test/e2e/react-compiler/app/library-react-server/page.tsx @@ -0,0 +1,5 @@ +import { Container } from 'reference-library' + +export default function Page() { + return Library react-server +} diff --git a/test/e2e/react-compiler/package.json b/test/e2e/react-compiler/package.json new file mode 100644 index 0000000000000..6d3c8fd7c2b2e --- /dev/null +++ b/test/e2e/react-compiler/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "name": "test-e2e-react-compiler", + "dependencies": { + "reference-library": "link:./reference-library" + } +} diff --git a/test/e2e/react-compiler/react-compiler.test.ts b/test/e2e/react-compiler/react-compiler.test.ts index 55962aebd63be..0d674a27641aa 100644 --- a/test/e2e/react-compiler/react-compiler.test.ts +++ b/test/e2e/react-compiler/react-compiler.test.ts @@ -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, }, }) @@ -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) + } + }) }) diff --git a/test/e2e/react-compiler/reference-library/compiled/README.md b/test/e2e/react-compiler/reference-library/compiled/README.md new file mode 100644 index 0000000000000..9364f7b44c6ed --- /dev/null +++ b/test/e2e/react-compiler/reference-library/compiled/README.md @@ -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 | diff --git a/test/e2e/react-compiler/reference-library/compiled/client.js b/test/e2e/react-compiler/reference-library/compiled/client.js new file mode 100644 index 0000000000000..e6005e1dae797 --- /dev/null +++ b/test/e2e/react-compiler/reference-library/compiled/client.js @@ -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 +} diff --git a/test/e2e/react-compiler/reference-library/compiled/index.js b/test/e2e/react-compiler/reference-library/compiled/index.js new file mode 100644 index 0000000000000..c71a80a8f20ab --- /dev/null +++ b/test/e2e/react-compiler/reference-library/compiled/index.js @@ -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 +} diff --git a/test/e2e/react-compiler/reference-library/compiled/index.react-server.js b/test/e2e/react-compiler/reference-library/compiled/index.react-server.js new file mode 100644 index 0000000000000..1c879eb9822a4 --- /dev/null +++ b/test/e2e/react-compiler/reference-library/compiled/index.react-server.js @@ -0,0 +1,6 @@ +import { jsx as _jsx } from 'react/jsx-runtime' +export function Container({ children }) { + return /*#__PURE__*/ _jsx('p', { + children: children, + }) +} diff --git a/test/e2e/react-compiler/reference-library/compiled/missing-react-server.js b/test/e2e/react-compiler/reference-library/compiled/missing-react-server.js new file mode 100644 index 0000000000000..c71a80a8f20ab --- /dev/null +++ b/test/e2e/react-compiler/reference-library/compiled/missing-react-server.js @@ -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 +} diff --git a/test/e2e/react-compiler/reference-library/index.d.ts b/test/e2e/react-compiler/reference-library/index.d.ts new file mode 100644 index 0000000000000..1671d93fd1e6e --- /dev/null +++ b/test/e2e/react-compiler/reference-library/index.d.ts @@ -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 +} diff --git a/test/e2e/react-compiler/reference-library/package.json b/test/e2e/react-compiler/reference-library/package.json new file mode 100644 index 0000000000000..d79a6aadfb746 --- /dev/null +++ b/test/e2e/react-compiler/reference-library/package.json @@ -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" + } + } +} diff --git a/test/e2e/react-compiler/reference-library/src/index.js b/test/e2e/react-compiler/reference-library/src/index.js new file mode 100644 index 0000000000000..6645ddf48aac1 --- /dev/null +++ b/test/e2e/react-compiler/reference-library/src/index.js @@ -0,0 +1,3 @@ +export function Container({ children }) { + return

+}