From 465b62841a004748348aaea77badd40344723ad0 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Mon, 6 May 2024 22:11:57 +0200 Subject: [PATCH 01/13] Apply server-only and react-server conditions to middleware and pages api --- packages/next/src/build/webpack-config.ts | 21 ++--- packages/next/src/lib/constants.ts | 7 +- test/e2e/module-layer/module-layer.test.ts | 92 ++++++++++++++-------- test/e2e/module-layer/pages/api/mixed.js | 2 +- 4 files changed, 70 insertions(+), 52 deletions(-) diff --git a/packages/next/src/build/webpack-config.ts b/packages/next/src/build/webpack-config.ts index 6417163780d3e..d70f7488e51d3 100644 --- a/packages/next/src/build/webpack-config.ts +++ b/packages/next/src/build/webpack-config.ts @@ -509,7 +509,7 @@ export default async function getBaseWebpackConfig( // This will cause some performance overhead but // acceptable as Babel will not be recommended. getSwcLoader({ - serverComponents: false, + serverComponents: true, bundleLayer: WEBPACK_LAYERS.middleware, }), babelLoader, @@ -560,7 +560,7 @@ export default async function getBaseWebpackConfig( const apiRoutesLayerLoaders = hasAppDir && useSWCLoader ? getSwcLoader({ - serverComponents: false, + serverComponents: true, bundleLayer: WEBPACK_LAYERS.api, }) : defaultLoaders.babel @@ -1206,10 +1206,7 @@ export default async function getBaseWebpackConfig( // Alias server-only and client-only to proper exports based on bundling layers { issuerLayer: { - or: [ - ...WEBPACK_LAYERS.GROUP.serverOnly, - ...WEBPACK_LAYERS.GROUP.nonClientServerTarget, - ], + or: WEBPACK_LAYERS.GROUP.serverOnly, }, resolve: { // Error on client-only but allow server-only @@ -1218,10 +1215,7 @@ export default async function getBaseWebpackConfig( }, { issuerLayer: { - not: [ - ...WEBPACK_LAYERS.GROUP.serverOnly, - ...WEBPACK_LAYERS.GROUP.nonClientServerTarget, - ], + not: WEBPACK_LAYERS.GROUP.serverOnly, }, resolve: { // Error on server-only but allow client-only @@ -1250,10 +1244,7 @@ export default async function getBaseWebpackConfig( ], loader: 'next-invalid-import-error-loader', issuerLayer: { - not: [ - ...WEBPACK_LAYERS.GROUP.serverOnly, - ...WEBPACK_LAYERS.GROUP.nonClientServerTarget, - ], + not: WEBPACK_LAYERS.GROUP.serverOnly, }, options: { message: @@ -1270,7 +1261,7 @@ export default async function getBaseWebpackConfig( ], loader: 'empty-loader', issuerLayer: { - or: WEBPACK_LAYERS.GROUP.nonClientServerTarget, + not: WEBPACK_LAYERS.GROUP.serverOnly, }, }, ...(hasAppDir diff --git a/packages/next/src/lib/constants.ts b/packages/next/src/lib/constants.ts index f8ebd11e33c13..fa2031e3cb714 100644 --- a/packages/next/src/lib/constants.ts +++ b/packages/next/src/lib/constants.ts @@ -168,16 +168,13 @@ const WEBPACK_LAYERS = { WEBPACK_LAYERS_NAMES.appMetadataRoute, WEBPACK_LAYERS_NAMES.appRouteHandler, WEBPACK_LAYERS_NAMES.instrument, + WEBPACK_LAYERS_NAMES.middleware, + WEBPACK_LAYERS_NAMES.api, ], clientOnly: [ WEBPACK_LAYERS_NAMES.serverSideRendering, WEBPACK_LAYERS_NAMES.appPagesBrowser, ], - nonClientServerTarget: [ - // middleware and pages api - WEBPACK_LAYERS_NAMES.middleware, - WEBPACK_LAYERS_NAMES.api, - ], app: [ WEBPACK_LAYERS_NAMES.reactServerComponents, WEBPACK_LAYERS_NAMES.actionBrowser, diff --git a/test/e2e/module-layer/module-layer.test.ts b/test/e2e/module-layer/module-layer.test.ts index 4131b7633656a..cbba925b51491 100644 --- a/test/e2e/module-layer/module-layer.test.ts +++ b/test/e2e/module-layer/module-layer.test.ts @@ -1,7 +1,8 @@ import { nextTestSetup } from 'e2e-utils' +import { retry } from 'next-test-utils' describe('module layer', () => { - const { next, isNextStart } = nextTestSetup({ + const { next, isNextStart, isNextDev } = nextTestSetup({ files: __dirname, }) @@ -59,43 +60,72 @@ describe('module layer', () => { } } - describe('no server-only in server targets', () => { - const middlewareFile = 'middleware.js' - // const pagesApiFile = 'pages/api/hello.js' - let middlewareContent = '' - // let pagesApiContent = '' + if (isNextDev) { + describe('client packages in middleware', () => { + const middlewareFile = 'middleware.js' + let middlewareContent = '' - beforeAll(async () => { - await next.stop() + beforeAll(async () => { + await next.stop() - middlewareContent = await next.readFile(middlewareFile) - // pagesApiContent = await next.readFile(pagesApiFile) + middlewareContent = await next.readFile(middlewareFile) - await next.patchFile( - middlewareFile, - middlewareContent - .replace("import 'server-only'", "// import 'server-only'") - .replace("// import './lib/mixed-lib'", "import './lib/mixed-lib'") - ) + await next.patchFile( + middlewareFile, + middlewareContent + .replace("import 'server-only'", "// import 'server-only'") + .replace("// import './lib/mixed-lib'", "import './lib/mixed-lib'") + ) + + await next.start() + }) + afterAll(async () => { + await next.patchFile(middlewareFile, middlewareContent) + }) - // await next.patchFile( - // pagesApiFile, - // pagesApiContent - // .replace("import 'server-only'", "// import 'server-only'") - // .replace( - // "// import '../../lib/mixed-lib'", - // "import '../../lib/mixed-lib'" - // ) - // ) + it('should error when import server packages in middleware', async () => { + const existingCliOutputLength = next.cliOutput.length + await next.fetch('/') - await next.start() + const newCliOutput = next.cliOutput.slice(existingCliOutputLength) + expect(newCliOutput).toContain('./middleware.js') + expect(newCliOutput).toContain( + `'client-only' cannot be imported from a Server Component module. It should only be used from a Client Component` + ) + }) }) - afterAll(async () => { - await next.patchFile(middlewareFile, middlewareContent) - // await next.patchFile(pagesApiFile, pagesApiContent) + + describe('client packages in pages api', () => { + const pagesApiFile = 'pages/api/mixed.js' + let pagesApiContent + beforeAll(async () => { + pagesApiContent = await next.readFile(pagesApiFile) + await next.patchFile( + pagesApiFile, + pagesApiContent.replace( + "// import '../../lib/mixed-lib'", + "import '../../lib/mixed-lib'" + ) + ) + }) + afterAll(async () => { + await next.patchFile(pagesApiFile, pagesApiContent) + }) + + it('should error when import client packages in pages/api', async () => { + const existingCliOutputLength = next.cliOutput.length + await retry(async () => { + await next.fetch('/api/mixed') + const newCliOutput = next.cliOutput.slice(existingCliOutputLength) + expect(newCliOutput).toContain('./pages/api/mixed.js') + expect(newCliOutput).toContain( + `'client-only' cannot be imported from a Server Component module. It should only be used from a Client Component.` + ) + }) + }) }) - runTests() - }) + } + describe('with server-only in server targets', () => { runTests() }) diff --git a/test/e2e/module-layer/pages/api/mixed.js b/test/e2e/module-layer/pages/api/mixed.js index d49398953e56a..ac3df04dcdb67 100644 --- a/test/e2e/module-layer/pages/api/mixed.js +++ b/test/e2e/module-layer/pages/api/mixed.js @@ -1,4 +1,4 @@ -import '../../lib/mixed-lib' +// import '../../lib/mixed-lib' export default function handler(req, res) { return res.send('pages/api/mixed.js:') From b19a06ab9ee8753dcf305947e22938416e88c81d Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Mon, 6 May 2024 22:17:23 +0200 Subject: [PATCH 02/13] add react assertions --- test/e2e/module-layer/middleware.js | 4 ++++ test/e2e/module-layer/pages/api/hello.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/test/e2e/module-layer/middleware.js b/test/e2e/module-layer/middleware.js index 5a1050e4c6651..8a4d11761dd78 100644 --- a/test/e2e/module-layer/middleware.js +++ b/test/e2e/module-layer/middleware.js @@ -1,7 +1,11 @@ import 'server-only' +import React from 'react' import { NextResponse } from 'next/server' // import './lib/mixed-lib' export function middleware(request) { + if (React.useState) { + throw new Error('React.useState should not be defined in server layer') + } return NextResponse.next() } diff --git a/test/e2e/module-layer/pages/api/hello.js b/test/e2e/module-layer/pages/api/hello.js index b806023b05a2c..97eff319a6a64 100644 --- a/test/e2e/module-layer/pages/api/hello.js +++ b/test/e2e/module-layer/pages/api/hello.js @@ -1,5 +1,9 @@ import 'server-only' +import React from 'react' export default function handler(req, res) { + if (React.useState) { + throw new Error('React.useState should not be defined in server layer') + } return res.send('pages/api/hello.js:') } From 840619f05e85e9d313eb4fb5931f7cf34d93c42b Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Mon, 6 May 2024 23:27:54 +0200 Subject: [PATCH 03/13] update server layer detection --- packages/next/src/build/swc/options.ts | 4 ++-- packages/next/src/build/webpack-config.ts | 21 +++++++++++++++------ packages/next/src/lib/constants.ts | 3 +++ test/e2e/module-layer/module-layer.test.ts | 11 +++-------- test/e2e/module-layer/pages/api/hello.js | 6 +----- 5 files changed, 24 insertions(+), 21 deletions(-) diff --git a/packages/next/src/build/swc/options.ts b/packages/next/src/build/swc/options.ts index 8b88160db5a0c..805e3fde6a9a7 100644 --- a/packages/next/src/build/swc/options.ts +++ b/packages/next/src/build/swc/options.ts @@ -6,6 +6,7 @@ import type { StyledComponentsConfig, } from '../../server/config-shared' import type { ResolvedBaseUrl } from '../load-jsconfig' +import { isWebpackServerOnlyLayer } from '../utils' const nextDistPath = /(next[\\/]dist[\\/]shared[\\/]lib)|(next[\\/]dist[\\/]client)|(next[\\/]dist[\\/]pages)/ @@ -78,8 +79,7 @@ function getBaseSWCOptions({ serverComponents?: boolean bundleLayer?: WebpackLayerName }) { - const isReactServerLayer = - bundleLayer === WEBPACK_LAYERS.reactServerComponents + const isReactServerLayer = isWebpackServerOnlyLayer(bundleLayer) const parserConfig = getParserOptions({ filename, jsConfig }) const paths = jsConfig?.compilerOptions?.paths const enableDecorators = Boolean( diff --git a/packages/next/src/build/webpack-config.ts b/packages/next/src/build/webpack-config.ts index d70f7488e51d3..9b8fad90b9922 100644 --- a/packages/next/src/build/webpack-config.ts +++ b/packages/next/src/build/webpack-config.ts @@ -509,7 +509,7 @@ export default async function getBaseWebpackConfig( // This will cause some performance overhead but // acceptable as Babel will not be recommended. getSwcLoader({ - serverComponents: true, + serverComponents: false, bundleLayer: WEBPACK_LAYERS.middleware, }), babelLoader, @@ -560,7 +560,7 @@ export default async function getBaseWebpackConfig( const apiRoutesLayerLoaders = hasAppDir && useSWCLoader ? getSwcLoader({ - serverComponents: true, + serverComponents: false, bundleLayer: WEBPACK_LAYERS.api, }) : defaultLoaders.babel @@ -1206,7 +1206,10 @@ export default async function getBaseWebpackConfig( // Alias server-only and client-only to proper exports based on bundling layers { issuerLayer: { - or: WEBPACK_LAYERS.GROUP.serverOnly, + or: [ + ...WEBPACK_LAYERS.GROUP.serverOnly, + ...WEBPACK_LAYERS.GROUP.neutralTarget, + ], }, resolve: { // Error on client-only but allow server-only @@ -1215,7 +1218,10 @@ export default async function getBaseWebpackConfig( }, { issuerLayer: { - not: WEBPACK_LAYERS.GROUP.serverOnly, + not: [ + ...WEBPACK_LAYERS.GROUP.serverOnly, + ...WEBPACK_LAYERS.GROUP.neutralTarget, + ], }, resolve: { // Error on server-only but allow client-only @@ -1244,7 +1250,10 @@ export default async function getBaseWebpackConfig( ], loader: 'next-invalid-import-error-loader', issuerLayer: { - not: WEBPACK_LAYERS.GROUP.serverOnly, + not: [ + ...WEBPACK_LAYERS.GROUP.serverOnly, + ...WEBPACK_LAYERS.GROUP.neutralTarget, + ], }, options: { message: @@ -1261,7 +1270,7 @@ export default async function getBaseWebpackConfig( ], loader: 'empty-loader', issuerLayer: { - not: WEBPACK_LAYERS.GROUP.serverOnly, + or: WEBPACK_LAYERS.GROUP.neutralTarget, }, }, ...(hasAppDir diff --git a/packages/next/src/lib/constants.ts b/packages/next/src/lib/constants.ts index fa2031e3cb714..7a0eaef0de69a 100644 --- a/packages/next/src/lib/constants.ts +++ b/packages/next/src/lib/constants.ts @@ -169,6 +169,9 @@ const WEBPACK_LAYERS = { WEBPACK_LAYERS_NAMES.appRouteHandler, WEBPACK_LAYERS_NAMES.instrument, WEBPACK_LAYERS_NAMES.middleware, + ], + neutralTarget: [ + // pages api WEBPACK_LAYERS_NAMES.api, ], clientOnly: [ diff --git a/test/e2e/module-layer/module-layer.test.ts b/test/e2e/module-layer/module-layer.test.ts index cbba925b51491..e1df9d2500385 100644 --- a/test/e2e/module-layer/module-layer.test.ts +++ b/test/e2e/module-layer/module-layer.test.ts @@ -112,15 +112,10 @@ describe('module layer', () => { await next.patchFile(pagesApiFile, pagesApiContent) }) - it('should error when import client packages in pages/api', async () => { - const existingCliOutputLength = next.cliOutput.length + it('should not error when import client packages in pages/api', async () => { await retry(async () => { - await next.fetch('/api/mixed') - const newCliOutput = next.cliOutput.slice(existingCliOutputLength) - expect(newCliOutput).toContain('./pages/api/mixed.js') - expect(newCliOutput).toContain( - `'client-only' cannot be imported from a Server Component module. It should only be used from a Client Component.` - ) + const { status } = await next.fetch('/api/mixed') + expect(status).toBe(200) }) }) }) diff --git a/test/e2e/module-layer/pages/api/hello.js b/test/e2e/module-layer/pages/api/hello.js index 97eff319a6a64..d1fe5339d8e98 100644 --- a/test/e2e/module-layer/pages/api/hello.js +++ b/test/e2e/module-layer/pages/api/hello.js @@ -1,9 +1,5 @@ import 'server-only' -import React from 'react' export default function handler(req, res) { - if (React.useState) { - throw new Error('React.useState should not be defined in server layer') - } - return res.send('pages/api/hello.js:') + return res.send('pages/api/hello.js') } From 05a5ddb9e124cae4fcf06e242e4e6e1d1ec69187 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 7 May 2024 22:25:57 +0200 Subject: [PATCH 04/13] add turbopack change --- .../crates/next-core/src/next_edge/context.rs | 25 +++++++++++++ .../crates/next-core/src/next_import_map.rs | 36 ++++++++++--------- .../next-core/src/next_server/context.rs | 8 ++--- 3 files changed, 48 insertions(+), 21 deletions(-) diff --git a/packages/next-swc/crates/next-core/src/next_edge/context.rs b/packages/next-swc/crates/next-core/src/next_edge/context.rs index a3b11a95add37..5767b1ebd36f6 100644 --- a/packages/next-swc/crates/next-core/src/next_edge/context.rs +++ b/packages/next-swc/crates/next-core/src/next_edge/context.rs @@ -25,6 +25,7 @@ use crate::{ next_import_map::get_next_edge_import_map, next_server::context::ServerContextType, next_shared::resolve::{ + get_invalid_client_only_resolve_plugin, get_invalid_styled_jsx_resolve_plugin, ModuleFeatureReportResolvePlugin, NextSharedRuntimeResolvePlugin, UnsupportedModulesResolvePlugin, }, @@ -98,6 +99,29 @@ pub async fn get_edge_resolve_options_context( get_next_edge_import_map(project_path, ty, next_config, execution_context); let ty = ty.into_value(); + let invalid_client_only_resolve_plugin = get_invalid_client_only_resolve_plugin(project_path); + let invalid_styled_jsx_client_only_resolve_plugin = + get_invalid_styled_jsx_resolve_plugin(project_path); + + let plugins = match ty { + ServerContextType::Pages { .. } => { + vec![] + } + ServerContextType::PagesData { .. } + | ServerContextType::PagesApi { .. } + | ServerContextType::AppRSC { .. } + | ServerContextType::AppRoute { .. } + | ServerContextType::Middleware { .. } + | ServerContextType::Instrumentation => { + vec![ + Vc::upcast(invalid_client_only_resolve_plugin), + Vc::upcast(invalid_styled_jsx_client_only_resolve_plugin), + ] + } + ServerContextType::AppSSR { .. } => { + vec![] + } + }; // https://github.com/vercel/next.js/blob/bf52c254973d99fed9d71507a2e818af80b8ade7/packages/next/src/build/webpack-config.ts#L96-L102 let mut custom_conditions = vec![mode.await?.condition().to_string()]; @@ -137,6 +161,7 @@ pub async fn get_edge_resolve_options_context( foreign_code_context_condition(next_config, project_path).await?, resolve_options_context.clone().cell(), )], + plugins, ..resolve_options_context } .cell()) diff --git a/packages/next-swc/crates/next-core/src/next_import_map.rs b/packages/next-swc/crates/next-core/src/next_import_map.rs index 26c1e67c70117..799ebbdc9bf51 100644 --- a/packages/next-swc/crates/next-core/src/next_import_map.rs +++ b/packages/next-swc/crates/next-core/src/next_import_map.rs @@ -613,6 +613,7 @@ async fn insert_next_server_special_aliases( | ServerContextType::PagesApi { .. } | ServerContextType::AppRSC { .. } | ServerContextType::AppRoute { .. } + | ServerContextType::Middleware { .. } | ServerContextType::Instrumentation => { insert_exact_alias_map( import_map, @@ -636,23 +637,24 @@ async fn insert_next_server_special_aliases( "next/dist/compiled/client-only" => "next/dist/compiled/client-only/index".to_string(), }, ); - } - // Potential the bundle introduced into middleware and api can be poisoned by - // client-only but not being used, so we disabled the `client-only` erroring - // on these layers. `server-only` is still available. - ServerContextType::Middleware => { - insert_exact_alias_map( - import_map, - project_path, - indexmap! { - "server-only" => "next/dist/compiled/server-only/empty".to_string(), - "client-only" => "next/dist/compiled/client-only/index".to_string(), - "next/dist/compiled/server-only" => "next/dist/compiled/server-only/empty".to_string(), - "next/dist/compiled/client-only" => "next/dist/compiled/client-only/index".to_string(), - "next/dist/compiled/client-only/error" => "next/dist/compiled/client-only/index".to_string(), - }, - ); - } + } /* Potential the bundle introduced into middleware and api can be poisoned by + * client-only but not being used, so we disabled the `client-only` erroring + * on these layers. `server-only` is still available. + * ServerContextType::Middleware => { + * insert_exact_alias_map( + * import_map, + * project_path, + * indexmap! { + * "server-only" => "next/dist/compiled/server-only/empty".to_string(), + * "client-only" => "next/dist/compiled/client-only/index".to_string(), + * "next/dist/compiled/server-only" => + * "next/dist/compiled/server-only/empty".to_string(), + * "next/dist/compiled/client-only" => + * "next/dist/compiled/client-only/index".to_string(), + * "next/dist/compiled/client-only/error" => + * "next/dist/compiled/client-only/index".to_string(), }, + * ); + * } */ } import_map.insert_exact_alias( diff --git a/packages/next-swc/crates/next-core/src/next_server/context.rs b/packages/next-swc/crates/next-core/src/next_server/context.rs index aaeefdf883b8a..6679fe8b79437 100644 --- a/packages/next-swc/crates/next-core/src/next_server/context.rs +++ b/packages/next-swc/crates/next-core/src/next_server/context.rs @@ -232,6 +232,7 @@ pub async fn get_server_resolve_options_context( | ServerContextType::PagesApi { .. } | ServerContextType::AppRSC { .. } | ServerContextType::AppRoute { .. } + | ServerContextType::Middleware { .. } | ServerContextType::Instrumentation => { plugins.push(Vc::upcast(invalid_client_only_resolve_plugin)); plugins.push(Vc::upcast(invalid_styled_jsx_client_only_resolve_plugin)); @@ -239,10 +240,9 @@ pub async fn get_server_resolve_options_context( ServerContextType::AppSSR { .. } => { //[TODO] Build error in this context makes rsc-build-error.ts fail which expects runtime error code // looks like webpack and turbopack have different order, webpack runs rsc transform first, turbopack triggers resolve plugin first. - } - ServerContextType::Middleware => { - //noop - } + } /* ServerContextType::Middleware => { + * //noop + * } */ } let resolve_options_context = ResolveOptionsContext { From 386073b47ff651ae4228eaf2ba4449847825107f Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Fri, 10 May 2024 11:10:20 +0200 Subject: [PATCH 05/13] fix middleware --- packages/next-swc/crates/next-core/src/next_server/context.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/next-swc/crates/next-core/src/next_server/context.rs b/packages/next-swc/crates/next-core/src/next_server/context.rs index 6679fe8b79437..6df6489ca1d4e 100644 --- a/packages/next-swc/crates/next-core/src/next_server/context.rs +++ b/packages/next-swc/crates/next-core/src/next_server/context.rs @@ -105,6 +105,7 @@ impl ServerContextType { ServerContextType::AppRSC { .. } | ServerContextType::AppRoute { .. } | ServerContextType::PagesApi { .. } + | ServerContextType::Middleware { .. } ) } } From 59d7ad487d54cdfe4eb1883eb62011e181628c46 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Fri, 10 May 2024 12:39:28 +0200 Subject: [PATCH 06/13] workaround test --- test/e2e/module-layer/module-layer.test.ts | 39 ++++++++++++---------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/test/e2e/module-layer/module-layer.test.ts b/test/e2e/module-layer/module-layer.test.ts index e1df9d2500385..5a2d927f6a4a6 100644 --- a/test/e2e/module-layer/module-layer.test.ts +++ b/test/e2e/module-layer/module-layer.test.ts @@ -1,8 +1,8 @@ import { nextTestSetup } from 'e2e-utils' -import { retry } from 'next-test-utils' +import { getRedboxSource, hasRedbox, retry } from 'next-test-utils' describe('module layer', () => { - const { next, isNextStart, isNextDev } = nextTestSetup({ + const { next, isNextStart, isNextDev, isTurbopack } = nextTestSetup({ files: __dirname, }) @@ -65,8 +65,12 @@ describe('module layer', () => { const middlewareFile = 'middleware.js' let middlewareContent = '' - beforeAll(async () => { - await next.stop() + afterAll(async () => { + await next.patchFile(middlewareFile, middlewareContent) + }) + + it('should error when import server packages in middleware', async () => { + const browser = await next.browser('/') middlewareContent = await next.readFile(middlewareFile) @@ -77,21 +81,22 @@ describe('module layer', () => { .replace("// import './lib/mixed-lib'", "import './lib/mixed-lib'") ) - await next.start() - }) - afterAll(async () => { - await next.patchFile(middlewareFile, middlewareContent) - }) - - it('should error when import server packages in middleware', async () => { const existingCliOutputLength = next.cliOutput.length - await next.fetch('/') + await retry(async () => { + expect(await hasRedbox(browser)).toBe(true) + const source = await getRedboxSource(browser) + expect(source).toContain( + `'client-only' cannot be imported from a Server Component module. It should only be used from a Client Component.` + ) + }) - const newCliOutput = next.cliOutput.slice(existingCliOutputLength) - expect(newCliOutput).toContain('./middleware.js') - expect(newCliOutput).toContain( - `'client-only' cannot be imported from a Server Component module. It should only be used from a Client Component` - ) + if (!isTurbopack) { + const newCliOutput = next.cliOutput.slice(existingCliOutputLength) + expect(newCliOutput).toContain('./middleware.js') + expect(newCliOutput).toContain( + `'client-only' cannot be imported from a Server Component module. It should only be used from a Client Component` + ) + } }) }) From 6cdad98912b6dcf6831dddefe8da6bbb57fbac86 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Sat, 11 May 2024 00:37:59 +0200 Subject: [PATCH 07/13] comment out pages and ssr layer --- .../crates/next-core/src/next_edge/context.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/next-swc/crates/next-core/src/next_edge/context.rs b/packages/next-swc/crates/next-core/src/next_edge/context.rs index 5767b1ebd36f6..6530225709cf3 100644 --- a/packages/next-swc/crates/next-core/src/next_edge/context.rs +++ b/packages/next-swc/crates/next-core/src/next_edge/context.rs @@ -104,12 +104,13 @@ pub async fn get_edge_resolve_options_context( get_invalid_styled_jsx_resolve_plugin(project_path); let plugins = match ty { - ServerContextType::Pages { .. } => { + ServerContextType::AppSSR { .. } + | ServerContextType::PagesData { .. } + | ServerContextType::PagesApi { .. } + | ServerContextType::Pages { .. } => { vec![] } - ServerContextType::PagesData { .. } - | ServerContextType::PagesApi { .. } - | ServerContextType::AppRSC { .. } + ServerContextType::AppRSC { .. } | ServerContextType::AppRoute { .. } | ServerContextType::Middleware { .. } | ServerContextType::Instrumentation => { @@ -118,9 +119,6 @@ pub async fn get_edge_resolve_options_context( Vc::upcast(invalid_styled_jsx_client_only_resolve_plugin), ] } - ServerContextType::AppSSR { .. } => { - vec![] - } }; // https://github.com/vercel/next.js/blob/bf52c254973d99fed9d71507a2e818af80b8ade7/packages/next/src/build/webpack-config.ts#L96-L102 From 2d117f6d86be362da80a644736220d8ed0f3c798 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Sat, 11 May 2024 11:04:27 +0200 Subject: [PATCH 08/13] merge plugins --- .../crates/next-core/src/next_edge/context.rs | 23 +++++++++++-------- .../crates/next-core/src/next_import_map.rs | 19 +-------------- 2 files changed, 14 insertions(+), 28 deletions(-) diff --git a/packages/next-swc/crates/next-core/src/next_edge/context.rs b/packages/next-swc/crates/next-core/src/next_edge/context.rs index 6530225709cf3..b81f030c597b3 100644 --- a/packages/next-swc/crates/next-core/src/next_edge/context.rs +++ b/packages/next-swc/crates/next-core/src/next_edge/context.rs @@ -103,14 +103,14 @@ pub async fn get_edge_resolve_options_context( let invalid_styled_jsx_client_only_resolve_plugin = get_invalid_styled_jsx_resolve_plugin(project_path); - let plugins = match ty { - ServerContextType::AppSSR { .. } + let mut plugins = match ty { + ServerContextType::Pages { .. } | ServerContextType::PagesData { .. } - | ServerContextType::PagesApi { .. } - | ServerContextType::Pages { .. } => { + | ServerContextType::PagesApi { .. } => { vec![] } ServerContextType::AppRSC { .. } + | ServerContextType::AppSSR { .. } | ServerContextType::AppRoute { .. } | ServerContextType::Middleware { .. } | ServerContextType::Instrumentation => { @@ -121,6 +121,14 @@ pub async fn get_edge_resolve_options_context( } }; + let base_plugins = vec![ + Vc::upcast(ModuleFeatureReportResolvePlugin::new(project_path)), + Vc::upcast(UnsupportedModulesResolvePlugin::new(project_path)), + Vc::upcast(NextSharedRuntimeResolvePlugin::new(project_path)), + ]; + + plugins.extend_from_slice(&base_plugins); + // https://github.com/vercel/next.js/blob/bf52c254973d99fed9d71507a2e818af80b8ade7/packages/next/src/build/webpack-config.ts#L96-L102 let mut custom_conditions = vec![mode.await?.condition().to_string()]; custom_conditions.extend( @@ -141,11 +149,7 @@ pub async fn get_edge_resolve_options_context( import_map: Some(next_edge_import_map), module: true, browser: true, - plugins: vec![ - Vc::upcast(ModuleFeatureReportResolvePlugin::new(project_path)), - Vc::upcast(UnsupportedModulesResolvePlugin::new(project_path)), - Vc::upcast(NextSharedRuntimeResolvePlugin::new(project_path)), - ], + plugins, ..Default::default() }; @@ -159,7 +163,6 @@ pub async fn get_edge_resolve_options_context( foreign_code_context_condition(next_config, project_path).await?, resolve_options_context.clone().cell(), )], - plugins, ..resolve_options_context } .cell()) diff --git a/packages/next-swc/crates/next-core/src/next_import_map.rs b/packages/next-swc/crates/next-core/src/next_import_map.rs index 799ebbdc9bf51..cda1515bf86e5 100644 --- a/packages/next-swc/crates/next-core/src/next_import_map.rs +++ b/packages/next-swc/crates/next-core/src/next_import_map.rs @@ -637,24 +637,7 @@ async fn insert_next_server_special_aliases( "next/dist/compiled/client-only" => "next/dist/compiled/client-only/index".to_string(), }, ); - } /* Potential the bundle introduced into middleware and api can be poisoned by - * client-only but not being used, so we disabled the `client-only` erroring - * on these layers. `server-only` is still available. - * ServerContextType::Middleware => { - * insert_exact_alias_map( - * import_map, - * project_path, - * indexmap! { - * "server-only" => "next/dist/compiled/server-only/empty".to_string(), - * "client-only" => "next/dist/compiled/client-only/index".to_string(), - * "next/dist/compiled/server-only" => - * "next/dist/compiled/server-only/empty".to_string(), - * "next/dist/compiled/client-only" => - * "next/dist/compiled/client-only/index".to_string(), - * "next/dist/compiled/client-only/error" => - * "next/dist/compiled/client-only/index".to_string(), }, - * ); - * } */ + } } import_map.insert_exact_alias( From 348fbd85fff7374aff640a5a7d3912409412c87f Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Sat, 11 May 2024 11:24:55 +0200 Subject: [PATCH 09/13] change app ssr rules --- .../crates/next-core/src/next_edge/context.rs | 6 +++--- .../crates/next-core/src/next_server/context.rs | 11 +++-------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/next-swc/crates/next-core/src/next_edge/context.rs b/packages/next-swc/crates/next-core/src/next_edge/context.rs index b81f030c597b3..5a6933edbcaca 100644 --- a/packages/next-swc/crates/next-core/src/next_edge/context.rs +++ b/packages/next-swc/crates/next-core/src/next_edge/context.rs @@ -105,13 +105,13 @@ pub async fn get_edge_resolve_options_context( let mut plugins = match ty { ServerContextType::Pages { .. } - | ServerContextType::PagesData { .. } - | ServerContextType::PagesApi { .. } => { + | ServerContextType::PagesApi { .. } + | ServerContextType::AppSSR { .. } => { vec![] } ServerContextType::AppRSC { .. } - | ServerContextType::AppSSR { .. } | ServerContextType::AppRoute { .. } + | ServerContextType::PagesData { .. } | ServerContextType::Middleware { .. } | ServerContextType::Instrumentation => { vec![ diff --git a/packages/next-swc/crates/next-core/src/next_server/context.rs b/packages/next-swc/crates/next-core/src/next_server/context.rs index 6df6489ca1d4e..52fcc28c97d37 100644 --- a/packages/next-swc/crates/next-core/src/next_server/context.rs +++ b/packages/next-swc/crates/next-core/src/next_server/context.rs @@ -179,9 +179,7 @@ pub async fn get_server_resolve_options_context( NextNodeSharedRuntimeResolvePlugin::new(project_path, Value::new(ty)); let mut plugins = match ty { - ServerContextType::Pages { .. } - | ServerContextType::PagesData { .. } - | ServerContextType::PagesApi { .. } => { + ServerContextType::Pages { .. } | ServerContextType::PagesApi { .. } => { vec![ Vc::upcast(module_feature_report_resolve_plugin), Vc::upcast(unsupported_modules_resolve_plugin), @@ -226,11 +224,10 @@ pub async fn get_server_resolve_options_context( // means each resolve plugin must be injected only for the context where the // alias resolves into the error. The alias lives in here: https://github.com/vercel/next.js/blob/0060de1c4905593ea875fa7250d4b5d5ce10897d/packages/next-swc/crates/next-core/src/next_import_map.rs#L534 match ty { - ServerContextType::Pages { .. } => { + ServerContextType::Pages { .. } | ServerContextType::PagesApi { .. } => { //noop } ServerContextType::PagesData { .. } - | ServerContextType::PagesApi { .. } | ServerContextType::AppRSC { .. } | ServerContextType::AppRoute { .. } | ServerContextType::Middleware { .. } @@ -241,9 +238,7 @@ pub async fn get_server_resolve_options_context( ServerContextType::AppSSR { .. } => { //[TODO] Build error in this context makes rsc-build-error.ts fail which expects runtime error code // looks like webpack and turbopack have different order, webpack runs rsc transform first, turbopack triggers resolve plugin first. - } /* ServerContextType::Middleware => { - * //noop - * } */ + } } let resolve_options_context = ResolveOptionsContext { From b107f323e2a2bc3dff272c415d30c5052825f194 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Sat, 11 May 2024 11:35:18 +0200 Subject: [PATCH 10/13] missing page data --- packages/next-swc/crates/next-core/src/next_server/context.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/next-swc/crates/next-core/src/next_server/context.rs b/packages/next-swc/crates/next-core/src/next_server/context.rs index 52fcc28c97d37..666c3609ce75c 100644 --- a/packages/next-swc/crates/next-core/src/next_server/context.rs +++ b/packages/next-swc/crates/next-core/src/next_server/context.rs @@ -188,7 +188,8 @@ pub async fn get_server_resolve_options_context( Vc::upcast(next_external_plugin), ] } - ServerContextType::AppSSR { .. } + ServerContextType::PagesData { .. } + | ServerContextType::AppSSR { .. } | ServerContextType::AppRSC { .. } | ServerContextType::AppRoute { .. } => { vec![ From c65197fefb9f2443f8138d9d514b0e2b58af7369 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Sat, 11 May 2024 11:59:20 +0200 Subject: [PATCH 11/13] fix pages data --- .../next-swc/crates/next-core/src/next_server/context.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/next-swc/crates/next-core/src/next_server/context.rs b/packages/next-swc/crates/next-core/src/next_server/context.rs index 666c3609ce75c..d1b08ab264fc4 100644 --- a/packages/next-swc/crates/next-core/src/next_server/context.rs +++ b/packages/next-swc/crates/next-core/src/next_server/context.rs @@ -179,7 +179,9 @@ pub async fn get_server_resolve_options_context( NextNodeSharedRuntimeResolvePlugin::new(project_path, Value::new(ty)); let mut plugins = match ty { - ServerContextType::Pages { .. } | ServerContextType::PagesApi { .. } => { + ServerContextType::Pages { .. } + | ServerContextType::PagesApi { .. } + | ServerContextType::PagesData { .. } => { vec![ Vc::upcast(module_feature_report_resolve_plugin), Vc::upcast(unsupported_modules_resolve_plugin), @@ -188,8 +190,7 @@ pub async fn get_server_resolve_options_context( Vc::upcast(next_external_plugin), ] } - ServerContextType::PagesData { .. } - | ServerContextType::AppSSR { .. } + ServerContextType::AppSSR { .. } | ServerContextType::AppRSC { .. } | ServerContextType::AppRoute { .. } => { vec![ From 6e59e6e3d5bbc25b384bd7a7135c279902028eab Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Sun, 12 May 2024 11:28:49 +0200 Subject: [PATCH 12/13] remove duplicated test --- test/e2e/module-layer/module-layer.test.ts | 25 ---------------------- test/e2e/module-layer/pages/api/mixed.js | 2 +- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/test/e2e/module-layer/module-layer.test.ts b/test/e2e/module-layer/module-layer.test.ts index 5a2d927f6a4a6..bf665e1428df8 100644 --- a/test/e2e/module-layer/module-layer.test.ts +++ b/test/e2e/module-layer/module-layer.test.ts @@ -99,31 +99,6 @@ describe('module layer', () => { } }) }) - - describe('client packages in pages api', () => { - const pagesApiFile = 'pages/api/mixed.js' - let pagesApiContent - beforeAll(async () => { - pagesApiContent = await next.readFile(pagesApiFile) - await next.patchFile( - pagesApiFile, - pagesApiContent.replace( - "// import '../../lib/mixed-lib'", - "import '../../lib/mixed-lib'" - ) - ) - }) - afterAll(async () => { - await next.patchFile(pagesApiFile, pagesApiContent) - }) - - it('should not error when import client packages in pages/api', async () => { - await retry(async () => { - const { status } = await next.fetch('/api/mixed') - expect(status).toBe(200) - }) - }) - }) } describe('with server-only in server targets', () => { diff --git a/test/e2e/module-layer/pages/api/mixed.js b/test/e2e/module-layer/pages/api/mixed.js index ac3df04dcdb67..d49398953e56a 100644 --- a/test/e2e/module-layer/pages/api/mixed.js +++ b/test/e2e/module-layer/pages/api/mixed.js @@ -1,4 +1,4 @@ -// import '../../lib/mixed-lib' +import '../../lib/mixed-lib' export default function handler(req, res) { return res.send('pages/api/mixed.js:') From 7a3dfd69be96f42a02a2a5e2c81f6c4677574d23 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Sun, 12 May 2024 14:06:14 +0200 Subject: [PATCH 13/13] simplify --- .../next-swc/crates/next-core/src/next_edge/context.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/next-swc/crates/next-core/src/next_edge/context.rs b/packages/next-swc/crates/next-core/src/next_edge/context.rs index 5a6933edbcaca..8541bf74153a1 100644 --- a/packages/next-swc/crates/next-core/src/next_edge/context.rs +++ b/packages/next-swc/crates/next-core/src/next_edge/context.rs @@ -99,9 +99,6 @@ pub async fn get_edge_resolve_options_context( get_next_edge_import_map(project_path, ty, next_config, execution_context); let ty = ty.into_value(); - let invalid_client_only_resolve_plugin = get_invalid_client_only_resolve_plugin(project_path); - let invalid_styled_jsx_client_only_resolve_plugin = - get_invalid_styled_jsx_resolve_plugin(project_path); let mut plugins = match ty { ServerContextType::Pages { .. } @@ -115,8 +112,8 @@ pub async fn get_edge_resolve_options_context( | ServerContextType::Middleware { .. } | ServerContextType::Instrumentation => { vec![ - Vc::upcast(invalid_client_only_resolve_plugin), - Vc::upcast(invalid_styled_jsx_client_only_resolve_plugin), + Vc::upcast(get_invalid_client_only_resolve_plugin(project_path)), + Vc::upcast(get_invalid_styled_jsx_resolve_plugin(project_path)), ] } };