Stop using old JSX transform (#12253)

This commit is contained in:
Mateusz Burzyński 2020-12-08 22:41:13 +01:00 committed by GitHub
parent aca5edb339
commit c2fcd69e94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 202 additions and 256 deletions

View File

@ -10,6 +10,31 @@ const DEFAULT = {
};
export function helper(babel, options) {
// TODO (Babel 8): Remove `useBuiltIns` & `useSpread`
const { useSpread = false, useBuiltIns = false } = options;
if (options.runtime === "classic") {
if (typeof useSpread !== "boolean") {
throw new Error(
"transform-react-jsx currently only accepts a boolean option for " +
"useSpread (defaults to false)",
);
}
if (typeof useBuiltIns !== "boolean") {
throw new Error(
"transform-react-jsx currently only accepts a boolean option for " +
"useBuiltIns (defaults to false)",
);
}
if (useSpread && useBuiltIns) {
throw new Error(
"transform-react-jsx currently only accepts useBuiltIns or useSpread " +
"but not both",
);
}
}
const FILE_NAME_VAR = "_jsxFileName";
const JSX_SOURCE_ANNOTATION_REGEX = /\*?\s*@jsxImportSource\s+([^\s]+)/;
@ -43,16 +68,16 @@ export function helper(babel, options) {
}
}
const source = t.jsxAttribute(
t.jsxIdentifier("__source"),
t.jsxExpressionContainer(makeSource(path, state)),
);
const self = t.jsxAttribute(
t.jsxIdentifier("__self"),
t.jsxExpressionContainer(t.thisExpression()),
);
const source = t.jsxAttribute(
t.jsxIdentifier("__source"),
t.jsxExpressionContainer(makeSource(path, state)),
);
path.pushContainer("attributes", [source, self]);
path.pushContainer("attributes", [self, source]);
},
};
@ -246,19 +271,22 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
}
},
exit(path, state) {
if (
state.get("@babel/plugin-react-jsx/runtime") === "classic" &&
state.get("@babel/plugin-react-jsx/pragmaSet") &&
state.get("@babel/plugin-react-jsx/usedFragment") &&
!state.get("@babel/plugin-react-jsx/pragmaFragSet")
) {
throw new Error(
"transform-react-jsx: pragma has been set but " +
"pragmaFrag has not been set",
);
}
},
// TODO (Babel 8): Decide if this should be removed or brought back.
// see: https://github.com/babel/babel/pull/12253#discussion_r513086528
//
// exit(path, state) {
// if (
// state.get("@babel/plugin-react-jsx/runtime") === "classic" &&
// state.get("@babel/plugin-react-jsx/pragmaSet") &&
// state.get("@babel/plugin-react-jsx/usedFragment") &&
// !state.get("@babel/plugin-react-jsx/pragmaFragSet")
// ) {
// throw new Error(
// "transform-react-jsx: pragma has been set but " +
// "pragmaFrag has not been set",
// );
// }
// },
},
};
@ -781,6 +809,7 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
}
const attribs = buildCreateElementOpeningElementAttributes(
file,
path,
openingPath.node.attributes,
);
@ -804,7 +833,12 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
* breaking on spreads, we then push a new object containing
* all prior attributes to an array for later processing.
*/
function buildCreateElementOpeningElementAttributes(path, attribs) {
function buildCreateElementOpeningElementAttributes(file, path, attribs) {
// TODO (Babel 8): Only leave this branch of the code and remove the rest
if (
RUNTIME_DEFAULT === "automatic" ||
file.get("@babel/plugin-react-jsx/runtime") === "automatic"
) {
const props = [];
const found = Object.create(null);
@ -826,6 +860,63 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
return props.length > 0 ? t.objectExpression(props) : t.nullLiteral();
}
let props = [];
const objs = [];
const found = Object.create(null);
for (const attr of attribs) {
const name =
t.isJSXAttribute(attr) &&
t.isJSXIdentifier(attr.name) &&
attr.name.name;
if (name === "__source" || name === "__self") {
if (found[name]) throw sourceSelfError(path, name);
found[name] = true;
if (!options.development) continue;
}
if (useSpread || !t.isJSXSpreadAttribute(attr)) {
props.push(convertAttribute(attr));
} else {
if (props.length) {
objs.push(t.objectExpression(props));
props = [];
}
objs.push(attr.argument);
}
}
if (!props.length && !objs.length) {
return t.nullLiteral();
}
if (useSpread) {
return props.length > 0 ? t.objectExpression(props) : t.nullLiteral();
}
if (props.length) {
objs.push(t.objectExpression(props));
props = [];
}
if (objs.length === 1) {
return objs[0];
}
// looks like we have multiple objects
if (!t.isObjectExpression(objs[0])) {
objs.unshift(t.objectExpression([]));
}
const helper = useBuiltIns
? t.memberExpression(t.identifier("Object"), t.identifier("assign"))
: file.addHelper("extends");
// spread it
return t.callExpression(helper, objs);
}
function sourceSelfError(path, name) {
const pluginName = `transform-react-jsx-${name.slice(2)}`;

View File

@ -21,12 +21,12 @@ var x = /*#__PURE__*/_jsxDEV(_Fragment, {
columnNumber: 7
}, this), /*#__PURE__*/_createElement("div", { ...props,
key: "4",
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 7,
columnNumber: 7
},
__self: this
}
})]
}, void 0, true, {
fileName: _jsxFileName,

View File

@ -1,42 +1,45 @@
var _jsxFileName = "<CWD>/packages/babel-plugin-transform-react-jsx-development/test/fixtures/linux/classic-runtime/input.js";
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
var x = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 3,
columnNumber: 5
},
__self: this
}
}, /*#__PURE__*/React.createElement("div", {
key: "1",
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 4,
columnNumber: 9
},
__self: this
}
}), /*#__PURE__*/React.createElement("div", {
key: "2",
meow: "wolf",
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 5,
columnNumber: 9
},
__self: this
}
}), /*#__PURE__*/React.createElement("div", {
key: "3",
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 6,
columnNumber: 9
},
__self: this
}), /*#__PURE__*/React.createElement("div", { ...props,
}
}), /*#__PURE__*/React.createElement("div", _extends({}, props, {
key: "4",
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 7,
columnNumber: 9
},
__self: this
})));
}
}))));

View File

@ -21,12 +21,12 @@ var x = /*#__PURE__*/_jsxDEV(_Fragment, {
columnNumber: 7
}, this), /*#__PURE__*/_createElement("div", { ...props,
key: "4",
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 7,
columnNumber: 7
},
__self: this
}
})]
}, void 0, true, {
fileName: _jsxFileName,

View File

@ -1,42 +1,45 @@
var _jsxFileName = "<CWD>\\packages\\babel-plugin-transform-react-jsx-development\\test\\fixtures\\windows\\classic-runtime-windows\\input.js";
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
var x = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 3,
columnNumber: 5
},
__self: this
}
}, /*#__PURE__*/React.createElement("div", {
key: "1",
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 4,
columnNumber: 7
},
__self: this
}
}), /*#__PURE__*/React.createElement("div", {
key: "2",
meow: "wolf",
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 5,
columnNumber: 7
},
__self: this
}
}), /*#__PURE__*/React.createElement("div", {
key: "3",
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 6,
columnNumber: 7
},
__self: this
}), /*#__PURE__*/React.createElement("div", { ...props,
}
}), /*#__PURE__*/React.createElement("div", _extends({}, props, {
key: "4",
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 7,
columnNumber: 7
},
__self: this
})));
}
}))));

View File

@ -1,18 +1,54 @@
/* eslint-disable-next-line @babel/development/plugin-name */
import transformClassic from "./transform-classic";
/* eslint-disable-next-line @babel/development/plugin-name */
import transformAutomatic from "./transform-automatic";
import jsx from "@babel/plugin-syntax-jsx";
import { helper } from "@babel/helper-builder-react-jsx-experimental";
import { declare } from "@babel/helper-plugin-utils";
import { types as t } from "@babel/core";
export default declare((api, options) => {
const { runtime = "classic" } = options;
const PURE_ANNOTATION = options.pure;
// we throw a warning in helper-builder-react-jsx-experimental if runtime
// is neither automatic or classic because we will remove this file
// in v8.0.0
if (runtime === "classic") {
return transformClassic(api, options);
const visitor = helper(api, {
pre(state) {
const tagName = state.tagName;
const args = state.args;
if (t.react.isCompatTag(tagName)) {
args.push(t.stringLiteral(tagName));
} else {
return transformAutomatic(api, options);
args.push(state.tagExpr);
}
},
post(state, pass) {
if (pass.get("@babel/plugin-react-jsx/runtime") === "classic") {
state.createElementCallee = pass.get(
"@babel/plugin-react-jsx/createElementIdentifier",
)();
state.pure =
PURE_ANNOTATION ?? !pass.get("@babel/plugin-react-jsx/pragmaSet");
} else {
state.jsxCallee = pass.get("@babel/plugin-react-jsx/jsxIdentifier")();
state.jsxStaticCallee = pass.get(
"@babel/plugin-react-jsx/jsxStaticIdentifier",
)();
state.createElementCallee = pass.get(
"@babel/plugin-react-jsx/createElementIdentifier",
)();
state.pure =
PURE_ANNOTATION ??
!pass.get("@babel/plugin-react-jsx/importSourceSet");
}
},
...options,
development: false,
runtime,
});
return {
name: "transform-react-jsx",
inherits: jsx,
visitor,
};
});

View File

@ -1,52 +0,0 @@
import jsx from "@babel/plugin-syntax-jsx";
import { helper } from "@babel/helper-builder-react-jsx-experimental";
import { declare } from "@babel/helper-plugin-utils";
import { types as t } from "@babel/core";
export default declare((api, options) => {
const PURE_ANNOTATION = options.pure;
const visitor = helper(api, {
pre(state) {
const tagName = state.tagName;
const args = state.args;
if (t.react.isCompatTag(tagName)) {
args.push(t.stringLiteral(tagName));
} else {
args.push(state.tagExpr);
}
},
post(state, pass) {
if (pass.get("@babel/plugin-react-jsx/runtime") === "classic") {
state.createElementCallee = pass.get(
"@babel/plugin-react-jsx/createElementIdentifier",
)();
state.pure =
PURE_ANNOTATION ?? !pass.get("@babel/plugin-react-jsx/pragmaSet");
} else {
state.jsxCallee = pass.get("@babel/plugin-react-jsx/jsxIdentifier")();
state.jsxStaticCallee = pass.get(
"@babel/plugin-react-jsx/jsxStaticIdentifier",
)();
state.createElementCallee = pass.get(
"@babel/plugin-react-jsx/createElementIdentifier",
)();
state.pure =
PURE_ANNOTATION ??
!pass.get("@babel/plugin-react-jsx/importSourceSet");
}
},
...options,
development: false,
});
return {
name: "transform-react-jsx",
inherits: jsx,
visitor,
};
});

View File

@ -1,106 +0,0 @@
import { declare } from "@babel/helper-plugin-utils";
import jsx from "@babel/plugin-syntax-jsx";
import helper from "@babel/helper-builder-react-jsx";
import { types as t } from "@babel/core";
const DEFAULT = {
pragma: "React.createElement",
pragmaFrag: "React.Fragment",
};
export default declare((api, options) => {
const THROW_IF_NAMESPACE =
options.throwIfNamespace === undefined ? true : !!options.throwIfNamespace;
const PRAGMA_DEFAULT = options.pragma || DEFAULT.pragma;
const PRAGMA_FRAG_DEFAULT = options.pragmaFrag || DEFAULT.pragmaFrag;
const PURE_ANNOTATION = options.pure;
const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/;
const JSX_FRAG_ANNOTATION_REGEX = /\*?\s*@jsxFrag\s+([^\s]+)/;
// returns a closure that returns an identifier or memberExpression node
// based on the given id
const createIdentifierParser = (id: string) => () => {
return id
.split(".")
.map(name => t.identifier(name))
.reduce((object, property) => t.memberExpression(object, property));
};
const visitor = helper({
pre(state) {
const tagName = state.tagName;
const args = state.args;
if (t.react.isCompatTag(tagName)) {
args.push(t.stringLiteral(tagName));
} else {
args.push(state.tagExpr);
}
},
post(state, pass) {
state.callee = pass.get("jsxIdentifier")();
state.pure = PURE_ANNOTATION ?? pass.get("pragma") === DEFAULT.pragma;
},
throwIfNamespace: THROW_IF_NAMESPACE,
});
visitor.Program = {
enter(path, state) {
const { file } = state;
let pragma = PRAGMA_DEFAULT;
let pragmaFrag = PRAGMA_FRAG_DEFAULT;
let pragmaSet = !!options.pragma;
let pragmaFragSet = !!options.pragma;
if (file.ast.comments) {
for (const comment of (file.ast.comments: Array<Object>)) {
const jsxMatches = JSX_ANNOTATION_REGEX.exec(comment.value);
if (jsxMatches) {
pragma = jsxMatches[1];
pragmaSet = true;
}
const jsxFragMatches = JSX_FRAG_ANNOTATION_REGEX.exec(comment.value);
if (jsxFragMatches) {
pragmaFrag = jsxFragMatches[1];
pragmaFragSet = true;
}
}
}
state.set("jsxIdentifier", createIdentifierParser(pragma));
state.set("jsxFragIdentifier", createIdentifierParser(pragmaFrag));
state.set("usedFragment", false);
state.set("pragma", pragma);
state.set("pragmaSet", pragmaSet);
state.set("pragmaFragSet", pragmaFragSet);
},
exit(path, state) {
if (
state.get("pragmaSet") &&
state.get("usedFragment") &&
!state.get("pragmaFragSet")
) {
throw new Error(
"transform-react-jsx: pragma has been set but " +
"pragmaFrag has not been set",
);
}
},
};
visitor.JSXAttribute = function (path) {
if (t.isJSXElement(path.node.value)) {
path.node.value = t.jsxExpressionContainer(path.node.value);
}
};
return {
name: "transform-react-jsx",
inherits: jsx,
visitor,
};
});

View File

@ -1,3 +0,0 @@
{
"throws": "transform-react-jsx: pragma has been set but pragmaFrag has not been set"
}

View File

@ -1,3 +0,0 @@
{
"throws": "transform-react-jsx: pragma has been set but pragmaFrag has not been set"
}

View File

@ -19,8 +19,6 @@
"@babel/plugin-transform-react-display-name": "workspace:^7.12.1",
"@babel/plugin-transform-react-jsx": "workspace:^7.12.7",
"@babel/plugin-transform-react-jsx-development": "workspace:^7.12.7",
"@babel/plugin-transform-react-jsx-self": "workspace:^7.12.1",
"@babel/plugin-transform-react-jsx-source": "workspace:^7.12.1",
"@babel/plugin-transform-react-pure-annotations": "workspace:^7.12.1"
},
"peerDependencies": {

View File

@ -2,8 +2,6 @@ import { declare } from "@babel/helper-plugin-utils";
import transformReactJSX from "@babel/plugin-transform-react-jsx";
import transformReactJSXDevelopment from "@babel/plugin-transform-react-jsx-development";
import transformReactDisplayName from "@babel/plugin-transform-react-display-name";
import transformReactJSXSource from "@babel/plugin-transform-react-jsx-source";
import transformReactJSXSelf from "@babel/plugin-transform-react-jsx-self";
import transformReactPure from "@babel/plugin-transform-react-pure-annotations";
export default declare((api, opts) => {
@ -14,7 +12,6 @@ export default declare((api, opts) => {
const {
pure,
throwIfNamespace = true,
useSpread,
runtime = "classic",
importSource,
} = opts;
@ -25,35 +22,27 @@ export default declare((api, opts) => {
pragmaFrag = pragmaFrag || "React.Fragment";
}
// TODO: (Babel 8) Don't cast these options but validate it
// TODO: (Babel 8) Don't cast this option but validate it
const development = !!opts.development;
const useBuiltIns = !!opts.useBuiltIns;
const transformReactJSXPlugin =
runtime === "automatic" && development
? transformReactJSXDevelopment
: transformReactJSX;
return {
plugins: [
[
transformReactJSXPlugin,
development ? transformReactJSXDevelopment : transformReactJSX,
{
importSource,
pragma,
pragmaFrag,
runtime,
throwIfNamespace,
useBuiltIns,
useSpread,
pure,
// TODO (Babel 8): Remove `useBuiltIns` & `useSpread`
useBuiltIns: !!opts.useBuiltIns,
useSpread: opts.useSpread,
},
],
transformReactDisplayName,
pure !== false && transformReactPure,
development && runtime === "classic" && transformReactJSXSource,
development && runtime === "classic" && transformReactJSXSelf,
].filter(Boolean),
};
});

View File

@ -2532,7 +2532,7 @@ __metadata:
languageName: unknown
linkType: soft
"@babel/plugin-transform-react-jsx-self@workspace:*, @babel/plugin-transform-react-jsx-self@workspace:^7.12.1, @babel/plugin-transform-react-jsx-self@workspace:packages/babel-plugin-transform-react-jsx-self":
"@babel/plugin-transform-react-jsx-self@workspace:*, @babel/plugin-transform-react-jsx-self@workspace:packages/babel-plugin-transform-react-jsx-self":
version: 0.0.0-use.local
resolution: "@babel/plugin-transform-react-jsx-self@workspace:packages/babel-plugin-transform-react-jsx-self"
dependencies:
@ -2545,7 +2545,7 @@ __metadata:
languageName: unknown
linkType: soft
"@babel/plugin-transform-react-jsx-source@workspace:*, @babel/plugin-transform-react-jsx-source@workspace:^7.12.1, @babel/plugin-transform-react-jsx-source@workspace:packages/babel-plugin-transform-react-jsx-source":
"@babel/plugin-transform-react-jsx-source@workspace:*, @babel/plugin-transform-react-jsx-source@workspace:packages/babel-plugin-transform-react-jsx-source":
version: 0.0.0-use.local
resolution: "@babel/plugin-transform-react-jsx-source@workspace:packages/babel-plugin-transform-react-jsx-source"
dependencies:
@ -3094,8 +3094,6 @@ __metadata:
"@babel/plugin-transform-react-display-name": "workspace:^7.12.1"
"@babel/plugin-transform-react-jsx": "workspace:^7.12.7"
"@babel/plugin-transform-react-jsx-development": "workspace:^7.12.7"
"@babel/plugin-transform-react-jsx-self": "workspace:^7.12.1"
"@babel/plugin-transform-react-jsx-source": "workspace:^7.12.1"
"@babel/plugin-transform-react-pure-annotations": "workspace:^7.12.1"
peerDependencies:
"@babel/core": ^7.0.0-0