From 7cdba8f191a13d78f4346494c3aacd93a2558871 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Mon, 11 Apr 2022 19:54:28 +0200 Subject: [PATCH 1/6] fix `export from` in server component, fix native module in server component --- .../loaders/next-flight-server-loader.ts | 85 ++++++++++++++++--- .../webpack/plugins/flight-manifest-plugin.ts | 2 +- .../app/pages/native-module.server.js | 16 ++++ .../app/pages/re-export.server.js | 1 + .../test/rsc.js | 15 ++++ 5 files changed, 105 insertions(+), 14 deletions(-) create mode 100644 test/integration/react-streaming-and-server-components/app/pages/native-module.server.js create mode 100644 test/integration/react-streaming-and-server-components/app/pages/re-export.server.js diff --git a/packages/next/build/webpack/loaders/next-flight-server-loader.ts b/packages/next/build/webpack/loaders/next-flight-server-loader.ts index 0419079a76f7..2a4910fead21 100644 --- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts @@ -1,3 +1,5 @@ +import { builtinModules } from 'module' + import { parse } from '../../swc' import { buildExports } from './utils' @@ -70,13 +72,59 @@ async function parseModuleInfo({ const isEsm = type === 'Module' + async function getModuleType(importSource: string) { + const isBuiltinModule = builtinModules.includes(importSource) + const resolvedPath = isBuiltinModule + ? importSource + : await resolver(importSource) + + const isNodeModuleImport = resolvedPath.includes('/node_modules/') + + return [isBuiltinModule, isNodeModuleImport] as const + } + + function addClientImport( + importSource: string, + { + isNodeModuleImport, + isBuiltinModule, + }: { isNodeModuleImport: boolean; isBuiltinModule: boolean } + ) { + // For now we assume there is no .client.js inside node_modules. + // TODO: properly handle this. + if (isNodeModuleImport) { + return false + } + + if (isBuiltinModule) { + return false + } + + if ( + isServerComponent(importSource) || + hasFlightLoader(importSource, 'server') + ) { + // If it's a server component, we recursively import its dependencies. + imports.push(importSource) + } else if (isClientComponent(importSource)) { + // Client component. + imports.push(importSource) + } else { + // Shared component. + imports.push(createFlightServerRequest(importSource, extensions)) + } + return true + } + for (let i = 0; i < body.length; i++) { const node = body[i] switch (node.type) { case 'ImportDeclaration': const importSource = node.source.value - const resolvedPath = await resolver(importSource) - const isNodeModuleImport = resolvedPath.includes('/node_modules/') + + const [isBuiltinModule, isNodeModuleImport] = await getModuleType( + importSource + ) // matching node_module package but excluding react cores since react is required to be shared const isReactImports = [ @@ -104,9 +152,10 @@ async function parseModuleInfo({ imports.push(importSource) } else { // A shared component. It should be handled as a server component. - const serverImportSource = isReactImports - ? importSource - : createFlightServerRequest(importSource, extensions) + const serverImportSource = + isReactImports || isBuiltinModule + ? importSource + : createFlightServerRequest(importSource, extensions) transformedSource += importDeclarations transformedSource += JSON.stringify(serverImportSource) @@ -116,18 +165,14 @@ async function parseModuleInfo({ } } } else { - // For the client compilation, we skip all modules imports but - // always keep client/shared components in the bundle. All client components - // have to be imported from either server or client components. if ( - isServerComponent(importSource) || - hasFlightLoader(importSource, 'server') || - // TODO: support handling RSC components from node_modules - isNodeModuleImport + !addClientImport(importSource, { + isNodeModuleImport, + isBuiltinModule, + }) ) { continue } - imports.push(importSource) } lastIndex = node.source.span.end @@ -158,6 +203,20 @@ async function parseModuleInfo({ } } break + case 'ExportNamedDeclaration': + if (isClientCompilation) { + if (node.source) { + // export { ... } from '...' + const importSource = node.source.value + const [isBuiltinModule, isNodeModuleImport] = await getModuleType( + importSource + ) + addClientImport(importSource, { + isNodeModuleImport, + isBuiltinModule, + }) + } + } default: break } diff --git a/packages/next/build/webpack/plugins/flight-manifest-plugin.ts b/packages/next/build/webpack/plugins/flight-manifest-plugin.ts index dac02a1bb60c..59156b0c7579 100644 --- a/packages/next/build/webpack/plugins/flight-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/flight-manifest-plugin.ts @@ -72,7 +72,7 @@ export class FlightManifestPlugin { // TODO: Hook into deps instead of the target module. // That way we know by the type of dep whether to include. // It also resolves conflicts when the same module is in multiple chunks. - if (!isClientComponent(resource)) { + if (!resource || !isClientComponent(resource)) { return } const moduleExports: any = manifest[resource] || {} diff --git a/test/integration/react-streaming-and-server-components/app/pages/native-module.server.js b/test/integration/react-streaming-and-server-components/app/pages/native-module.server.js new file mode 100644 index 000000000000..c109332b28e3 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/native-module.server.js @@ -0,0 +1,16 @@ +import fs from 'fs' + +import Foo from '../components/foo.client' + +export default function Page() { + return ( + <> +

fs: {typeof fs.readFile}

+ + + ) +} + +export const config = { + runtime: 'nodejs', +} diff --git a/test/integration/react-streaming-and-server-components/app/pages/re-export.server.js b/test/integration/react-streaming-and-server-components/app/pages/re-export.server.js new file mode 100644 index 000000000000..de73532c227f --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/re-export.server.js @@ -0,0 +1 @@ +export { default } from './css-modules.server' diff --git a/test/integration/react-streaming-and-server-components/test/rsc.js b/test/integration/react-streaming-and-server-components/test/rsc.js index a9f043f4e162..4643d8359c9b 100644 --- a/test/integration/react-streaming-and-server-components/test/rsc.js +++ b/test/integration/react-streaming-and-server-components/test/rsc.js @@ -221,6 +221,21 @@ export default function (context, { runtime, env }) { expect(hydratedContent).toContain('Export All: one, two, two') }) + it('should support native modules in server component', async () => { + const html = await renderViaHTTP(context.appPort, '/native-module') + const content = getNodeBySelector(html, '#__next').text() + + expect(content).toContain('fs: function') + expect(content).toContain('foo.client') + }) + + it('should support the re-export syntax in server component', async () => { + const html = await renderViaHTTP(context.appPort, '/re-export') + const content = getNodeBySelector(html, '#__next').text() + + expect(content).toContain('This should be in red') + }) + it('should handle 404 requests and missing routes correctly', async () => { const id = '#text' const content = 'custom-404-page' From d97b1c925a427dad6d67be1f641e29f781d76ada Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Mon, 11 Apr 2022 22:13:01 +0200 Subject: [PATCH 2/6] fix --- .../loaders/next-flight-server-loader.ts | 38 ++++--------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/packages/next/build/webpack/loaders/next-flight-server-loader.ts b/packages/next/build/webpack/loaders/next-flight-server-loader.ts index 2a4910fead21..6f7e4ac49598 100644 --- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts @@ -83,23 +83,7 @@ async function parseModuleInfo({ return [isBuiltinModule, isNodeModuleImport] as const } - function addClientImport( - importSource: string, - { - isNodeModuleImport, - isBuiltinModule, - }: { isNodeModuleImport: boolean; isBuiltinModule: boolean } - ) { - // For now we assume there is no .client.js inside node_modules. - // TODO: properly handle this. - if (isNodeModuleImport) { - return false - } - - if (isBuiltinModule) { - return false - } - + function addClientImport(importSource: string) { if ( isServerComponent(importSource) || hasFlightLoader(importSource, 'server') @@ -113,7 +97,6 @@ async function parseModuleInfo({ // Shared component. imports.push(createFlightServerRequest(importSource, extensions)) } - return true } for (let i = 0; i < body.length; i++) { @@ -165,14 +148,10 @@ async function parseModuleInfo({ } } } else { - if ( - !addClientImport(importSource, { - isNodeModuleImport, - isBuiltinModule, - }) - ) { - continue - } + // For now we assume there is no .client.js inside node_modules. + // TODO: properly handle this. + if (isNodeModuleImport || isBuiltinModule) continue + addClientImport(importSource) } lastIndex = node.source.span.end @@ -211,10 +190,9 @@ async function parseModuleInfo({ const [isBuiltinModule, isNodeModuleImport] = await getModuleType( importSource ) - addClientImport(importSource, { - isNodeModuleImport, - isBuiltinModule, - }) + if (!isBuiltinModule && !isNodeModuleImport) { + addClientImport(importSource) + } } } default: From e432237c5f89ed4199f59018615aa3131215a9de Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Tue, 12 Apr 2022 17:11:43 +0200 Subject: [PATCH 3/6] fix flight loader option --- .../webpack/loaders/next-flight-server-loader.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/next/build/webpack/loaders/next-flight-server-loader.ts b/packages/next/build/webpack/loaders/next-flight-server-loader.ts index 6f7e4ac49598..dc31197f8840 100644 --- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts @@ -26,10 +26,8 @@ export const createServerComponentFilter = (extensions: string[]) => { return (importSource: string) => regex.test(importSource) } -function createFlightServerRequest(request: string, extensions: string[]) { - return `next-flight-server-loader?${JSON.stringify({ - extensions, - })}!${request}` +function createFlightServerRequest(request: string, options: object) { + return `next-flight-server-loader?${JSON.stringify(options)}!${request}` } function hasFlightLoader(request: string, type: 'client' | 'server') { @@ -95,7 +93,12 @@ async function parseModuleInfo({ imports.push(importSource) } else { // Shared component. - imports.push(createFlightServerRequest(importSource, extensions)) + imports.push( + createFlightServerRequest(importSource, { + extensions, + client: 1, + }) + ) } } @@ -138,7 +141,7 @@ async function parseModuleInfo({ const serverImportSource = isReactImports || isBuiltinModule ? importSource - : createFlightServerRequest(importSource, extensions) + : createFlightServerRequest(importSource, { extensions }) transformedSource += importDeclarations transformedSource += JSON.stringify(serverImportSource) From 21fddb213ed404ebf16b8e205efd2a61c35e24a2 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Tue, 12 Apr 2022 21:46:15 +0200 Subject: [PATCH 4/6] fix lint errors --- .../webpack/loaders/next-flight-server-loader.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/next/build/webpack/loaders/next-flight-server-loader.ts b/packages/next/build/webpack/loaders/next-flight-server-loader.ts index dc31197f8840..804ca3ee366a 100644 --- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts @@ -81,20 +81,17 @@ async function parseModuleInfo({ return [isBuiltinModule, isNodeModuleImport] as const } - function addClientImport(importSource: string) { - if ( - isServerComponent(importSource) || - hasFlightLoader(importSource, 'server') - ) { + function addClientImport(source: string) { + if (isServerComponent(source) || hasFlightLoader(source, 'server')) { // If it's a server component, we recursively import its dependencies. - imports.push(importSource) - } else if (isClientComponent(importSource)) { + imports.push(source) + } else if (isClientComponent(source)) { // Client component. - imports.push(importSource) + imports.push(source) } else { // Shared component. imports.push( - createFlightServerRequest(importSource, { + createFlightServerRequest(source, { extensions, client: 1, }) @@ -198,6 +195,7 @@ async function parseModuleInfo({ } } } + break default: break } From abd763a62449d353ca1d5223ac6ce404a1ef7980 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Tue, 12 Apr 2022 23:33:31 +0200 Subject: [PATCH 5/6] fix linter --- .../loaders/next-flight-server-loader.ts | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/packages/next/build/webpack/loaders/next-flight-server-loader.ts b/packages/next/build/webpack/loaders/next-flight-server-loader.ts index 804ca3ee366a..1b4c2a773faf 100644 --- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts @@ -67,31 +67,31 @@ async function parseModuleInfo({ let imports = [] let __N_SSP = false let pageRuntime = null + let isBuiltinModule + let isNodeModuleImport const isEsm = type === 'Module' - async function getModuleType(importSource: string) { - const isBuiltinModule = builtinModules.includes(importSource) - const resolvedPath = isBuiltinModule - ? importSource - : await resolver(importSource) + async function getModuleType(path: string) { + const isBuiltinModule = builtinModules.includes(path) + const resolvedPath = isBuiltinModule ? path : await resolver(path) const isNodeModuleImport = resolvedPath.includes('/node_modules/') return [isBuiltinModule, isNodeModuleImport] as const } - function addClientImport(source: string) { - if (isServerComponent(source) || hasFlightLoader(source, 'server')) { + function addClientImport(path: string) { + if (isServerComponent(path) || hasFlightLoader(path, 'server')) { // If it's a server component, we recursively import its dependencies. - imports.push(source) - } else if (isClientComponent(source)) { + imports.push(path) + } else if (isClientComponent(path)) { // Client component. - imports.push(source) + imports.push(path) } else { // Shared component. imports.push( - createFlightServerRequest(source, { + createFlightServerRequest(path, { extensions, client: 1, }) @@ -105,7 +105,7 @@ async function parseModuleInfo({ case 'ImportDeclaration': const importSource = node.source.value - const [isBuiltinModule, isNodeModuleImport] = await getModuleType( + ;[isBuiltinModule, isNodeModuleImport] = await getModuleType( importSource ) @@ -186,12 +186,10 @@ async function parseModuleInfo({ if (isClientCompilation) { if (node.source) { // export { ... } from '...' - const importSource = node.source.value - const [isBuiltinModule, isNodeModuleImport] = await getModuleType( - importSource - ) + const path = node.source.value + ;[isBuiltinModule, isNodeModuleImport] = await getModuleType(path) if (!isBuiltinModule && !isNodeModuleImport) { - addClientImport(importSource) + addClientImport(path) } } } From d3c8324760ec16401f86bade4759b6ee3ce212d2 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Wed, 13 Apr 2022 00:40:25 +0200 Subject: [PATCH 6/6] fix lint errors --- .../build/webpack/loaders/next-flight-server-loader.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/next/build/webpack/loaders/next-flight-server-loader.ts b/packages/next/build/webpack/loaders/next-flight-server-loader.ts index 1b4c2a773faf..8e37149a3f7e 100644 --- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts @@ -73,12 +73,12 @@ async function parseModuleInfo({ const isEsm = type === 'Module' async function getModuleType(path: string) { - const isBuiltinModule = builtinModules.includes(path) - const resolvedPath = isBuiltinModule ? path : await resolver(path) + const isBuiltinModule_ = builtinModules.includes(path) + const resolvedPath = isBuiltinModule_ ? path : await resolver(path) - const isNodeModuleImport = resolvedPath.includes('/node_modules/') + const isNodeModuleImport_ = resolvedPath.includes('/node_modules/') - return [isBuiltinModule, isNodeModuleImport] as const + return [isBuiltinModule_, isNodeModuleImport_] as const } function addClientImport(path: string) {