Skip to content

Commit

Permalink
fix(esm/api): tsImport() to parse CJS exports
Browse files Browse the repository at this point in the history
fixes #575
  • Loading branch information
privatenumber authored Jun 7, 2024
1 parent 0eb4e91 commit 0a78bfd
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 18 deletions.
40 changes: 27 additions & 13 deletions src/esm/hook/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { parent } from '../../utils/ipc/client.js';
import type { Message } from '../types.js';
import { fileMatcher } from '../../utils/tsconfig.js';
import { isJsonPattern, tsExtensionsPattern } from '../../utils/path-utils.js';
import { parseEsm } from '../../utils/es-module-lexer.js';
import { getNamespace } from './utils.js';
import { data } from './initialize.js';

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

// CJS Annotations for Node
platform: 'node',
// TODO: disable source maps
},
);

const parameters = new URLSearchParams({ filePath });
if (urlNamespace) {
parameters.set('namespace', urlNamespace);
}
loaded.responseURL = `data:text/javascript,${encodeURIComponent(transformed.code)}?${parameters.toString()}`;
return loaded;
}

Expand Down
5 changes: 4 additions & 1 deletion src/utils/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { writeSync } from 'node:fs';
export const log = (
...args: any[]
) => {
writeSync(1, `${inspect(args, { colors: true })}\n\n`);
writeSync(
1,
`${args.map(argument => inspect(argument, { colors: true })).join(' ')}\n\n`,
);
};

export const time = <T extends (...args: any[]) => unknown>(
Expand Down
13 changes: 9 additions & 4 deletions tests/specs/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ const tsFiles = {
export const foo = \`foo \${bar}\` as string
export const async = setTimeout(10).then(() => require('./async')).catch((error) => error);
`,
'cts.cts': 'export const cts = \'cts\' as string',
'exports-no.cts': 'console.log("cts loaded" as string)',
'exports-yes.cts': 'module.exports.cts = require("./esm-syntax.js").default as string',
'esm-syntax.js': 'export default "cts export"',
'bar.ts': 'export type A = 1; export { bar } from "pkg"',
'async.ts': 'export default "async"',
'node_modules/pkg': {
Expand Down Expand Up @@ -508,7 +510,10 @@ export default testSuite(({ describe }, node: NodeApis) => {
const { message } = await tsImport('./file.ts', import.meta.url);
console.log(message);
const cts = await tsImport('./cts.cts', import.meta.url).then(m => m.cts, err => err.constructor.name);
// Loads cts vis CJS namespace even if there are no exports
await tsImport('./exports-no.cts', import.meta.url).catch((error) => console.log(error.constructor.name))
const cts = await tsImport('./exports-yes.cts', import.meta.url).then(m => m.cts, err => err.constructor.name);
console.log(cts);
const { message: message2 } = await tsImport('./file.ts?with-query', import.meta.url);
Expand All @@ -528,9 +533,9 @@ export default testSuite(({ describe }, node: NodeApis) => {
});

if (node.supports.cjsInterop) {
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/);
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/);
} else {
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/);
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/);
}
});

Expand Down

0 comments on commit 0a78bfd

Please sign in to comment.