Skip to content

Commit 5166122

Browse files
authoredJun 13, 2024
fix(cjs): handle re-exports from relative paths
1 parent 531fafa commit 5166122

File tree

4 files changed

+50
-25
lines changed

4 files changed

+50
-25
lines changed
 

‎src/@types/module.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ declare module 'module' {
3030

3131
export function _resolveFilename(
3232
request: string,
33-
parent: Parent,
33+
parent: Parent | undefined,
3434
isMain: boolean,
3535
options?: Record<PropertyKey, unknown>,
3636
): string;

‎src/cjs/api/module-resolve-filename.ts

+21-6
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,36 @@ import { createImplicitResolver } from './resolve-implicit-extensions.js';
1111

1212
const nodeModulesPath = `${path.sep}node_modules${path.sep}`;
1313

14-
export const interopCjsExports = (
14+
const getOriginalFilePath = (
1515
request: string,
1616
) => {
1717
if (!request.startsWith('data:text/javascript,')) {
18-
return request;
18+
return;
1919
}
2020

2121
const queryIndex = request.indexOf('?');
2222
if (queryIndex === -1) {
23-
return request;
23+
return;
2424
}
2525

2626
const searchParams = new URLSearchParams(request.slice(queryIndex + 1));
2727
const filePath = searchParams.get('filePath');
28+
if (filePath) {
29+
return filePath;
30+
}
31+
};
32+
33+
export const interopCjsExports = (
34+
request: string,
35+
) => {
36+
const filePath = getOriginalFilePath(request);
2837
if (filePath) {
2938
// The CJS module cache needs to be updated with the actual path for export parsing to work
3039
// https://github.com/nodejs/node/blob/v22.2.0/lib/internal/modules/esm/translators.js#L338
3140
Module._cache[filePath] = Module._cache[request];
3241
delete Module._cache[request];
3342
request = filePath;
3443
}
35-
3644
return request;
3745
};
3846

@@ -42,7 +50,7 @@ export const interopCjsExports = (
4250
const resolveTsFilename = (
4351
resolve: SimpleResolve,
4452
request: string,
45-
parent: Module.Parent,
53+
parent: Module.Parent | undefined,
4654
) => {
4755
if (
4856
!(parent?.filename && tsExtensionsPattern.test(parent.filename))
@@ -73,7 +81,7 @@ const resolveTsFilename = (
7381

7482
const resolveRequest = (
7583
request: string,
76-
parent: Module.Parent,
84+
parent: Module.Parent | undefined,
7785
resolve: SimpleResolve,
7886
) => {
7987
// Support file protocol
@@ -175,6 +183,13 @@ export const createResolveFilename = (
175183

176184
request = interopCjsExports(request);
177185

186+
if (parent?.filename) {
187+
const filePath = getOriginalFilePath(parent.filename);
188+
if (filePath) {
189+
parent.filename = filePath.split('?')[0];
190+
}
191+
}
192+
178193
// Strip query string
179194
const requestAndQuery = request.split('?');
180195
const searchParams = new URLSearchParams(requestAndQuery[1]);

‎src/esm/hook/load.ts

-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ export const load: LoadHook = async (
100100

101101
const filePathWithNamespace = urlNamespace ? `${filePath}?namespace=${encodeURIComponent(urlNamespace)}` : filePath;
102102

103-
// TODO: re-exports from relative paths cant get detected because of the data URL
104103
loaded.responseURL = `data:text/javascript,${encodeURIComponent(transformed.code)}?filePath=${encodeURIComponent(filePathWithNamespace)}`;
105104
return loaded;
106105
}

‎tests/specs/api.ts

+28-17
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,25 @@ const tsFiles = {
2424
export const foo = \`foo \${bar}\` as string
2525
export const async = setTimeout(10).then(() => require('./async')).catch((error) => error);
2626
`,
27-
'exports-no.cts': `
28-
// Supports decorators
29-
const log = (target, key, descriptor) => descriptor;
30-
class Example {
31-
@log
32-
greet() {}
33-
}
34-
console.log("cts loaded" as string)
35-
`,
36-
'exports-yes.cts': 'module.exports.cts = require("./esm-syntax.js").default as string',
37-
'esm-syntax.js': 'export default "cts export"',
27+
28+
cjs: {
29+
'exports-no.cts': `
30+
// Supports decorators
31+
const log = (target, key, descriptor) => descriptor;
32+
class Example {
33+
@log
34+
greet() {}
35+
}
36+
console.log("cts loaded" as string)
37+
`,
38+
'exports-yes.cts': 'module.exports = require("./reexport.cjs") as string',
39+
'esm-syntax.js': 'export const esmSyntax = "esm syntax"',
40+
'reexport.cjs': `
41+
exports.cjsReexport = "cjsReexport";
42+
exports.esmSyntax = require("./esm-syntax.js").esmSyntax;
43+
`,
44+
},
45+
3846
'bar.ts': 'export type A = 1; export { bar } from "pkg"',
3947
'async.ts': 'export default "async"',
4048
'json.json': JSON.stringify({ json: 'json' }),
@@ -240,7 +248,10 @@ export default testSuite(({ describe }, node: NodeApis) => {
240248
test('cli', async () => {
241249
await using fixture = await createFixture({
242250
'package.json': createPackageJson({ type: 'module' }),
243-
'index.ts': 'import { message } from \'./file\';\n\nconsole.log(message, new Error().stack);',
251+
'index.ts': `
252+
import { message } from "./file";
253+
console.log(message, new Error().stack);
254+
`,
244255
...tsFiles,
245256
});
246257

@@ -249,7 +260,7 @@ export default testSuite(({ describe }, node: NodeApis) => {
249260
nodeOptions: [node.supports.moduleRegister ? '--import' : '--loader', tsxEsmPath],
250261
});
251262
expect(stdout).toContain('foo bar');
252-
expect(stdout).toContain('index.ts:3:22');
263+
expect(stdout).toContain('index.ts:3:27');
253264
});
254265

255266
if (node.supports.moduleRegister) {
@@ -526,10 +537,10 @@ export default testSuite(({ describe }, node: NodeApis) => {
526537
console.log(message);
527538
528539
// Loads cts vis CJS namespace even if there are no exports
529-
await tsImport('./exports-no.cts', import.meta.url).catch((error) => console.log(error.constructor.name))
540+
await tsImport('./cjs/exports-no.cts', import.meta.url).catch((error) => console.log(error.constructor.name))
530541
531-
const cts = await tsImport('./exports-yes.cts', import.meta.url).then(m => m.cts, err => err.constructor.name);
532-
console.log(cts);
542+
const cjsExport = await tsImport('./cjs/exports-yes.cts', import.meta.url).then(({ cjsReexport, esmSyntax }) => \`\${cjsReexport} \${esmSyntax}\`, err => err.constructor.name);
543+
console.log(cjsExport);
533544
534545
const { message: message2 } = await tsImport('./file.ts?with-query', import.meta.url);
535546
console.log(message2);
@@ -549,7 +560,7 @@ export default testSuite(({ describe }, node: NodeApis) => {
549560
});
550561

551562
if (node.supports.cjsInterop) {
552-
expect(stdout).toMatch(/Fails as expected 1\nfoo bar json file\.ts\?tsx-namespace=\d+\ncts loaded\ncts export\nfoo bar json file\.ts\?with-query=&tsx-namespace=\d+\nFails as expected 2/);
563+
expect(stdout).toMatch(/Fails as expected 1\nfoo bar json file\.ts\?tsx-namespace=\d+\ncts loaded\ncjsReexport esm syntax\nfoo bar json file\.ts\?with-query=&tsx-namespace=\d+\nFails as expected 2/);
553564
} else {
554565
expect(stdout).toMatch(/Fails as expected 1\nfoo bar json file\.ts\?tsx-namespace=\d+\nSyntaxError\nSyntaxError\nfoo bar json file\.ts\?with-query=&tsx-namespace=\d+\nFails as expected 2/);
555566
}

0 commit comments

Comments
 (0)
Please sign in to comment.