diff --git a/CHANGELOG.md b/CHANGELOG.md index 25f7b3a3b2e5..b513ec69015e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - `[*]` [**BREAKING**] Only support Node LTS releases and Node 15 ([#10685](https://github.com/facebook/jest/pull/10685)) - `[*]` [**BREAKING**] Add `exports` field to all `package.json`s ([#9921](https://github.com/facebook/jest/pull/9921)) +- `[*]` Make it easier for Jest's packages to use the VM escape hatch ([#10824](https://github.com/facebook/jest/pull/10824)) - `[jest-config]` [**BREAKING**] Remove `enabledTestsMap` config, use `filter` instead ([#10787](https://github.com/facebook/jest/pull/10787)) - `[jest-resolve]` [**BREAKING**] Migrate to ESM ([#10688](https://github.com/facebook/jest/pull/10688)) diff --git a/babel.config.js b/babel.config.js index b0dc52a720ba..d0ead878ed16 100644 --- a/babel.config.js +++ b/babel.config.js @@ -56,6 +56,7 @@ module.exports = { ['@babel/plugin-transform-modules-commonjs', {allowTopLevelThis: true}], '@babel/plugin-transform-strict-mode', '@babel/plugin-proposal-class-properties', + require.resolve('./scripts/babel-plugin-jest-require-outside-vm'), ], presets: [ [ diff --git a/packages/jest-runtime/src/__tests__/runtime_require_resolve.test.ts b/packages/jest-runtime/src/__tests__/runtime_require_resolve.test.ts index 77e35813eceb..493fc115501e 100644 --- a/packages/jest-runtime/src/__tests__/runtime_require_resolve.test.ts +++ b/packages/jest-runtime/src/__tests__/runtime_require_resolve.test.ts @@ -44,7 +44,7 @@ describe('Runtime require.resolve', () => { ); }); - describe('with the OUTSIDE_JEST_VM_RESOLVE_OPTION', () => { + describe('with the jest-resolve-outside-vm-option', () => { it('forwards to the real Node require in an internal context', async () => { const runtime = await createRuntime(__filename); const module = runtime.requireInternalModule( diff --git a/packages/jest-runtime/src/__tests__/test_root/resolve_and_require_outside.js b/packages/jest-runtime/src/__tests__/test_root/resolve_and_require_outside.js index e39ba2527380..05c7c2cd6187 100644 --- a/packages/jest-runtime/src/__tests__/test_root/resolve_and_require_outside.js +++ b/packages/jest-runtime/src/__tests__/test_root/resolve_and_require_outside.js @@ -8,7 +8,7 @@ 'use strict'; const resolved = require.resolve('./create_require_module', { - [Symbol.for('OUTSIDE_JEST_VM_RESOLVE_OPTION')]: true, + [Symbol.for('jest-resolve-outside-vm-option')]: true, }); if (typeof resolved !== 'string') { throw new Error('require.resolve not spec-compliant: must return a string'); diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 65376a7c7403..9252124b4e6f 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -92,11 +92,11 @@ const defaultTransformOptions: InternalModuleOptions = { type InitialModule = Omit; type ModuleRegistry = Map; -const OUTSIDE_JEST_VM_RESOLVE_OPTION = Symbol.for( - 'OUTSIDE_JEST_VM_RESOLVE_OPTION', +const JEST_RESOLVE_OUTSIDE_VM_OPTION = Symbol.for( + 'jest-resolve-outside-vm-option', ); type ResolveOptions = Parameters[1] & { - [OUTSIDE_JEST_VM_RESOLVE_OPTION]?: true; + [JEST_RESOLVE_OUTSIDE_VM_OPTION]?: true; }; type StringMap = Map; @@ -1402,7 +1402,7 @@ export default class Runtime { resolveOptions, ); if ( - resolveOptions?.[OUTSIDE_JEST_VM_RESOLVE_OPTION] && + resolveOptions?.[JEST_RESOLVE_OUTSIDE_VM_OPTION] && options?.isInternalModule ) { return createOutsideJestVmPath(resolved); diff --git a/scripts/babel-plugin-jest-require-outside-vm.js b/scripts/babel-plugin-jest-require-outside-vm.js new file mode 100644 index 000000000000..07bd0eab714c --- /dev/null +++ b/scripts/babel-plugin-jest-require-outside-vm.js @@ -0,0 +1,53 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +const assert = require('assert'); + +/* +Replace + +requireOutside('package') + +with + +require(require.resolve('package', { + [Symbol.for('jest-resolve-outside-vm-option')]: true, +})); +*/ + +const REQUIRE_OUTSIDE_FUNCTION_NAME = 'requireOutside'; + +module.exports = ({template, types: t}) => { + const replacement = template(` + require(require.resolve(IMPORT_PATH, { + [(global['jest-symbol-do-not-touch'] || global.Symbol).for('jest-resolve-outside-vm-option')]: true, + })); + `); + return { + name: 'jest-require-outside-vm', + visitor: { + CallExpression(path) { + const {callee, arguments: args} = path.node; + if ( + t.isIdentifier(callee) && + callee.name === REQUIRE_OUTSIDE_FUNCTION_NAME && + !path.scope.hasBinding(REQUIRE_OUTSIDE_FUNCTION_NAME) + ) { + assert.strictEqual( + args.length, + 1, + 'requireOutside must be called with exactly one argument', + ); + const importPath = args[0]; + path.replaceWith(replacement({IMPORT_PATH: importPath})); + } + }, + }, + }; +};