Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use conditional exports in @babel/runtime for CJS/ESM #12632

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 12 additions & 6 deletions .gitignore
Expand Up @@ -26,18 +26,22 @@ package-lock.json
/packages/babel-compat-data/build

/packages/babel-runtime/helpers/*.js
!/packages/babel-runtime/helpers/toArray.js
!/packages/babel-runtime/helpers/iterableToArray.js
!/packages/babel-runtime/helpers/temporalRef.js
/packages/babel-runtime/helpers/*/*.js
/packages/babel-runtime/helpers/*/*.mjs
!/packages/babel-runtime/helpers/toArray/*
!/packages/babel-runtime/helpers/iterableToArray/*
!/packages/babel-runtime/helpers/temporalRef/*
/packages/babel-runtime/helpers/esm/*.js
!/packages/babel-runtime/helpers/esm/toArray.js
!/packages/babel-runtime/helpers/esm/iterableToArray.js
!/packages/babel-runtime/helpers/esm/temporalRef.js

/packages/babel-runtime-corejs2/helpers/*.js
!/packages/babel-runtime-corejs2/helpers/toArray.js
!/packages/babel-runtime-corejs2/helpers/iterableToArray.js
!/packages/babel-runtime-corejs2/helpers/temporalRef.js
/packages/babel-runtime-corejs2/helpers/*/*.js
/packages/babel-runtime-corejs2/helpers/*/*.mjs
!/packages/babel-runtime-corejs2/helpers/toArray/*
!/packages/babel-runtime-corejs2/helpers/iterableToArray/*
!/packages/babel-runtime-corejs2/helpers/temporalRef/*
/packages/babel-runtime-corejs2/helpers/esm/*.js
!/packages/babel-runtime-corejs2/helpers/esm/toArray.js
!/packages/babel-runtime-corejs2/helpers/esm/iterableToArray.js
Expand All @@ -46,6 +50,8 @@ package-lock.json
!/packages/babel-runtime-corejs2/core-js/map.js

/packages/babel-runtime-corejs3/helpers/*.js
/packages/babel-runtime-corejs3/helpers/*/*.js
/packages/babel-runtime-corejs3/helpers/*/*.mjs
/packages/babel-runtime-corejs3/helpers/esm/*.js
/packages/babel-runtime-corejs3/core-js/**/*.js
/packages/babel-runtime-corejs3/core-js-stable/**/*.js
Expand Down
4 changes: 2 additions & 2 deletions Gulpfile.mjs
Expand Up @@ -298,10 +298,10 @@ function buildRollup(packages, targetBrowsers) {
babelrc: false,
babelHelpers: "bundled",
extends: "./babel.config.js",
extensions: [".mjs", ".cjs", ".ts", ".js"],
extensions: [".ts", ".js", ".mjs", ".cjs"],
}),
rollupNodeResolve({
extensions: [".mjs", ".cjs", ".ts", ".js", ".json"],
extensions: [".ts", ".js", ".mjs", ".cjs", ".json"],
browser: nodeResolveBrowser,
preferBuiltins: true,
}),
Expand Down
8 changes: 8 additions & 0 deletions Makefile
Expand Up @@ -196,6 +196,14 @@ prepublish:
IS_PUBLISH=true $(MAKE) test

new-version:
@echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
@echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
@echo "!!!!!! !!!!!!"
@echo "!!!!!! Update DUAL_MODE_RUNTIME in transform-runtime !!!!!!"
@echo "!!!!!! !!!!!!"
@echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
@echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
@exit 1
git pull --rebase
$(YARN) release-tool version -f @babel/standalone

Expand Down
109 changes: 75 additions & 34 deletions packages/babel-plugin-transform-runtime/scripts/build-dist.js
Expand Up @@ -112,51 +112,74 @@ function writeCorejsExports(pkgDirname, runtimeRoot, paths) {
outputFile(pkgJsonPath, JSON.stringify(pkgJson, undefined, 2) + "\n");
}

function writeHelpers(runtimeName, { corejs } = {}) {
const helperPaths = writeHelperFiles(runtimeName, { corejs, esm: false });
const helperESMPaths = writeHelperFiles(runtimeName, { corejs, esm: true });
writeHelperExports(runtimeName, helperPaths.concat(helperESMPaths));
function writeHelperFile(
runtimeName,
pkgDirname,
helperPath,
helperName,
{ esm, corejs }
) {
const filePath = path.join(helperPath, esm ? "index.mjs" : "index.js");
const fullPath = path.join(pkgDirname, filePath);

outputFile(
fullPath,
buildHelper(runtimeName, pkgDirname, fullPath, helperName, { esm, corejs })
);

return `./${filePath}`;
}

function writeHelperExports(runtimeName, helperPaths) {
function writeHelperLegacyESMFile(pkgDirname, helperName) {
const fullPath = path.join(pkgDirname, "helpers", "esm", `${helperName}.js`);

outputFile(fullPath, `export { default } from "../${helperName}/index.mjs"`);
}

function writeHelpers(runtimeName, { corejs } = {}) {
const pkgDirname = getRuntimeRoot(runtimeName);
const helperSubExports = {};
for (const helperPath of helperPaths) {
helperSubExports[helperPath.replace(".js", "")] = helperPath;
for (const helperName of helpers.list) {
const helperPath = path.join("helpers", helperName);
helperSubExports[`./${helperPath}`] = {
module: writeHelperFile(runtimeName, pkgDirname, helperPath, helperName, {
esm: true,
corejs,
}),
node: writeHelperFile(runtimeName, pkgDirname, helperPath, helperName, {
esm: false,
corejs,
}),
get default() {
return this.module;
},
};
writeHelperLegacyESMFile(pkgDirname, helperName);
}

writeHelperExports(runtimeName, helperSubExports);
}

function writeHelperExports(runtimeName, helperSubExports) {
const exports = {
"./helpers/": "./helpers/",
...helperSubExports,
"./package": "./package.json",
"./package.json": "./package.json",
"./regenerator": "./regenerator/index.js",
"./regenerator/*.js": "./regenerator/*.js",
"./helpers/esm/*": "./helpers/esm/*.js",
// These patterns are deprecated, but since patterns
// containing * are not supported in every Node.js
// version we keep them for better compatibility.
JLHwung marked this conversation as resolved.
Show resolved Hide resolved
"./regenerator/": "./regenerator/",
"./helpers/esm/": "./helpers/esm/",
};
const pkgDirname = getRuntimeRoot(runtimeName);
const pkgJsonPath = require.resolve(`${pkgDirname}/package.json`);
const pkgJson = require(pkgJsonPath);
pkgJson.exports = exports;
outputFile(pkgJsonPath, JSON.stringify(pkgJson, undefined, 2) + "\n");
}
function writeHelperFiles(runtimeName, { esm, corejs }) {
const pkgDirname = getRuntimeRoot(runtimeName);
const helperPaths = [];
for (const helperName of helpers.list) {
const helperPath =
"./" + path.join("helpers", esm ? "esm" : "", `${helperName}.js`);
const helperFilename = path.join(pkgDirname, helperPath);
outputFile(
helperFilename,
buildHelper(runtimeName, pkgDirname, helperFilename, helperName, {
esm,
corejs,
})
);

helperPaths.push(helperPath);
}

return helperPaths;
}

function getRuntimeRoot(runtimeName) {
return path.resolve(
Expand Down Expand Up @@ -184,7 +207,7 @@ function buildHelper(
for (const dep of helpers.getDependencies(helperName)) {
const id = (dependencies[dep] = t.identifier(t.toIdentifier(dep)));
tree.body.push(template.statement.ast`
var ${id} = require("${`./${dep}`}");
var ${id} = require("${dep}");
`);
bindings.push(id.name);
}
Expand All @@ -211,8 +234,9 @@ function buildHelper(
transformRuntime,
{ corejs, useESModules: esm, version: runtimeVersion },
],
buildRuntimeRewritePlugin(runtimeName, helperName, esm),
],
buildRuntimeRewritePlugin(runtimeName, helperName),
esm ? null : addDefaultCJSExport,
].filter(Boolean),
overrides: [
{
exclude: /typeof/,
Expand All @@ -222,8 +246,7 @@ function buildHelper(
}).code;
}

function buildRuntimeRewritePlugin(runtimeName, helperName, esm) {
const helperPath = esm ? "helpers/esm" : "helpers";
function buildRuntimeRewritePlugin(runtimeName, helperName) {
/**
* rewrite helpers imports to runtime imports
* @example
Expand All @@ -233,7 +256,7 @@ function buildRuntimeRewritePlugin(runtimeName, helperName, esm) {
*/
function adjustImportPath(node) {
if (helpers.list.includes(node.value)) {
node.value = `${runtimeName}/${helperPath}/${node.value}`;
node.value = `${runtimeName}/helpers/${node.value}`;
}
}

Expand Down Expand Up @@ -266,3 +289,21 @@ function buildRuntimeRewritePlugin(runtimeName, helperName, esm) {
},
};
}

function addDefaultCJSExport({ template }) {
return {
visitor: {
Program: {
exit(path) {
path.pushContainer(
"body",
template.statements.ast`
module.exports.default = module.exports;
module.exports.__esModule = true;
`
);
},
},
},
};
}
27 changes: 24 additions & 3 deletions packages/babel-plugin-transform-runtime/src/index.js
Expand Up @@ -4,7 +4,7 @@ import { types as t } from "@babel/core";

import getCoreJS2Definitions from "./runtime-corejs2-definitions";
import getCoreJS3Definitions from "./runtime-corejs3-definitions";
import { typeAnnotationToString } from "./helpers";
import { typeAnnotationToString, hasMinVersion } from "./helpers";
import getRuntimePath from "./get-runtime-path";

function supportsStaticESM(caller) {
Expand Down Expand Up @@ -78,6 +78,20 @@ export default declare((api, options, dirname) => {
throw new Error(`The 'version' option must be a version string.`);
}

// In recent @babel/runtime versions, we can use require("helper").default
// instead of require("helper") so that it has the same interface as the
// ESM helper, and bundlers can better exchange one format for the other.
// TODO(Babel 8): Remove this check, it's always true
const DUAL_MODE_RUNTIME = "7.12.12";
const supportsCJSDefault = hasMinVersion(DUAL_MODE_RUNTIME, runtimeVersion);
if (supportsCJSDefault && useESModules && !absoluteRuntime) {
console.warn(
`[@babel/plugin-transform-runtime] The 'useESModules' option is not necessary when using` +
` a @babel/runtime version >= ${DUAL_MODE_RUNTIME} and not using the 'absoluteRuntime'` +
` option, because it automatically detects the necessary module format.`,
);
}

function has(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
Expand Down Expand Up @@ -211,13 +225,19 @@ export default declare((api, options, dirname) => {
`${modulePath}/${helpersDir}/${name}`,
name,
blockHoist,
true,
);
});
}

const cache = new Map();

this.addDefaultImport = (source, nameHint, blockHoist) => {
this.addDefaultImport = (
source,
nameHint,
blockHoist,
isHelper = false,
) => {
// If something on the page adds a helper when the file is an ES6
// file, we can't reused the cached helper name after things have been
// transformed because it has almost certainly been renamed.
Expand All @@ -229,7 +249,8 @@ export default declare((api, options, dirname) => {
cached = t.cloneNode(cached);
} else {
cached = addDefault(file.path, source, {
importedInterop: "uncompiled",
importedInterop:
isHelper && supportsCJSDefault ? "compiled" : "uncompiled",
nameHint,
blockHoist,
});
Expand Down
@@ -0,0 +1 @@
class A {}
@@ -0,0 +1,7 @@
{
"plugins": [
["transform-runtime", { "version": "7.100.0" }],
"transform-classes",
"transform-modules-commonjs"
]
}
@@ -0,0 +1,9 @@
"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

let A = function A() {
(0, _classCallCheck2.default)(this, A);
};
@@ -0,0 +1 @@
class A {}
@@ -0,0 +1,7 @@
{
"plugins": [
"transform-runtime",
"transform-classes",
"transform-modules-commonjs"
]
}
@@ -0,0 +1,9 @@
"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

let A = function A() {
(0, _classCallCheck2.default)(this, A);
};
@@ -0,0 +1 @@
class A {}
@@ -0,0 +1,6 @@
{
"plugins": [
["transform-runtime", { "version": "7.100.0" }],
"transform-classes"
]
}
@@ -0,0 +1,5 @@
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";

let A = function A() {
_classCallCheck(this, A);
};
@@ -0,0 +1 @@
class A {}
@@ -0,0 +1,3 @@
{
"plugins": ["transform-runtime", "transform-classes"]
}
@@ -0,0 +1,5 @@
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";

let A = function A() {
_classCallCheck(this, A);
};
@@ -0,0 +1,3 @@
{
"validateLogs": true
}
@@ -0,0 +1 @@
class A {}
@@ -0,0 +1,6 @@
{
"plugins": [
["transform-runtime", { "version": "7.100.0" }],
"transform-classes"
]
}
@@ -0,0 +1,7 @@
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck").default;

let A = function A() {
"use strict";

_classCallCheck(this, A);
};
@@ -0,0 +1 @@
class A {}
@@ -0,0 +1,3 @@
{
"plugins": ["transform-runtime", "transform-classes"]
}
@@ -0,0 +1,7 @@
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");

let A = function A() {
"use strict";

_classCallCheck(this, A);
};
@@ -0,0 +1 @@
class A {}