Skip to content

Commit 554d7d2

Browse files
SimenBtheoludwig
andauthored
feat(resolver): support node: prefix when loading core modules (#11331)
Co-authored-by: Divlo <[email protected]>
1 parent 499ef4f commit 554d7d2

File tree

9 files changed

+81
-26
lines changed

9 files changed

+81
-26
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- `[jest-haste-map]` Use watchman suffix-set option for faster file indexing. ([#11784](https://github.com/facebook/jest/pull/11784))
66
- `[jest-cli]` Adds a new config options `snapshotFormat` which offers a way to override any of the formatting settings which come with [pretty-format](https://www.npmjs.com/package/pretty-format#usage-with-options). ([#11654](https://github.com/facebook/jest/pull/11654))
7+
- `[jest-resolver]` Support `node:` prefix when importing Node core modules ([#11331](https://github.com/facebook/jest/pull/11331))
78

89
### Fixes
910

@@ -116,14 +117,14 @@
116117
- `[jest-haste-map]` Add `enableSymlinks` configuration option to follow symlinks for test files ([#9351](https://github.com/facebook/jest/pull/9351))
117118
- `[jest-repl, jest-runner]` [**BREAKING**] Run transforms over environment ([#8751](https://github.com/facebook/jest/pull/8751))
118119
- `[jest-repl]` Add support for `testEnvironment` written in ESM ([#11232](https://github.com/facebook/jest/pull/11232))
120+
- `[jest-reporters]` Add static filepath property to all reporters ([#11015](https://github.com/facebook/jest/pull/11015))
119121
- `[jest-runner]` [**BREAKING**] set exit code to 1 if test logs after teardown ([#10728](https://github.com/facebook/jest/pull/10728))
120122
- `[jest-runner]` [**BREAKING**] Run transforms over `runner` ([#8823](https://github.com/facebook/jest/pull/8823))
121123
- `[jest-runner]` [**BREAKING**] Run transforms over `testRunner` ([#8823](https://github.com/facebook/jest/pull/8823))
122124
- `[jest-runner]` Possibility to use ESM for test environment ([11033](https://github.com/facebook/jest/pull/11033))
123125
- `[jest-runner]` Add support for `testRunner` written in ESM ([#11232](https://github.com/facebook/jest/pull/11232))
124126
- `[jest-runtime]` Detect reexports from CJS as named exports in ESM ([#10988](https://github.com/facebook/jest/pull/10988))
125127
- `[jest-runtime]` Support for async code transformations ([#11191](https://github.com/facebook/jest/pull/11191) & [#11220](https://github.com/facebook/jest/pull/11220))
126-
- `[jest-reporters]` Add static filepath property to all reporters ([#11015](https://github.com/facebook/jest/pull/11015))
127128
- `[jest-snapshot]` [**BREAKING**] Make prettier optional for inline snapshots - fall back to string replacement ([#7792](https://github.com/facebook/jest/pull/7792) & [#11192](https://github.com/facebook/jest/pull/11192))
128129
- `[jest-snapshot]` [**BREAKING**] Run transforms over `snapshotResolver` ([#8751](https://github.com/facebook/jest/pull/8829))
129130
- `[jest-transform]` Pass config options defined in Jest's config to transformer's `process` and `getCacheKey` functions ([#10926](https://github.com/facebook/jest/pull/10926))

e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ FAIL __tests__/index.js
4141
12 | module.exports = () => 'test';
4242
13 |
4343
44-
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:558:17)
44+
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:561:17)
4545
at Object.require (index.js:10:1)
4646
`;
4747

@@ -70,6 +70,6 @@ FAIL __tests__/index.js
7070
12 | module.exports = () => 'test';
7171
13 |
7272
73-
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:558:17)
73+
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:561:17)
7474
at Object.require (index.js:10:1)
7575
`;

e2e/__tests__/__snapshots__/resolveNoFileExtensions.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,6 @@ FAIL __tests__/test.js
3737
| ^
3838
9 |
3939
40-
at Resolver.resolveModule (../../packages/jest-resolve/build/resolver.js:311:11)
40+
at Resolver.resolveModule (../../packages/jest-resolve/build/resolver.js:313:11)
4141
at Object.require (index.js:8:18)
4242
`;

packages/jest-resolve/src/__tests__/resolve.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,20 @@ describe('isCoreModule', () => {
7474
const isCore = resolver.isCoreModule('constants');
7575
expect(isCore).toEqual(false);
7676
});
77+
78+
it('returns true if using `node:` URLs and `moduleName` is a core module.', () => {
79+
const moduleMap = ModuleMap.create('/');
80+
const resolver = new Resolver(moduleMap, {} as ResolverConfig);
81+
const isCore = resolver.isCoreModule('node:assert');
82+
expect(isCore).toEqual(true);
83+
});
84+
85+
it('returns false if using `node:` URLs and `moduleName` is not a core module.', () => {
86+
const moduleMap = ModuleMap.create('/');
87+
const resolver = new Resolver(moduleMap, {} as ResolverConfig);
88+
const isCore = resolver.isCoreModule('node:not-a-core-module');
89+
expect(isCore).toEqual(false);
90+
});
7791
});
7892

7993
describe('findNodeModule', () => {

packages/jest-resolve/src/isBuiltinModule.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,11 @@
77

88
import module = require('module');
99

10-
// "private" api
11-
declare const process: NodeJS.Process & {
12-
binding(type: string): Record<string, unknown>;
13-
};
14-
10+
// TODO: remove when we drop support for node v10 - it is included from node v12
1511
const EXPERIMENTAL_MODULES = ['worker_threads'];
1612

1713
const BUILTIN_MODULES = new Set(
18-
module.builtinModules
19-
? module.builtinModules.concat(EXPERIMENTAL_MODULES)
20-
: Object.keys(process.binding('natives'))
21-
.filter((module: string) => !/^internal\//.test(module))
22-
.concat(EXPERIMENTAL_MODULES),
14+
module.builtinModules.concat(EXPERIMENTAL_MODULES),
2315
);
2416

2517
export default function isBuiltinModule(module: string): boolean {

packages/jest-resolve/src/resolver.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ export default class Resolver {
135135
moduleName: string,
136136
options?: ResolveModuleConfig,
137137
): Config.Path | null {
138-
const paths = (options && options.paths) || this._options.modulePaths;
138+
const paths = options?.paths || this._options.modulePaths;
139139
const moduleDirectory = this._options.moduleDirectories;
140140
const key = dirname + path.delimiter + moduleName;
141141
const defaultPlatform = this._options.defaultPlatform;
@@ -253,7 +253,9 @@ export default class Resolver {
253253
isCoreModule(moduleName: string): boolean {
254254
return (
255255
this._options.hasCoreModules &&
256-
isBuiltinModule(moduleName) &&
256+
(isBuiltinModule(moduleName) ||
257+
(moduleName.startsWith('node:') &&
258+
isBuiltinModule(moduleName.slice('node:'.length)))) &&
257259
!this._isAliasModule(moduleName)
258260
);
259261
}
@@ -313,10 +315,8 @@ export default class Resolver {
313315
getModuleID(
314316
virtualMocks: Map<string, boolean>,
315317
from: Config.Path,
316-
_moduleName?: string,
318+
moduleName = '',
317319
): string {
318-
const moduleName = _moduleName || '';
319-
320320
const key = from + path.delimiter + moduleName;
321321
const cachedModuleID = this._moduleIDCache.get(key);
322322
if (cachedModuleID) {

packages/jest-runtime/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"chalk": "^4.0.0",
2727
"cjs-module-lexer": "^1.0.0",
2828
"collect-v8-coverage": "^1.0.0",
29+
"execa": "^5.0.0",
2930
"exit": "^0.1.2",
3031
"glob": "^7.1.3",
3132
"graceful-fs": "^4.2.4",
@@ -47,7 +48,6 @@
4748
"@types/glob": "^7.1.1",
4849
"@types/graceful-fs": "^4.1.2",
4950
"@types/node": "^14.0.27",
50-
"execa": "^5.0.0",
5151
"jest-environment-node": "^27.0.6",
5252
"jest-snapshot-serializer-raw": "^1.1.0"
5353
},

packages/jest-runtime/src/__tests__/runtime_require_module.test.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,19 @@ describe('Runtime requireModule', () => {
188188
}).not.toThrow();
189189
});
190190

191+
onNodeVersions('^16.0.0', () => {
192+
it('finds node core built-in modules with node:prefix', async () => {
193+
const runtime = await createRuntime(__filename);
194+
195+
expect(runtime.requireModule(runtime.__mockRootPath, 'fs')).toBe(
196+
runtime.requireModule(runtime.__mockRootPath, 'node:fs'),
197+
);
198+
expect(runtime.requireModule(runtime.__mockRootPath, 'module')).toBe(
199+
runtime.requireModule(runtime.__mockRootPath, 'node:module'),
200+
);
201+
});
202+
});
203+
191204
it('finds and loads JSON files without file extension', async () => {
192205
const runtime = await createRuntime(__filename);
193206
const exports = runtime.requireModule(runtime.__mockRootPath, './JSONFile');

packages/jest-runtime/src/index.ts

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
} from 'vm';
2121
import {parse as parseCjs} from 'cjs-module-lexer';
2222
import {CoverageInstrumenter, V8Coverage} from 'collect-v8-coverage';
23+
import execa = require('execa');
2324
import * as fs from 'graceful-fs';
2425
import stripBOM = require('strip-bom');
2526
import type {
@@ -149,6 +150,29 @@ const supportsTopLevelAwait =
149150
}
150151
})();
151152

153+
const supportsNodeColonModulePrefixInRequire = (() => {
154+
try {
155+
require('node:fs');
156+
157+
return true;
158+
} catch {
159+
return false;
160+
}
161+
})();
162+
163+
const supportsNodeColonModulePrefixInImport = (() => {
164+
const {stdout} = execa.sync(
165+
'node',
166+
[
167+
'--eval',
168+
'import("node:fs").then(() => console.log(true), () => console.log(false));',
169+
],
170+
{reject: false},
171+
);
172+
173+
return stdout === 'true';
174+
})();
175+
152176
export default class Runtime {
153177
private readonly _cacheFS: Map<string, string>;
154178
private readonly _config: Config.ProjectConfig;
@@ -645,7 +669,10 @@ export default class Runtime {
645669
}
646670

647671
if (moduleName && this._resolver.isCoreModule(moduleName)) {
648-
return this._requireCoreModule(moduleName);
672+
return this._requireCoreModule(
673+
moduleName,
674+
supportsNodeColonModulePrefixInRequire,
675+
);
649676
}
650677

651678
if (!modulePath) {
@@ -1333,20 +1360,28 @@ export default class Runtime {
13331360
}
13341361
}
13351362

1336-
private _requireCoreModule(moduleName: string) {
1337-
if (moduleName === 'process') {
1363+
private _requireCoreModule(moduleName: string, supportPrefix: boolean) {
1364+
const moduleWithoutNodePrefix =
1365+
supportPrefix && moduleName.startsWith('node:')
1366+
? moduleName.slice('node:'.length)
1367+
: moduleName;
1368+
1369+
if (moduleWithoutNodePrefix === 'process') {
13381370
return this._environment.global.process;
13391371
}
13401372

1341-
if (moduleName === 'module') {
1373+
if (moduleWithoutNodePrefix === 'module') {
13421374
return this._getMockedNativeModule();
13431375
}
13441376

1345-
return require(moduleName);
1377+
return require(moduleWithoutNodePrefix);
13461378
}
13471379

13481380
private _importCoreModule(moduleName: string, context: VMContext) {
1349-
const required = this._requireCoreModule(moduleName);
1381+
const required = this._requireCoreModule(
1382+
moduleName,
1383+
supportsNodeColonModulePrefixInImport,
1384+
);
13501385

13511386
const module = new SyntheticModule(
13521387
['default', ...Object.keys(required)],

0 commit comments

Comments
 (0)