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

feat(resolver): support node: prefix when loading core modules #11331

Merged
merged 6 commits into from Aug 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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