Skip to content

Commit 15bb8ce

Browse files
authored
fix: force module format for virtual client-proxy file (vercel#74162)
Fixes vercel#74062 (`jotai` ran into this error [when they added `"type": "commonjs"` to their package.json](pmndrs/jotai#2579 (reply in thread))) > this is a bug in the transform we do when a `'use client'` directive is encountered. I think what's happening is that we're creating a virtual file that uses ESM import/export syntax, but it's called proxy.js (not proxy.mjs), so the `"type": "commonjs" `makes turbopack "correctly" upset at the ESM syntax. vercel#74062 (comment) The (slightly kludgy) solution is to use `proxy.mjs` or `proxy.cjs` to force the module format, bypassing the issue where `proxy.js` changes meaning depending on `package.json#type`.
1 parent 450a04f commit 15bb8ce

File tree

14 files changed

+131
-1
lines changed

14 files changed

+131
-1
lines changed

crates/next-core/src/next_client_reference/ecmascript_client_reference/ecmascript_client_reference_proxy_module.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,13 @@ impl EcmascriptClientReferenceProxyModule {
7070
#[turbo_tasks::function]
7171
async fn proxy_module(&self) -> Result<Vc<Box<dyn EcmascriptChunkPlaceable>>> {
7272
let mut code = CodeBuilder::default();
73+
let is_esm: bool;
7374

7475
let server_module_path = &*self.server_module_ident.to_string().await?;
7576

7677
// Adapted from https://github.com/facebook/react/blob/c5b9375767e2c4102d7e5559d383523736f1c902/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeLoader.js#L323-L354
7778
if let EcmascriptExports::EsmExports(exports) = &*self.client_module.get_exports().await? {
79+
is_esm = true;
7880
let exports = exports.expand_exports().await?;
7981

8082
if !exports.dynamic_exports.is_empty() {
@@ -130,6 +132,7 @@ impl EcmascriptClientReferenceProxyModule {
130132
}
131133
}
132134
} else {
135+
is_esm = false;
133136
writedoc!(
134137
code,
135138
r#"
@@ -146,7 +149,13 @@ impl EcmascriptClientReferenceProxyModule {
146149
AssetContent::file(File::from(code.source_code().clone()).into());
147150

148151
let proxy_source = VirtualSource::new(
149-
self.server_module_ident.path().join("proxy.js".into()),
152+
self.server_module_ident.path().join(
153+
// Depending on the original format, we call the file `proxy.mjs` or `proxy.cjs`.
154+
// This is because we're placing the virtual module next to the original code, so
155+
// its parsing will be affected by `type` fields in package.json --
156+
// a bare `proxy.js` may end up being unexpectedly parsed as the wrong format.
157+
format!("proxy.{}", if is_esm { "mjs" } else { "cjs" }).into(),
158+
),
150159
proxy_module_content,
151160
);
152161

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as React from 'react'
2+
3+
import EsmFromCjs from 'lib-cjs'
4+
5+
export default function Page() {
6+
return (
7+
<p>
8+
lib-cjs: <EsmFromCjs />
9+
</p>
10+
)
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as React from 'react'
2+
3+
import EsmFromEsm from 'lib-esm'
4+
5+
export default function Page() {
6+
return (
7+
<p>
8+
lib-esm: <EsmFromEsm />
9+
</p>
10+
)
11+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { ReactNode } from 'react'
2+
export default function Root({ children }: { children: ReactNode }) {
3+
return (
4+
<html>
5+
<body>{children}</body>
6+
</html>
7+
)
8+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as React from 'react'
2+
3+
const CjsFromCjs = require('lib-cjs')
4+
5+
export default function Page() {
6+
return (
7+
<p>
8+
lib-cjs: <CjsFromCjs />
9+
</p>
10+
)
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as React from 'react'
2+
3+
const CjsFromEsm = require('lib-esm')
4+
5+
export default function Page() {
6+
return (
7+
<p>
8+
lib-esm: <CjsFromEsm />
9+
</p>
10+
)
11+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { nextTestSetup } from 'e2e-utils'
2+
3+
describe('esm-client-module-without-exports', () => {
4+
const { next } = nextTestSetup({
5+
files: __dirname,
6+
})
7+
8+
describe('"type": "commonjs" in package.json', () => {
9+
it('should render without errors: import cjs', async () => {
10+
const $ = await next.render$('/import-cjs')
11+
expect($('p').text()).toContain('lib-cjs: esm')
12+
})
13+
14+
it('should render without errors: require cjs', async () => {
15+
const $ = await next.render$('/require-cjs')
16+
expect($('p').text()).toContain('lib-cjs: cjs')
17+
})
18+
})
19+
20+
describe('"type": "module" in package.json', () => {
21+
it('should render without errors: import esm', async () => {
22+
const $ = await next.render$('/import-esm')
23+
expect($('p').text()).toContain('lib-esm: esm')
24+
})
25+
26+
it('should render without errors: require esm', async () => {
27+
const $ = await next.render$('/require-esm')
28+
expect($('p').text()).toContain('lib-esm: cjs')
29+
})
30+
})
31+
})
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* @type {import('next').NextConfig}
3+
*/
4+
const nextConfig = {}
5+
6+
module.exports = nextConfig

test/e2e/app-dir/client-module-with-package-type/node_modules/lib-cjs/index.js

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/e2e/app-dir/client-module-with-package-type/node_modules/lib-cjs/index.mjs

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/e2e/app-dir/client-module-with-package-type/node_modules/lib-cjs/package.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/e2e/app-dir/client-module-with-package-type/node_modules/lib-esm/index.cjs

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/e2e/app-dir/client-module-with-package-type/node_modules/lib-esm/index.js

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/e2e/app-dir/client-module-with-package-type/node_modules/lib-esm/package.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)