diff --git a/packages/plugin-rsc/src/plugins/cjs.ts b/packages/plugin-rsc/src/plugins/cjs.ts index e955fb4b..73315615 100644 --- a/packages/plugin-rsc/src/plugins/cjs.ts +++ b/packages/plugin-rsc/src/plugins/cjs.ts @@ -52,7 +52,7 @@ export function cjsModuleRunnerPlugin(): Plugin[] { } const ast = await parseAstAsync(code) - const result = transformCjsToEsm(code, ast) + const result = transformCjsToEsm(code, ast, { id }) const output = result.output return { code: output.toString(), diff --git a/packages/plugin-rsc/src/transforms/cjs.test.ts b/packages/plugin-rsc/src/transforms/cjs.test.ts index dc5a9406..d14d29fd 100644 --- a/packages/plugin-rsc/src/transforms/cjs.test.ts +++ b/packages/plugin-rsc/src/transforms/cjs.test.ts @@ -7,7 +7,7 @@ import path from 'node:path' describe(transformCjsToEsm, () => { async function testTransform(input: string) { const ast = await parseAstAsync(input) - const { output } = transformCjsToEsm(input, ast) + const { output } = transformCjsToEsm(input, ast, { id: '/test.js' }) if (!output.hasChanged()) { return } @@ -22,7 +22,8 @@ describe(transformCjsToEsm, () => { exports.ok = true; ` expect(await testTransform(input)).toMatchInlineSnapshot(` - "let exports = {}; const module = { exports }; + "let __filename = "/test.js"; let __dirname = "/"; + let exports = {}; const module = { exports }; exports.ok = true; ;__vite_ssr_exportAll__(module.exports); @@ -41,7 +42,8 @@ if (true) { } ` expect(await testTransform(input)).toMatchInlineSnapshot(` - "let exports = {}; const module = { exports }; + "let __filename = "/test.js"; let __dirname = "/"; + let exports = {}; const module = { exports }; function __cjs_interop__(m) { return m.__cjs_module_runner_transform ? m.default : m; } if (true) { module.exports = (__cjs_interop__(await import('./cjs/use-sync-external-store.production.js'))); @@ -65,7 +67,8 @@ if (true) { })() ` expect(await testTransform(input)).toMatchInlineSnapshot(` - "let exports = {}; const module = { exports }; + "let __filename = "/test.js"; let __dirname = "/"; + let exports = {}; const module = { exports }; function __cjs_interop__(m) { return m.__cjs_module_runner_transform ? m.default : m; } const __cjs_to_esm_hoist_0 = __cjs_interop__(await import("react")); const __cjs_to_esm_hoist_1 = __cjs_interop__(await import("react-dom")); @@ -95,7 +98,8 @@ function test() { } ` expect(await testTransform(input)).toMatchInlineSnapshot(` - "let exports = {}; const module = { exports }; + "let __filename = "/test.js"; let __dirname = "/"; + let exports = {}; const module = { exports }; function __cjs_interop__(m) { return m.__cjs_module_runner_transform ? m.default : m; } const __cjs_to_esm_hoist_0 = __cjs_interop__(await import("te" + "st")); const __cjs_to_esm_hoist_1 = __cjs_interop__(await import("test")); @@ -125,7 +129,8 @@ function test() { } ` expect(await testTransform(input)).toMatchInlineSnapshot(` - "let exports = {}; const module = { exports }; + "let __filename = "/test.js"; let __dirname = "/"; + let exports = {}; const module = { exports }; { const require = () => {}; require("test"); @@ -149,7 +154,7 @@ function test() { async transform(code, id) { if (id.endsWith('.cjs')) { const ast = await parseAstAsync(code) - const { output } = transformCjsToEsm(code, ast) + const { output } = transformCjsToEsm(code, ast, { id }) return { code: output.toString(), map: output.generateMap({ hires: 'boundary' }), @@ -165,6 +170,12 @@ function test() { const mod = await runner.import('/entry.mjs') expect(mod).toMatchInlineSnapshot(` { + "cjsGlobals": { + "test": [ + "string", + "string", + ], + }, "depDefault": { "a": "a", "b": "b", diff --git a/packages/plugin-rsc/src/transforms/cjs.ts b/packages/plugin-rsc/src/transforms/cjs.ts index ba14faec..c5781fec 100644 --- a/packages/plugin-rsc/src/transforms/cjs.ts +++ b/packages/plugin-rsc/src/transforms/cjs.ts @@ -2,6 +2,8 @@ import type { Program, Node } from 'estree' import MagicString from 'magic-string' import { analyze } from 'periscopic' import { walk } from 'estree-walker' +import { fileURLToPath, pathToFileURL } from 'node:url' +import path from 'node:path' // TODO: // replacing require("xxx") into import("xxx") affects Vite's resolution. @@ -14,6 +16,7 @@ const CJS_INTEROP_HELPER = `function __cjs_interop__(m) { return m.__cjs_module_ export function transformCjsToEsm( code: string, ast: Program, + options: { id: string }, ): { output: MagicString } { const output = new MagicString(code) const analyzed = analyze(ast) @@ -85,6 +88,14 @@ export function transformCjsToEsm( // https://nodejs.org/docs/v22.19.0/api/modules.html#exports-shortcut output.prepend(`let exports = {}; const module = { exports };\n`) + // https://nodejs.org/docs/v22.19.0/api/modules.html#the-module-scope + // https://github.com/vitest-dev/vitest/blob/965cefc19722a6c899cd1d3decb3cc33e72af696/packages/vite-node/src/client.ts#L548-L554 + const __filename = fileURLToPath(pathToFileURL(options.id).href) + const __dirname = path.dirname(__filename) + output.prepend( + `let __filename = ${JSON.stringify(__filename)}; let __dirname = ${JSON.stringify(__dirname)};\n`, + ) + // TODO: can we use cjs-module-lexer to properly define named exports? // for re-exports, we need to eagerly transform dependencies though. // https://github.com/nodejs/node/blob/f3adc11e37b8bfaaa026ea85c1cf22e3a0e29ae9/lib/internal/modules/esm/translators.js#L382-L409 diff --git a/packages/plugin-rsc/src/transforms/fixtures/cjs/entry.mjs b/packages/plugin-rsc/src/transforms/fixtures/cjs/entry.mjs index 191d29e4..63c854c5 100644 --- a/packages/plugin-rsc/src/transforms/fixtures/cjs/entry.mjs +++ b/packages/plugin-rsc/src/transforms/fixtures/cjs/entry.mjs @@ -5,6 +5,7 @@ import depPrimitive from './primitive.cjs' import depExports from './exports.cjs' import depFnRequire from './function-require.cjs' import dualLib from './dual-lib.cjs' +import cjsGlobals from './globals.cjs' export { depDefault, depNamespace, @@ -13,4 +14,5 @@ export { depExports, depFnRequire, dualLib, + cjsGlobals, } diff --git a/packages/plugin-rsc/src/transforms/fixtures/cjs/globals.cjs b/packages/plugin-rsc/src/transforms/fixtures/cjs/globals.cjs new file mode 100644 index 00000000..9a1c50f8 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/cjs/globals.cjs @@ -0,0 +1 @@ +exports.test = [typeof __filename, typeof __dirname]