Skip to content

Commit 0a78bfd

Browse files
authoredJun 7, 2024··
fix(esm/api): tsImport() to parse CJS exports
fixes #575
1 parent 0eb4e91 commit 0a78bfd

File tree

3 files changed

+40
-18
lines changed

3 files changed

+40
-18
lines changed
 

‎src/esm/hook/load.ts

+27-13
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { parent } from '../../utils/ipc/client.js';
1010
import type { Message } from '../types.js';
1111
import { fileMatcher } from '../../utils/tsconfig.js';
1212
import { isJsonPattern, tsExtensionsPattern } from '../../utils/path-utils.js';
13-
import { parseEsm } from '../../utils/es-module-lexer.js';
1413
import { getNamespace } from './utils.js';
1514
import { data } from './initialize.js';
1615

@@ -69,20 +68,35 @@ export const load: LoadHook = async (
6968
loaded.format === 'commonjs'
7069
&& isFeatureSupported(esmLoadReadFile)
7170
&& loaded.responseURL?.startsWith('file:') // Could be data:
71+
&& !filePath.endsWith('.cjs') // CJS syntax doesn't need to be transformed for interop
7272
) {
73-
const code = await readFile(new URL(url), 'utf8');
74-
const [, exports] = parseEsm(code);
75-
if (exports.length > 0) {
76-
const cjsExports = `module.exports={${
77-
exports.map(exported => exported.n).filter(name => name !== 'default').join(',')
78-
}}`;
79-
const parameters = new URLSearchParams({ filePath });
80-
if (urlNamespace) {
81-
parameters.set('namespace', urlNamespace);
82-
}
83-
loaded.responseURL = `data:text/javascript,${encodeURIComponent(cjsExports)}?${parameters.toString()}`;
84-
}
73+
/**
74+
* es or cjs module lexer unfortunately cannot be used because it doesn't support
75+
* typescript syntax
76+
*
77+
* While the full code is transformed, only the exports are used for parsing.
78+
* In fact, the code can't even run because imports cannot be resolved relative
79+
* from the data: URL.
80+
*
81+
* TODO: extract exports only
82+
*/
83+
const transformed = await transform(
84+
await readFile(new URL(url), 'utf8'),
85+
filePath,
86+
{
87+
format: 'cjs',
8588

89+
// CJS Annotations for Node
90+
platform: 'node',
91+
// TODO: disable source maps
92+
},
93+
);
94+
95+
const parameters = new URLSearchParams({ filePath });
96+
if (urlNamespace) {
97+
parameters.set('namespace', urlNamespace);
98+
}
99+
loaded.responseURL = `data:text/javascript,${encodeURIComponent(transformed.code)}?${parameters.toString()}`;
86100
return loaded;
87101
}
88102

‎src/utils/debug.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import { writeSync } from 'node:fs';
44
export const log = (
55
...args: any[]
66
) => {
7-
writeSync(1, `${inspect(args, { colors: true })}\n\n`);
7+
writeSync(
8+
1,
9+
`${args.map(argument => inspect(argument, { colors: true })).join(' ')}\n\n`,
10+
);
811
};
912

1013
export const time = <T extends (...args: any[]) => unknown>(

‎tests/specs/api.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ const tsFiles = {
2323
export const foo = \`foo \${bar}\` as string
2424
export const async = setTimeout(10).then(() => require('./async')).catch((error) => error);
2525
`,
26-
'cts.cts': 'export const cts = \'cts\' as string',
26+
'exports-no.cts': 'console.log("cts loaded" as string)',
27+
'exports-yes.cts': 'module.exports.cts = require("./esm-syntax.js").default as string',
28+
'esm-syntax.js': 'export default "cts export"',
2729
'bar.ts': 'export type A = 1; export { bar } from "pkg"',
2830
'async.ts': 'export default "async"',
2931
'node_modules/pkg': {
@@ -508,7 +510,10 @@ export default testSuite(({ describe }, node: NodeApis) => {
508510
const { message } = await tsImport('./file.ts', import.meta.url);
509511
console.log(message);
510512
511-
const cts = await tsImport('./cts.cts', import.meta.url).then(m => m.cts, err => err.constructor.name);
513+
// Loads cts vis CJS namespace even if there are no exports
514+
await tsImport('./exports-no.cts', import.meta.url).catch((error) => console.log(error.constructor.name))
515+
516+
const cts = await tsImport('./exports-yes.cts', import.meta.url).then(m => m.cts, err => err.constructor.name);
512517
console.log(cts);
513518
514519
const { message: message2 } = await tsImport('./file.ts?with-query', import.meta.url);
@@ -528,9 +533,9 @@ export default testSuite(({ describe }, node: NodeApis) => {
528533
});
529534

530535
if (node.supports.cjsInterop) {
531-
expect(stdout).toMatch(/Fails as expected 1\nfoo bar file\.ts\?tsx-namespace=\d+\ncts\nfoo bar file\.ts\?with-query=&tsx-namespace=\d+\nFails as expected 2/);
536+
expect(stdout).toMatch(/Fails as expected 1\nfoo bar file\.ts\?tsx-namespace=\d+\ncts loaded\ncts export\nfoo bar file\.ts\?with-query=&tsx-namespace=\d+\nFails as expected 2/);
532537
} else {
533-
expect(stdout).toMatch(/Fails as expected 1\nfoo bar file\.ts\?tsx-namespace=\d+\nSyntaxError\nfoo bar file\.ts\?with-query=&tsx-namespace=\d+\nFails as expected 2/);
538+
expect(stdout).toMatch(/Fails as expected 1\nfoo bar file\.ts\?tsx-namespace=\d+\nSyntaxError\nSyntaxError\nfoo bar file\.ts\?with-query=&tsx-namespace=\d+\nFails as expected 2/);
534539
}
535540
});
536541

0 commit comments

Comments
 (0)
Please sign in to comment.