Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: isolated cts import in Node v18 #605

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/esm/api/ts-import.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { register as cjsRegister } from '../../cjs/api/index.js';
import { isFeatureSupported, esmLoadReadFile } from '../../utils/node-features.js';
import { register, type TsconfigOptions } from './register.js';

type Options = {
parentURL: string;
onImport?: (url: string) => void;
tsconfig?: TsconfigOptions;
};

const commonjsPattern = /[/\\].+\.(?:cts|cjs)(?:$|\?)/;
const tsImport = (
specifier: string,
options: string | Options,
Expand All @@ -22,10 +25,23 @@ const tsImport = (
const namespace = Date.now().toString();

// Keep registered for hanging require() calls
cjsRegister({
const cjs = cjsRegister({
namespace,
});

/**
* In Node v18, the loader doesn't support reading the CommonJS from
* a data URL, so it can't actually relay the namespace. This is a workaround
* to preemptively determine whether the file is a CommonJS file, and shortcut
* to using the CommonJS loader instead of going through the ESM loader first
*/
if (
!isFeatureSupported(esmLoadReadFile)
&& commonjsPattern.test(specifier)
) {
return Promise.resolve(cjs.require(specifier, parentURL));
}

/**
* We don't want to unregister this after load since there can be child import() calls
* that need TS support
Expand Down
53 changes: 25 additions & 28 deletions tests/specs/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,8 +662,11 @@ export default testSuite(({ describe }, node: NodeApis) => {
// Loads cts vis CJS namespace even if there are no exports
await tsImport('./cjs/exports-no.cts', import.meta.url).catch((error) => console.log(error.constructor.name))

const cjsExport = await tsImport('./cjs/exports-yes.cts', import.meta.url).then(({ cjsReexport, esmSyntax }) => \`\${cjsReexport} \${esmSyntax}\`, err => err.constructor.name);
console.log(cjsExport);
const cts = await tsImport('./cjs/exports-yes.cts', import.meta.url).then(({ cjsReexport, esmSyntax }) => \`\${cjsReexport} \${esmSyntax}\`, err => err.constructor.name);
console.log(cts);

const cjs = await tsImport('./cjs/reexport.cjs?query', import.meta.url).then(({ cjsReexport, esmSyntax }) => \`\${cjsReexport} \${esmSyntax}\`, err => err.constructor.name);
console.log(cjs);

const { message: message2 } = await tsImport('./file.ts?with-query', import.meta.url);
console.log(message2);
Expand All @@ -682,11 +685,15 @@ export default testSuite(({ describe }, node: NodeApis) => {
nodeOptions: [],
});

if (node.supports.cjsInterop) {
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/);
} else {
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/);
}
expect(stdout).toMatch(new RegExp([
'Fails as expected 1',
String.raw`foo bar json file\.ts\?tsx-namespace=\d+`,
'cts loaded',
'cjsReexport esm syntax',
'cjsReexport esm syntax',
String.raw`foo bar json file\.ts\?with-query=&tsx-namespace=\d+`,
'Fails as expected 2',
].join(String.raw`\n`)));
});

test('commonjs', async () => {
Expand All @@ -706,10 +713,10 @@ export default testSuite(({ describe }, node: NodeApis) => {
const { message: message2 } = await tsImport('./file.ts?with-query', __filename);
console.log(message2);

const cts = await tsImport('./cjs/exports-yes.cts', __filename).then(({ cjsReexport, esmSyntax }) => \`\${cjsReexport} \${esmSyntax}\`, err => err.constructor.name);
const cts = await tsImport('./cjs/exports-yes.cts?query', __filename).then(({ cjsReexport, esmSyntax }) => \`\${cjsReexport} \${esmSyntax}\`, err => err.constructor.name);
console.log(cts);

const cjs = await tsImport('./cjs/reexport.cjs', __filename).then(({ cjsReexport, esmSyntax }) => \`\${cjsReexport} \${esmSyntax}\`, err => err.constructor.name);
const cjs = await tsImport('./cjs/reexport.cjs?query', __filename).then(({ cjsReexport, esmSyntax }) => \`\${cjsReexport} \${esmSyntax}\`, err => err.constructor.name);
console.log(cjs);

// Global not polluted
Expand All @@ -725,25 +732,15 @@ export default testSuite(({ describe }, node: NodeApis) => {
nodePath: node.path,
nodeOptions: [],
});
if (node.supports.cjsInterop) {
expect(stdout).toMatch(new RegExp(
`${String.raw`Fails as expected 1\n`
+ String.raw`foo bar json file\.ts\?tsx-namespace=\d+\n`
+ String.raw`foo bar json file\.ts\?with-query=&tsx-namespace=\d+\n`
+ String.raw`cjsReexport esm syntax\n`
+ String.raw`cjsReexport esm syntax\n`
}Fails as expected 2`,
));
} else {
expect(stdout).toMatch(new RegExp(
`${String.raw`Fails as expected 1\n`
+ String.raw`foo bar json file\.ts\?tsx-namespace=\d+\n`
+ String.raw`foo bar json file\.ts\?with-query=&tsx-namespace=\d+\n`
+ String.raw`SyntaxError\n`
+ String.raw`Error\n`
}Fails as expected 2`,
));
}

expect(stdout).toMatch(new RegExp([
'Fails as expected 1',
String.raw`foo bar json file\.ts\?tsx-namespace=\d+`,
String.raw`foo bar json file\.ts\?with-query=&tsx-namespace=\d+`,
'cjsReexport esm syntax',
'cjsReexport esm syntax',
'Fails as expected 2',
].join(String.raw`\n`)));
});

test('mts from commonjs', async () => {
Expand Down
Loading