Skip to content

Commit c703300

Browse files
authoredJun 7, 2024
feat(cjs/api): register() to support namespace (#35)
1 parent 4be7c7e commit c703300

10 files changed

+343
-160
lines changed
 

‎docs/node/tsx-require.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Use this function for importing TypeScript files in CommonJS mode without adding
77
Note, the current file path must be passed in as the second argument to resolve the import context.
88

99
::: warning Caveats
10-
- `import()` & asynchronous `require()` calls in the loaded files are not enhanced.
10+
- `import()` calls in the loaded files are not enhanced.
1111
- Because it compiles ESM syntax to run in CommonJS mode, top-level await is not supported
1212
:::
1313

‎src/cjs/api/module-extensions.ts

+95-81
Original file line numberDiff line numberDiff line change
@@ -22,103 +22,117 @@ const transformExtensions = [
2222
'.mjs',
2323
] as const;
2424

25-
// Clone Module._extensions with null prototype
26-
export const extensions: NodeJS.RequireExtensions = Object.assign(
27-
Object.create(null),
28-
Module._extensions,
29-
);
30-
31-
const defaultLoader = extensions['.js'];
32-
33-
const transformer = (
34-
module: Module,
35-
filePath: string,
25+
export const createExtensions = (
26+
extendExtensions: NodeJS.RequireExtensions,
27+
namespace?: string,
3628
) => {
37-
// Make sure __filename doesnt contain query
38-
const cleanFilePath = filePath.split('?')[0];
39-
40-
// For tracking dependencies in watch mode
41-
if (parent?.send) {
42-
parent.send({
43-
type: 'dependency',
44-
path: cleanFilePath,
45-
});
46-
}
29+
// Clone Module._extensions with null prototype
30+
const extensions: NodeJS.RequireExtensions = Object.assign(
31+
Object.create(null),
32+
extendExtensions,
33+
);
34+
35+
const defaultLoader = extensions['.js'];
36+
37+
const transformer = (
38+
module: Module,
39+
filePath: string,
40+
) => {
41+
// Make sure __filename doesnt contain query
42+
const [cleanFilePath, query] = filePath.split('?');
43+
44+
const searchParams = new URLSearchParams(query);
45+
46+
// If request namespace doesnt match the namespace, ignore
47+
if ((searchParams.get('namespace') ?? undefined) !== namespace) {
48+
return defaultLoader(module, cleanFilePath);
49+
}
4750

48-
const transformTs = typescriptExtensions.some(extension => cleanFilePath.endsWith(extension));
49-
const transformJs = transformExtensions.some(extension => cleanFilePath.endsWith(extension));
50-
if (!transformTs && !transformJs) {
51-
return defaultLoader(module, cleanFilePath);
52-
}
51+
// For tracking dependencies in watch mode
52+
if (parent?.send) {
53+
parent.send({
54+
type: 'dependency',
55+
path: cleanFilePath,
56+
});
57+
}
5358

54-
let code = fs.readFileSync(cleanFilePath, 'utf8');
59+
const transformTs = typescriptExtensions.some(extension => cleanFilePath.endsWith(extension));
60+
const transformJs = transformExtensions.some(extension => cleanFilePath.endsWith(extension));
61+
if (!transformTs && !transformJs) {
62+
return defaultLoader(module, cleanFilePath);
63+
}
64+
65+
let code = fs.readFileSync(cleanFilePath, 'utf8');
66+
67+
if (cleanFilePath.endsWith('.cjs')) {
68+
// Contains native ESM check
69+
const transformed = transformDynamicImport(filePath, code);
70+
if (transformed) {
71+
code = (
72+
shouldApplySourceMap()
73+
? inlineSourceMap(transformed)
74+
: transformed.code
75+
);
76+
}
77+
} else if (
78+
transformTs
79+
80+
// CommonJS file but uses ESM import/export
81+
|| isESM(code)
82+
) {
83+
const transformed = transformSync(
84+
code,
85+
filePath,
86+
{
87+
tsconfigRaw: fileMatcher?.(cleanFilePath) as TransformOptions['tsconfigRaw'],
88+
},
89+
);
5590

56-
if (cleanFilePath.endsWith('.cjs')) {
57-
// Contains native ESM check
58-
const transformed = transformDynamicImport(filePath, code);
59-
if (transformed) {
6091
code = (
6192
shouldApplySourceMap()
6293
? inlineSourceMap(transformed)
6394
: transformed.code
6495
);
6596
}
66-
} else if (
67-
transformTs
68-
69-
// CommonJS file but uses ESM import/export
70-
|| isESM(code)
71-
) {
72-
const transformed = transformSync(
73-
code,
74-
filePath,
75-
{
76-
tsconfigRaw: fileMatcher?.(cleanFilePath) as TransformOptions['tsconfigRaw'],
77-
},
78-
);
79-
80-
code = (
81-
shouldApplySourceMap()
82-
? inlineSourceMap(transformed)
83-
: transformed.code
84-
);
85-
}
86-
87-
module._compile(code, cleanFilePath);
88-
};
8997

90-
/**
91-
* Handles .cjs, .cts, .mts & any explicitly specified extension that doesn't match any loaders
92-
*
93-
* Any file requested with an explicit extension will be loaded using the .js loader:
94-
* https://github.com/nodejs/node/blob/e339e9c5d71b72fd09e6abd38b10678e0c592ae7/lib/internal/modules/cjs/loader.js#L430
95-
*/
96-
extensions['.js'] = transformer;
97-
98-
[
99-
'.ts',
100-
'.tsx',
101-
'.jsx',
98+
module._compile(code, cleanFilePath);
99+
};
102100

103101
/**
104-
* Loaders for extensions .cjs, .cts, & .mts don't need to be
105-
* registered because they're explicitly specified. And unknown
106-
* extensions (incl .cjs) fallsback to using the '.js' loader:
107-
* https://github.com/nodejs/node/blob/v18.4.0/lib/internal/modules/cjs/loader.js#L430
102+
* Handles .cjs, .cts, .mts & any explicitly specified extension that doesn't match any loaders
108103
*
109-
* That said, it's actually ".js" and ".mjs" that get special treatment
110-
* rather than ".cjs" (it might as well be ".random-ext")
104+
* Any file requested with an explicit extension will be loaded using the .js loader:
105+
* https://github.com/nodejs/node/blob/e339e9c5d71b72fd09e6abd38b10678e0c592ae7/lib/internal/modules/cjs/loader.js#L430
111106
*/
112-
'.mjs',
113-
].forEach((extension) => {
114-
Object.defineProperty(extensions, extension, {
115-
value: transformer,
107+
extensions['.js'] = transformer;
108+
109+
[
110+
'.ts',
111+
'.tsx',
112+
'.jsx',
116113

117114
/**
118-
* Prevent Object.keys from detecting these extensions
119-
* when CJS loader iterates over the possible extensions
120-
* https://github.com/nodejs/node/blob/v22.2.0/lib/internal/modules/cjs/loader.js#L609
115+
* Loaders for extensions .cjs, .cts, & .mts don't need to be
116+
* registered because they're explicitly specified. And unknown
117+
* extensions (incl .cjs) fallsback to using the '.js' loader:
118+
* https://github.com/nodejs/node/blob/v18.4.0/lib/internal/modules/cjs/loader.js#L430
119+
*
120+
* That said, it's actually ".js" and ".mjs" that get special treatment
121+
* rather than ".cjs" (it might as well be ".random-ext")
121122
*/
122-
enumerable: false,
123+
'.mjs',
124+
].forEach((extension) => {
125+
Object.defineProperty(extensions, extension, {
126+
value: transformer,
127+
128+
/**
129+
* Prevent Object.keys from detecting these extensions
130+
* when CJS loader iterates over the possible extensions
131+
* https://github.com/nodejs/node/blob/v22.2.0/lib/internal/modules/cjs/loader.js#L609
132+
*/
133+
enumerable: false,
134+
});
123135
});
124-
});
136+
137+
return extensions;
138+
};

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

+33-12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { resolveTsPath } from '../../utils/resolve-ts-path.js';
55
import type { NodeError } from '../../types.js';
66
import { isRelativePath, fileUrlPrefix, tsExtensionsPattern } from '../../utils/path-utils.js';
77
import { tsconfigPathsMatcher, allowJs } from '../../utils/tsconfig.js';
8+
import { urlSearchParamsStringify } from '../../utils/url-search-params-stringify.js';
89

910
type ResolveFilename = typeof Module._resolveFilename;
1011

@@ -87,33 +88,50 @@ const tryExtensions = (
8788

8889
export const createResolveFilename = (
8990
nextResolve: ResolveFilename,
91+
namespace?: string,
9092
): ResolveFilename => (
9193
request,
9294
parent,
9395
isMain,
9496
options,
9597
) => {
98+
const resolve: SimpleResolve = request_ => nextResolve(
99+
request_,
100+
parent,
101+
isMain,
102+
options,
103+
);
104+
96105
request = interopCjsExports(request);
97106

98107
// Strip query string
99-
const queryIndex = request.indexOf('?');
100-
const query = queryIndex === -1 ? '' : request.slice(queryIndex);
101-
if (queryIndex !== -1) {
102-
request = request.slice(0, queryIndex);
108+
const [cleanRequest, queryString] = request.split('?');
109+
const searchParams = new URLSearchParams(queryString);
110+
111+
// Inherit parent namespace if it exists
112+
if (parent?.filename) {
113+
const parentQuery = new URLSearchParams(parent.filename.split('?')[1]);
114+
const parentNamespace = parentQuery.get('namespace');
115+
if (parentNamespace) {
116+
searchParams.append('namespace', parentNamespace);
117+
}
118+
}
119+
120+
// If request namespace doesnt match the namespace, ignore
121+
if ((searchParams.get('namespace') ?? undefined) !== namespace) {
122+
return resolve(request);
103123
}
104124

125+
const query = urlSearchParamsStringify(searchParams);
126+
127+
// Temporarily remove query since default resolver can't handle it. Added back later.
128+
request = cleanRequest;
129+
105130
// Support file protocol
106131
if (request.startsWith(fileUrlPrefix)) {
107132
request = fileURLToPath(request);
108133
}
109134

110-
const resolve: SimpleResolve = request_ => nextResolve(
111-
request_,
112-
parent,
113-
isMain,
114-
options,
115-
);
116-
117135
// Resolve TS path alias
118136
if (
119137
tsconfigPathsMatcher
@@ -157,7 +175,10 @@ export const createResolveFilename = (
157175
}
158176

159177
try {
160-
return resolve(request) + query;
178+
const resolved = resolve(request);
179+
180+
// Can be a node core module
181+
return resolved + (path.isAbsolute(resolved) ? query : '');
161182
} catch (error) {
162183
const resolved = (
163184
tryExtensions(resolve, request)

‎src/cjs/api/register.ts

+92-3
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,72 @@
11
import Module from 'node:module';
2+
import path from 'node:path';
3+
import { fileURLToPath } from 'node:url';
24
import { loadTsconfig } from '../../utils/tsconfig.js';
3-
import { extensions } from './module-extensions.js';
5+
import type { RequiredProperty } from '../../types.js';
6+
import { urlSearchParamsStringify } from '../../utils/url-search-params-stringify.js';
7+
import { createExtensions } from './module-extensions.js';
48
import { createResolveFilename } from './module-resolve-filename.js';
59

6-
export const register = () => {
10+
const resolveContext = (
11+
id: string,
12+
fromFile: string | URL,
13+
) => {
14+
if (!fromFile) {
15+
throw new Error('The current file path (__filename or import.meta.url) must be provided in the second argument of tsx.require()');
16+
}
17+
18+
if (
19+
(typeof fromFile === 'string' && fromFile.startsWith('file://'))
20+
|| fromFile instanceof URL
21+
) {
22+
fromFile = fileURLToPath(fromFile);
23+
}
24+
25+
return path.resolve(path.dirname(fromFile), id);
26+
};
27+
28+
type RegisterOptions = {
29+
namespace?: string;
30+
};
31+
32+
export type Unregister = () => void;
33+
34+
type ScopedRequire = (
35+
id: string,
36+
fromFile: string | URL,
37+
) => any; // eslint-disable-line @typescript-eslint/no-explicit-any
38+
39+
type ScopedResolve = (
40+
id: string,
41+
fromFile: string | URL,
42+
resolveOptions?: { paths?: string[] | undefined },
43+
) => string;
44+
45+
export type NamespacedUnregister = Unregister & {
46+
require: ScopedRequire;
47+
resolve: ScopedResolve;
48+
unregister: Unregister;
49+
};
50+
51+
export type Register = {
52+
(options: RequiredProperty<RegisterOptions, 'namespace'>): NamespacedUnregister;
53+
(options?: RegisterOptions): Unregister;
54+
};
55+
56+
export const register: Register = (
57+
options,
58+
) => {
759
const { sourceMapsEnabled } = process;
860
const { _extensions, _resolveFilename } = Module;
961

1062
loadTsconfig(process.env.TSX_TSCONFIG_PATH);
1163

1264
// register
1365
process.setSourceMapsEnabled(true);
14-
const resolveFilename = createResolveFilename(_resolveFilename);
66+
const resolveFilename = createResolveFilename(_resolveFilename, options?.namespace);
1567
Module._resolveFilename = resolveFilename;
68+
69+
const extensions = createExtensions(Module._extensions, options?.namespace);
1670
// @ts-expect-error overwriting read-only property
1771
Module._extensions = extensions;
1872

@@ -26,5 +80,40 @@ export const register = () => {
2680
Module._resolveFilename = _resolveFilename;
2781
};
2882

83+
if (options?.namespace) {
84+
const scopedRequire: ScopedRequire = (id, fromFile) => {
85+
const resolvedId = resolveContext(id, fromFile);
86+
const [request, query] = resolvedId.split('?');
87+
88+
const parameters = new URLSearchParams(query);
89+
if (options.namespace) {
90+
parameters.set('namespace', options.namespace);
91+
}
92+
93+
// eslint-disable-next-line n/global-require,import-x/no-dynamic-require
94+
return require(request + urlSearchParamsStringify(parameters));
95+
};
96+
unregister.require = scopedRequire;
97+
98+
const scopedResolve: ScopedResolve = (id, fromFile, resolveOptions) => {
99+
const resolvedId = resolveContext(id, fromFile);
100+
const [request, query] = resolvedId.split('?');
101+
102+
const parameters = new URLSearchParams(query);
103+
if (options.namespace) {
104+
parameters.set('namespace', options.namespace);
105+
}
106+
107+
return resolveFilename(
108+
request + urlSearchParamsStringify(parameters),
109+
module,
110+
false,
111+
resolveOptions,
112+
);
113+
};
114+
unregister.resolve = scopedResolve;
115+
unregister.unregister = unregister;
116+
}
117+
29118
return unregister;
30119
};

‎src/cjs/api/require.ts

+13-34
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,29 @@
1-
import Module from 'node:module';
2-
import path from 'node:path';
3-
import { fileURLToPath } from 'node:url';
4-
import { register } from './register.js';
5-
import { createResolveFilename } from './module-resolve-filename.js';
6-
7-
const getRequestContext = (
8-
id: string,
9-
fromFile: string | URL,
10-
) => {
11-
if (!fromFile) {
12-
throw new Error('The current file path (__filename or import.meta.url) must be provided in the second argument of tsx.require()');
13-
}
14-
15-
if (
16-
(typeof fromFile === 'string' && fromFile.startsWith('file://'))
17-
|| fromFile instanceof URL
18-
) {
19-
fromFile = fileURLToPath(fromFile);
20-
}
21-
22-
return path.resolve(path.dirname(fromFile), id);
23-
};
1+
import { register, type NamespacedUnregister } from './register.js';
242

3+
let api: NamespacedUnregister | undefined;
254
const tsxRequire = (
265
id: string,
276
fromFile: string | URL,
287
) => {
29-
const contextId = getRequestContext(id, fromFile);
30-
const unregister = register();
31-
try {
32-
// eslint-disable-next-line import-x/no-dynamic-require, n/global-require
33-
return require(contextId);
34-
} finally {
35-
unregister();
8+
if (!api) {
9+
api = register({
10+
namespace: Date.now().toString(),
11+
});
3612
}
13+
return api.require(id, fromFile);
3714
};
3815

39-
const resolveFilename = createResolveFilename(Module._resolveFilename);
40-
4116
const resolve = (
4217
id: string,
4318
fromFile: string | URL,
4419
options?: { paths?: string[] | undefined },
4520
) => {
46-
const contextId = getRequestContext(id, fromFile);
47-
return resolveFilename(contextId, module, false, options);
21+
if (!api) {
22+
api = register({
23+
namespace: Date.now().toString(),
24+
});
25+
}
26+
return api.resolve(id, fromFile, options);
4827
};
4928
resolve.paths = require.resolve.paths;
5029

‎src/esm/api/register.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import module from 'node:module';
22
import { MessageChannel, type MessagePort } from 'node:worker_threads';
33
import type { Message } from '../types.js';
4+
import type { RequiredProperty } from '../../types.js';
45
import { interopCjsExports } from '../../cjs/api/module-resolve-filename.js';
56
import { createScopedImport, type ScopedImport } from './scoped-import.js';
67

@@ -25,8 +26,6 @@ export type NamespacedUnregister = Unregister & {
2526
unregister: Unregister;
2627
};
2728

28-
type RequiredProperty<Type, Keys extends keyof Type> = Type & { [P in Keys]-?: Type[P] };
29-
3029
export type Register = {
3130
(options: RequiredProperty<RegisterOptions, 'namespace'>): NamespacedUnregister;
3231
(options?: RegisterOptions): Unregister;

‎src/esm/hook/utils.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,8 @@ import { tsExtensionsPattern } from '../../utils/path-utils.js';
44
import { getPackageType } from './package-json.js';
55

66
const getFormatFromExtension = (fileUrl: string): ModuleFormat | undefined => {
7-
const queryIndex = fileUrl.indexOf('?');
8-
fileUrl = (
9-
queryIndex === -1
10-
? fileUrl
11-
: fileUrl.slice(0, queryIndex)
12-
);
7+
[fileUrl] = fileUrl.split('?');
8+
139
const extension = path.extname(fileUrl);
1410
if (extension === '.json') {
1511
return 'json';

‎src/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
export type NodeError = Error & {
22
code: string;
33
};
4+
5+
export type RequiredProperty<Type, Keys extends keyof Type> = Type & { [P in Keys]-?: Type[P] };
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const urlSearchParamsStringify = (
2+
searchParams: URLSearchParams,
3+
) => {
4+
// URLSearchParams#size not implemented in Node 18.0.0
5+
const size = Array.from(searchParams).length;
6+
return size > 0 ? `?${searchParams.toString()}` : '';
7+
};

‎tests/specs/api.ts

+97-21
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,22 @@ import {
99
tsxEsmApiCjsPath,
1010
type NodeApis,
1111
} from '../utils/tsx.js';
12-
import { createPackageJson, createTsconfig } from '../fixtures.js';
12+
import { createPackageJson, createTsconfig, expectErrors } from '../fixtures.js';
1313

1414
const tsFiles = {
1515
'file.ts': `
1616
import { foo } from './foo'
17-
export const message = foo as string
17+
export const message = \`\${foo} \${(typeof __filename === 'undefined' ? import.meta.url : __filename).split(/[\\\\/]/).pop()}\` as string
18+
export { async } from './foo'
1819
`,
1920
'foo.ts': `
21+
import { setTimeout } from 'node:timers/promises'
2022
import { bar } from './bar.js'
2123
export const foo = \`foo \${bar}\` as string
24+
export const async = setTimeout(10).then(() => require('./async')).catch((error) => error);
2225
`,
2326
'bar.ts': 'export type A = 1; export { bar } from "pkg"',
27+
'async.ts': 'export default "async"',
2428
'node_modules/pkg': {
2529
'package.json': createPackageJson({
2630
name: 'pkg',
@@ -29,6 +33,7 @@ const tsFiles = {
2933
}),
3034
'index.js': 'import "node:process"; export const bar = "bar";',
3135
},
36+
...expectErrors,
3237
};
3338

3439
export default testSuite(({ describe }, node: NodeApis) => {
@@ -83,7 +88,7 @@ export default testSuite(({ describe }, node: NodeApis) => {
8388
nodeOptions: [],
8489
});
8590

86-
expect(stdout).toBe('Fails as expected\nfoo bar\nUnregistered');
91+
expect(stdout).toBe('Fails as expected\nfoo bar file.ts\nUnregistered');
8792
});
8893

8994
describe('tsx.require()', ({ test }) => {
@@ -120,16 +125,16 @@ export default testSuite(({ describe }, node: NodeApis) => {
120125
nodeOptions: [],
121126
});
122127

123-
expect(stdout).toBe('Fails as expected\nfoo bar\nfile.ts\nUnpolluted global require');
128+
expect(stdout).toMatch(/Fails as expected\nfoo bar file.ts\nfile.ts\?namespace=\d+\nUnpolluted global require/);
124129
});
125130

126131
test('catchable', async () => {
127132
await using fixture = await createFixture({
128133
'require.cjs': `
129134
const tsx = require(${JSON.stringify(tsxCjsApiPath)});
130-
try { tsx.require('./file', __filename); } catch {}
135+
try { tsx.require('./syntax-error', __filename); } catch {}
131136
`,
132-
'file.ts': 'if',
137+
'syntax-error.ts': 'if',
133138
});
134139

135140
const { all } = await execaNode(fixture.getPath('require.cjs'), [], {
@@ -139,6 +144,77 @@ export default testSuite(({ describe }, node: NodeApis) => {
139144
});
140145
expect(all).toBe('');
141146
});
147+
148+
test('chainable', async () => {
149+
await using fixture = await createFixture({
150+
'require.cjs': `
151+
const path = require('node:path');
152+
const tsx = require(${JSON.stringify(tsxCjsApiPath)});
153+
154+
const unregister = tsx.register();
155+
console.log(require('./file').message);
156+
delete require.cache[require.resolve('./file')];
157+
158+
const loaded = tsx.require('./file', __filename);
159+
console.log(loaded.message);
160+
161+
// Remove from cache
162+
const loadedPath = tsx.require.resolve('./file', __filename);
163+
delete require.cache[loadedPath];
164+
165+
console.log(require('./file').message);
166+
delete require.cache[require.resolve('./file')];
167+
168+
unregister();
169+
170+
try {
171+
require('./file');
172+
} catch {
173+
console.log('Unregistered');
174+
}
175+
`,
176+
...tsFiles,
177+
});
178+
179+
const { stdout } = await execaNode(fixture.getPath('require.cjs'), [], {
180+
nodePath: node.path,
181+
nodeOptions: [],
182+
});
183+
184+
expect(stdout).toBe('foo bar file.ts\nfoo bar file.ts\nfoo bar file.ts\nUnregistered');
185+
});
186+
187+
test('namespace', async () => {
188+
await using fixture = await createFixture({
189+
'require.cjs': `
190+
const { expectErrors } = require('expect-errors');
191+
const path = require('node:path');
192+
const tsx = require(${JSON.stringify(tsxCjsApiPath)});
193+
194+
const api = tsx.register({ namespace: 'abcd' });
195+
196+
expectErrors(
197+
// Loading explicit/resolved file path should be ignored by loader (extensions)
198+
[() => require('./file.ts'), 'SyntaxError'],
199+
200+
// resolver should preserve full file path when ignoring
201+
[() => require('./file.ts?asdf'), "Cannot find module './file.ts?asdf'"]
202+
);
203+
204+
const { message, async } = api.require('./file', __filename);
205+
console.log(message);
206+
async.then(m => console.log(m.default));
207+
`,
208+
...tsFiles,
209+
});
210+
211+
const { stdout } = await execaNode(fixture.getPath('require.cjs'), [], {
212+
nodePath: node.path,
213+
nodeOptions: [],
214+
});
215+
216+
expect(stdout).toBe('foo bar file.ts\nasync');
217+
});
142218
});
143219
});
144220

@@ -185,7 +261,7 @@ export default testSuite(({ describe }, node: NodeApis) => {
185261
nodeOptions: [],
186262
});
187263

188-
expect(stdout).toBe('Fails as expected\nfoo bar');
264+
expect(stdout).toBe('Fails as expected\nfoo bar file.ts?nocache');
189265
});
190266

191267
describe('register / unregister', ({ test, describe }) => {
@@ -199,7 +275,7 @@ export default testSuite(({ describe }, node: NodeApis) => {
199275
} catch {
200276
console.log('Fails as expected 1');
201277
}
202-
278+
203279
{
204280
const unregister = register();
205281
@@ -231,7 +307,7 @@ export default testSuite(({ describe }, node: NodeApis) => {
231307
nodePath: node.path,
232308
nodeOptions: [],
233309
});
234-
expect(stdout).toBe('Fails as expected 1\nfoo bar\nFails as expected 2\nfoo bar');
310+
expect(stdout).toBe('Fails as expected 1\nfoo bar file.ts?2\nFails as expected 2\nfoo bar file.ts?4');
235311
});
236312

237313
test('onImport', async () => {
@@ -245,7 +321,7 @@ export default testSuite(({ describe }, node: NodeApis) => {
245321
console.log(file.split('/').pop());
246322
},
247323
});
248-
324+
249325
await import('./file');
250326
`,
251327
...tsFiles,
@@ -255,7 +331,7 @@ export default testSuite(({ describe }, node: NodeApis) => {
255331
nodePath: node.path,
256332
nodeOptions: [],
257333
});
258-
expect(stdout).toBe('file.ts\nfoo.ts\nbar.ts\nindex.js\nnode:process');
334+
expect(stdout).toBe('file.ts\nfoo.ts\npromises\nbar.ts\nindex.js\nnode:process');
259335
});
260336

261337
test('namespace & onImport', async () => {
@@ -423,17 +499,17 @@ export default testSuite(({ describe }, node: NodeApis) => {
423499
'package.json': createPackageJson({ type: 'module' }),
424500
'import.mjs': `
425501
import { tsImport } from ${JSON.stringify(tsxEsmApiPath)};
426-
502+
427503
await import('./file.ts').catch((error) => {
428504
console.log('Fails as expected 1');
429505
});
430-
506+
431507
const { message } = await tsImport('./file.ts', import.meta.url);
432508
console.log(message);
433-
509+
434510
const { message: message2 } = await tsImport('./file.ts?with-query', import.meta.url);
435511
console.log(message2);
436-
512+
437513
// Global not polluted
438514
await import('./file.ts?nocache').catch((error) => {
439515
console.log('Fails as expected 2');
@@ -446,26 +522,26 @@ export default testSuite(({ describe }, node: NodeApis) => {
446522
nodePath: node.path,
447523
nodeOptions: [],
448524
});
449-
expect(stdout).toBe('Fails as expected 1\nfoo bar\nfoo bar\nFails as expected 2');
525+
expect(stdout).toMatch(/Fails as expected 1\nfoo bar file\.ts\?tsx-namespace=\d+\nfoo bar file\.ts\?with-query=&tsx-namespace=\d+\nFails as expected 2/);
450526
});
451527

452528
test('commonjs', async () => {
453529
await using fixture = await createFixture({
454530
'package.json': createPackageJson({ type: 'module' }),
455531
'import.cjs': `
456532
const { tsImport } = require(${JSON.stringify(tsxEsmApiCjsPath)});
457-
533+
458534
(async () => {
459535
await import('./file.ts').catch((error) => {
460536
console.log('Fails as expected 1');
461537
});
462-
538+
463539
const { message } = await tsImport('./file.ts', __filename);
464540
console.log(message);
465-
541+
466542
const { message: message2 } = await tsImport('./file.ts?with-query', __filename);
467543
console.log(message2);
468-
544+
469545
// Global not polluted
470546
await import('./file.ts?nocache').catch((error) => {
471547
console.log('Fails as expected 2');
@@ -479,7 +555,7 @@ export default testSuite(({ describe }, node: NodeApis) => {
479555
nodePath: node.path,
480556
nodeOptions: [],
481557
});
482-
expect(stdout).toBe('Fails as expected 1\nfoo bar\nfoo bar\nFails as expected 2');
558+
expect(stdout).toMatch(/Fails as expected 1\nfoo bar file\.ts\?tsx-namespace=\d+\nfoo bar file\.ts\?with-query=&tsx-namespace=\d+\nFails as expected 2/);
483559
});
484560

485561
test('mts from commonjs', async () => {

0 commit comments

Comments
 (0)
Please sign in to comment.