diff --git a/babel.config.js b/babel.config.js index e70ac5e721d4..a044be148037 100644 --- a/babel.config.js +++ b/babel.config.js @@ -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; @@ -54,7 +54,7 @@ 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": @@ -62,7 +62,7 @@ module.exports = function (api) { envOpts.targets = { node: nodeVersion, }; - polyfillRequireResolve = true; + needsPolyfillsForOldNode = true; break; case "development": envOpts.debug = true; @@ -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) { @@ -129,7 +129,7 @@ module.exports = function (api) { pluginToggleBabel8Breaking, { breaking: bool(process.env.BABEL_8_BREAKING) }, ], - polyfillRequireResolve && pluginPolyfillRequireResolve, + needsPolyfillsForOldNode && pluginPolyfillsOldNode, ].filter(Boolean), overrides: [ { @@ -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; + } }, }, }; diff --git a/packages/babel-cli/src/babel/dir.js b/packages/babel-cli/src/babel/dir.js index bbdcf4ac3286..97e2f0e2f82e 100644 --- a/packages/babel-cli/src/babel/dir.js +++ b/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"; @@ -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); } @@ -163,7 +162,7 @@ export default async function ({ util.deleteDir(cliOptions.outDir); } - makeDirSync(cliOptions.outDir); + fs.mkdirSync(cliOptions.outDir, { recursive: true }); startTime = process.hrtime(); diff --git a/packages/babel-cli/src/babel/file.js b/packages/babel-cli/src/babel/file.js index a6927157d524..71478fafdb9e 100644 --- a/packages/babel-cli/src/babel/file.js +++ b/packages/babel-cli/src/babel/file.js @@ -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"; @@ -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") { diff --git a/packages/babel-node/src/babel-node.js b/packages/babel-node/src/babel-node.js index 6d0866251b1d..b7b7ce0e5008 100755 --- a/packages/babel-node/src/babel-node.js +++ b/packages/babel-node/src/babel-node.js @@ -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); @@ -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 { diff --git a/packages/babel-plugin-transform-runtime/scripts/build-dist.js b/packages/babel-plugin-transform-runtime/scripts/build-dist.js index 05d4b841e7a7..1cdbf6491b7a 100644 --- a/packages/babel-plugin-transform-runtime/scripts/build-dist.js +++ b/packages/babel-plugin-transform-runtime/scripts/build-dist.js @@ -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"); @@ -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); } diff --git a/packages/babel-register/src/cache.js b/packages/babel-register/src/cache.js index 39eb9b78284b..8f135be70e43 100644 --- a/packages/babel-register/src/cache.js +++ b/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"; @@ -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) { @@ -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; diff --git a/packages/babel-register/test/index.js b/packages/babel-register/test/index.js index c83bb5e04c88..ca594af66330 100644 --- a/packages/babel-register/test/index.js +++ b/packages/babel-register/test/index.js @@ -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;