Lazily inject imports to the JSX runtime (#12493)

This commit is contained in:
Nicolò Ribaudo 2020-12-15 12:55:17 +01:00 committed by GitHub
parent a0c1a9a9e4
commit 28d2cbcbf3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 88 additions and 133 deletions

View File

@ -43,11 +43,6 @@ export function helper(babel, options) {
const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/; const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/;
const JSX_FRAG_ANNOTATION_REGEX = /\*?\s*@jsxFrag\s+([^\s]+)/; const JSX_FRAG_ANNOTATION_REGEX = /\*?\s*@jsxFrag\s+([^\s]+)/;
// This is the number of possible import names
// development: jsxDEV, Fragment, createElement
// production: jsx, jsxs, Fragment, createElement
const IMPORT_NAME_SIZE = options.development ? 3 : 4;
const { const {
importSource: IMPORT_SOURCE_DEFAULT = DEFAULT.importSource, importSource: IMPORT_SOURCE_DEFAULT = DEFAULT.importSource,
runtime: RUNTIME_DEFAULT = DEFAULT.runtime, runtime: RUNTIME_DEFAULT = DEFAULT.runtime,
@ -214,44 +209,33 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
); );
} }
const importName = addAutoImports(path, {
...state.opts,
source,
});
state.set( state.set(
"@babel/plugin-react-jsx/jsxIdentifier", "@babel/plugin-react-jsx/jsxIdentifier",
createIdentifierParser( createImportLazily(
createIdentifierName( state,
path, path,
options.development ? "jsxDEV" : "jsx", options.development ? "jsxDEV" : "jsx",
importName, source,
),
), ),
); );
state.set( state.set(
"@babel/plugin-react-jsx/jsxStaticIdentifier", "@babel/plugin-react-jsx/jsxStaticIdentifier",
createIdentifierParser( createImportLazily(
createIdentifierName( state,
path, path,
options.development ? "jsxDEV" : "jsxs", options.development ? "jsxDEV" : "jsxs",
importName, source,
),
), ),
); );
state.set( state.set(
"@babel/plugin-react-jsx/createElementIdentifier", "@babel/plugin-react-jsx/createElementIdentifier",
createIdentifierParser( createImportLazily(state, path, "createElement", source),
createIdentifierName(path, "createElement", importName),
),
); );
state.set( state.set(
"@babel/plugin-react-jsx/jsxFragIdentifier", "@babel/plugin-react-jsx/jsxFragIdentifier",
createIdentifierParser( createImportLazily(state, path, "Fragment", source),
createIdentifierName(path, "Fragment", importName),
),
); );
state.set( state.set(
@ -313,44 +297,6 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
return false; return false;
} }
function createIdentifierName(path, name, importName) {
if (isModule(path)) {
const identifierName = `${importName[name]}`;
return identifierName;
} else {
return `${importName[name]}.${name}`;
}
}
function getImportNames(parentPath) {
const imports = new Set();
parentPath.traverse({
"JSXElement|JSXFragment"(path) {
if (path.type === "JSXFragment") imports.add("Fragment");
const openingPath = path.get("openingElement");
const validChildren = t.react.buildChildren(openingPath.parent);
let importName;
if (path.type === "JSXElement" && shouldUseCreateElement(path)) {
importName = "createElement";
} else if (options.development) {
importName = "jsxDEV";
} else if (validChildren.length > 1) {
importName = "jsxs";
} else {
importName = "jsx";
}
imports.add(importName);
if (imports.size === IMPORT_NAME_SIZE) {
path.stop();
}
},
});
return imports;
}
function getSource(source, importName) { function getSource(source, importName) {
switch (importName) { switch (importName) {
case "Fragment": case "Fragment":
@ -367,47 +313,40 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
} }
} }
function addAutoImports(path, state) { function createImportLazily(pass, path, importName, source) {
const imports = getImportNames(path, state); return () => {
const actualSource = getSource(source, importName);
if (isModule(path)) { if (isModule(path)) {
// import {jsx} from "react"; let reference = pass.get(
// import {createElement} from "react"; `@babel/plugin-react-jsx/imports/${importName}`,
const importMap = {}; );
if (reference) return t.cloneNode(reference);
imports.forEach(importName => { reference = addNamed(path, importName, actualSource, {
if (!importMap[importName]) {
importMap[importName] = addNamed(
path,
importName,
getSource(state.source, importName),
{
importedInterop: "uncompiled", importedInterop: "uncompiled",
ensureLiveReference: true,
},
).name;
}
}); });
pass.set(`@babel/plugin-react-jsx/imports/${importName}`, reference);
return importMap; return reference;
} else { } else {
const importMap = {}; let reference = pass.get(
const sourceMap = {}; `@babel/plugin-react-jsx/requires/${actualSource}`,
imports.forEach(importName => { );
const source = getSource(state.source, importName); if (reference) {
if (!importMap[importName]) { reference = t.cloneNode(reference);
if (!sourceMap[source]) { } else {
// var _react = require("react") reference = addNamespace(path, actualSource, {
sourceMap[source] = addNamespace(path, source, {
importedInterop: "uncompiled", importedInterop: "uncompiled",
ensureLiveReference: true, });
}).name; pass.set(
`@babel/plugin-react-jsx/requires/${actualSource}`,
reference,
);
} }
importMap[importName] = sourceMap[source]; return t.memberExpression(reference, t.identifier(importName));
}
});
return importMap;
} }
};
} }
function createIdentifierParser(id) { function createIdentifierParser(id) {

View File

@ -26,13 +26,20 @@ export default declare((api, options) => {
state.pure = state.pure =
PURE_ANNOTATION ?? !pass.get("@babel/plugin-react-jsx/pragmaSet"); PURE_ANNOTATION ?? !pass.get("@babel/plugin-react-jsx/pragmaSet");
} else { } else {
state.jsxCallee = pass.get("@babel/plugin-react-jsx/jsxIdentifier")(); const getter = get => ({ enumerable: true, configurable: true, get });
state.jsxStaticCallee = pass.get(
"@babel/plugin-react-jsx/jsxStaticIdentifier", // TODO(Babel 8): helper-builder-react-jsx expects those properties to be AST nodes, but we want to
)(); // generate them lazily so that we only inject imports when needed.
state.createElementCallee = pass.get( // These should actually be functions.
"@babel/plugin-react-jsx/createElementIdentifier", Object.defineProperties(state, {
)(); jsxCallee: getter(pass.get("@babel/plugin-react-jsx/jsxIdentifier")),
jsxStaticCallee: getter(
pass.get("@babel/plugin-react-jsx/jsxStaticIdentifier"),
),
createElementCallee: getter(
pass.get("@babel/plugin-react-jsx/createElementIdentifier"),
),
});
state.pure = state.pure =
PURE_ANNOTATION ?? PURE_ANNOTATION ??

View File

@ -1,6 +1,6 @@
import { Fragment as _Fragment } from "react/jsx-dev-runtime";
import { createElement as _createElement } from "react"; import { createElement as _createElement } from "react";
import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime"; import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime";
import { Fragment as _Fragment } from "react/jsx-dev-runtime";
var _jsxFileName = "<CWD>/packages/babel-plugin-transform-react-jsx-development/test/fixtures/linux/auto-import-dev/input.js"; var _jsxFileName = "<CWD>/packages/babel-plugin-transform-react-jsx-development/test/fixtures/linux/auto-import-dev/input.js";
var x = /*#__PURE__*/_jsxDEV(_Fragment, { var x = /*#__PURE__*/_jsxDEV(_Fragment, {

View File

@ -1,6 +1,6 @@
import { Fragment as _Fragment } from "react/jsx-dev-runtime";
import { createElement as _createElement } from "react"; import { createElement as _createElement } from "react";
import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime"; import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime";
import { Fragment as _Fragment } from "react/jsx-dev-runtime";
var _jsxFileName = "<CWD>\\packages\\babel-plugin-transform-react-jsx-development\\test\\fixtures\\windows\\auto-import-dev-windows\\input.js"; var _jsxFileName = "<CWD>\\packages\\babel-plugin-transform-react-jsx-development\\test\\fixtures\\windows\\auto-import-dev-windows\\input.js";
var x = /*#__PURE__*/_jsxDEV(_Fragment, { var x = /*#__PURE__*/_jsxDEV(_Fragment, {

View File

@ -27,13 +27,20 @@ export default declare((api, options) => {
state.pure = state.pure =
PURE_ANNOTATION ?? !pass.get("@babel/plugin-react-jsx/pragmaSet"); PURE_ANNOTATION ?? !pass.get("@babel/plugin-react-jsx/pragmaSet");
} else { } else {
state.jsxCallee = pass.get("@babel/plugin-react-jsx/jsxIdentifier")(); const getter = get => ({ enumerable: true, configurable: true, get });
state.jsxStaticCallee = pass.get(
"@babel/plugin-react-jsx/jsxStaticIdentifier", // TODO(Babel 8): helper-builder-react-jsx expects those properties to be AST nodes, but we want to
)(); // generate them lazily so that we only inject imports when needed.
state.createElementCallee = pass.get( // These should actually be functions.
"@babel/plugin-react-jsx/createElementIdentifier", Object.defineProperties(state, {
)(); jsxCallee: getter(pass.get("@babel/plugin-react-jsx/jsxIdentifier")),
jsxStaticCallee: getter(
pass.get("@babel/plugin-react-jsx/jsxStaticIdentifier"),
),
createElementCallee: getter(
pass.get("@babel/plugin-react-jsx/createElementIdentifier"),
),
});
state.pure = state.pure =
PURE_ANNOTATION ?? PURE_ANNOTATION ??

View File

@ -1,7 +1,7 @@
import { createElement as _createElement } from "react";
import { jsxs as _jsxs } from "react/jsx-runtime";
import { jsx as _jsx } from "react/jsx-runtime";
import { Fragment as _Fragment } from "react/jsx-runtime"; import { Fragment as _Fragment } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
import { createElement as _createElement } from "react";
import { jsx as _jsx } from "react/jsx-runtime";
var x = /*#__PURE__*/_jsx(_Fragment, { var x = /*#__PURE__*/_jsx(_Fragment, {
children: /*#__PURE__*/_jsxs("div", { children: /*#__PURE__*/_jsxs("div", {

View File

@ -1,6 +1,6 @@
import { jsxs as _jsxs } from "react/jsx-runtime";
import { createElement as _createElement } from "react"; import { createElement as _createElement } from "react";
import { jsx as _jsx } from "react/jsx-runtime"; import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
import * as react from "react"; import * as react from "react";
var y = react.createElement("div", { var y = react.createElement("div", {
foo: 1 foo: 1

View File

@ -1,5 +1,5 @@
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime"; import { jsxs as _jsxs } from "react/jsx-runtime";
import { jsx as _jsx } from "react/jsx-runtime";
var x = /*#__PURE__*/_jsxs("div", { var x = /*#__PURE__*/_jsxs("div", {
children: ["foo", "bar", "baz", /*#__PURE__*/_jsx("div", { children: ["foo", "bar", "baz", /*#__PURE__*/_jsx("div", {

View File

@ -1,7 +1,7 @@
import { createElement as _createElement } from "react";
import { jsxs as _jsxs } from "react/jsx-runtime";
import { jsx as _jsx } from "react/jsx-runtime";
import { Fragment as _Fragment } from "react/jsx-runtime"; import { Fragment as _Fragment } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
import { createElement as _createElement } from "react";
import { jsx as _jsx } from "react/jsx-runtime";
var x = /*#__PURE__*/_jsx(_Fragment, { var x = /*#__PURE__*/_jsx(_Fragment, {
children: /*#__PURE__*/_jsxs("div", { children: /*#__PURE__*/_jsxs("div", {

View File

@ -1,5 +1,5 @@
import { jsx as _jsx } from "react/jsx-runtime";
import { Fragment as _Fragment } from "react/jsx-runtime"; import { Fragment as _Fragment } from "react/jsx-runtime";
import { jsx as _jsx } from "react/jsx-runtime";
var x = /*#__PURE__*/_jsx(_Fragment, { var x = /*#__PURE__*/_jsx(_Fragment, {
children: /*#__PURE__*/_jsx("div", {}) children: /*#__PURE__*/_jsx("div", {})

View File

@ -1,5 +1,5 @@
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime"; import { jsxs as _jsxs } from "react/jsx-runtime";
import { jsx as _jsx } from "react/jsx-runtime";
var x = /*#__PURE__*/_jsxs("div", { var x = /*#__PURE__*/_jsxs("div", {
children: [/*#__PURE__*/_jsx("span", {}), [/*#__PURE__*/_jsx("span", {}, '0'), /*#__PURE__*/_jsx("span", {}, '1')]] children: [/*#__PURE__*/_jsx("span", {}), [/*#__PURE__*/_jsx("span", {}, '0'), /*#__PURE__*/_jsx("span", {}, '1')]]

View File

@ -1,5 +1,5 @@
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime"; import { jsxs as _jsxs } from "react/jsx-runtime";
import { jsx as _jsx } from "react/jsx-runtime";
var x = /*#__PURE__*/_jsxs("div", { var x = /*#__PURE__*/_jsxs("div", {
children: [/*#__PURE__*/_jsx("div", { children: [/*#__PURE__*/_jsx("div", {

View File

@ -1,5 +1,5 @@
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime"; import { jsxs as _jsxs } from "react/jsx-runtime";
import { jsx as _jsx } from "react/jsx-runtime";
var x = /*#__PURE__*/_jsxs("div", { var x = /*#__PURE__*/_jsxs("div", {
children: [/*#__PURE__*/_jsx("div", {}, "1"), /*#__PURE__*/_jsx("div", { children: [/*#__PURE__*/_jsx("div", {}, "1"), /*#__PURE__*/_jsx("div", {

View File

@ -1 +1,3 @@
const foo = /*#__PURE__*/undefined.jsx("p", {}); var _reactJsxRuntime = require("react/jsx-runtime");
const foo = /*#__PURE__*/_reactJsxRuntime.jsx("p", {});