diff --git a/packages/babel-register/package.json b/packages/babel-register/package.json index cec7f05486..b7f1784411 100644 --- a/packages/babel-register/package.json +++ b/packages/babel-register/package.json @@ -13,9 +13,11 @@ "home-or-tmp": "^3.0.0", "lodash": "^4.2.0", "mkdirp": "^0.5.1", + "pirates": "^3.0.1", "source-map-support": "^0.4.2" }, "devDependencies": { - "decache": "^4.1.0" + "decache": "^4.1.0", + "default-require-extensions": "^1.0.0" } } diff --git a/packages/babel-register/src/node.js b/packages/babel-register/src/node.js index 6e988f0eca..6f9254b362 100644 --- a/packages/babel-register/src/node.js +++ b/packages/babel-register/src/node.js @@ -2,12 +2,16 @@ import deepClone from "lodash/cloneDeep"; import sourceMapSupport from "source-map-support"; import * as registerCache from "./cache"; import escapeRegExp from "lodash/escapeRegExp"; -import extend from "lodash/extend"; import * as babel from "babel-core"; import { OptionManager, DEFAULT_EXTENSIONS } from "babel-core"; +import { addHook } from "pirates"; import fs from "fs"; import path from "path"; +const maps = {}; +const transformOpts = {}; +let piratesRevert = null; + sourceMapSupport.install({ handleUncaughtExceptions: false, environment: "node", @@ -27,27 +31,20 @@ sourceMapSupport.install({ registerCache.load(); let cache = registerCache.get(); -const transformOpts = {}; - -let oldHandlers = {}; -const maps = {}; - function mtime(filename) { return +fs.statSync(filename).mtime; } -function compile(filename) { - let result; - +function compile(code, filename) { // merge in base options and resolve all the plugins and presets relative to this file - const opts = new OptionManager().init(extend( + const opts = new OptionManager().init(Object.assign( { sourceRoot: path.dirname(filename) }, // sourceRoot can be overwritten deepClone(transformOpts), { filename } )); // Bail out ASAP if the file has been ignored. - if (opts === null) return null; + if (opts === null) return code; let cacheKey = `${JSON.stringify(opts)}:${babel.version}`; @@ -58,19 +55,17 @@ function compile(filename) { if (cache) { const cached = cache[cacheKey]; if (cached && cached.mtime === mtime(filename)) { - result = cached; + return cached.code; } } - if (!result) { - result = babel.transformFileSync(filename, extend(opts, { - // Do not process config files since has already been done with the OptionManager - // calls above and would introduce duplicates. - babelrc: false, - sourceMaps: "both", - ast: false, - })); - } + const result = babel.transform(code, Object.assign(opts, { + // Do not process config files since has already been done with the OptionManager + // calls above and would introduce duplicates. + babelrc: false, + sourceMaps: "both", + ast: false, + })); if (cache) { cache[cacheKey] = result; @@ -82,33 +77,14 @@ function compile(filename) { return result.code; } -function registerExtension(ext) { - const old = oldHandlers[ext] || oldHandlers[".js"] || require.extensions[".js"]; - - require.extensions[ext] = function (m, filename) { - const result = compile(filename); - - if (result === null) old(m, filename); - else m._compile(result, filename); - }; +function hookExtensions(exts) { + if (piratesRevert) piratesRevert(); + piratesRevert = addHook(compile, { exts, ignoreNodeModules: false }); } -function hookExtensions(_exts) { - Object.keys(oldHandlers).forEach(function (ext) { - const old = oldHandlers[ext]; - if (old === undefined) { - delete require.extensions[ext]; - } else { - require.extensions[ext] = old; - } - }); - - oldHandlers = {}; - - _exts.forEach(function (ext) { - oldHandlers[ext] = require.extensions[ext]; - registerExtension(ext); - }); +export function revert() { + if (piratesRevert) piratesRevert(); + delete require.cache[require.resolve(__filename)]; } register({ @@ -123,7 +99,7 @@ export default function register(opts?: Object = {}) { delete opts.extensions; delete opts.cache; - extend(transformOpts, opts); + Object.assign(transformOpts, opts); if (!transformOpts.ignore && !transformOpts.only) { transformOpts.ignore = [ diff --git a/packages/babel-register/test/__data__/es2015.js b/packages/babel-register/test/__data__/es2015.js new file mode 100644 index 0000000000..4305d43375 --- /dev/null +++ b/packages/babel-register/test/__data__/es2015.js @@ -0,0 +1,3 @@ +import a from "assert"; + +export default () => { a.foo(); return "b"; }; diff --git a/packages/babel-register/test/cache.js b/packages/babel-register/test/cache.js new file mode 100644 index 0000000000..e736f3e484 --- /dev/null +++ b/packages/babel-register/test/cache.js @@ -0,0 +1,84 @@ +import { expect } from "chai"; +import fs from "fs"; +import path from "path"; +import decache from "decache"; + +const testCacheFilename = path.join(__dirname, ".babel"); +const oldBabelDisableCacheValue = process.env.BABEL_DISABLE_CACHE; + +process.env.BABEL_CACHE_PATH = testCacheFilename; +delete process.env.BABEL_DISABLE_CACHE; + +function writeCache(data) { + if (typeof data === "object") { + data = JSON.stringify(data); + } + + fs.writeFileSync(testCacheFilename, data); +} + +function cleanCache() { + + try { + fs.unlinkSync(testCacheFilename); + } catch (e) { + // It is convenient to always try to clear + } +} + +function resetCache() { + process.env.BABEL_CACHE_PATH = null; + process.env.BABEL_DISABLE_CACHE = oldBabelDisableCacheValue; +} + +describe("babel register", () => { + + describe("cache", () => { + let load, get, save; + + beforeEach(() => { + // Since lib/cache is a singleton we need to fully reload it + decache("../lib/cache"); + const cache = require("../lib/cache"); + + load = cache.load; + get = cache.get; + save = cache.save; + }); + + afterEach(cleanCache); + after(resetCache); + + it("should load and get cached data", () => { + writeCache({ foo: "bar" }); + + load(); + + expect(get()).to.be.an("object"); + expect(get()).to.deep.equal({ foo: "bar" }); + }); + + it("should load and get an object with no cached data", () => { + load(); + + expect(get()).to.be.an("object"); + expect(get()).to.deep.equal({}); + }); + + it("should load and get an object with invalid cached data", () => { + writeCache("foobar"); + + load(); + + expect(get()).to.be.an("object"); + expect(get()).to.deep.equal({}); + }); + + it("should create the cache on save", () => { + save(); + + expect(fs.existsSync(testCacheFilename)).to.be.true; + expect(get()).to.deep.equal({}); + }); + }); +}); diff --git a/packages/babel-register/test/index.js b/packages/babel-register/test/index.js index e736f3e484..262b129a1e 100644 --- a/packages/babel-register/test/index.js +++ b/packages/babel-register/test/index.js @@ -1,84 +1,57 @@ -import { expect } from "chai"; -import fs from "fs"; +import chai from "chai"; import path from "path"; import decache from "decache"; -const testCacheFilename = path.join(__dirname, ".babel"); -const oldBabelDisableCacheValue = process.env.BABEL_DISABLE_CACHE; +const DATA_ES2015 = require.resolve("./__data__/es2015"); -process.env.BABEL_CACHE_PATH = testCacheFilename; -delete process.env.BABEL_DISABLE_CACHE; +describe("babel-register", function () { + let babelRegister; + let oldCompiler; -function writeCache(data) { - if (typeof data === "object") { - data = JSON.stringify(data); + function setupRegister(config = {}) { + babelRegister = require("../lib/node"); + babelRegister.default(Object.assign({ + presets: [path.join(__dirname, "../../babel-preset-es2015")], + babelrc: false, + }, config)); } - fs.writeFileSync(testCacheFilename, data); -} - -function cleanCache() { - - try { - fs.unlinkSync(testCacheFilename); - } catch (e) { - // It is convenient to always try to clear + function revertRegister() { + if (babelRegister) { + babelRegister.revert(); + babelRegister = null; + } } -} -function resetCache() { - process.env.BABEL_CACHE_PATH = null; - process.env.BABEL_DISABLE_CACHE = oldBabelDisableCacheValue; -} + before(() => { + const js = require("default-require-extensions/js"); + oldCompiler = require.extensions[".js"]; + require.extensions[".js"] = js; + }); -describe("babel register", () => { + after(() => { + require.extensions[".js"] = oldCompiler; + }); - describe("cache", () => { - let load, get, save; + afterEach(() => { + revertRegister(); + decache(DATA_ES2015); + }); - beforeEach(() => { - // Since lib/cache is a singleton we need to fully reload it - decache("../lib/cache"); - const cache = require("../lib/cache"); + it("registers correctly", () => { + setupRegister(); - load = cache.load; - get = cache.get; - save = cache.save; - }); + chai.expect(require(DATA_ES2015)).to.be.ok; + }); - afterEach(cleanCache); - after(resetCache); + it("reverts correctly", () => { + setupRegister(); - it("should load and get cached data", () => { - writeCache({ foo: "bar" }); + chai.expect(require(DATA_ES2015)).to.be.ok; + decache(DATA_ES2015); - load(); + revertRegister(); - expect(get()).to.be.an("object"); - expect(get()).to.deep.equal({ foo: "bar" }); - }); - - it("should load and get an object with no cached data", () => { - load(); - - expect(get()).to.be.an("object"); - expect(get()).to.deep.equal({}); - }); - - it("should load and get an object with invalid cached data", () => { - writeCache("foobar"); - - load(); - - expect(get()).to.be.an("object"); - expect(get()).to.deep.equal({}); - }); - - it("should create the cache on save", () => { - save(); - - expect(fs.existsSync(testCacheFilename)).to.be.true; - expect(get()).to.deep.equal({}); - }); + chai.expect(() => { require(DATA_ES2015); }).to.throw(SyntaxError); }); });