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,
},
);