diff --git a/packages/next/build/webpack/loaders/next-flight-client-loader.ts b/packages/next/build/webpack/loaders/next-flight-client-loader.ts index 18b370e7a98dbb0..326fe9589ab26f9 100644 --- a/packages/next/build/webpack/loaders/next-flight-client-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-client-loader.ts @@ -5,10 +5,13 @@ * LICENSE file in the root directory of this source tree. */ +import { promisify } from 'util' + import { parse } from '../../swc' import { buildExports } from './utils' function addExportNames(names: string[], node: any) { + if (!node) return switch (node.type) { case 'Identifier': names.push(node.value) @@ -40,20 +43,33 @@ function addExportNames(names: string[], node: any) { } } -async function parseModuleInfo( +async function collectExports( resourcePath: string, transformedSource: string, - names: Array -): Promise { + { + resolve, + loadModule, + }: { + resolve: (request: string) => Promise + loadModule: (request: string) => Promise + } +) { + const names: string[] = [] + + // Next.js built-in client components + if (/[\\/]next[\\/](link|image)\.js$/.test(resourcePath)) { + names.push('default') + } + const { body } = await parse(transformedSource, { filename: resourcePath, isModule: 'unknown', }) + for (let i = 0; i < body.length; i++) { const node = body[i] + switch (node.type) { - // TODO: support export * from module path - // case 'ExportAllDeclaration': case 'ExportDefaultExpression': case 'ExportDefaultDeclaration': names.push('default') @@ -69,10 +85,10 @@ async function parseModuleInfo( addExportNames(names, node.declaration.id) } } - if (node.specificers) { - const specificers = node.specificers - for (let j = 0; j < specificers.length; j++) { - addExportNames(names, specificers[j].exported) + if (node.specifiers) { + const specifiers = node.specifiers + for (let j = 0; j < specifiers.length; j++) { + addExportNames(names, specifiers[j].exported) } } break @@ -96,30 +112,48 @@ async function parseModuleInfo( } break } + case 'ExportAllDeclaration': + if (node.exported) { + addExportNames(names, node.exported) + break + } + + const reexportedFromResourcePath = await resolve(node.source.value) + const reexportedFromResourceSource = await loadModule( + reexportedFromResourcePath + ) + + names.push( + ...(await collectExports( + reexportedFromResourcePath, + reexportedFromResourceSource, + { resolve, loadModule } + )) + ) + continue default: break } } + + return names } export default async function transformSource( this: any, source: string ): Promise { - const { resourcePath } = this + const { resourcePath, resolve, loadModule, context } = this const transformedSource = source if (typeof transformedSource !== 'string') { throw new Error('Expected source to have been transformed to a string.') } - const names: string[] = [] - await parseModuleInfo(resourcePath, transformedSource, names) - - // Next.js built-in client components - if (/[\\/]next[\\/](link|image)\.js$/.test(resourcePath)) { - names.push('default') - } + const names = await collectExports(resourcePath, transformedSource, { + resolve: (...args) => promisify(resolve)(context, ...args), + loadModule: promisify(loadModule), + }) const moduleRefDef = "const MODULE_REFERENCE = Symbol.for('react.module.reference');\n" diff --git a/packages/next/build/webpack/plugins/flight-manifest-plugin.ts b/packages/next/build/webpack/plugins/flight-manifest-plugin.ts index a2c0b9b55e20d79..dac02a1bb60c456 100644 --- a/packages/next/build/webpack/plugins/flight-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/flight-manifest-plugin.ts @@ -67,7 +67,7 @@ export class FlightManifestPlugin { const isClientComponent = createClientComponentFilter(this.pageExtensions) compilation.chunkGroups.forEach((chunkGroup: any) => { function recordModule(id: string, _chunk: any, mod: any) { - const resource = mod.resource?.replace(/\?__sc_client__$/, '') + const resource = mod.resource // TODO: Hook into deps instead of the target module. // That way we know by the type of dep whether to include. diff --git a/test/integration/react-streaming-and-server-components/app/components/export-all/index.client.js b/test/integration/react-streaming-and-server-components/app/components/export-all/index.client.js new file mode 100644 index 000000000000000..1087c9872326493 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/components/export-all/index.client.js @@ -0,0 +1 @@ +export * from './one' diff --git a/test/integration/react-streaming-and-server-components/app/components/export-all/one.js b/test/integration/react-streaming-and-server-components/app/components/export-all/one.js new file mode 100644 index 000000000000000..c06971f1cbd5ddb --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/components/export-all/one.js @@ -0,0 +1,6 @@ +export function One() { + return 'one' +} + +export * from './two' +export { Two as TwoAliased } from './two' diff --git a/test/integration/react-streaming-and-server-components/app/components/export-all/two.js b/test/integration/react-streaming-and-server-components/app/components/export-all/two.js new file mode 100644 index 000000000000000..efcee31004001bb --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/components/export-all/two.js @@ -0,0 +1,3 @@ +export function Two() { + return 'two' +} diff --git a/test/integration/react-streaming-and-server-components/app/pages/various-exports.server.js b/test/integration/react-streaming-and-server-components/app/pages/various-exports.server.js index 7d8a12fe2968f1e..ef49fd829ffe823 100644 --- a/test/integration/react-streaming-and-server-components/app/pages/various-exports.server.js +++ b/test/integration/react-streaming-and-server-components/app/pages/various-exports.server.js @@ -4,9 +4,12 @@ import { a, b, c, d, e } from '../components/shared-exports' import DefaultArrow, { Named as ClientNamed, } from '../components/client-exports.client' + import { Cjs as CjsShared } from '../components/cjs' import { Cjs as CjsClient } from '../components/cjs.client' +import { One, Two, TwoAliased } from '../components/export-all/index.client' + export default function Page() { return (
@@ -29,6 +32,9 @@ export default function Page() {
+
+ Export All: , , +
) } 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 cb5e0537d15104a..a9f043f4e162a35 100644 --- a/test/integration/react-streaming-and-server-components/test/rsc.js +++ b/test/integration/react-streaming-and-server-components/test/rsc.js @@ -218,6 +218,7 @@ export default function (context, { runtime, env }) { expect(hydratedContent).toContain('named.client') expect(hydratedContent).toContain('cjs-shared') expect(hydratedContent).toContain('cjs-client') + expect(hydratedContent).toContain('Export All: one, two, two') }) it('should handle 404 requests and missing routes correctly', async () => {