From 6286798f1ba206627d5456319ca4af336446cbfe Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Sun, 19 Apr 2020 21:30:06 +0300 Subject: [PATCH 1/7] feat(runtime): populate require.cache --- .../__tests__/runtime_require_cache.test.js | 41 +++++++++++++++++++ packages/jest-runtime/src/index.ts | 32 ++++++++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 packages/jest-runtime/src/__tests__/runtime_require_cache.test.js diff --git a/packages/jest-runtime/src/__tests__/runtime_require_cache.test.js b/packages/jest-runtime/src/__tests__/runtime_require_cache.test.js new file mode 100644 index 000000000000..ec06833c9d04 --- /dev/null +++ b/packages/jest-runtime/src/__tests__/runtime_require_cache.test.js @@ -0,0 +1,41 @@ +/** + * 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'; + +let createRuntime; + +describe('Runtime require.cache', () => { + beforeEach(() => { + createRuntime = require('createRuntime'); + }); + + it('require.cache returns loaded module list as native Nodejs require does', () => + createRuntime(__filename).then(runtime => { + const regularModule = runtime.requireModule( + runtime.__mockRootPath, + 'RegularModule', + ).module; + + expect(regularModule.require.cache[regularModule.id]).toBe(regularModule); + })); + + it('require.cache is tolerant readonly', () => + createRuntime(__filename).then(runtime => { + const regularModule = runtime.requireModule( + runtime.__mockRootPath, + 'RegularModule', + ).module; + + delete regularModule.require.cache[regularModule.id]; + expect(regularModule.require.cache[regularModule.id]).toBe(regularModule); + + regularModule.require.cache[regularModule.id] = 'something'; + expect(regularModule.require.cache[regularModule.id]).toBe(regularModule); + })); +}); diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 1a5cff8361a7..bf35be69a169 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -1238,6 +1238,8 @@ class Runtime { resolve.paths = (moduleName: string) => this._requireResolvePaths(from.filename, moduleName); + const moduleRegistry = this._moduleRegistry; + const esmoduleRegistry = this._esmoduleRegistry; const moduleRequire = (options && options.isInternalModule ? (moduleName: string) => this.requireInternalModule(from.filename, moduleName) @@ -1245,12 +1247,40 @@ class Runtime { this, from.filename, )) as LocalModuleRequire; - moduleRequire.cache = Object.create(null); moduleRequire.extensions = Object.create(null); moduleRequire.requireActual = this.requireActual.bind(this, from.filename); moduleRequire.requireMock = this.requireMock.bind(this, from.filename); moduleRequire.resolve = resolve; + Object.defineProperty(moduleRequire, 'cache', { + enumerable: true, + get() { + const cache: {[key: string]: any} = {}; + const notPermittedMethod = () => { + console.warn('`require.cache` modification is not permitted'); + return true; + }; + moduleRegistry.forEach((value, key) => { + cache[key] = value; + }); + esmoduleRegistry.forEach((value, key) => { + cache[key] = value; + }); + + return new Proxy(cache, { + defineProperty: notPermittedMethod, + deleteProperty: notPermittedMethod, + get(target, key) { + return typeof key === 'string' ? target[key] : undefined; + }, + has(target, key) { + return typeof key === 'string' && !!target[key]; + }, + set: notPermittedMethod, + }); + }, + }); + Object.defineProperty(moduleRequire, 'main', { enumerable: true, get() { From 0d5ab75b043fdfa420512c5c85fb3c2f181b19dd Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Mon, 20 Apr 2020 07:38:10 +0300 Subject: [PATCH 2/7] fix(runtime): exclude esm registry from require.cache --- packages/jest-runtime/src/index.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index bf35be69a169..d161c934e18b 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -1239,7 +1239,6 @@ class Runtime { this._requireResolvePaths(from.filename, moduleName); const moduleRegistry = this._moduleRegistry; - const esmoduleRegistry = this._esmoduleRegistry; const moduleRequire = (options && options.isInternalModule ? (moduleName: string) => this.requireInternalModule(from.filename, moduleName) @@ -1255,7 +1254,7 @@ class Runtime { Object.defineProperty(moduleRequire, 'cache', { enumerable: true, get() { - const cache: {[key: string]: any} = {}; + const cache: {[key: string]: InitialModule | Module} = {}; const notPermittedMethod = () => { console.warn('`require.cache` modification is not permitted'); return true; @@ -1263,9 +1262,6 @@ class Runtime { moduleRegistry.forEach((value, key) => { cache[key] = value; }); - esmoduleRegistry.forEach((value, key) => { - cache[key] = value; - }); return new Proxy(cache, { defineProperty: notPermittedMethod, From 91c109a29bd5cdb8bc72d12f022d325793f2fd39 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 20 Apr 2020 07:49:06 +0200 Subject: [PATCH 3/7] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 839115831aea..de22b98fd823 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Features +- `[jest-runtime]` Populate `require.cache` ([#9841](https://github.com/facebook/jest/pull/9841)) + ### Fixes ### Chore & Maintenance From c0e4934bd0461443391ddb841e306d61abfacfbc Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Mon, 20 Apr 2020 09:01:21 +0300 Subject: [PATCH 4/7] refactor: remove redundant require.cache wrap --- packages/jest-runtime/src/index.ts | 45 +++++++++++++----------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index fa5fdbcd7ead..2edb009d3f98 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -80,6 +80,8 @@ type ResolveOptions = Parameters[1]; type BooleanObject = Record; type CacheFS = {[path: string]: string}; +type RequireCache = {[key: string]: Module}; + namespace Runtime { export type Context = JestContext; // ditch this export when moving to esm - for now we need it for to avoid faulty type elision @@ -1275,32 +1277,23 @@ class Runtime { moduleRequire.requireActual = this.requireActual.bind(this, from.filename); moduleRequire.requireMock = this.requireMock.bind(this, from.filename); moduleRequire.resolve = resolve; - - Object.defineProperty(moduleRequire, 'cache', { - enumerable: true, - get() { - const cache: {[key: string]: InitialModule | Module} = {}; - const notPermittedMethod = () => { - console.warn('`require.cache` modification is not permitted'); - return true; - }; - moduleRegistry.forEach((value, key) => { - cache[key] = value; - }); - - return new Proxy(cache, { - defineProperty: notPermittedMethod, - deleteProperty: notPermittedMethod, - get(target, key) { - return typeof key === 'string' ? target[key] : undefined; - }, - has(target, key) { - return typeof key === 'string' && !!target[key]; - }, - set: notPermittedMethod, - }); - }, - }); + moduleRequire.cache = (target => { + const notPermittedMethod = () => { + console.warn('`require.cache` modification is not permitted'); + return true; + }; + return new Proxy(target, { + defineProperty: notPermittedMethod, + deleteProperty: notPermittedMethod, + get(_target, key) { + return typeof key === 'string' ? moduleRegistry.get(key) : undefined; + }, + has(_target, key) { + return typeof key === 'string' && moduleRegistry.has(key); + }, + set: notPermittedMethod, + }); + })(Object.create(null)) as RequireCache; Object.defineProperty(moduleRequire, 'main', { enumerable: true, From 2c6d879ae150f6e4eb20eec6ab2829cad0fe3e90 Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Mon, 20 Apr 2020 09:32:45 +0300 Subject: [PATCH 5/7] refactor: encapsulate require.cache inner vars in closure --- packages/jest-runtime/src/index.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 2edb009d3f98..4dcf13c9d767 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -1265,7 +1265,6 @@ class Runtime { resolve.paths = (moduleName: string) => this._requireResolvePaths(from.filename, moduleName); - const moduleRegistry = this._moduleRegistry; const moduleRequire = (options && options.isInternalModule ? (moduleName: string) => this.requireInternalModule(from.filename, moduleName) @@ -1277,7 +1276,8 @@ class Runtime { moduleRequire.requireActual = this.requireActual.bind(this, from.filename); moduleRequire.requireMock = this.requireMock.bind(this, from.filename); moduleRequire.resolve = resolve; - moduleRequire.cache = (target => { + moduleRequire.cache = ((target: RequireCache) => { + const moduleRegistry = this._moduleRegistry; const notPermittedMethod = () => { console.warn('`require.cache` modification is not permitted'); return true; @@ -1285,15 +1285,19 @@ class Runtime { return new Proxy(target, { defineProperty: notPermittedMethod, deleteProperty: notPermittedMethod, - get(_target, key) { - return typeof key === 'string' ? moduleRegistry.get(key) : undefined; + get(target, key) { + return typeof key === 'string' + ? moduleRegistry.get(key) || target[key] + : undefined; }, - has(_target, key) { - return typeof key === 'string' && moduleRegistry.has(key); + has(target, key) { + return typeof key === 'string' + ? moduleRegistry.has(key) || !!target[key] + : false; }, set: notPermittedMethod, }); - })(Object.create(null)) as RequireCache; + })(Object.create(null)); Object.defineProperty(moduleRequire, 'main', { enumerable: true, From d2b3c9db21132338f95a8c46e5bad2901b3bc5cc Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Mon, 20 Apr 2020 09:41:51 +0300 Subject: [PATCH 6/7] chore: require.cache polishing --- packages/jest-runtime/src/index.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 4dcf13c9d767..42be2020f28c 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -1276,28 +1276,24 @@ class Runtime { moduleRequire.requireActual = this.requireActual.bind(this, from.filename); moduleRequire.requireMock = this.requireMock.bind(this, from.filename); moduleRequire.resolve = resolve; - moduleRequire.cache = ((target: RequireCache) => { + moduleRequire.cache = ((): RequireCache => { const moduleRegistry = this._moduleRegistry; const notPermittedMethod = () => { console.warn('`require.cache` modification is not permitted'); return true; }; - return new Proxy(target, { + return new Proxy(Object.create(null), { defineProperty: notPermittedMethod, deleteProperty: notPermittedMethod, - get(target, key) { - return typeof key === 'string' - ? moduleRegistry.get(key) || target[key] - : undefined; + get(_target, key) { + return typeof key === 'string' ? moduleRegistry.get(key) : undefined; }, - has(target, key) { - return typeof key === 'string' - ? moduleRegistry.has(key) || !!target[key] - : false; + has(_target, key) { + return typeof key === 'string' && moduleRegistry.has(key); }, set: notPermittedMethod, }); - })(Object.create(null)); + })(); Object.defineProperty(moduleRequire, 'main', { enumerable: true, From 6b595f58af7f7a6b7b5a6cf46cca26ce208bdfc8 Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Mon, 20 Apr 2020 10:19:49 +0300 Subject: [PATCH 7/7] refactor: use arrow fns for require.cache proxy to avoid redundant vars --- packages/jest-runtime/src/index.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 42be2020f28c..d516e903d1e6 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -1276,21 +1276,18 @@ class Runtime { moduleRequire.requireActual = this.requireActual.bind(this, from.filename); moduleRequire.requireMock = this.requireMock.bind(this, from.filename); moduleRequire.resolve = resolve; - moduleRequire.cache = ((): RequireCache => { - const moduleRegistry = this._moduleRegistry; + moduleRequire.cache = (() => { const notPermittedMethod = () => { console.warn('`require.cache` modification is not permitted'); return true; }; - return new Proxy(Object.create(null), { + return new Proxy(Object.create(null), { defineProperty: notPermittedMethod, deleteProperty: notPermittedMethod, - get(_target, key) { - return typeof key === 'string' ? moduleRegistry.get(key) : undefined; - }, - has(_target, key) { - return typeof key === 'string' && moduleRegistry.has(key); - }, + get: (_target, key) => + typeof key === 'string' ? this._moduleRegistry.get(key) : undefined, + has: (_target, key) => + typeof key === 'string' && this._moduleRegistry.has(key), set: notPermittedMethod, }); })();