Skip to content

Commit 523ba3b

Browse files
authored
feat: override module.createRequire (#9469)
1 parent 5196b71 commit 523ba3b

File tree

4 files changed

+147
-9
lines changed

4 files changed

+147
-9
lines changed

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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88

99
'use strict';
1010

11+
import {builtinModules, createRequire} from 'module';
1112
import path from 'path';
13+
import {pathToFileURL} from 'url';
1214
// eslint-disable-next-line import/default
1315
import slash from 'slash';
16+
import {onNodeVersions} from '@jest/test-utils';
1417

1518
let createRuntime;
1619

@@ -347,4 +350,70 @@ describe('Runtime requireModule', () => {
347350
);
348351
expect(exports.isJSONModuleEncodedInUTF8WithBOM).toBe(true);
349352
}));
353+
354+
onNodeVersions('>=12.12.0', () => {
355+
it('overrides module.createRequire', () =>
356+
createRuntime(__filename).then(runtime => {
357+
const exports = runtime.requireModule(runtime.__mockRootPath, 'module');
358+
359+
expect(exports.createRequire).not.toBe(createRequire);
360+
361+
// createRequire with string
362+
{
363+
const customRequire = exports.createRequire(runtime.__mockRootPath);
364+
expect(customRequire('./create_require_module').foo).toBe('foo');
365+
}
366+
367+
// createRequire with URL object
368+
{
369+
const customRequire = exports.createRequire(
370+
pathToFileURL(runtime.__mockRootPath),
371+
);
372+
expect(customRequire('./create_require_module').foo).toBe('foo');
373+
}
374+
375+
// createRequire with file URL string
376+
{
377+
const customRequire = exports.createRequire(
378+
pathToFileURL(runtime.__mockRootPath).toString(),
379+
);
380+
expect(customRequire('./create_require_module').foo).toBe('foo');
381+
}
382+
383+
// createRequire with absolute module path
384+
{
385+
const customRequire = exports.createRequire(runtime.__mockRootPath);
386+
expect(customRequire('./create_require_module').foo).toBe('foo');
387+
}
388+
389+
// createRequire with relative module path
390+
expect(() => exports.createRequireFromPath('./relative/path')).toThrow(
391+
new TypeError(
392+
`The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received './relative/path'`,
393+
),
394+
);
395+
396+
// createRequireFromPath with absolute module path
397+
{
398+
const customRequire = exports.createRequireFromPath(
399+
runtime.__mockRootPath,
400+
);
401+
expect(customRequire('./create_require_module').foo).toBe('foo');
402+
}
403+
404+
// createRequireFromPath with file URL object
405+
expect(() =>
406+
exports.createRequireFromPath(pathToFileURL(runtime.__mockRootPath)),
407+
).toThrow(
408+
new TypeError(
409+
`The argument 'filename' must be string. Received '${pathToFileURL(
410+
runtime.__mockRootPath,
411+
)}'. Use createRequire for URL filename.`,
412+
),
413+
);
414+
415+
expect(exports.syncBuiltinESMExports).not.toThrow();
416+
expect(exports.builtinModules).toEqual(builtinModules);
417+
}));
418+
});
350419
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
10+
module.exports.foo = 'foo';

packages/jest-runtime/src/index.ts

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
import {URL, fileURLToPath} from 'url';
89
import * as path from 'path';
910
import {Script, compileFunction} from 'vm';
10-
import {fileURLToPath} from 'url';
11+
import * as nativeModule from 'module';
1112
import {Config} from '@jest/types';
1213
import {
1314
Jest,
@@ -17,13 +18,9 @@ import {
1718
ModuleWrapper,
1819
} from '@jest/environment';
1920
import {SourceMapRegistry} from '@jest/source-map';
20-
import jestMock = require('jest-mock');
21-
import HasteMap = require('jest-haste-map');
2221
import {formatStackTrace, separateMessageFromStack} from 'jest-message-util';
23-
import Resolver = require('jest-resolve');
2422
import {createDirectory, deepCyclicCopy} from 'jest-util';
2523
import {escapePathForRegex} from 'jest-regex-util';
26-
import Snapshot = require('jest-snapshot');
2724
import {
2825
ScriptTransformer,
2926
ShouldInstrumentOptions,
@@ -35,11 +32,15 @@ import {
3532
import {V8CoverageResult} from '@jest/test-result';
3633
import {CoverageInstrumenter, V8Coverage} from 'collect-v8-coverage';
3734
import * as fs from 'graceful-fs';
38-
import stripBOM = require('strip-bom');
3935
import {run as cliRun} from './cli';
4036
import {options as cliOptions} from './cli/args';
4137
import {findSiblingsWithFileExtension} from './helpers';
4238
import {Context as JestContext} from './types';
39+
import jestMock = require('jest-mock');
40+
import HasteMap = require('jest-haste-map');
41+
import Resolver = require('jest-resolve');
42+
import Snapshot = require('jest-snapshot');
43+
import stripBOM = require('strip-bom');
4344

4445
type HasteMapOptions = {
4546
console?: Console;
@@ -887,6 +888,64 @@ class Runtime {
887888
return this._environment.global.process;
888889
}
889890

891+
if (moduleName === 'module') {
892+
const createRequire = (modulePath: string | URL) => {
893+
const filename =
894+
typeof modulePath === 'string'
895+
? modulePath.startsWith('file:///')
896+
? fileURLToPath(new URL(modulePath))
897+
: modulePath
898+
: fileURLToPath(modulePath);
899+
900+
if (!path.isAbsolute(filename)) {
901+
const error = new TypeError(
902+
`The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received '${filename}'`,
903+
);
904+
// @ts-ignore
905+
error.code = 'ERR_INVALID_ARG_TYPE';
906+
throw error;
907+
}
908+
909+
return this._createRequireImplementation({
910+
children: [],
911+
exports: {},
912+
filename,
913+
id: filename,
914+
loaded: false,
915+
});
916+
};
917+
918+
const overriddenModules: Partial<typeof nativeModule> = {};
919+
920+
if ('createRequire' in nativeModule) {
921+
overriddenModules.createRequire = createRequire;
922+
}
923+
if ('createRequireFromPath' in nativeModule) {
924+
overriddenModules.createRequireFromPath = (filename: string | URL) => {
925+
if (typeof filename !== 'string') {
926+
const error = new TypeError(
927+
`The argument 'filename' must be string. Received '${filename}'.${
928+
filename instanceof URL
929+
? ' Use createRequire for URL filename.'
930+
: ''
931+
}`,
932+
);
933+
// @ts-ignore
934+
error.code = 'ERR_INVALID_ARG_TYPE';
935+
throw error;
936+
}
937+
return createRequire(filename);
938+
};
939+
}
940+
if ('syncBuiltinESMExports' in nativeModule) {
941+
overriddenModules.syncBuiltinESMExports = () => {};
942+
}
943+
944+
return Object.keys(overriddenModules).length > 0
945+
? {...nativeModule, ...overriddenModules}
946+
: nativeModule;
947+
}
948+
890949
return require(moduleName);
891950
}
892951

yarn.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2244,9 +2244,9 @@
22442244
"@types/node" "*"
22452245

22462246
"@types/node@*", "@types/node@>= 8":
2247-
version "13.5.2"
2248-
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.5.2.tgz#3de53b55fd39efc428a901a0f6db31f761cfa131"
2249-
integrity sha512-Fr6a47c84PRLfd7M7u3/hEknyUdQrrBA6VoPmkze0tcflhU5UnpWEX2kn12ktA/lb+MNHSqFlSiPHIHsaErTPA==
2247+
version "13.7.0"
2248+
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.0.tgz#b417deda18cf8400f278733499ad5547ed1abec4"
2249+
integrity sha512-GnZbirvmqZUzMgkFn70c74OQpTTUcCzlhQliTzYjQMqg+hVKcDnxdL19Ne3UdYzdMA/+W3eb646FWn/ZaT1NfQ==
22502250

22512251
"@types/prettier@^1.16.1":
22522252
version "1.19.0"

0 commit comments

Comments
 (0)