From 1f1632979c78b3edfe59fd85d8cce62efcdee688 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Wed, 2 Mar 2022 21:57:50 +0100 Subject: [PATCH] Fix named export missing from client components (#34974) Closes #33538. ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint` --- packages/next/build/webpack-config.ts | 7 ++++++- .../webpack/loaders/next-flight-client-loader.ts | 5 +++++ .../build/webpack/plugins/flight-manifest-plugin.ts | 13 ++++++++----- .../app/components/named.client.js | 3 +++ .../app/pages/index.server.js | 5 +++++ .../test/rsc.js | 1 + 6 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 test/integration/react-streaming-and-server-components/app/components/named.client.js diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 82d9cb2d9e60..b6326c8c4add 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -1598,7 +1598,12 @@ export default async function getBaseWebpackConfig( if (!webpack5Config.optimization) { webpack5Config.optimization = {} } - webpack5Config.optimization.providedExports = false + + // For Server Components, it's necessary to have provided exports collected + // to generate the correct flight manifest. + if (!hasServerComponents) { + webpack5Config.optimization.providedExports = false + } webpack5Config.optimization.usedExports = false } 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 afe614e85906..c08689af661e 100644 --- a/packages/next/build/webpack/loaders/next-flight-client-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-client-loader.ts @@ -75,6 +75,11 @@ async function parseExportNamesInto( } } continue + case 'ExportDeclaration': + if (node.declaration?.identifier) { + addExportNames(names, node.declaration.identifier) + } + continue 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 29cbd09aac86..dbc3ecc70f5c 100644 --- a/packages/next/build/webpack/plugins/flight-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/flight-manifest-plugin.ts @@ -82,12 +82,15 @@ export class FlightManifestPlugin { const moduleExports: any = json[resource] || {} const exportsInfo = compilation.moduleGraph.getExportsInfo(mod) - const providedExports = exportsInfo.getProvidedExports() const moduleExportedKeys = ['', '*'].concat( - // TODO: improve exports detection - providedExports === true || providedExports == null - ? 'default' - : providedExports + [...exportsInfo.exports] + .map((exportInfo) => { + if (exportInfo.provided) { + return exportInfo.name + } + return null + }) + .filter(Boolean) ) moduleExportedKeys.forEach((name) => { diff --git a/test/integration/react-streaming-and-server-components/app/components/named.client.js b/test/integration/react-streaming-and-server-components/app/components/named.client.js new file mode 100644 index 000000000000..9f087bbc83c2 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/components/named.client.js @@ -0,0 +1,3 @@ +export function Named() { + return 'named export: named.client' +} diff --git a/test/integration/react-streaming-and-server-components/app/pages/index.server.js b/test/integration/react-streaming-and-server-components/app/pages/index.server.js index cec07820c64b..d5e157ca6a12 100644 --- a/test/integration/react-streaming-and-server-components/app/pages/index.server.js +++ b/test/integration/react-streaming-and-server-components/app/pages/index.server.js @@ -1,4 +1,6 @@ import Foo from '../components/foo.client' +import { Named } from '../components/named.client' + import Link from 'next/link' const envVar = process.env.ENV_VAR_TEST @@ -11,6 +13,9 @@ export default function Index({ header, router }) {
{'path:' + router.pathname}
{'env:' + envVar}
{'header:' + header}
+
+ +
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 6e8b76d8c2a4..da7130b826b3 100644 --- a/test/integration/react-streaming-and-server-components/test/rsc.js +++ b/test/integration/react-streaming-and-server-components/test/rsc.js @@ -28,6 +28,7 @@ export default function (context, { runtime, env }) { expect(homeHTML).toContain('header:test-util') expect(homeHTML).toContain('path:/') expect(homeHTML).toContain('foo.client') + expect(homeHTML).toContain('named.client') }) it('should reuse the inline flight response without sending extra requests', async () => {