From 8d6ea827ca588359dd166bb1ba91059174b1667f Mon Sep 17 00:00:00 2001 From: eps1lon Date: Thu, 13 Jun 2024 15:57:41 +0200 Subject: [PATCH] Current behavior of import conditions --- .../import-conditions/app/app/ClientPage.tsx | 41 ++++ .../app/app/edge-page/page.tsx | 28 +++ .../app/app/node-page/page.tsx | 28 +++ .../app/edge-route/route.tsx | 13 + test/e2e/import-conditions/app/layout.tsx | 7 + .../app/node-route/route.tsx | 13 + .../import-conditions.test.ts | 228 ++++++++++++++++++ .../library-with-exports/browser.js | 1 + .../library-with-exports/default.js | 1 + .../library-with-exports/edge-light.js | 1 + .../library-with-exports/index.d.ts | 11 + .../library-with-exports/netlify.js | 1 + .../library-with-exports/never.js | 3 + .../library-with-exports/node.js | 1 + .../library-with-exports/package.json | 30 +++ .../library-with-exports/react-server.js | 1 + .../library-with-exports/worker.js | 1 + test/e2e/import-conditions/middleware.ts | 20 ++ test/e2e/import-conditions/package.json | 7 + .../pages/pages/edge-page.tsx | 43 ++++ .../pages/pages/node-page.tsx | 43 ++++ 21 files changed, 522 insertions(+) create mode 100644 test/e2e/import-conditions/app/app/ClientPage.tsx create mode 100644 test/e2e/import-conditions/app/app/edge-page/page.tsx create mode 100644 test/e2e/import-conditions/app/app/node-page/page.tsx create mode 100644 test/e2e/import-conditions/app/edge-route/route.tsx create mode 100644 test/e2e/import-conditions/app/layout.tsx create mode 100644 test/e2e/import-conditions/app/node-route/route.tsx create mode 100644 test/e2e/import-conditions/import-conditions.test.ts create mode 100644 test/e2e/import-conditions/library-with-exports/browser.js create mode 100644 test/e2e/import-conditions/library-with-exports/default.js create mode 100644 test/e2e/import-conditions/library-with-exports/edge-light.js create mode 100644 test/e2e/import-conditions/library-with-exports/index.d.ts create mode 100644 test/e2e/import-conditions/library-with-exports/netlify.js create mode 100644 test/e2e/import-conditions/library-with-exports/never.js create mode 100644 test/e2e/import-conditions/library-with-exports/node.js create mode 100644 test/e2e/import-conditions/library-with-exports/package.json create mode 100644 test/e2e/import-conditions/library-with-exports/react-server.js create mode 100644 test/e2e/import-conditions/library-with-exports/worker.js create mode 100644 test/e2e/import-conditions/middleware.ts create mode 100644 test/e2e/import-conditions/package.json create mode 100644 test/e2e/import-conditions/pages/pages/edge-page.tsx create mode 100644 test/e2e/import-conditions/pages/pages/node-page.tsx diff --git a/test/e2e/import-conditions/app/app/ClientPage.tsx b/test/e2e/import-conditions/app/app/ClientPage.tsx new file mode 100644 index 0000000000000..71b13a3d15ac5 --- /dev/null +++ b/test/e2e/import-conditions/app/app/ClientPage.tsx @@ -0,0 +1,41 @@ +'use client' +import * as React from 'react' +import * as react from 'library-with-exports/react' +import * as serverFavoringBrowser from 'library-with-exports/server-favoring-browser' +import * as serverFavoringEdge from 'library-with-exports/server-favoring-edge' + +export default function ClientPage({ + action, + server, +}: { + action: () => Promise> + server: unknown +}) { + const [actionValue, formAction, isPending] = React.useActionState( + action, + undefined + ) + const [client, setClient] = React.useState(null) + React.useEffect(() => { + setClient({ + react: react.condition, + serverFavoringBrowser: serverFavoringBrowser.condition, + serverFavoringEdge: serverFavoringEdge.condition, + }) + }, []) + + return ( +
+ + + {client === null ? ( +
{JSON.stringify({ server }, null, 2)}
+ ) : ( +
+            {JSON.stringify({ server, client, action: actionValue }, null, 2)}
+          
+ )} +
+
+ ) +} diff --git a/test/e2e/import-conditions/app/app/edge-page/page.tsx b/test/e2e/import-conditions/app/app/edge-page/page.tsx new file mode 100644 index 0000000000000..2b7d038669097 --- /dev/null +++ b/test/e2e/import-conditions/app/app/edge-page/page.tsx @@ -0,0 +1,28 @@ +import * as react from 'library-with-exports/react' +import * as serverFavoringBrowser from 'library-with-exports/server-favoring-browser' +import * as serverFavoringEdge from 'library-with-exports/server-favoring-edge' +import ClientPage from '../ClientPage' + +export const runtime = 'edge' + +async function action() { + 'use server' + return { + react: react.condition, + serverFavoringBrowser: serverFavoringBrowser.condition, + serverFavoringEdge: serverFavoringEdge.condition, + } +} + +export default function Page() { + return ( + + ) +} diff --git a/test/e2e/import-conditions/app/app/node-page/page.tsx b/test/e2e/import-conditions/app/app/node-page/page.tsx new file mode 100644 index 0000000000000..05f283680c7e6 --- /dev/null +++ b/test/e2e/import-conditions/app/app/node-page/page.tsx @@ -0,0 +1,28 @@ +import * as react from 'library-with-exports/react' +import * as serverFavoringBrowser from 'library-with-exports/server-favoring-browser' +import * as serverFavoringEdge from 'library-with-exports/server-favoring-edge' +import ClientPage from '../ClientPage' + +export const runtime = 'nodejs' + +async function action() { + 'use server' + return { + react: react.condition, + serverFavoringBrowser: serverFavoringBrowser.condition, + serverFavoringEdge: serverFavoringEdge.condition, + } +} + +export default function Page() { + return ( + + ) +} diff --git a/test/e2e/import-conditions/app/edge-route/route.tsx b/test/e2e/import-conditions/app/edge-route/route.tsx new file mode 100644 index 0000000000000..c0b03ebaae272 --- /dev/null +++ b/test/e2e/import-conditions/app/edge-route/route.tsx @@ -0,0 +1,13 @@ +import * as react from 'library-with-exports/react' +import * as serverFavoringBrowser from 'library-with-exports/server-favoring-browser' +import * as serverFavoringEdge from 'library-with-exports/server-favoring-edge' + +export const runtime = 'edge' + +export function GET() { + return Response.json({ + react: react.condition, + serverFavoringBrowser: serverFavoringBrowser.condition, + serverFavoringEdge: serverFavoringEdge.condition, + }) +} diff --git a/test/e2e/import-conditions/app/layout.tsx b/test/e2e/import-conditions/app/layout.tsx new file mode 100644 index 0000000000000..e7077399c03ce --- /dev/null +++ b/test/e2e/import-conditions/app/layout.tsx @@ -0,0 +1,7 @@ +export default function Root({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ) +} diff --git a/test/e2e/import-conditions/app/node-route/route.tsx b/test/e2e/import-conditions/app/node-route/route.tsx new file mode 100644 index 0000000000000..2961d5b282603 --- /dev/null +++ b/test/e2e/import-conditions/app/node-route/route.tsx @@ -0,0 +1,13 @@ +import * as react from 'library-with-exports/react' +import * as serverFavoringBrowser from 'library-with-exports/server-favoring-browser' +import * as serverFavoringEdge from 'library-with-exports/server-favoring-edge' + +export const runtime = 'nodejs' + +export function GET() { + return Response.json({ + react: react.condition, + serverFavoringBrowser: serverFavoringBrowser.condition, + serverFavoringEdge: serverFavoringEdge.condition, + }) +} diff --git a/test/e2e/import-conditions/import-conditions.test.ts b/test/e2e/import-conditions/import-conditions.test.ts new file mode 100644 index 0000000000000..a6dc66f27c7c3 --- /dev/null +++ b/test/e2e/import-conditions/import-conditions.test.ts @@ -0,0 +1,228 @@ +import { nextTestSetup } from 'e2e-utils' + +describe('react version', () => { + const { next } = nextTestSetup({ + files: __dirname, + dependencies: { + 'library-with-exports': 'link:./library-with-exports', + }, + }) + + it('Pages Router page headers with edge runtime', async () => { + const response = await next.fetch('/pages/edge-page') + + const middlewareHeaders = { + react: response.headers.get('x-react-condition'), + serverFavoringBrowser: response.headers.get( + 'x-server-favoring-browser-condition' + ), + serverFavoringEdge: response.headers.get( + 'x-server-favoring-edge-condition' + ), + } + expect(middlewareHeaders).toEqual({ + react: 'react-server', + serverFavoringBrowser: 'worker', + serverFavoringEdge: 'worker', + }) + }) + + it('Pages Router page with edge runtime', async () => { + const browser = await next.browser('/pages/edge-page') + + const json = await browser.elementByCss('output').text() + expect(JSON.parse(json)).toEqual({ + server: { + react: 'default', + serverFavoringBrowser: 'worker', + serverFavoringEdge: 'worker', + }, + client: { + react: 'default', + serverFavoringBrowser: 'browser', + serverFavoringEdge: 'browser', + }, + }) + }) + + it('Pages Router page headers with nodejs runtime', async () => { + const response = await next.fetch('/pages/node-page') + + const middlewareHeaders = { + react: response.headers.get('x-react-condition'), + serverFavoringBrowser: response.headers.get( + 'x-server-favoring-browser-condition' + ), + serverFavoringEdge: response.headers.get( + 'x-server-favoring-edge-condition' + ), + } + expect(middlewareHeaders).toEqual({ + react: 'react-server', + serverFavoringBrowser: 'worker', + serverFavoringEdge: 'worker', + }) + }) + + it('Pages Router page with nodejs runtime after hydration', async () => { + const browser = await next.browser('/pages/node-page') + + const json = await browser.elementByCss('output').text() + expect(JSON.parse(json)).toEqual({ + server: { + react: 'default', + serverFavoringBrowser: 'node', + serverFavoringEdge: 'node', + }, + client: { + react: 'default', + serverFavoringBrowser: 'browser', + serverFavoringEdge: 'browser', + }, + }) + }) + + it('App Router page headers with edge runtime', async () => { + const response = await next.fetch('/app/edge-page') + + const middlewareHeaders = { + react: response.headers.get('x-react-condition'), + serverFavoringBrowser: response.headers.get( + 'x-server-favoring-browser-condition' + ), + serverFavoringEdge: response.headers.get( + 'x-server-favoring-edge-condition' + ), + } + expect(middlewareHeaders).toEqual({ + react: 'react-server', + serverFavoringBrowser: 'worker', + serverFavoringEdge: 'worker', + }) + }) + + it('App Router page with edge runtime', async () => { + const browser = await next.browser('/app/edge-page') + + await browser.waitForElementByCss('input[type="submit"]').click() + await browser.waitForElementByCss('output[aria-busy="false"]') + + const json = await browser.elementByCss('output').text() + expect(JSON.parse(json)).toEqual({ + server: { + react: 'react-server', + serverFavoringBrowser: 'worker', + serverFavoringEdge: 'worker', + }, + client: { + react: 'default', + serverFavoringBrowser: 'browser', + serverFavoringEdge: 'browser', + }, + action: { + react: 'react-server', + serverFavoringBrowser: 'worker', + serverFavoringEdge: 'worker', + }, + }) + }) + + it('App Router page headers with nodejs runtime', async () => { + const response = await next.fetch('/app/node-page') + + const middlewareHeaders = { + react: response.headers.get('x-react-condition'), + serverFavoringBrowser: response.headers.get( + 'x-server-favoring-browser-condition' + ), + serverFavoringEdge: response.headers.get( + 'x-server-favoring-edge-condition' + ), + } + expect(middlewareHeaders).toEqual({ + react: 'react-server', + serverFavoringBrowser: 'worker', + serverFavoringEdge: 'worker', + }) + }) + + it('App Router page with nodejs runtime', async () => { + const browser = await next.browser('/app/node-page') + + await browser.waitForElementByCss('input[type="submit"]').click() + await browser.waitForElementByCss('output[aria-busy="false"]') + + const json = await browser.elementByCss('output').text() + expect(JSON.parse(json)).toEqual({ + server: { + react: 'react-server', + serverFavoringBrowser: 'node', + serverFavoringEdge: 'node', + }, + client: { + react: 'default', + serverFavoringBrowser: 'browser', + serverFavoringEdge: 'browser', + }, + action: { + react: 'react-server', + serverFavoringBrowser: 'node', + serverFavoringEdge: 'node', + }, + }) + }) + + it('App Router Route Handler with nodejs runtime', async () => { + const response = await next.fetch('/node-route') + + const middlewareHeaders = { + react: response.headers.get('x-react-condition'), + serverFavoringBrowser: response.headers.get( + 'x-server-favoring-browser-condition' + ), + serverFavoringEdge: response.headers.get( + 'x-server-favoring-edge-condition' + ), + } + const data = await response.json() + expect({ middlewareHeaders, data }).toEqual({ + middlewareHeaders: { + react: 'react-server', + serverFavoringBrowser: 'worker', + serverFavoringEdge: 'worker', + }, + data: { + react: 'react-server', + serverFavoringBrowser: 'node', + serverFavoringEdge: 'node', + }, + }) + }) + + it('App Router Route Handler with edge runtime', async () => { + const response = await next.fetch('/edge-route') + + const middlewareHeaders = { + react: response.headers.get('x-react-condition'), + serverFavoringBrowser: response.headers.get( + 'x-server-favoring-browser-condition' + ), + serverFavoringEdge: response.headers.get( + 'x-server-favoring-edge-condition' + ), + } + const data = await response.json() + expect({ middlewareHeaders, data }).toEqual({ + middlewareHeaders: { + react: 'react-server', + serverFavoringBrowser: 'worker', + serverFavoringEdge: 'worker', + }, + data: { + react: 'react-server', + serverFavoringBrowser: 'worker', + serverFavoringEdge: 'worker', + }, + }) + }) +}) diff --git a/test/e2e/import-conditions/library-with-exports/browser.js b/test/e2e/import-conditions/library-with-exports/browser.js new file mode 100644 index 0000000000000..917d9dcf37f4c --- /dev/null +++ b/test/e2e/import-conditions/library-with-exports/browser.js @@ -0,0 +1 @@ +export const condition = 'browser' diff --git a/test/e2e/import-conditions/library-with-exports/default.js b/test/e2e/import-conditions/library-with-exports/default.js new file mode 100644 index 0000000000000..bf17163684620 --- /dev/null +++ b/test/e2e/import-conditions/library-with-exports/default.js @@ -0,0 +1 @@ +export const condition = 'default' diff --git a/test/e2e/import-conditions/library-with-exports/edge-light.js b/test/e2e/import-conditions/library-with-exports/edge-light.js new file mode 100644 index 0000000000000..c24057047c4e0 --- /dev/null +++ b/test/e2e/import-conditions/library-with-exports/edge-light.js @@ -0,0 +1 @@ +export const condition = 'edge-light' diff --git a/test/e2e/import-conditions/library-with-exports/index.d.ts b/test/e2e/import-conditions/library-with-exports/index.d.ts new file mode 100644 index 0000000000000..f8b581075c11f --- /dev/null +++ b/test/e2e/import-conditions/library-with-exports/index.d.ts @@ -0,0 +1,11 @@ +declare module 'library-with-exports/react' { + export const condition: string +} + +declare module 'library-with-exports/server-favoring-edge' { + export const condition: string +} + +declare module 'library-with-exports/server-favoring-browser' { + export const condition: string +} diff --git a/test/e2e/import-conditions/library-with-exports/netlify.js b/test/e2e/import-conditions/library-with-exports/netlify.js new file mode 100644 index 0000000000000..f6f8ad1abceab --- /dev/null +++ b/test/e2e/import-conditions/library-with-exports/netlify.js @@ -0,0 +1 @@ +export const condition = 'netlify' diff --git a/test/e2e/import-conditions/library-with-exports/never.js b/test/e2e/import-conditions/library-with-exports/never.js new file mode 100644 index 0000000000000..f1bcb801b7eef --- /dev/null +++ b/test/e2e/import-conditions/library-with-exports/never.js @@ -0,0 +1,3 @@ +throw new Error( + 'Imported entrypoint that should never be used. This is a bug in Next.js' +) diff --git a/test/e2e/import-conditions/library-with-exports/node.js b/test/e2e/import-conditions/library-with-exports/node.js new file mode 100644 index 0000000000000..ae85ba8c99d83 --- /dev/null +++ b/test/e2e/import-conditions/library-with-exports/node.js @@ -0,0 +1 @@ +export const condition = 'node' diff --git a/test/e2e/import-conditions/library-with-exports/package.json b/test/e2e/import-conditions/library-with-exports/package.json new file mode 100644 index 0000000000000..4c26e85621efe --- /dev/null +++ b/test/e2e/import-conditions/library-with-exports/package.json @@ -0,0 +1,30 @@ +{ + "name": "library-with-exports", + "version": "1.0.0", + "types": "./index.d.ts", + "exports": { + "./server-favoring-edge": { + "types": "./index.d.ts", + "worker": "./worker.js", + "workerd": "./workerd.js", + "edge-light": "./edge-light.js", + "node": "./node.js", + "browser": "./browser.js", + "default": "./default.js" + }, + "./server-favoring-browser": { + "types": "./index.d.ts", + "worker": "./worker.js", + "workerd": "./workerd.js", + "browser": "./browser.js", + "node": "./node.js", + "edge-light": "./edge-light.js", + "default": "./default.js" + }, + "./react": { + "types": "./index.d.ts", + "react-server": "./react-server.js", + "default": "./default.js" + } + } +} diff --git a/test/e2e/import-conditions/library-with-exports/react-server.js b/test/e2e/import-conditions/library-with-exports/react-server.js new file mode 100644 index 0000000000000..42477c730114b --- /dev/null +++ b/test/e2e/import-conditions/library-with-exports/react-server.js @@ -0,0 +1 @@ +export const condition = 'react-server' diff --git a/test/e2e/import-conditions/library-with-exports/worker.js b/test/e2e/import-conditions/library-with-exports/worker.js new file mode 100644 index 0000000000000..4128b88e675ec --- /dev/null +++ b/test/e2e/import-conditions/library-with-exports/worker.js @@ -0,0 +1 @@ +export const condition = 'worker' diff --git a/test/e2e/import-conditions/middleware.ts b/test/e2e/import-conditions/middleware.ts new file mode 100644 index 0000000000000..817e591df9cb7 --- /dev/null +++ b/test/e2e/import-conditions/middleware.ts @@ -0,0 +1,20 @@ +import { NextResponse } from 'next/server' +import * as react from 'library-with-exports/react' +import * as serverFavoringBrowser from 'library-with-exports/server-favoring-browser' +import * as serverFavoringEdge from 'library-with-exports/server-favoring-edge' + +export function middleware() { + const response = NextResponse.next() + + response.headers.set('x-react-condition', react.condition) + response.headers.set( + 'x-server-favoring-browser-condition', + serverFavoringBrowser.condition + ) + response.headers.set( + 'x-server-favoring-edge-condition', + serverFavoringEdge.condition + ) + + return response +} diff --git a/test/e2e/import-conditions/package.json b/test/e2e/import-conditions/package.json new file mode 100644 index 0000000000000..fdd6b93b878bc --- /dev/null +++ b/test/e2e/import-conditions/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "name": "import-conditions", + "dependencies": { + "library-with-exports": "link:./library-with-exports" + } +} diff --git a/test/e2e/import-conditions/pages/pages/edge-page.tsx b/test/e2e/import-conditions/pages/pages/edge-page.tsx new file mode 100644 index 0000000000000..2e1451af55901 --- /dev/null +++ b/test/e2e/import-conditions/pages/pages/edge-page.tsx @@ -0,0 +1,43 @@ +import * as React from 'react' +import * as react from 'library-with-exports/react' +import * as serverFavoringBrowser from 'library-with-exports/server-favoring-browser' +import * as serverFavoringEdge from 'library-with-exports/server-favoring-edge' + +export const config = { + runtime: 'experimental-edge', +} + +let server = { + react: react.condition, + serverFavoringBrowser: serverFavoringBrowser.condition, + serverFavoringEdge: serverFavoringEdge.condition, +} +if (typeof window !== 'undefined') { + server = JSON.parse( + document.querySelector('[data-testid="server"]')!.textContent! + ) +} + +export default function Page() { + const [client, setClient] = React.useState(null) + + React.useLayoutEffect(() => { + setClient({ + react: react.condition, + serverFavoringBrowser: serverFavoringBrowser.condition, + serverFavoringEdge: serverFavoringEdge.condition, + }) + }, []) + + return ( + + {client === null ? ( +
{JSON.stringify(server, null, 2)}
+ ) : ( +
+          {JSON.stringify({ server, client }, null, 2)}
+        
+ )} +
+ ) +} diff --git a/test/e2e/import-conditions/pages/pages/node-page.tsx b/test/e2e/import-conditions/pages/pages/node-page.tsx new file mode 100644 index 0000000000000..e326d47fe0e8b --- /dev/null +++ b/test/e2e/import-conditions/pages/pages/node-page.tsx @@ -0,0 +1,43 @@ +import * as React from 'react' +import * as react from 'library-with-exports/react' +import * as serverFavoringBrowser from 'library-with-exports/server-favoring-browser' +import * as serverFavoringEdge from 'library-with-exports/server-favoring-edge' + +export const config = { + runtime: 'nodejs', +} + +let server = { + react: react.condition, + serverFavoringBrowser: serverFavoringBrowser.condition, + serverFavoringEdge: serverFavoringEdge.condition, +} +if (typeof window !== 'undefined') { + server = JSON.parse( + document.querySelector('[data-testid="server"]')!.textContent! + ) +} + +export default function Page() { + const [client, setClient] = React.useState(null) + + React.useLayoutEffect(() => { + setClient({ + react: react.condition, + serverFavoringBrowser: serverFavoringBrowser.condition, + serverFavoringEdge: serverFavoringEdge.condition, + }) + }, []) + + return ( + + {client === null ? ( +
{JSON.stringify(server, null, 2)}
+ ) : ( +
+          {JSON.stringify({ server, client }, null, 2)}
+        
+ )} +
+ ) +}