diff --git a/packages/babel-register/package.json b/packages/babel-register/package.json index 949f5498b8..8c4f92813d 100644 --- a/packages/babel-register/package.json +++ b/packages/babel-register/package.json @@ -14,7 +14,7 @@ "author": "Sebastian McKenzie ", "main": "lib/index.js", "browser": { - "./lib/node.js": "./lib/browser.js" + "./lib/nodeWrapper.js": "./lib/browser.js" }, "dependencies": { "find-cache-dir": "^2.0.0", diff --git a/packages/babel-register/src/index.js b/packages/babel-register/src/index.js index baf93d9367..aea88df53e 100644 --- a/packages/babel-register/src/index.js +++ b/packages/babel-register/src/index.js @@ -9,7 +9,7 @@ exports = module.exports = function (...args) { }; exports.__esModule = true; -const node = require("./node"); +const node = require("./nodeWrapper"); const register = node.default; Object.assign(exports, node); diff --git a/packages/babel-register/src/node.js b/packages/babel-register/src/node.js index ed527cdac5..a27b0c87fd 100644 --- a/packages/babel-register/src/node.js +++ b/packages/babel-register/src/node.js @@ -7,6 +7,7 @@ import { OptionManager, DEFAULT_EXTENSIONS } from "@babel/core"; import { addHook } from "pirates"; import fs from "fs"; import path from "path"; +import Module from "module"; const maps = {}; let transformOpts = {}; @@ -82,15 +83,19 @@ function compile(code, filename) { } let compiling = false; +const internalModuleCache = Module._cache; function compileHook(code, filename) { if (compiling) return code; + const globalModuleCache = Module._cache; try { compiling = true; + Module._cache = internalModuleCache; return compile(code, filename); } finally { compiling = false; + Module._cache = globalModuleCache; } } diff --git a/packages/babel-register/src/nodeWrapper.js b/packages/babel-register/src/nodeWrapper.js new file mode 100644 index 0000000000..97666b3ef9 --- /dev/null +++ b/packages/babel-register/src/nodeWrapper.js @@ -0,0 +1,21 @@ +/** + * This file wraps the implementation of register so all modules `require()`-ed + * internally within register are stored in a separate module cache. + * This prevents un-transformed modules being stored in global module cache, + * and allows register to transform these modules if they are loaded externally. + */ + +const Module = require("module"); + +const globalModuleCache = Module._cache; +const internalModuleCache = Object.create(null); + +Module._cache = internalModuleCache; +const node = require("./node"); +Module._cache = globalModuleCache; + +// Add source-map-support to global cache as it's stateful +const smsPath = require.resolve("source-map-support"); +globalModuleCache[smsPath] = internalModuleCache[smsPath]; + +module.exports = node; diff --git a/packages/babel-register/test/fixtures/internal-modules/index.js b/packages/babel-register/test/fixtures/internal-modules/index.js new file mode 100644 index 0000000000..d59a323368 --- /dev/null +++ b/packages/babel-register/test/fixtures/internal-modules/index.js @@ -0,0 +1,28 @@ +const register = require('../../..'); + +// Plugin to add '/* transformed */' comment to start of function bodies +const plugin = () => ( { + visitor: { + Function(path) { + const bodyNode = path.node.body; + (bodyNode.leadingComments || (bodyNode.leadingComments = [])).push( { + type: 'CommentBlock', + value: ' transformed ' + } ); + }, + }, +} ); + +register( { + ignore: [], + babelrc: false, + configFile: false, + plugins: [plugin] +} ); + +console.log( + JSON.stringify({ + convertSourceMap: require('convert-source-map').fromObject.toString(), + isPlainObject: require('lodash/isPlainObject').toString() + }) +); diff --git a/packages/babel-register/test/index.js b/packages/babel-register/test/index.js index ca594af663..181a768bb8 100644 --- a/packages/babel-register/test/index.js +++ b/packages/babel-register/test/index.js @@ -6,7 +6,7 @@ let currentHook; let currentOptions; let sourceMapSupport = false; -const registerFile = require.resolve("../lib/node"); +const registerFile = require.resolve("../lib/index"); const testCacheFilename = path.join(__dirname, ".babel"); const testFile = require.resolve("./fixtures/babelrc/es2015"); const testFileContent = fs.readFileSync(testFile); @@ -14,6 +14,9 @@ const sourceMapTestFile = require.resolve("./fixtures/source-map/index"); const sourceMapNestedTestFile = require.resolve( "./fixtures/source-map/foo/bar", ); +const internalModulesTestFile = require.resolve( + "./fixtures/internal-modules/index", +); jest.mock("pirates", () => { return { @@ -152,18 +155,7 @@ describe("@babel/register", function () { // that working inside a test, possibly because of jest’s mocking // hooks, so we spawn a separate process. - const args = ["-r", registerFile, sourceMapTestFile]; - const spawn = child.spawn(process.execPath, args, { cwd: __dirname }); - - let output = ""; - - for (const stream of [spawn.stderr, spawn.stdout]) { - stream.on("data", chunk => { - output += chunk; - }); - } - - spawn.on("close", function () { + spawnNode(["-r", registerFile, sourceMapTestFile], output => { let err; try { @@ -201,4 +193,40 @@ describe("@babel/register", function () { expect(result).toBe('"use strict";\n\nrequire("assert");'); }); + + test("transforms modules used within register", callback => { + // Need a clean environment without `convert-source-map` + // and `lodash/isPlainObject` already in the require cache, + // so we spawn a separate process + + spawnNode([internalModulesTestFile], output => { + let err; + + try { + const { convertSourceMap, isPlainObject } = JSON.parse(output); + expect(convertSourceMap).toMatch("/* transformed */"); + expect(isPlainObject).toMatch("/* transformed */"); + } catch (e) { + err = e; + } + + callback(err); + }); + }); }); + +function spawnNode(args, callback) { + const spawn = child.spawn(process.execPath, args, { cwd: __dirname }); + + let output = ""; + + for (const stream of [spawn.stderr, spawn.stdout]) { + stream.on("data", chunk => { + output += chunk; + }); + } + + spawn.on("close", function () { + callback(output); + }); +}