From af933a2f9e616b3e84d6c95cb3c42616a463f3af Mon Sep 17 00:00:00 2001 From: Brian Donovan <1938+eventualbuddha@users.noreply.github.com> Date: Tue, 29 Jan 2019 16:40:20 -0800 Subject: [PATCH] fix: snapshot `global` when loading plugins This allows us to load anything that modifies the `global` object and then restore it afterward. This is needed in particular for `@babel/polyfill` since it sets a global property to try to warn the user if it's being loaded more than once. --- packages/cli/src/ProcessSnapshot.ts | 10 +++ packages/cli/test/unit/ProcessSnapshotTest.ts | 83 +++++++++++++++++-- 2 files changed, 84 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/ProcessSnapshot.ts b/packages/cli/src/ProcessSnapshot.ts index faa2aa80..8db7b9fc 100644 --- a/packages/cli/src/ProcessSnapshot.ts +++ b/packages/cli/src/ProcessSnapshot.ts @@ -32,11 +32,13 @@ export default class ProcessSnapshot { constructor( private readonly requireImpl: typeof require = require, private readonly processImpl: typeof process = process, + private readonly originalGlobal: typeof global = global, private readonly log: typeof console.log = () => {} ) { this.cacheEntries = this.snapshotRequireCache(); this.extensions = this.snapshotRequireExtensions(); this.processEvents = this.snapshotProcessEvents(); + this.originalGlobal.global = Object.create(originalGlobal); } /** @@ -46,6 +48,7 @@ export default class ProcessSnapshot { this.restoreRequireCache(); this.restoreExtensions(); this.restoreProcessEvents(); + this.restoreGlobal(); } /** @@ -172,6 +175,13 @@ export default class ProcessSnapshot { } } + /** + * Restores the state of the `global` object. + */ + private restoreGlobal(): void { + this.originalGlobal.global = this.originalGlobal; + } + /** * Snapshots the current state of `require.cache`. */ diff --git a/packages/cli/test/unit/ProcessSnapshotTest.ts b/packages/cli/test/unit/ProcessSnapshotTest.ts index 9f05202f..d3f2f8bc 100644 --- a/packages/cli/test/unit/ProcessSnapshotTest.ts +++ b/packages/cli/test/unit/ProcessSnapshotTest.ts @@ -5,6 +5,7 @@ import ProcessSnapshot from '../../src/ProcessSnapshot'; describe('ProcessSnapshot', function() { let fakeRequire: typeof require; let fakeProcess: typeof process; + let fakeGlobal: typeof global; let messages: Array; function log(...args: Array<{}>): void { @@ -21,12 +22,19 @@ describe('ProcessSnapshot', function() { fakeProcess['_events'] = Object.create(fakeProcess['_events']); fakeProcess.removeAllListeners(); + fakeGlobal = Object.create(global); + messages = []; }); it('removes added require entries from the cache', function() { // Take a snapshot. - let snapshot = new ProcessSnapshot(fakeRequire, fakeProcess, log); + let snapshot = new ProcessSnapshot( + fakeRequire, + fakeProcess, + fakeGlobal, + log + ); // Add a file to the cache. let path = '/some/added/file'; @@ -47,7 +55,12 @@ describe('ProcessSnapshot', function() { ok(path in fakeRequire.cache); // Take a snapshot. - let snapshot = new ProcessSnapshot(fakeRequire, fakeProcess, log); + let snapshot = new ProcessSnapshot( + fakeRequire, + fakeProcess, + fakeGlobal, + log + ); // Remove a file from the cache. delete fakeRequire.cache[path]; @@ -70,7 +83,12 @@ describe('ProcessSnapshot', function() { ok(path in fakeRequire.cache); // Take a snapshot. - let snapshot = new ProcessSnapshot(fakeRequire, fakeProcess, log); + let snapshot = new ProcessSnapshot( + fakeRequire, + fakeProcess, + fakeGlobal, + log + ); // Replace a file in the cache. fakeRequire.cache[path] = new Module(path); @@ -86,7 +104,12 @@ describe('ProcessSnapshot', function() { it('removes added require extensions', function() { // Take a snapshot. - let snapshot = new ProcessSnapshot(fakeRequire, fakeProcess, log); + let snapshot = new ProcessSnapshot( + fakeRequire, + fakeProcess, + fakeGlobal, + log + ); // Add a require extension. fakeRequire.extensions['.omg'] = () => {}; @@ -103,7 +126,12 @@ describe('ProcessSnapshot', function() { fakeRequire.extensions['.omg'] = () => {}; // Take a snapshot. - let snapshot = new ProcessSnapshot(fakeRequire, fakeProcess, log); + let snapshot = new ProcessSnapshot( + fakeRequire, + fakeProcess, + fakeGlobal, + log + ); // Remove the extension. delete fakeRequire.extensions['.omg']; @@ -121,7 +149,12 @@ describe('ProcessSnapshot', function() { fakeRequire.extensions['.omg'] = originalLoader; // Take a snapshot. - let snapshot = new ProcessSnapshot(fakeRequire, fakeProcess, log); + let snapshot = new ProcessSnapshot( + fakeRequire, + fakeProcess, + fakeGlobal, + log + ); // Replace a require extension. fakeRequire.extensions['.omg'] = () => {}; @@ -135,7 +168,12 @@ describe('ProcessSnapshot', function() { it('removes added process event listeners', function() { // Take a snapshot. - let snapshot = new ProcessSnapshot(fakeRequire, fakeProcess, log); + let snapshot = new ProcessSnapshot( + fakeRequire, + fakeProcess, + fakeGlobal, + log + ); // Add an event listener. let listener = (): void => {}; @@ -154,7 +192,12 @@ describe('ProcessSnapshot', function() { fakeProcess.on('exit', listener); // Take a snapshot. - let snapshot = new ProcessSnapshot(fakeRequire, fakeProcess, log); + let snapshot = new ProcessSnapshot( + fakeRequire, + fakeProcess, + fakeGlobal, + log + ); // Remove the event listener. fakeProcess.removeListener('exit', listener); @@ -172,7 +215,12 @@ describe('ProcessSnapshot', function() { fakeProcess.on('exit', existingListener); // Take a snapshot. - let snapshot = new ProcessSnapshot(fakeRequire, fakeProcess, log); + let snapshot = new ProcessSnapshot( + fakeRequire, + fakeProcess, + fakeGlobal, + log + ); // Add an event listener. let listener = (): void => {}; @@ -184,4 +232,21 @@ describe('ProcessSnapshot', function() { deepEqual(messages, [`removing added 'exit' event listener`]); deepEqual(fakeProcess.listeners('exit'), [existingListener]); }); + + it('restores `global`', function() { + // Take a snapshot. + let snapshot = new ProcessSnapshot( + fakeRequire, + fakeProcess, + fakeGlobal, + log + ); + + fakeGlobal.global['foo'] = 123; + + // Restore the snapshot. + snapshot.restore(); + + strictEqual(fakeGlobal.global['foo'], undefined); + }); });