From e98662916dfdce878848d26fe9f764bde8574286 Mon Sep 17 00:00:00 2001 From: Tomer Date: Mon, 3 Dec 2018 15:48:53 +0200 Subject: [PATCH 1/2] use config to specify extra global variables --- CHANGELOG.md | 1 + TestUtils.js | 2 ++ docs/Configuration.md | 17 ++++++++++ packages/jest-config/src/ValidConfig.js | 1 + packages/jest-config/src/index.js | 2 ++ packages/jest-config/src/normalize.js | 1 + .../script_transformer.test.js.snap | 5 +++ .../src/__tests__/script_transformer.test.js | 6 ++++ packages/jest-runtime/src/index.js | 16 ++++++--- .../jest-runtime/src/script_transformer.js | 33 ++++++++++++++----- types/Config.js | 3 ++ 11 files changed, 75 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 925911127054..af38c706dbb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - `[jest-haste-map]` [**BREAKING**] Remove name from hash in `HasteMap.getCacheFilePath` ([#7218](https://github.com/facebook/jest/pull/7218)) - `[babel-preset-jest]` [**BREAKING**] Export a function instead of an object for Babel 7 compatibility ([#7203](https://github.com/facebook/jest/pull/7203)) - `[jest-haste-map]` [**BREAKING**] Expose relative paths when getting the file iterator ([#7321](https://github.com/facebook/jest/pull/7321)) +- `[jest-runtime]` Add `extraGlobals` to config to load extra global variables into the execution vm ([#7454](https://github.com/facebook/jest/pull/7454)) - `[jest-validate]` Add support for comments in `package.json` using a `"//"` key ([#7295](https://github.com/facebook/jest/pull/7295)) - `[jest-config]` Add shorthand for watch plugins and runners ([#7213](https://github.com/facebook/jest/pull/7213)) - `[jest-jasmine2/jest-circus/jest-cli]` Add test.todo ([#6996](https://github.com/facebook/jest/pull/6996)) diff --git a/TestUtils.js b/TestUtils.js index 3734d8ec5a55..16cd4e16a02a 100644 --- a/TestUtils.js +++ b/TestUtils.js @@ -26,6 +26,7 @@ const DEFAULT_GLOBAL_CONFIG: GlobalConfig = { enabledTestsMap: null, errorOnDeprecated: false, expand: false, + extraGlobals: [], filter: null, findRelatedTests: false, forceExit: false, @@ -77,6 +78,7 @@ const DEFAULT_PROJECT_CONFIG: ProjectConfig = { detectOpenHandles: false, displayName: undefined, errorOnDeprecated: false, + extraGlobals: [], filter: null, forceCoverageMatch: [], globals: {}, diff --git a/docs/Configuration.md b/docs/Configuration.md index 8cc5493f7864..50ab7fa5c4be 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -273,6 +273,23 @@ Default: `false` Make calling deprecated APIs throw helpful error messages. Useful for easing the upgrade process. +### `extraGlobals` [array] + +Default: `undefined` + +Test files run inside a [vm](https://nodejs.org/api/vm.html), which slows calls to global context properties (e.g. `Math`). With this option you can specify extra properties to be defined inside the vm for faster lookups. + +For example, if your tests call `Math` often, you can pass it by setting `extraGlobals`. + +```json +{ + ... + "jest": { + "extraGlobals": ["Math"] + } +} +``` + ### `forceCoverageMatch` [array] Default: `['']` diff --git a/packages/jest-config/src/ValidConfig.js b/packages/jest-config/src/ValidConfig.js index c3da446f08a5..c6a3179ebee3 100644 --- a/packages/jest-config/src/ValidConfig.js +++ b/packages/jest-config/src/ValidConfig.js @@ -44,6 +44,7 @@ export default ({ displayName: 'project-name', errorOnDeprecated: false, expand: false, + extraGlobals: [], filter: '/filter.js', forceCoverageMatch: ['**/*.t.js'], forceExit: false, diff --git a/packages/jest-config/src/index.js b/packages/jest-config/src/index.js index b1832b94af6a..118677010793 100644 --- a/packages/jest-config/src/index.js +++ b/packages/jest-config/src/index.js @@ -114,6 +114,7 @@ const groupOptions = ( enabledTestsMap: options.enabledTestsMap, errorOnDeprecated: options.errorOnDeprecated, expand: options.expand, + extraGlobals: options.extraGlobals, filter: options.filter, findRelatedTests: options.findRelatedTests, forceExit: options.forceExit, @@ -165,6 +166,7 @@ const groupOptions = ( detectOpenHandles: options.detectOpenHandles, displayName: options.displayName, errorOnDeprecated: options.errorOnDeprecated, + extraGlobals: options.extraGlobals, filter: options.filter, forceCoverageMatch: options.forceCoverageMatch, globals: options.globals, diff --git a/packages/jest-config/src/normalize.js b/packages/jest-config/src/normalize.js index b29e3e690091..2227762eb1ad 100644 --- a/packages/jest-config/src/normalize.js +++ b/packages/jest-config/src/normalize.js @@ -632,6 +632,7 @@ export default function normalize(options: InitialOptions, argv: Argv) { case 'displayName': case 'errorOnDeprecated': case 'expand': + case 'extraGlobals': case 'globals': case 'findRelatedTests': case 'forceCoverageMatch': diff --git a/packages/jest-runtime/src/__tests__/__snapshots__/script_transformer.test.js.snap b/packages/jest-runtime/src/__tests__/__snapshots__/script_transformer.test.js.snap index 68522bebc832..b85b85df5272 100644 --- a/packages/jest-runtime/src/__tests__/__snapshots__/script_transformer.test.js.snap +++ b/packages/jest-runtime/src/__tests__/__snapshots__/script_transformer.test.js.snap @@ -154,6 +154,11 @@ module.exports = () => { }});" `; +exports[`ScriptTransformer transforms a file properly 3`] = ` +"({\\"Object.\\":function(module,exports,require,__dirname,__filename,global,jest,Math){module.exports = \\"banana\\"; +}});" +`; + exports[`ScriptTransformer uses multiple preprocessors 1`] = ` "({\\"Object.\\":function(module,exports,require,__dirname,__filename,global,jest){ const TRANSFORMED = { diff --git a/packages/jest-runtime/src/__tests__/script_transformer.test.js b/packages/jest-runtime/src/__tests__/script_transformer.test.js index 60ae65c665ff..66b77007bbee 100644 --- a/packages/jest-runtime/src/__tests__/script_transformer.test.js +++ b/packages/jest-runtime/src/__tests__/script_transformer.test.js @@ -224,6 +224,12 @@ describe('ScriptTransformer', () => { // If we disable coverage, we get a different result. scriptTransformer.transform('/fruits/kiwi.js', {collectCoverage: false}); expect(vm.Script.mock.calls[1][0]).toEqual(snapshot); + + scriptTransformer.transform('/fruits/banana.js', { + // to make sure jest isn't declared twice + extraGlobals: ['Math', 'jest'], + }).script; + expect(vm.Script.mock.calls[3][0]).toMatchSnapshot(); }); it('does not transform Node core modules', () => { diff --git a/packages/jest-runtime/src/index.js b/packages/jest-runtime/src/index.js index f91e481a2925..33da6954140f 100644 --- a/packages/jest-runtime/src/index.js +++ b/packages/jest-runtime/src/index.js @@ -616,13 +616,14 @@ class Runtime { Object.defineProperty(localModule, 'require', { value: this._createRequireImplementation(localModule, options), }); - + const extraGlobals = this._config.extraGlobals || []; const transformedFile = this._scriptTransformer.transform( filename, { collectCoverage: this._coverageOptions.collectCoverage, collectCoverageFrom: this._coverageOptions.collectCoverageFrom, collectCoverageOnlyFrom: this._coverageOptions.collectCoverageOnlyFrom, + extraGlobals, isInternalModule, }, this._cacheFS[filename], @@ -657,8 +658,7 @@ class Runtime { } const wrapper = runScript[ScriptTransformer.EVAL_RESULT_VARIABLE]; - wrapper.call( - localModule.exports, // module context + const moduleArguments = new Set([ localModule, // module object localModule.exports, // module exports localModule.require, // require implementation @@ -670,7 +670,15 @@ class Runtime { // $FlowFixMe (localModule.require: LocalModuleRequire), ), // jest object - ); + ...extraGlobals.map(globalVariable => { + if (this._environment.global[globalVariable]) + return this._environment.global[globalVariable]; + throw new Error( + `You have requested '${globalVariable}' as a global variable, but it was not present. Please check your config or your global environment.`, + ); + }), + ]); + wrapper.call(localModule.exports, ...Array.from(moduleArguments)); this._isCurrentlyExecutingManualMock = origCurrExecutingManualMock; this._currentlyExecutingModulePath = lastExecutingModulePath; diff --git a/packages/jest-runtime/src/script_transformer.js b/packages/jest-runtime/src/script_transformer.js index a25683156afd..4f6f0ee7e85b 100644 --- a/packages/jest-runtime/src/script_transformer.js +++ b/packages/jest-runtime/src/script_transformer.js @@ -36,6 +36,7 @@ export type Options = {| collectCoverage: boolean, collectCoverageFrom: Array, collectCoverageOnlyFrom: ?{[key: string]: boolean, __proto__: null}, + extraGlobals?: Array, isCoreModule?: boolean, isInternalModule?: boolean, |}; @@ -305,6 +306,8 @@ export default class ScriptTransformer { (this._shouldTransform(filename) || instrument); try { + const extraGlobals = (options && options.extraGlobals) || []; + if (willTransform) { const transformedSource = this.transformSource( filename, @@ -312,11 +315,11 @@ export default class ScriptTransformer { instrument, ); - wrappedCode = wrap(transformedSource.code); + wrappedCode = wrap(transformedSource.code, ...extraGlobals); sourceMapPath = transformedSource.sourceMapPath; mapCoverage = transformedSource.mapCoverage; } else { - wrappedCode = wrap(content); + wrappedCode = wrap(content, ...extraGlobals); } return { @@ -517,11 +520,25 @@ const calcIgnorePatternRegexp = (config: ProjectConfig): ?RegExp => { return new RegExp(config.transformIgnorePatterns.join('|')); }; -const wrap = content => - '({"' + - ScriptTransformer.EVAL_RESULT_VARIABLE + - '":function(module,exports,require,__dirname,__filename,global,jest){' + - content + - '\n}});'; +const wrap = (content, ...extras) => { + const globals = new Set([ + 'module', + 'exports', + 'require', + '__dirname', + '__filename', + 'global', + 'jest', + ...extras, + ]); + + return ( + '({"' + + ScriptTransformer.EVAL_RESULT_VARIABLE + + `":function(${Array.from(globals).join(',')}){` + + content + + '\n}});' + ); +}; ScriptTransformer.EVAL_RESULT_VARIABLE = 'Object.'; diff --git a/types/Config.js b/types/Config.js index 39eac782b764..17b7a1e562f6 100644 --- a/types/Config.js +++ b/types/Config.js @@ -111,6 +111,7 @@ export type InitialOptions = { detectOpenHandles?: boolean, displayName?: string, expand?: boolean, + extraGlobals?: Array, filter?: Path, findRelatedTests?: boolean, forceCoverageMatch?: Array, @@ -204,6 +205,7 @@ export type GlobalConfig = {| detectOpenHandles: boolean, enabledTestsMap: ?{[key: string]: {[key: string]: boolean}}, expand: boolean, + extraGlobals: Array, filter: ?Path, findRelatedTests: boolean, forceExit: boolean, @@ -257,6 +259,7 @@ export type ProjectConfig = {| detectOpenHandles: boolean, displayName: ?string, errorOnDeprecated: boolean, + extraGlobals: Array, filter: ?Path, forceCoverageMatch: Array, globals: ConfigGlobals, From dc3e4e0300a016ccfef88ebbe24fecaa7ccf481f Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 24 Dec 2018 13:42:30 +0100 Subject: [PATCH 2/2] Update index.js --- packages/jest-runtime/src/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/jest-runtime/src/index.js b/packages/jest-runtime/src/index.js index 33da6954140f..2303dbc5acef 100644 --- a/packages/jest-runtime/src/index.js +++ b/packages/jest-runtime/src/index.js @@ -671,8 +671,10 @@ class Runtime { (localModule.require: LocalModuleRequire), ), // jest object ...extraGlobals.map(globalVariable => { - if (this._environment.global[globalVariable]) + if (this._environment.global[globalVariable]) { return this._environment.global[globalVariable]; + } + throw new Error( `You have requested '${globalVariable}' as a global variable, but it was not present. Please check your config or your global environment.`, );