Skip to content

Commit

Permalink
feat(resolver): support node: prefix when loading core modules (#11331
Browse files Browse the repository at this point in the history
)

Co-authored-by: Divlo <contact@divlo.fr>
  • Loading branch information
SimenB and theoludwig committed Aug 27, 2021
1 parent 499ef4f commit 554d7d2
Show file tree
Hide file tree
Showing 9 changed files with 81 additions and 26 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Expand Up @@ -4,6 +4,7 @@

- `[jest-haste-map]` Use watchman suffix-set option for faster file indexing. ([#11784](https://github.com/facebook/jest/pull/11784))
- `[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))
- `[jest-resolver]` Support `node:` prefix when importing Node core modules ([#11331](https://github.com/facebook/jest/pull/11331))

### Fixes

Expand Down Expand Up @@ -116,14 +117,14 @@
- `[jest-haste-map]` Add `enableSymlinks` configuration option to follow symlinks for test files ([#9351](https://github.com/facebook/jest/pull/9351))
- `[jest-repl, jest-runner]` [**BREAKING**] Run transforms over environment ([#8751](https://github.com/facebook/jest/pull/8751))
- `[jest-repl]` Add support for `testEnvironment` written in ESM ([#11232](https://github.com/facebook/jest/pull/11232))
- `[jest-reporters]` Add static filepath property to all reporters ([#11015](https://github.com/facebook/jest/pull/11015))
- `[jest-runner]` [**BREAKING**] set exit code to 1 if test logs after teardown ([#10728](https://github.com/facebook/jest/pull/10728))
- `[jest-runner]` [**BREAKING**] Run transforms over `runner` ([#8823](https://github.com/facebook/jest/pull/8823))
- `[jest-runner]` [**BREAKING**] Run transforms over `testRunner` ([#8823](https://github.com/facebook/jest/pull/8823))
- `[jest-runner]` Possibility to use ESM for test environment ([11033](https://github.com/facebook/jest/pull/11033))
- `[jest-runner]` Add support for `testRunner` written in ESM ([#11232](https://github.com/facebook/jest/pull/11232))
- `[jest-runtime]` Detect reexports from CJS as named exports in ESM ([#10988](https://github.com/facebook/jest/pull/10988))
- `[jest-runtime]` Support for async code transformations ([#11191](https://github.com/facebook/jest/pull/11191) & [#11220](https://github.com/facebook/jest/pull/11220))
- `[jest-reporters]` Add static filepath property to all reporters ([#11015](https://github.com/facebook/jest/pull/11015))
- `[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))
- `[jest-snapshot]` [**BREAKING**] Run transforms over `snapshotResolver` ([#8751](https://github.com/facebook/jest/pull/8829))
- `[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))
Expand Down
4 changes: 2 additions & 2 deletions e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap
Expand Up @@ -41,7 +41,7 @@ FAIL __tests__/index.js
12 | module.exports = () => 'test';
13 |
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:558:17)
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:561:17)
at Object.require (index.js:10:1)
`;

Expand Down Expand Up @@ -70,6 +70,6 @@ FAIL __tests__/index.js
12 | module.exports = () => 'test';
13 |
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:558:17)
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:561:17)
at Object.require (index.js:10:1)
`;
Expand Up @@ -37,6 +37,6 @@ FAIL __tests__/test.js
| ^
9 |
at Resolver.resolveModule (../../packages/jest-resolve/build/resolver.js:311:11)
at Resolver.resolveModule (../../packages/jest-resolve/build/resolver.js:313:11)
at Object.require (index.js:8:18)
`;
14 changes: 14 additions & 0 deletions packages/jest-resolve/src/__tests__/resolve.test.ts
Expand Up @@ -74,6 +74,20 @@ describe('isCoreModule', () => {
const isCore = resolver.isCoreModule('constants');
expect(isCore).toEqual(false);
});

it('returns true if using `node:` URLs and `moduleName` is a core module.', () => {
const moduleMap = ModuleMap.create('/');
const resolver = new Resolver(moduleMap, {} as ResolverConfig);
const isCore = resolver.isCoreModule('node:assert');
expect(isCore).toEqual(true);
});

it('returns false if using `node:` URLs and `moduleName` is not a core module.', () => {
const moduleMap = ModuleMap.create('/');
const resolver = new Resolver(moduleMap, {} as ResolverConfig);
const isCore = resolver.isCoreModule('node:not-a-core-module');
expect(isCore).toEqual(false);
});
});

describe('findNodeModule', () => {
Expand Down
12 changes: 2 additions & 10 deletions packages/jest-resolve/src/isBuiltinModule.ts
Expand Up @@ -7,19 +7,11 @@

import module = require('module');

// "private" api
declare const process: NodeJS.Process & {
binding(type: string): Record<string, unknown>;
};

// TODO: remove when we drop support for node v10 - it is included from node v12
const EXPERIMENTAL_MODULES = ['worker_threads'];

const BUILTIN_MODULES = new Set(
module.builtinModules
? module.builtinModules.concat(EXPERIMENTAL_MODULES)
: Object.keys(process.binding('natives'))
.filter((module: string) => !/^internal\//.test(module))
.concat(EXPERIMENTAL_MODULES),
module.builtinModules.concat(EXPERIMENTAL_MODULES),
);

export default function isBuiltinModule(module: string): boolean {
Expand Down
10 changes: 5 additions & 5 deletions packages/jest-resolve/src/resolver.ts
Expand Up @@ -135,7 +135,7 @@ export default class Resolver {
moduleName: string,
options?: ResolveModuleConfig,
): Config.Path | null {
const paths = (options && options.paths) || this._options.modulePaths;
const paths = options?.paths || this._options.modulePaths;
const moduleDirectory = this._options.moduleDirectories;
const key = dirname + path.delimiter + moduleName;
const defaultPlatform = this._options.defaultPlatform;
Expand Down Expand Up @@ -253,7 +253,9 @@ export default class Resolver {
isCoreModule(moduleName: string): boolean {
return (
this._options.hasCoreModules &&
isBuiltinModule(moduleName) &&
(isBuiltinModule(moduleName) ||
(moduleName.startsWith('node:') &&
isBuiltinModule(moduleName.slice('node:'.length)))) &&
!this._isAliasModule(moduleName)
);
}
Expand Down Expand Up @@ -313,10 +315,8 @@ export default class Resolver {
getModuleID(
virtualMocks: Map<string, boolean>,
from: Config.Path,
_moduleName?: string,
moduleName = '',
): string {
const moduleName = _moduleName || '';

const key = from + path.delimiter + moduleName;
const cachedModuleID = this._moduleIDCache.get(key);
if (cachedModuleID) {
Expand Down
2 changes: 1 addition & 1 deletion packages/jest-runtime/package.json
Expand Up @@ -26,6 +26,7 @@
"chalk": "^4.0.0",
"cjs-module-lexer": "^1.0.0",
"collect-v8-coverage": "^1.0.0",
"execa": "^5.0.0",
"exit": "^0.1.2",
"glob": "^7.1.3",
"graceful-fs": "^4.2.4",
Expand All @@ -47,7 +48,6 @@
"@types/glob": "^7.1.1",
"@types/graceful-fs": "^4.1.2",
"@types/node": "^14.0.27",
"execa": "^5.0.0",
"jest-environment-node": "^27.0.6",
"jest-snapshot-serializer-raw": "^1.1.0"
},
Expand Down
13 changes: 13 additions & 0 deletions packages/jest-runtime/src/__tests__/runtime_require_module.test.js
Expand Up @@ -188,6 +188,19 @@ describe('Runtime requireModule', () => {
}).not.toThrow();
});

onNodeVersions('^16.0.0', () => {
it('finds node core built-in modules with node:prefix', async () => {
const runtime = await createRuntime(__filename);

expect(runtime.requireModule(runtime.__mockRootPath, 'fs')).toBe(
runtime.requireModule(runtime.__mockRootPath, 'node:fs'),
);
expect(runtime.requireModule(runtime.__mockRootPath, 'module')).toBe(
runtime.requireModule(runtime.__mockRootPath, 'node:module'),
);
});
});

it('finds and loads JSON files without file extension', async () => {
const runtime = await createRuntime(__filename);
const exports = runtime.requireModule(runtime.__mockRootPath, './JSONFile');
Expand Down
47 changes: 41 additions & 6 deletions packages/jest-runtime/src/index.ts
Expand Up @@ -20,6 +20,7 @@ import {
} from 'vm';
import {parse as parseCjs} from 'cjs-module-lexer';
import {CoverageInstrumenter, V8Coverage} from 'collect-v8-coverage';
import execa = require('execa');
import * as fs from 'graceful-fs';
import stripBOM = require('strip-bom');
import type {
Expand Down Expand Up @@ -149,6 +150,29 @@ const supportsTopLevelAwait =
}
})();

const supportsNodeColonModulePrefixInRequire = (() => {
try {
require('node:fs');

return true;
} catch {
return false;
}
})();

const supportsNodeColonModulePrefixInImport = (() => {
const {stdout} = execa.sync(
'node',
[
'--eval',
'import("node:fs").then(() => console.log(true), () => console.log(false));',
],
{reject: false},
);

return stdout === 'true';
})();

export default class Runtime {
private readonly _cacheFS: Map<string, string>;
private readonly _config: Config.ProjectConfig;
Expand Down Expand Up @@ -645,7 +669,10 @@ export default class Runtime {
}

if (moduleName && this._resolver.isCoreModule(moduleName)) {
return this._requireCoreModule(moduleName);
return this._requireCoreModule(
moduleName,
supportsNodeColonModulePrefixInRequire,
);
}

if (!modulePath) {
Expand Down Expand Up @@ -1333,20 +1360,28 @@ export default class Runtime {
}
}

private _requireCoreModule(moduleName: string) {
if (moduleName === 'process') {
private _requireCoreModule(moduleName: string, supportPrefix: boolean) {
const moduleWithoutNodePrefix =
supportPrefix && moduleName.startsWith('node:')
? moduleName.slice('node:'.length)
: moduleName;

if (moduleWithoutNodePrefix === 'process') {
return this._environment.global.process;
}

if (moduleName === 'module') {
if (moduleWithoutNodePrefix === 'module') {
return this._getMockedNativeModule();
}

return require(moduleName);
return require(moduleWithoutNodePrefix);
}

private _importCoreModule(moduleName: string, context: VMContext) {
const required = this._requireCoreModule(moduleName);
const required = this._requireCoreModule(
moduleName,
supportsNodeColonModulePrefixInImport,
);

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

0 comments on commit 554d7d2

Please sign in to comment.