Skip to content

Commit

Permalink
Use the new "extensions" option in @babel/register
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Oct 8, 2020
1 parent d0f56c4 commit 7eb2a67
Show file tree
Hide file tree
Showing 19 changed files with 151 additions and 12 deletions.
42 changes: 36 additions & 6 deletions packages/babel-register/src/node.js
Expand Up @@ -7,6 +7,35 @@ import { OptionManager, DEFAULT_EXTENSIONS } from "@babel/core";
import { addHook } from "pirates";
import fs from "fs";
import path from "path";
import { Module } from "module";

// The "pirates" library, that we use to register require hooks, does
// not allow hooking into all the loaded files, but requries us to
// specify the extensions upfront.
// In order to hook into all of them, we need to do two things:
// 1) Add hooks for all the already registered extensions, defined in
// Module._extensions. By doing so, we can shadow the loaders already
// defined.
// 2) Node throws an error when requireing .mjs unless a hook has been
// defined. For compatibility reason (@babel/register can load .mjs files)
// we add the extension to the list.
// 3) Node fallbacks to the .js loader for unknown extensions, however
// pirates will only run our hook if it actually matches the extension
// (without a fallback mechanism). However, it checks if an extension
// has been registered by checking 'extensions.indexOf(...)'.
// And... we can make it always return true! ^-^
// Since this is not technically part of the public API of "pirates", the
// version in package.json is fixed to avoid untested updates.
const NODE_EXTENSIONS = Object.defineProperty(
Object.keys(Module._extensions).concat(".mjs"),
"indexOf",
{
configurable: true,
writable: true,
enumerable: false,
value: () => true,
},
);

const maps = {};
let transformOpts = {};
Expand Down Expand Up @@ -47,7 +76,7 @@ function compile(code, filename) {
},
);

// Bail out ASAP if the file has been ignored.
// Bail out ASAP if the file has been ignored or has an unsupported extension
if (opts === null) return code;

let cacheKey = `${JSON.stringify(opts)}:${babel.version}`;
Expand Down Expand Up @@ -106,11 +135,10 @@ export function revert() {
register();

export default function register(opts?: Object = {}) {
hookExtensions(NODE_EXTENSIONS);

// Clone to avoid mutating the arguments object with the 'delete's below.
opts = {
...opts,
};
hookExtensions(opts.extensions || DEFAULT_EXTENSIONS);
opts = { ...opts };

if (opts.cache === false && cache) {
registerCache.clear();
Expand All @@ -120,10 +148,12 @@ export default function register(opts?: Object = {}) {
cache = registerCache.get();
}

delete opts.extensions;
delete opts.cache;

transformOpts = {
// TODO(Babel 8): At some point @babel/core will default to DEFAULT_EXTENSIONS
// instead of ["*"], and we can avoid setting it here.
extensions: DEFAULT_EXTENSIONS,
...opts,
caller: {
name: "@babel/register",
Expand Down
@@ -0,0 +1,3 @@
{
"plugins": ["../logger"]
}
@@ -0,0 +1 @@
console.log("DONE: foo.cjs");
@@ -0,0 +1 @@
console.log("DONE: foo.es");
@@ -0,0 +1 @@
console.log("DONE: foo.es6");
@@ -0,0 +1 @@
console.log("DONE: foo.jsx");
@@ -0,0 +1 @@
console.log("DONE: foo.mjs");
@@ -0,0 +1 @@
console.log("DONE: foo.ts");
@@ -0,0 +1 @@
console.log("DONE: foo.tsx");
@@ -0,0 +1,11 @@
require("./foo.jsx");
require("./foo.es6");
require("./foo.es");
require("./foo.mjs");

// Not enabled by default
require("./foo.ts");
require("./foo.tsx");
require("./foo.cjs");

console.log("DONE: index.js");
@@ -0,0 +1,4 @@
{
"extensions": [".js", ".ts"],
"plugins": ["../logger"]
}
@@ -0,0 +1 @@
console.log("DONE: foo.ts");
@@ -0,0 +1,3 @@
require("./foo.ts");

console.log("DONE: index.js");
9 changes: 9 additions & 0 deletions packages/babel-register/test/fixtures/integration/logger.js
@@ -0,0 +1,9 @@
module.exports = function () {
return {
pre() {
console.log(
`LOADED: ${JSON.stringify(this.filename.replace(__dirname, "<ROOT>"))}`,
);
},
};
};
@@ -0,0 +1,4 @@
{
"extensions": [".js"],
"plugins": ["../logger"]
}
@@ -0,0 +1 @@
console.log("DONE: foo.ts");
@@ -0,0 +1,3 @@
require("./foo.ts");

console.log("DONE: index.js");
8 changes: 2 additions & 6 deletions packages/babel-register/test/index.js
Expand Up @@ -37,11 +37,6 @@ jest.mock("source-map-support", () => {
};
});

const defaultOptions = {
exts: [".js", ".jsx", ".es6", ".es", ".mjs"],
ignoreNodeModules: false,
};

function cleanCache() {
try {
fs.unlinkSync(testCacheFilename);
Expand Down Expand Up @@ -93,7 +88,8 @@ describe("@babel/register", function () {
setupRegister();

expect(typeof currentHook).toBe("function");
expect(currentOptions).toEqual(defaultOptions);
expect(currentOptions.exts).toContain(".js");
expect(currentOptions.ignoreNodeModules).toBe(false);
});

test("unregisters hook correctly", () => {
Expand Down
67 changes: 67 additions & 0 deletions packages/babel-register/test/integration.js
@@ -0,0 +1,67 @@
import { exec as execCb } from "child_process";

// TODO(Babel 8): Use util.promisify(execCb)
const exec = (...args) =>
new Promise((resolve, reject) => {
execCb(...args, (error, stdout, stderr) => {
if (error) reject(error);
else resolve({ stdout, stderr });
});
});

function fixture(name, file = "index.js") {
const cwd = `${__dirname}/fixtures/integration/${name}`;
return exec(`node -r ${__dirname}/.. ${cwd}/${file}`, {
cwd,
env: { ...process.env, BABEL_DISABLE_CACHE: true },
});
}

describe("integration tests", function () {
it("can hook into extensions defined by the config", async () => {
const { stdout, stderr } = await fixture("load-ts");

expect(stdout).toMatchInlineSnapshot(`
"LOADED: \\"<ROOT>/load-ts/index.js\\"
LOADED: \\"<ROOT>/load-ts/foo.ts\\"
DONE: foo.ts
DONE: index.js
"
`);
expect(stderr).toMatchInlineSnapshot(`""`);
});

it("does not hook into unknown extensions", async () => {
const { stdout, stderr } = await fixture("no-load-ts");

expect(stdout).toMatchInlineSnapshot(`
"LOADED: \\"<ROOT>/no-load-ts/index.js\\"
DONE: foo.ts
DONE: index.js
"
`);
expect(stderr).toMatchInlineSnapshot(`""`);
});

it("hooks into the default extensions", async () => {
const { stdout, stderr } = await fixture("default-extensions");

expect(stdout).toMatchInlineSnapshot(`
"LOADED: \\"<ROOT>/default-extensions/index.js\\"
LOADED: \\"<ROOT>/default-extensions/foo.jsx\\"
DONE: foo.jsx
LOADED: \\"<ROOT>/default-extensions/foo.es6\\"
DONE: foo.es6
LOADED: \\"<ROOT>/default-extensions/foo.es\\"
DONE: foo.es
LOADED: \\"<ROOT>/default-extensions/foo.mjs\\"
DONE: foo.mjs
DONE: foo.ts
DONE: foo.tsx
DONE: foo.cjs
DONE: index.js
"
`);
expect(stderr).toMatchInlineSnapshot(`""`);
});
});

0 comments on commit 7eb2a67

Please sign in to comment.