Skip to content

Commit

Permalink
Use native Node.js functions when available (#12458)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Dec 8, 2020
1 parent c2fcd69 commit 98aa72c
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 53 deletions.
133 changes: 97 additions & 36 deletions babel.config.js
Expand Up @@ -21,7 +21,7 @@ module.exports = function (api) {
let convertESM = true;
let ignoreLib = true;
let includeRegeneratorRuntime = false;
let polyfillRequireResolve = false;
let needsPolyfillsForOldNode = false;

let transformRuntimeOptions;

Expand Down Expand Up @@ -54,15 +54,15 @@ module.exports = function (api) {
"packages/babel-compat-data"
);
if (env === "rollup") envOpts.targets = { node: nodeVersion };
polyfillRequireResolve = true;
needsPolyfillsForOldNode = true;
break;
case "test-legacy": // In test-legacy environment, we build babel on latest node but test on minimum supported legacy versions
case "production":
// Config during builds before publish.
envOpts.targets = {
node: nodeVersion,
};
polyfillRequireResolve = true;
needsPolyfillsForOldNode = true;
break;
case "development":
envOpts.debug = true;
Expand All @@ -79,7 +79,7 @@ module.exports = function (api) {

if (process.env.STRIP_BABEL_8_FLAG && bool(process.env.BABEL_8_BREAKING)) {
// Never apply polyfills when compiling for Babel 8
polyfillRequireResolve = false;
needsPolyfillsForOldNode = false;
}

if (includeRegeneratorRuntime) {
Expand Down Expand Up @@ -129,7 +129,7 @@ module.exports = function (api) {
pluginToggleBabel8Breaking,
{ breaking: bool(process.env.BABEL_8_BREAKING) },
],
polyfillRequireResolve && pluginPolyfillRequireResolve,
needsPolyfillsForOldNode && pluginPolyfillsOldNode,
].filter(Boolean),
overrides: [
{
Expand Down Expand Up @@ -180,41 +180,102 @@ function bool(value) {
return value && value !== "false" && value !== "0";
}

// TODO(Babel 8) This polyfill is only needed for Node.js 6 and 8
function pluginPolyfillRequireResolve({ template, types: t }) {
// TODO(Babel 8) This polyfills are only needed for Node.js 6 and 8
/** @param {import("@babel/core")} api */
function pluginPolyfillsOldNode({ template, types: t }) {
const polyfills = [
{
name: "require.resolve",
necessary({ node, parent }) {
return (
t.isCallExpression(parent, { callee: node }) &&
parent.arguments.length > 1
);
},
supported({ parent: { arguments: args } }) {
return (
t.isObjectExpression(args[1]) &&
args[1].properties.length === 1 &&
t.isIdentifier(args[1].properties[0].key, { name: "paths" }) &&
t.isArrayExpression(args[1].properties[0].value) &&
args[1].properties[0].value.elements.length === 1
);
},
// require.resolve's paths option has been introduced in Node.js 8.9
// https://nodejs.org/api/modules.html#modules_require_resolve_request_options
replacement: template({ syntacticPlaceholders: true })`
parseFloat(process.versions.node) >= 8.9
? require.resolve
: (/* request */ r, { paths: [/* base */ b] }, M = require("module")) => {
let /* filename */ f = M._findPath(r, M._nodeModulePaths(b).concat(b));
if (f) return f;
f = new Error(\`Cannot resolve module '\${r}'\`);
f.code = "MODULE_NOT_FOUND";
throw f;
}
`,
},
{
// NOTE: This polyfills depends on the "make-dir" library. Any package
// using fs.mkdirSync must have "make-dir" as a dependency.
name: "fs.mkdirSync",
necessary({ node, parent }) {
return (
t.isCallExpression(parent, { callee: node }) &&
parent.arguments.length > 1
);
},
supported({ parent: { arguments: args } }) {
return (
t.isObjectExpression(args[1]) &&
args[1].properties.length === 1 &&
t.isIdentifier(args[1].properties[0].key, { name: "recursive" }) &&
t.isBooleanLiteral(args[1].properties[0].value, { value: true })
);
},
// fs.mkdirSync's recursive option has been introduced in Node.js 10.12
// https://nodejs.org/api/fs.html#fs_fs_mkdirsync_path_options
replacement: template`
parseFloat(process.versions.node) >= 10.12
? fs.mkdirSync
: require("make-dir").sync
`,
},
{
// NOTE: This polyfills depends on the "node-environment-flags"
// library. Any package using process.allowedNodeEnvironmentFlags
// must have "node-environment-flags" as a dependency.
name: "process.allowedNodeEnvironmentFlags",
necessary({ parent, node }) {
// To avoid infinite replacement loops
return !t.isLogicalExpression(parent, { operator: "||", left: node });
},
supported: () => true,
// process.allowedNodeEnvironmentFlags has been introduced in Node.js 10.10
// https://nodejs.org/api/process.html#process_process_allowednodeenvironmentflags
replacement: template`
process.allowedNodeEnvironmentFlags || require("node-environment-flags")
`,
},
];

return {
visitor: {
MemberExpression(path) {
if (!path.matchesPattern("require.resolve")) return;
if (!t.isCallExpression(path.parent, { callee: path.node })) return;

const args = path.parent.arguments;
if (args.length < 2) return;
if (
!t.isObjectExpression(args[1]) ||
args[1].properties.length !== 1 ||
!t.isIdentifier(args[1].properties[0].key, { name: "paths" }) ||
!t.isArrayExpression(args[1].properties[0].value) ||
args[1].properties[0].value.elements.length !== 1
) {
throw path.parentPath.buildCodeFrameError(
"This 'require.resolve' usage is not supported by the inline polyfill."
);
}
for (const polyfill of polyfills) {
if (!path.matchesPattern(polyfill.name)) continue;

if (!polyfill.necessary(path)) return;
if (!polyfill.supported(path)) {
throw path.buildCodeFrameError(
`This '${polyfill.name}' usage is not supported by the inline polyfill.`
);
}

// require.resolve's paths option has been introduced in Node.js 8.9
// https://nodejs.org/api/modules.html#modules_require_resolve_request_options
path.replaceWith(template.ast`
parseFloat(process.versions.node) >= 8.9
? require.resolve
: (/* request */ r, { paths: [/* base */ b] }, M = require("module")) => {
let /* filename */ f = M._findPath(r, M._nodeModulePaths(b).concat(b));
if (f) return f;
f = new Error(\`Cannot resolve module '\${r}'\`);
f.code = "MODULE_NOT_FOUND";
throw f;
}
`);
path.replaceWith(polyfill.replacement());

break;
}
},
},
};
Expand Down
5 changes: 2 additions & 3 deletions packages/babel-cli/src/babel/dir.js
@@ -1,7 +1,6 @@
// @flow

import debounce from "lodash/debounce";
import { sync as makeDirSync } from "make-dir";
import slash from "slash";
import path from "path";
import fs from "fs";
Expand All @@ -17,7 +16,7 @@ const FILE_TYPE = Object.freeze({
});

function outputFileSync(filePath: string, data: string | Buffer): void {
makeDirSync(path.dirname(filePath));
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, data);
}

Expand Down Expand Up @@ -163,7 +162,7 @@ export default async function ({
util.deleteDir(cliOptions.outDir);
}

makeDirSync(cliOptions.outDir);
fs.mkdirSync(cliOptions.outDir, { recursive: true });

startTime = process.hrtime();

Expand Down
3 changes: 1 addition & 2 deletions packages/babel-cli/src/babel/file.js
Expand Up @@ -3,7 +3,6 @@
import convertSourceMap from "convert-source-map";
import sourceMap from "source-map";
import slash from "slash";
import { sync as makeDirSync } from "make-dir";
import path from "path";
import fs from "fs";

Expand Down Expand Up @@ -89,7 +88,7 @@ export default async function ({
const result = buildResult(fileResults);

if (cliOptions.outFile) {
makeDirSync(path.dirname(cliOptions.outFile));
fs.mkdirSync(path.dirname(cliOptions.outFile), { recursive: true });

// we've requested for a sourcemap to be written to disk
if (babelOptions.sourceMaps && babelOptions.sourceMaps !== "inline") {
Expand Down
6 changes: 1 addition & 5 deletions packages/babel-node/src/babel-node.js
Expand Up @@ -6,10 +6,6 @@
import getV8Flags from "v8flags";
import path from "path";

// TODO: When support for node < 10.10 will be dropped, this package
// can be replaced with process.allowedNodeEnvironmentFlags
import allowedNodeEnvironmentFlags from "node-environment-flags";

let args = [path.join(__dirname, "_babel-node")];

let babelArgs = process.argv.slice(2);
Expand Down Expand Up @@ -59,7 +55,7 @@ getV8Flags(function (err, v8Flags) {
flag === "debug" || // node debug foo.js
flag === "inspect" ||
v8Flags.indexOf(getNormalizedV8Flag(flag)) >= 0 ||
allowedNodeEnvironmentFlags.has(flag)
process.allowedNodeEnvironmentFlags.has(flag)
) {
args.unshift(arg);
} else {
Expand Down
Expand Up @@ -2,7 +2,6 @@

const path = require("path");
const fs = require("fs");
const makeDirSync = require("make-dir").sync;
const helpers = require("@babel/helpers");
const babel = require("@babel/core");
const template = require("@babel/template");
Expand All @@ -15,7 +14,7 @@ const corejs2Definitions = require("../lib/runtime-corejs2-definitions").default
const corejs3Definitions = require("../lib/runtime-corejs3-definitions").default();

function outputFile(filePath, data) {
makeDirSync(path.dirname(filePath));
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, data);
}

Expand Down
7 changes: 3 additions & 4 deletions packages/babel-register/src/cache.js
@@ -1,7 +1,6 @@
import path from "path";
import fs from "fs";
import os from "os";
import { sync as makeDirSync } from "make-dir";
import * as babel from "@babel/core";
import findCacheDir from "find-cache-dir";

Expand Down Expand Up @@ -39,7 +38,7 @@ export function save() {
}

try {
makeDirSync(path.dirname(FILENAME));
fs.mkdirSync(path.dirname(FILENAME), { recursive: true });
fs.writeFileSync(FILENAME, serialised);
} catch (e) {
switch (e.code) {
Expand All @@ -49,14 +48,14 @@ export function save() {
case "EACCES":
case "EPERM":
console.warn(
`Babel could not write cache to file: ${FILENAME}
`Babel could not write cache to file: ${FILENAME}
due to a permission issue. Cache is disabled.`,
);
cacheDisabled = true;
break;
case "EROFS":
console.warn(
`Babel could not write cache to file: ${FILENAME}
`Babel could not write cache to file: ${FILENAME}
because it resides in a readonly filesystem. Cache is disabled.`,
);
cacheDisabled = true;
Expand Down
8 changes: 7 additions & 1 deletion packages/babel-register/test/index.js
Expand Up @@ -77,7 +77,13 @@ describe("@babel/register", function () {
cleanCache();
}

afterEach(() => {
afterEach(async () => {
// @babel/register saves the cache on process.nextTick.
// We need to wait for at least one tick so that when jest
// tears down the testing environment @babel/register has
// already finished.
await new Promise(setImmediate);

revertRegister();
currentHook = null;
currentOptions = null;
Expand Down

0 comments on commit 98aa72c

Please sign in to comment.