From 56523874d505bed71aff1bd803119d003da08e45 Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Wed, 18 Sep 2024 15:18:49 +0200 Subject: [PATCH 1/4] Add test --- test/e2e/app-dir/app-external/app-external.test.ts | 5 +++++ .../app-external/app/esm-client-ref-external/client.js | 7 +++++++ .../app-external/app/esm-client-ref-external/page.js | 9 +++++++++ test/e2e/app-dir/app-external/next.config.js | 1 + test/e2e/app-dir/app-external/node_modules/esm/index.js | 1 + .../app-dir/app-external/node_modules/esm/package.json | 5 +++++ 6 files changed, 28 insertions(+) create mode 100644 test/e2e/app-dir/app-external/app/esm-client-ref-external/client.js create mode 100644 test/e2e/app-dir/app-external/app/esm-client-ref-external/page.js create mode 100644 test/e2e/app-dir/app-external/node_modules/esm/index.js create mode 100644 test/e2e/app-dir/app-external/node_modules/esm/package.json diff --git a/test/e2e/app-dir/app-external/app-external.test.ts b/test/e2e/app-dir/app-external/app-external.test.ts index 7faa89f365921..4959d506df187 100644 --- a/test/e2e/app-dir/app-external/app-external.test.ts +++ b/test/e2e/app-dir/app-external/app-external.test.ts @@ -264,6 +264,11 @@ describe('app dir - external dependency', () => { expect(html).toContain('hello') }) + it('should support client module references with SSR-only ESM externals', async () => { + const html = await next.render('/esm-client-ref-external') + expect(html).toContain('client external-pure-esm-lib') + }) + it('should support exporting multiple star re-exports', async () => { const html = await next.render('/wildcard') expect(html).toContain('Foo') diff --git a/test/e2e/app-dir/app-external/app/esm-client-ref-external/client.js b/test/e2e/app-dir/app-external/app/esm-client-ref-external/client.js new file mode 100644 index 0000000000000..11fe92ee0aa66 --- /dev/null +++ b/test/e2e/app-dir/app-external/app/esm-client-ref-external/client.js @@ -0,0 +1,7 @@ +'use client' + +import name from 'esm' + +export function Client() { + return
{`client ${name}`}
+} diff --git a/test/e2e/app-dir/app-external/app/esm-client-ref-external/page.js b/test/e2e/app-dir/app-external/app/esm-client-ref-external/page.js new file mode 100644 index 0000000000000..e37844b0cb6f9 --- /dev/null +++ b/test/e2e/app-dir/app-external/app/esm-client-ref-external/page.js @@ -0,0 +1,9 @@ +import { Client } from './client' + +export default function Page() { + return ( +

+ +

+ ) +} diff --git a/test/e2e/app-dir/app-external/next.config.js b/test/e2e/app-dir/app-external/next.config.js index 2be2c35d204f7..53a9abbb2dd79 100644 --- a/test/e2e/app-dir/app-external/next.config.js +++ b/test/e2e/app-dir/app-external/next.config.js @@ -5,5 +5,6 @@ module.exports = { 'conditional-exports-optout', 'dual-pkg-optout', 'transitive-external', + 'esm', ], } diff --git a/test/e2e/app-dir/app-external/node_modules/esm/index.js b/test/e2e/app-dir/app-external/node_modules/esm/index.js new file mode 100644 index 0000000000000..44ebc3bb69a55 --- /dev/null +++ b/test/e2e/app-dir/app-external/node_modules/esm/index.js @@ -0,0 +1 @@ +export default 'external-pure-esm-lib' diff --git a/test/e2e/app-dir/app-external/node_modules/esm/package.json b/test/e2e/app-dir/app-external/node_modules/esm/package.json new file mode 100644 index 0000000000000..28e738c6e30f8 --- /dev/null +++ b/test/e2e/app-dir/app-external/node_modules/esm/package.json @@ -0,0 +1,5 @@ +{ + "name": "esm", + "type": "module", + "exports": "./index.js" +} From 87e8ae2737d1ffe0a618e45e73307126155c7dbb Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Thu, 19 Sep 2024 14:38:02 +0200 Subject: [PATCH 2/4] Allow ESM externals in SSR --- crates/next-core/src/next_server/context.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/next-core/src/next_server/context.rs b/crates/next-core/src/next_server/context.rs index 2454a86713f98..8773cb8dfeaeb 100644 --- a/crates/next-core/src/next_server/context.rs +++ b/crates/next-core/src/next_server/context.rs @@ -182,8 +182,7 @@ pub async fn get_server_resolve_options_context( project_path, project_path.root(), ExternalPredicate::Only(Vc::cell(external_packages)).cell(), - // app-ssr can't have esm externals as that would make the module async on the server only - *next_config.import_externals().await? && !matches!(ty, ServerContextType::AppSSR { .. }), + *next_config.import_externals().await?, ); let mut custom_conditions = vec![mode.await?.condition().to_string().into()]; From 7b4f68150505f323cc14144e929a1599b917579d Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Thu, 19 Sep 2024 16:53:37 +0200 Subject: [PATCH 3/4] Update test assertions --- test/e2e/esm-externals/esm-externals.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/e2e/esm-externals/esm-externals.test.ts b/test/e2e/esm-externals/esm-externals.test.ts index 329b3176ac804..dc6d827129669 100644 --- a/test/e2e/esm-externals/esm-externals.test.ts +++ b/test/e2e/esm-externals/esm-externals.test.ts @@ -43,9 +43,7 @@ describe('esm-externals', () => { // App dir describe.each(['/server', '/client'])('app dir url %s', (url) => { const expectedHtml = isTurbopack - ? url === '/client' - ? 'Hello Wrong+Wrong+Alternative' - : 'Hello World+World+World' + ? 'Hello World+World+World' : 'Hello World+World+Alternative' const expectedText = isTurbopack From 1825090c59cce33c37ce0ff3457dfcf58343787c Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Thu, 19 Sep 2024 16:54:42 +0200 Subject: [PATCH 4/4] Assume SSR and browser may not both be async --- .../client_reference_manifest.rs | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/crates/next-core/src/next_manifests/client_reference_manifest.rs b/crates/next-core/src/next_manifests/client_reference_manifest.rs index 96766f3a2864a..4c15d694c7f5a 100644 --- a/crates/next-core/src/next_manifests/client_reference_manifest.rs +++ b/crates/next-core/src/next_manifests/client_reference_manifest.rs @@ -104,16 +104,6 @@ impl ClientReferenceManifest { (Vec::new(), false) }; - entry_manifest.client_modules.module_exports.insert( - get_client_reference_module_key(&server_path, "*"), - ManifestNodeEntry { - name: "*".into(), - id: (&*client_module_id).into(), - chunks: client_chunks_paths, - r#async: client_is_async, - }, - ); - if let Some(ssr_chunking_context) = ssr_chunking_context { let ssr_chunk_item = ecmascript_client_reference .ssr_module @@ -154,6 +144,19 @@ impl ClientReferenceManifest { (Vec::new(), false) }; + entry_manifest.client_modules.module_exports.insert( + get_client_reference_module_key(&server_path, "*"), + ManifestNodeEntry { + name: "*".into(), + id: (&*client_module_id).into(), + chunks: client_chunks_paths, + // This should of course be client_is_async, but SSR can become async + // due to ESM externals, and the ssr_manifest_node is currently ignored + // by React. + r#async: client_is_async || ssr_is_async, + }, + ); + let mut ssr_manifest_node = ManifestNode::default(); ssr_manifest_node.module_exports.insert( "*".into(), @@ -161,7 +164,8 @@ impl ClientReferenceManifest { name: "*".into(), id: (&*ssr_module_id).into(), chunks: ssr_chunks_paths, - r#async: ssr_is_async, + // See above + r#async: client_is_async || ssr_is_async, }, );