refactor: Move react-jsx-development implementation into react-jsx (#12524)
* Move react-jsx-development implementation into react-jsx * Move helper-builder-jsx-experimental into transform-react-jsx * Move JSX validation * Move Program visitor * introduce get/set utils * pre -> getTag * Dedupe code * post -> getState * Simplify logic * Move final pieces * Other simplifications * Update lockfile * Fix standalone bundling
This commit is contained in:
parent
e08f68bf61
commit
17d62c3743
@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@babel/helper-builder-react-jsx-experimental",
|
|
||||||
"version": "7.12.11",
|
|
||||||
"description": "Helper function to build react jsx",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/babel/babel.git",
|
|
||||||
"directory": "packages/babel-helper-builder-react-jsx-experimental"
|
|
||||||
},
|
|
||||||
"license": "MIT",
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public"
|
|
||||||
},
|
|
||||||
"main": "lib/index.js",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/helper-annotate-as-pure": "workspace:^7.12.10",
|
|
||||||
"@babel/helper-module-imports": "workspace:^7.12.5",
|
|
||||||
"@babel/types": "workspace:^7.12.11"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,839 +0,0 @@
|
|||||||
import * as t from "@babel/types";
|
|
||||||
import { addNamed, addNamespace, isModule } from "@babel/helper-module-imports";
|
|
||||||
import annotateAsPure from "@babel/helper-annotate-as-pure";
|
|
||||||
|
|
||||||
const DEFAULT = {
|
|
||||||
importSource: "react",
|
|
||||||
runtime: "automatic",
|
|
||||||
pragma: "React.createElement",
|
|
||||||
pragmaFrag: "React.Fragment",
|
|
||||||
};
|
|
||||||
|
|
||||||
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]+)/;
|
|
||||||
const JSX_RUNTIME_ANNOTATION_REGEX = /\*?\s*@jsxRuntime\s+([^\s]+)/;
|
|
||||||
|
|
||||||
const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/;
|
|
||||||
const JSX_FRAG_ANNOTATION_REGEX = /\*?\s*@jsxFrag\s+([^\s]+)/;
|
|
||||||
|
|
||||||
const {
|
|
||||||
importSource: IMPORT_SOURCE_DEFAULT = DEFAULT.importSource,
|
|
||||||
runtime: RUNTIME_DEFAULT = DEFAULT.runtime,
|
|
||||||
pragma: PRAGMA_DEFAULT = DEFAULT.pragma,
|
|
||||||
pragmaFrag: PRAGMA_FRAG_DEFAULT = DEFAULT.pragmaFrag,
|
|
||||||
} = options;
|
|
||||||
|
|
||||||
const injectMetaPropertiesVisitor = {
|
|
||||||
JSXOpeningElement(path, state) {
|
|
||||||
for (const attr of path.get("attributes")) {
|
|
||||||
if (!attr.isJSXElement()) continue;
|
|
||||||
|
|
||||||
const { name } = attr.node.name;
|
|
||||||
if (name === "__source" || name === "__self") {
|
|
||||||
throw path.buildCodeFrameError(
|
|
||||||
`__source and __self should not be defined in props and are reserved for internal usage.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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", [self, source]);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
JSXNamespacedName(path, state) {
|
|
||||||
const throwIfNamespace =
|
|
||||||
state.opts.throwIfNamespace === undefined
|
|
||||||
? true
|
|
||||||
: !!state.opts.throwIfNamespace;
|
|
||||||
if (throwIfNamespace) {
|
|
||||||
throw path.buildCodeFrameError(
|
|
||||||
`Namespace tags are not supported by default. React's JSX doesn't support namespace tags. \
|
|
||||||
You can set \`throwIfNamespace: false\` to bypass this warning.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
JSXSpreadChild(path) {
|
|
||||||
throw path.buildCodeFrameError(
|
|
||||||
"Spread children are not supported in React.",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
JSXElement: {
|
|
||||||
exit(path, file) {
|
|
||||||
let callExpr;
|
|
||||||
if (
|
|
||||||
file.get("@babel/plugin-react-jsx/runtime") === "classic" ||
|
|
||||||
shouldUseCreateElement(path)
|
|
||||||
) {
|
|
||||||
callExpr = buildCreateElementCall(path, file);
|
|
||||||
} else {
|
|
||||||
callExpr = buildJSXElementCall(path, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
path.replaceWith(t.inherits(callExpr, path.node));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
JSXFragment: {
|
|
||||||
exit(path, file) {
|
|
||||||
let callExpr;
|
|
||||||
if (file.get("@babel/plugin-react-jsx/runtime") === "classic") {
|
|
||||||
callExpr = buildCreateElementFragmentCall(path, file);
|
|
||||||
} else {
|
|
||||||
callExpr = buildJSXFragmentCall(path, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
path.replaceWith(t.inherits(callExpr, path.node));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
JSXAttribute(path) {
|
|
||||||
if (t.isJSXElement(path.node.value)) {
|
|
||||||
path.node.value = t.jsxExpressionContainer(path.node.value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
Program: {
|
|
||||||
enter(path, state) {
|
|
||||||
const { file } = state;
|
|
||||||
let runtime = RUNTIME_DEFAULT;
|
|
||||||
|
|
||||||
// For jsx mode
|
|
||||||
let source = IMPORT_SOURCE_DEFAULT;
|
|
||||||
let sourceSet = !!options.importSource;
|
|
||||||
|
|
||||||
// For createElement mode
|
|
||||||
let pragma = PRAGMA_DEFAULT;
|
|
||||||
let pragmaFrag = PRAGMA_FRAG_DEFAULT;
|
|
||||||
let pragmaSet = !!options.pragma;
|
|
||||||
let pragmaFragSet = !!options.pragmaFrag;
|
|
||||||
|
|
||||||
if (file.ast.comments) {
|
|
||||||
for (const comment of (file.ast.comments: Array<Object>)) {
|
|
||||||
const sourceMatches = JSX_SOURCE_ANNOTATION_REGEX.exec(
|
|
||||||
comment.value,
|
|
||||||
);
|
|
||||||
if (sourceMatches) {
|
|
||||||
source = sourceMatches[1];
|
|
||||||
sourceSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const runtimeMatches = JSX_RUNTIME_ANNOTATION_REGEX.exec(
|
|
||||||
comment.value,
|
|
||||||
);
|
|
||||||
if (runtimeMatches) {
|
|
||||||
runtime = runtimeMatches[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
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("@babel/plugin-react-jsx/runtime", runtime);
|
|
||||||
if (runtime === "classic") {
|
|
||||||
if (sourceSet) {
|
|
||||||
throw path.buildCodeFrameError(
|
|
||||||
`importSource cannot be set when runtime is classic.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
state.set(
|
|
||||||
"@babel/plugin-react-jsx/createElementIdentifier",
|
|
||||||
createIdentifierParser(pragma),
|
|
||||||
);
|
|
||||||
state.set(
|
|
||||||
"@babel/plugin-react-jsx/jsxFragIdentifier",
|
|
||||||
createIdentifierParser(pragmaFrag),
|
|
||||||
);
|
|
||||||
state.set("@babel/plugin-react-jsx/usedFragment", false);
|
|
||||||
state.set(
|
|
||||||
"@babel/plugin-react-jsx/pragmaSet",
|
|
||||||
pragma !== DEFAULT.pragma,
|
|
||||||
);
|
|
||||||
state.set(
|
|
||||||
"@babel/plugin-react-jsx/pragmaFragSet",
|
|
||||||
pragmaFrag !== DEFAULT.pragmaFrag,
|
|
||||||
);
|
|
||||||
} else if (runtime === "automatic") {
|
|
||||||
if (pragmaSet || pragmaFragSet) {
|
|
||||||
throw path.buildCodeFrameError(
|
|
||||||
`pragma and pragmaFrag cannot be set when runtime is automatic.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
state.set(
|
|
||||||
"@babel/plugin-react-jsx/jsxIdentifier",
|
|
||||||
createImportLazily(
|
|
||||||
state,
|
|
||||||
path,
|
|
||||||
options.development ? "jsxDEV" : "jsx",
|
|
||||||
source,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
state.set(
|
|
||||||
"@babel/plugin-react-jsx/jsxStaticIdentifier",
|
|
||||||
createImportLazily(
|
|
||||||
state,
|
|
||||||
path,
|
|
||||||
options.development ? "jsxDEV" : "jsxs",
|
|
||||||
source,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
state.set(
|
|
||||||
"@babel/plugin-react-jsx/createElementIdentifier",
|
|
||||||
createImportLazily(state, path, "createElement", source),
|
|
||||||
);
|
|
||||||
|
|
||||||
state.set(
|
|
||||||
"@babel/plugin-react-jsx/jsxFragIdentifier",
|
|
||||||
createImportLazily(state, path, "Fragment", source),
|
|
||||||
);
|
|
||||||
|
|
||||||
state.set(
|
|
||||||
"@babel/plugin-react-jsx/importSourceSet",
|
|
||||||
source !== DEFAULT.importSource,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
throw path.buildCodeFrameError(
|
|
||||||
`Runtime must be either "classic" or "automatic".`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.development) {
|
|
||||||
path.traverse(injectMetaPropertiesVisitor, state);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 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",
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// We want to use React.createElement, even in the case of
|
|
||||||
// jsx, for <div {...props} key={key} /> to distinguish it
|
|
||||||
// from <div key={key} {...props} />. This is an intermediary
|
|
||||||
// step while we deprecate key spread from props. Afterwards,
|
|
||||||
// we will stop using createElement in the transform.
|
|
||||||
function shouldUseCreateElement(path) {
|
|
||||||
const openingPath = path.get("openingElement");
|
|
||||||
const attributes = openingPath.node.attributes;
|
|
||||||
|
|
||||||
let seenPropsSpread = false;
|
|
||||||
for (let i = 0; i < attributes.length; i++) {
|
|
||||||
const attr = attributes[i];
|
|
||||||
if (
|
|
||||||
seenPropsSpread &&
|
|
||||||
t.isJSXAttribute(attr) &&
|
|
||||||
attr.name.name === "key"
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
} else if (t.isJSXSpreadAttribute(attr)) {
|
|
||||||
seenPropsSpread = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSource(source, importName) {
|
|
||||||
switch (importName) {
|
|
||||||
case "Fragment":
|
|
||||||
return `${source}/${
|
|
||||||
options.development ? "jsx-dev-runtime" : "jsx-runtime"
|
|
||||||
}`;
|
|
||||||
case "jsxDEV":
|
|
||||||
return `${source}/jsx-dev-runtime`;
|
|
||||||
case "jsx":
|
|
||||||
case "jsxs":
|
|
||||||
return `${source}/jsx-runtime`;
|
|
||||||
case "createElement":
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createImportLazily(pass, path, importName, source) {
|
|
||||||
return () => {
|
|
||||||
const actualSource = getSource(source, importName);
|
|
||||||
if (isModule(path)) {
|
|
||||||
let reference = pass.get(
|
|
||||||
`@babel/plugin-react-jsx/imports/${importName}`,
|
|
||||||
);
|
|
||||||
if (reference) return t.cloneNode(reference);
|
|
||||||
|
|
||||||
reference = addNamed(path, importName, actualSource, {
|
|
||||||
importedInterop: "uncompiled",
|
|
||||||
});
|
|
||||||
pass.set(`@babel/plugin-react-jsx/imports/${importName}`, reference);
|
|
||||||
|
|
||||||
return reference;
|
|
||||||
} else {
|
|
||||||
let reference = pass.get(
|
|
||||||
`@babel/plugin-react-jsx/requires/${actualSource}`,
|
|
||||||
);
|
|
||||||
if (reference) {
|
|
||||||
reference = t.cloneNode(reference);
|
|
||||||
} else {
|
|
||||||
reference = addNamespace(path, actualSource, {
|
|
||||||
importedInterop: "uncompiled",
|
|
||||||
});
|
|
||||||
pass.set(
|
|
||||||
`@babel/plugin-react-jsx/requires/${actualSource}`,
|
|
||||||
reference,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return t.memberExpression(reference, t.identifier(importName));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createIdentifierParser(id) {
|
|
||||||
return () => {
|
|
||||||
return id
|
|
||||||
.split(".")
|
|
||||||
.map(name => t.identifier(name))
|
|
||||||
.reduce((object, property) => t.memberExpression(object, property));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeTrace(fileNameIdentifier, lineNumber, column0Based) {
|
|
||||||
const fileLineLiteral =
|
|
||||||
lineNumber != null ? t.numericLiteral(lineNumber) : t.nullLiteral();
|
|
||||||
|
|
||||||
const fileColumnLiteral =
|
|
||||||
column0Based != null
|
|
||||||
? t.numericLiteral(column0Based + 1)
|
|
||||||
: t.nullLiteral();
|
|
||||||
|
|
||||||
const fileNameProperty = t.objectProperty(
|
|
||||||
t.identifier("fileName"),
|
|
||||||
fileNameIdentifier,
|
|
||||||
);
|
|
||||||
const lineNumberProperty = t.objectProperty(
|
|
||||||
t.identifier("lineNumber"),
|
|
||||||
fileLineLiteral,
|
|
||||||
);
|
|
||||||
const columnNumberProperty = t.objectProperty(
|
|
||||||
t.identifier("columnNumber"),
|
|
||||||
fileColumnLiteral,
|
|
||||||
);
|
|
||||||
return t.objectExpression([
|
|
||||||
fileNameProperty,
|
|
||||||
lineNumberProperty,
|
|
||||||
columnNumberProperty,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeSource(path, state) {
|
|
||||||
const location = path.node.loc;
|
|
||||||
if (!location) {
|
|
||||||
// the element was generated and doesn't have location information
|
|
||||||
return path.scope.buildUndefinedNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!state.fileNameIdentifier) {
|
|
||||||
const { filename = "" } = state;
|
|
||||||
|
|
||||||
const fileNameIdentifier = path.scope.generateUidIdentifier(
|
|
||||||
FILE_NAME_VAR,
|
|
||||||
);
|
|
||||||
const scope = path.hub.getScope();
|
|
||||||
if (scope) {
|
|
||||||
scope.push({
|
|
||||||
id: fileNameIdentifier,
|
|
||||||
init: t.stringLiteral(filename),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
state.fileNameIdentifier = fileNameIdentifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
return makeTrace(
|
|
||||||
t.cloneNode(state.fileNameIdentifier),
|
|
||||||
location.start.line,
|
|
||||||
location.start.column,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertJSXIdentifier(node, parent) {
|
|
||||||
if (t.isJSXIdentifier(node)) {
|
|
||||||
if (node.name === "this" && t.isReferenced(node, parent)) {
|
|
||||||
return t.thisExpression();
|
|
||||||
} else if (t.isValidIdentifier(node.name, false)) {
|
|
||||||
node.type = "Identifier";
|
|
||||||
} else {
|
|
||||||
return t.stringLiteral(node.name);
|
|
||||||
}
|
|
||||||
} else if (t.isJSXMemberExpression(node)) {
|
|
||||||
return t.memberExpression(
|
|
||||||
convertJSXIdentifier(node.object, node),
|
|
||||||
convertJSXIdentifier(node.property, node),
|
|
||||||
);
|
|
||||||
} else if (t.isJSXNamespacedName(node)) {
|
|
||||||
/**
|
|
||||||
* If the flag "throwIfNamespace" is false
|
|
||||||
* print XMLNamespace like string literal
|
|
||||||
*/
|
|
||||||
return t.stringLiteral(`${node.namespace.name}:${node.name.name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertAttributeValue(node) {
|
|
||||||
if (t.isJSXExpressionContainer(node)) {
|
|
||||||
return node.expression;
|
|
||||||
} else {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertAttribute(node) {
|
|
||||||
const value = convertAttributeValue(node.value || t.booleanLiteral(true));
|
|
||||||
|
|
||||||
if (t.isJSXSpreadAttribute(node)) {
|
|
||||||
return t.spreadElement(node.argument);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t.isStringLiteral(value) && !t.isJSXExpressionContainer(node.value)) {
|
|
||||||
value.value = value.value.replace(/\n\s+/g, " ");
|
|
||||||
|
|
||||||
// "raw" JSXText should not be used from a StringLiteral because it needs to be escaped.
|
|
||||||
if (value.extra && value.extra.raw) {
|
|
||||||
delete value.extra.raw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t.isJSXNamespacedName(node.name)) {
|
|
||||||
node.name = t.stringLiteral(
|
|
||||||
node.name.namespace.name + ":" + node.name.name.name,
|
|
||||||
);
|
|
||||||
} else if (t.isValidIdentifier(node.name.name, false)) {
|
|
||||||
node.name.type = "Identifier";
|
|
||||||
} else {
|
|
||||||
node.name = t.stringLiteral(node.name.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
return t.inherits(t.objectProperty(node.name, value), node);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Builds JSX into:
|
|
||||||
// Production: React.jsx(type, arguments, key)
|
|
||||||
// Development: React.jsxDEV(type, arguments, key, isStaticChildren, source, self)
|
|
||||||
function buildJSXElementCall(path, file) {
|
|
||||||
const openingPath = path.get("openingElement");
|
|
||||||
openingPath.parent.children = t.react.buildChildren(openingPath.parent);
|
|
||||||
|
|
||||||
const tagExpr = convertJSXIdentifier(
|
|
||||||
openingPath.node.name,
|
|
||||||
openingPath.node,
|
|
||||||
);
|
|
||||||
const args = [];
|
|
||||||
|
|
||||||
let tagName;
|
|
||||||
if (t.isIdentifier(tagExpr)) {
|
|
||||||
tagName = tagExpr.name;
|
|
||||||
} else if (t.isLiteral(tagExpr)) {
|
|
||||||
tagName = tagExpr.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
tagExpr: tagExpr,
|
|
||||||
tagName: tagName,
|
|
||||||
args: args,
|
|
||||||
pure: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.pre) {
|
|
||||||
options.pre(state, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
let attribs = [];
|
|
||||||
const extracted = Object.create(null);
|
|
||||||
|
|
||||||
// for React.jsx, key, __source (dev), and __self (dev) is passed in as
|
|
||||||
// a separate argument rather than in the args object. We go through the
|
|
||||||
// props and filter out these three keywords so we can pass them in
|
|
||||||
// as separate arguments later
|
|
||||||
for (const attr of openingPath.get("attributes")) {
|
|
||||||
if (attr.isJSXAttribute() && t.isJSXIdentifier(attr.node.name)) {
|
|
||||||
const { name } = attr.node.name;
|
|
||||||
switch (name) {
|
|
||||||
case "__source":
|
|
||||||
case "__self":
|
|
||||||
if (extracted[name]) throw sourceSelfError(path, name);
|
|
||||||
/* falls through */
|
|
||||||
case "key":
|
|
||||||
extracted[name] = convertAttributeValue(attr.node.value);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
attribs.push(attr.node);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
attribs.push(attr.node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attribs.length || path.node.children.length) {
|
|
||||||
attribs = buildJSXOpeningElementAttributes(
|
|
||||||
attribs,
|
|
||||||
file,
|
|
||||||
path.node.children,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// attributes should never be null
|
|
||||||
attribs = t.objectExpression([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
args.push(attribs);
|
|
||||||
|
|
||||||
if (!options.development) {
|
|
||||||
if (extracted.key !== undefined) {
|
|
||||||
args.push(extracted.key);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// isStaticChildren, __source, and __self are only used in development
|
|
||||||
// automatically include __source and __self in this plugin
|
|
||||||
// so we can eliminate the need for separate Babel plugins in Babel 8
|
|
||||||
args.push(
|
|
||||||
extracted.key ?? path.scope.buildUndefinedNode(),
|
|
||||||
t.booleanLiteral(path.node.children.length > 1),
|
|
||||||
extracted.__source ?? path.scope.buildUndefinedNode(),
|
|
||||||
extracted.__self ?? t.thisExpression(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.post) {
|
|
||||||
options.post(state, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
const call =
|
|
||||||
state.call ||
|
|
||||||
t.callExpression(
|
|
||||||
path.node.children.length > 1 ? state.jsxStaticCallee : state.jsxCallee,
|
|
||||||
args,
|
|
||||||
);
|
|
||||||
if (state.pure) annotateAsPure(call);
|
|
||||||
|
|
||||||
return call;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Builds props for React.jsx. This function adds children into the props
|
|
||||||
// and ensures that props is always an object
|
|
||||||
function buildJSXOpeningElementAttributes(attribs, file, children) {
|
|
||||||
const props = attribs.map(convertAttribute);
|
|
||||||
|
|
||||||
// In React.jsx, children is no longer a separate argument, but passed in
|
|
||||||
// through the argument object
|
|
||||||
if (children && children.length > 0) {
|
|
||||||
if (children.length === 1) {
|
|
||||||
props.push(t.objectProperty(t.identifier("children"), children[0]));
|
|
||||||
} else {
|
|
||||||
props.push(
|
|
||||||
t.objectProperty(
|
|
||||||
t.identifier("children"),
|
|
||||||
t.arrayExpression(children),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return t.objectExpression(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Builds JSX Fragment <></> into
|
|
||||||
// Production: React.jsx(type, arguments)
|
|
||||||
// Development: React.jsxDEV(type, { children})
|
|
||||||
function buildJSXFragmentCall(path, file) {
|
|
||||||
const openingPath = path.get("openingElement");
|
|
||||||
openingPath.parent.children = t.react.buildChildren(openingPath.parent);
|
|
||||||
|
|
||||||
const args = [];
|
|
||||||
const tagName = null;
|
|
||||||
const tagExpr = file.get("@babel/plugin-react-jsx/jsxFragIdentifier")();
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
tagExpr: tagExpr,
|
|
||||||
tagName: tagName,
|
|
||||||
args: args,
|
|
||||||
pure: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.pre) {
|
|
||||||
options.pre(state, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
let childrenNode;
|
|
||||||
if (path.node.children.length > 0) {
|
|
||||||
if (path.node.children.length === 1) {
|
|
||||||
childrenNode = path.node.children[0];
|
|
||||||
} else {
|
|
||||||
childrenNode = t.arrayExpression(path.node.children);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
args.push(
|
|
||||||
t.objectExpression(
|
|
||||||
childrenNode !== undefined
|
|
||||||
? [t.objectProperty(t.identifier("children"), childrenNode)]
|
|
||||||
: [],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (options.development) {
|
|
||||||
args.push(
|
|
||||||
path.scope.buildUndefinedNode(),
|
|
||||||
t.booleanLiteral(path.node.children.length > 1),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.post) {
|
|
||||||
options.post(state, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
const call =
|
|
||||||
state.call ||
|
|
||||||
t.callExpression(
|
|
||||||
path.node.children.length > 1 ? state.jsxStaticCallee : state.jsxCallee,
|
|
||||||
args,
|
|
||||||
);
|
|
||||||
if (state.pure) annotateAsPure(call);
|
|
||||||
|
|
||||||
return call;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildCreateElementFragmentCall(path, file) {
|
|
||||||
if (options.filter && !options.filter(path.node, file)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const openingPath = path.get("openingElement");
|
|
||||||
openingPath.parent.children = t.react.buildChildren(openingPath.parent);
|
|
||||||
|
|
||||||
const args = [];
|
|
||||||
const tagName = null;
|
|
||||||
const tagExpr = file.get("@babel/plugin-react-jsx/jsxFragIdentifier")();
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
tagExpr: tagExpr,
|
|
||||||
tagName: tagName,
|
|
||||||
args: args,
|
|
||||||
pure: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.pre) {
|
|
||||||
options.pre(state, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
// no attributes are allowed with <> syntax
|
|
||||||
args.push(t.nullLiteral(), ...path.node.children);
|
|
||||||
|
|
||||||
if (options.post) {
|
|
||||||
options.post(state, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
file.set("@babel/plugin-react-jsx/usedFragment", true);
|
|
||||||
|
|
||||||
const call =
|
|
||||||
state.call || t.callExpression(state.createElementCallee, args);
|
|
||||||
if (state.pure) annotateAsPure(call);
|
|
||||||
|
|
||||||
return call;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Builds JSX into:
|
|
||||||
// Production: React.createElement(type, arguments, children)
|
|
||||||
// Development: React.createElement(type, arguments, children, source, self)
|
|
||||||
function buildCreateElementCall(path, file) {
|
|
||||||
const openingPath = path.get("openingElement");
|
|
||||||
openingPath.parent.children = t.react.buildChildren(openingPath.parent);
|
|
||||||
|
|
||||||
const tagExpr = convertJSXIdentifier(
|
|
||||||
openingPath.node.name,
|
|
||||||
openingPath.node,
|
|
||||||
);
|
|
||||||
const args = [];
|
|
||||||
|
|
||||||
let tagName;
|
|
||||||
if (t.isIdentifier(tagExpr)) {
|
|
||||||
tagName = tagExpr.name;
|
|
||||||
} else if (t.isLiteral(tagExpr)) {
|
|
||||||
tagName = tagExpr.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
tagExpr: tagExpr,
|
|
||||||
tagName: tagName,
|
|
||||||
args: args,
|
|
||||||
pure: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.pre) {
|
|
||||||
options.pre(state, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
const attribs = buildCreateElementOpeningElementAttributes(
|
|
||||||
file,
|
|
||||||
path,
|
|
||||||
openingPath.node.attributes,
|
|
||||||
);
|
|
||||||
|
|
||||||
args.push(attribs, ...path.node.children);
|
|
||||||
|
|
||||||
if (options.post) {
|
|
||||||
options.post(state, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
const call =
|
|
||||||
state.call || t.callExpression(state.createElementCallee, args);
|
|
||||||
if (state.pure) annotateAsPure(call);
|
|
||||||
|
|
||||||
return call;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The logic for this is quite terse. It's because we need to
|
|
||||||
* support spread elements. We loop over all attributes,
|
|
||||||
* breaking on spreads, we then push a new object containing
|
|
||||||
* all prior attributes to an array for later processing.
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
props.push(convertAttribute(attr));
|
|
||||||
}
|
|
||||||
|
|
||||||
return props.length > 0 ? t.objectExpression(props) : t.nullLiteral();
|
|
||||||
}
|
|
||||||
|
|
||||||
let props = [];
|
|
||||||
const objs = [];
|
|
||||||
|
|
||||||
for (const attr of attribs) {
|
|
||||||
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)}`;
|
|
||||||
|
|
||||||
return path.buildCodeFrameError(
|
|
||||||
`Duplicate ${name} prop found. You are most likely using the deprecated ${pluginName} Babel plugin. Both __source and __self are automatically set when using the automatic runtime. Please remove transform-react-jsx-source and transform-react-jsx-self from your Babel config.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -16,9 +16,7 @@
|
|||||||
"babel-plugin"
|
"babel-plugin"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-builder-react-jsx-experimental": "workspace:^7.12.11",
|
"@babel/plugin-transform-react-jsx": "workspace:^7.12.11"
|
||||||
"@babel/helper-plugin-utils": "workspace:^7.10.4",
|
|
||||||
"@babel/plugin-syntax-jsx": "workspace:^7.12.1"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@babel/core": "^7.0.0-0"
|
"@babel/core": "^7.0.0-0"
|
||||||
|
|||||||
@ -1,59 +1,3 @@
|
|||||||
import jsx from "@babel/plugin-syntax-jsx";
|
/* eslint-disable @babel/development/plugin-name */
|
||||||
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) => {
|
export { default } from "@babel/plugin-transform-react-jsx/lib/development.js";
|
||||||
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 {
|
|
||||||
const getter = get => ({ enumerable: true, configurable: true, get });
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
// These should actually be functions.
|
|
||||||
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 =
|
|
||||||
PURE_ANNOTATION ??
|
|
||||||
!pass.get("@babel/plugin-react-jsx/importSourceSet");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
...options,
|
|
||||||
development: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: "transform-react-jsx",
|
|
||||||
inherits: jsx,
|
|
||||||
visitor,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|||||||
@ -16,10 +16,11 @@
|
|||||||
"babel-plugin"
|
"babel-plugin"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-builder-react-jsx": "workspace:^7.10.4",
|
"@babel/helper-annotate-as-pure": "workspace:^7.12.10",
|
||||||
"@babel/helper-builder-react-jsx-experimental": "workspace:^7.12.11",
|
"@babel/helper-module-imports": "workspace:^7.12.5",
|
||||||
"@babel/helper-plugin-utils": "workspace:^7.10.4",
|
"@babel/helper-plugin-utils": "workspace:^7.10.4",
|
||||||
"@babel/plugin-syntax-jsx": "workspace:^7.12.1"
|
"@babel/plugin-syntax-jsx": "workspace:^7.12.1",
|
||||||
|
"@babel/types": "workspace:^7.12.11"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@babel/core": "^7.0.0-0"
|
"@babel/core": "^7.0.0-0"
|
||||||
|
|||||||
696
packages/babel-plugin-transform-react-jsx/src/create-plugin.js
Normal file
696
packages/babel-plugin-transform-react-jsx/src/create-plugin.js
Normal file
@ -0,0 +1,696 @@
|
|||||||
|
import jsx from "@babel/plugin-syntax-jsx";
|
||||||
|
import { declare } from "@babel/helper-plugin-utils";
|
||||||
|
import { types as t } from "@babel/core";
|
||||||
|
import { addNamed, addNamespace, isModule } from "@babel/helper-module-imports";
|
||||||
|
import annotateAsPure from "@babel/helper-annotate-as-pure";
|
||||||
|
|
||||||
|
const DEFAULT = {
|
||||||
|
importSource: "react",
|
||||||
|
runtime: "automatic",
|
||||||
|
pragma: "React.createElement",
|
||||||
|
pragmaFrag: "React.Fragment",
|
||||||
|
};
|
||||||
|
|
||||||
|
const JSX_SOURCE_ANNOTATION_REGEX = /\*?\s*@jsxImportSource\s+([^\s]+)/;
|
||||||
|
const JSX_RUNTIME_ANNOTATION_REGEX = /\*?\s*@jsxRuntime\s+([^\s]+)/;
|
||||||
|
|
||||||
|
const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/;
|
||||||
|
const JSX_FRAG_ANNOTATION_REGEX = /\*?\s*@jsxFrag\s+([^\s]+)/;
|
||||||
|
|
||||||
|
const get = (pass, name) => pass.get(`@babel/plugin-react-jsx/${name}`);
|
||||||
|
const set = (pass, name, v) => pass.set(`@babel/plugin-react-jsx/${name}`, v);
|
||||||
|
|
||||||
|
export default function createPlugin({ name, development }) {
|
||||||
|
return declare((api, options) => {
|
||||||
|
const {
|
||||||
|
pure: PURE_ANNOTATION,
|
||||||
|
|
||||||
|
throwIfNamespace = true,
|
||||||
|
|
||||||
|
// TODO (Babel 8): It should throw if this option is used with the automatic runtime
|
||||||
|
filter,
|
||||||
|
|
||||||
|
// TODO (Babel 8): Remove `useBuiltIns` & `useSpread`
|
||||||
|
useSpread = false,
|
||||||
|
useBuiltIns = false,
|
||||||
|
|
||||||
|
runtime: RUNTIME_DEFAULT = development ? "automatic" : "classic",
|
||||||
|
|
||||||
|
importSource: IMPORT_SOURCE_DEFAULT = DEFAULT.importSource,
|
||||||
|
pragma: PRAGMA_DEFAULT = DEFAULT.pragma,
|
||||||
|
pragmaFrag: PRAGMA_FRAG_DEFAULT = DEFAULT.pragmaFrag,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
// TOOD(Babel 8): If the runtime is 'automatic', we should throw when useSpread/useBuiltIns are set
|
||||||
|
if (RUNTIME_DEFAULT === "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 injectMetaPropertiesVisitor = {
|
||||||
|
JSXOpeningElement(path, state) {
|
||||||
|
for (const attr of path.get("attributes")) {
|
||||||
|
if (!attr.isJSXElement()) continue;
|
||||||
|
|
||||||
|
const { name } = attr.node.name;
|
||||||
|
if (name === "__source" || name === "__self") {
|
||||||
|
throw path.buildCodeFrameError(
|
||||||
|
`__source and __self should not be defined in props and are reserved for internal usage.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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", [self, source]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
inherits: jsx,
|
||||||
|
visitor: {
|
||||||
|
JSXNamespacedName(path) {
|
||||||
|
if (throwIfNamespace) {
|
||||||
|
throw path.buildCodeFrameError(
|
||||||
|
`Namespace tags are not supported by default. React's JSX doesn't support namespace tags. \
|
||||||
|
You can set \`throwIfNamespace: false\` to bypass this warning.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
JSXSpreadChild(path) {
|
||||||
|
throw path.buildCodeFrameError(
|
||||||
|
"Spread children are not supported in React.",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
Program: {
|
||||||
|
enter(path, state) {
|
||||||
|
const { file } = state;
|
||||||
|
let runtime = RUNTIME_DEFAULT;
|
||||||
|
|
||||||
|
let source = IMPORT_SOURCE_DEFAULT;
|
||||||
|
let pragma = PRAGMA_DEFAULT;
|
||||||
|
let pragmaFrag = PRAGMA_FRAG_DEFAULT;
|
||||||
|
|
||||||
|
let sourceSet = !!options.importSource;
|
||||||
|
let pragmaSet = !!options.pragma;
|
||||||
|
let pragmaFragSet = !!options.pragmaFrag;
|
||||||
|
|
||||||
|
if (file.ast.comments) {
|
||||||
|
for (const comment of (file.ast.comments: Array<Object>)) {
|
||||||
|
const sourceMatches = JSX_SOURCE_ANNOTATION_REGEX.exec(
|
||||||
|
comment.value,
|
||||||
|
);
|
||||||
|
if (sourceMatches) {
|
||||||
|
source = sourceMatches[1];
|
||||||
|
sourceSet = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const runtimeMatches = JSX_RUNTIME_ANNOTATION_REGEX.exec(
|
||||||
|
comment.value,
|
||||||
|
);
|
||||||
|
if (runtimeMatches) {
|
||||||
|
runtime = runtimeMatches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set(state, "runtime", runtime);
|
||||||
|
if (runtime === "classic") {
|
||||||
|
if (sourceSet) {
|
||||||
|
throw path.buildCodeFrameError(
|
||||||
|
`importSource cannot be set when runtime is classic.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const createElement = toMemberExpression(pragma);
|
||||||
|
const fragment = toMemberExpression(pragmaFrag);
|
||||||
|
|
||||||
|
set(state, "id/createElement", () => t.cloneNode(createElement));
|
||||||
|
set(state, "id/fragment", () => t.cloneNode(fragment));
|
||||||
|
|
||||||
|
set(state, "defaultPure", pragma === DEFAULT.pragma);
|
||||||
|
} else if (runtime === "automatic") {
|
||||||
|
if (pragmaSet || pragmaFragSet) {
|
||||||
|
throw path.buildCodeFrameError(
|
||||||
|
`pragma and pragmaFrag cannot be set when runtime is automatic.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const define = (name, id) =>
|
||||||
|
set(state, name, createImportLazily(state, path, id, source));
|
||||||
|
|
||||||
|
define("id/jsx", development ? "jsxDEV" : "jsx");
|
||||||
|
define("id/jsxs", development ? "jsxDEV" : "jsxs");
|
||||||
|
define("id/createElement", "createElement");
|
||||||
|
define("id/fragment", "Fragment");
|
||||||
|
|
||||||
|
set(state, "defaultPure", source === DEFAULT.importSource);
|
||||||
|
} else {
|
||||||
|
throw path.buildCodeFrameError(
|
||||||
|
`Runtime must be either "classic" or "automatic".`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (development) {
|
||||||
|
path.traverse(injectMetaPropertiesVisitor, state);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 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 (
|
||||||
|
// get(state, "runtime") === "classic" &&
|
||||||
|
// get(state, "pragmaSet") &&
|
||||||
|
// get(state, "usedFragment") &&
|
||||||
|
// !get(state, "pragmaFragSet")
|
||||||
|
// ) {
|
||||||
|
// throw new Error(
|
||||||
|
// "transform-react-jsx: pragma has been set but " +
|
||||||
|
// "pragmaFrag has not been set",
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
|
||||||
|
JSXElement: {
|
||||||
|
exit(path, file) {
|
||||||
|
let callExpr;
|
||||||
|
if (
|
||||||
|
get(file, "runtime") === "classic" ||
|
||||||
|
shouldUseCreateElement(path)
|
||||||
|
) {
|
||||||
|
callExpr = buildCreateElementCall(path, file);
|
||||||
|
} else {
|
||||||
|
callExpr = buildJSXElementCall(path, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
path.replaceWith(t.inherits(callExpr, path.node));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
JSXFragment: {
|
||||||
|
exit(path, file) {
|
||||||
|
let callExpr;
|
||||||
|
if (get(file, "runtime") === "classic") {
|
||||||
|
callExpr = buildCreateElementFragmentCall(path, file);
|
||||||
|
} else {
|
||||||
|
callExpr = buildJSXFragmentCall(path, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
path.replaceWith(t.inherits(callExpr, path.node));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
JSXAttribute(path) {
|
||||||
|
if (t.isJSXElement(path.node.value)) {
|
||||||
|
path.node.value = t.jsxExpressionContainer(path.node.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function call(pass, name, args) {
|
||||||
|
const node = t.callExpression(get(pass, `id/${name}`)(), args);
|
||||||
|
if (PURE_ANNOTATION ?? get(pass, "defaultPure")) annotateAsPure(node);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want to use React.createElement, even in the case of
|
||||||
|
// jsx, for <div {...props} key={key} /> to distinguish it
|
||||||
|
// from <div key={key} {...props} />. This is an intermediary
|
||||||
|
// step while we deprecate key spread from props. Afterwards,
|
||||||
|
// we will stop using createElement in the transform.
|
||||||
|
function shouldUseCreateElement(path) {
|
||||||
|
const openingPath = path.get("openingElement");
|
||||||
|
const attributes = openingPath.node.attributes;
|
||||||
|
|
||||||
|
let seenPropsSpread = false;
|
||||||
|
for (let i = 0; i < attributes.length; i++) {
|
||||||
|
const attr = attributes[i];
|
||||||
|
if (
|
||||||
|
seenPropsSpread &&
|
||||||
|
t.isJSXAttribute(attr) &&
|
||||||
|
attr.name.name === "key"
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
} else if (t.isJSXSpreadAttribute(attr)) {
|
||||||
|
seenPropsSpread = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertJSXIdentifier(node, parent) {
|
||||||
|
if (t.isJSXIdentifier(node)) {
|
||||||
|
if (node.name === "this" && t.isReferenced(node, parent)) {
|
||||||
|
return t.thisExpression();
|
||||||
|
} else if (t.isValidIdentifier(node.name, false)) {
|
||||||
|
node.type = "Identifier";
|
||||||
|
} else {
|
||||||
|
return t.stringLiteral(node.name);
|
||||||
|
}
|
||||||
|
} else if (t.isJSXMemberExpression(node)) {
|
||||||
|
return t.memberExpression(
|
||||||
|
convertJSXIdentifier(node.object, node),
|
||||||
|
convertJSXIdentifier(node.property, node),
|
||||||
|
);
|
||||||
|
} else if (t.isJSXNamespacedName(node)) {
|
||||||
|
/**
|
||||||
|
* If the flag "throwIfNamespace" is false
|
||||||
|
* print XMLNamespace like string literal
|
||||||
|
*/
|
||||||
|
return t.stringLiteral(`${node.namespace.name}:${node.name.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertAttributeValue(node) {
|
||||||
|
if (t.isJSXExpressionContainer(node)) {
|
||||||
|
return node.expression;
|
||||||
|
} else {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertAttribute(node) {
|
||||||
|
const value = convertAttributeValue(node.value || t.booleanLiteral(true));
|
||||||
|
|
||||||
|
if (t.isJSXSpreadAttribute(node)) {
|
||||||
|
return t.spreadElement(node.argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.isStringLiteral(value) && !t.isJSXExpressionContainer(node.value)) {
|
||||||
|
value.value = value.value.replace(/\n\s+/g, " ");
|
||||||
|
|
||||||
|
// "raw" JSXText should not be used from a StringLiteral because it needs to be escaped.
|
||||||
|
delete value.extra?.raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.isJSXNamespacedName(node.name)) {
|
||||||
|
node.name = t.stringLiteral(
|
||||||
|
node.name.namespace.name + ":" + node.name.name.name,
|
||||||
|
);
|
||||||
|
} else if (t.isValidIdentifier(node.name.name, false)) {
|
||||||
|
node.name.type = "Identifier";
|
||||||
|
} else {
|
||||||
|
node.name = t.stringLiteral(node.name.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.inherits(t.objectProperty(node.name, value), node);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildChildrenProperty(children) {
|
||||||
|
let childrenNode;
|
||||||
|
if (children.length === 1) {
|
||||||
|
childrenNode = children[0];
|
||||||
|
} else if (children.length > 1) {
|
||||||
|
childrenNode = t.arrayExpression(children);
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.objectProperty(t.identifier("children"), childrenNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builds JSX into:
|
||||||
|
// Production: React.jsx(type, arguments, key)
|
||||||
|
// Development: React.jsxDEV(type, arguments, key, isStaticChildren, source, self)
|
||||||
|
function buildJSXElementCall(path, file) {
|
||||||
|
const openingPath = path.get("openingElement");
|
||||||
|
const args = [getTag(openingPath)];
|
||||||
|
|
||||||
|
let attribs = [];
|
||||||
|
const extracted = Object.create(null);
|
||||||
|
|
||||||
|
// for React.jsx, key, __source (dev), and __self (dev) is passed in as
|
||||||
|
// a separate argument rather than in the args object. We go through the
|
||||||
|
// props and filter out these three keywords so we can pass them in
|
||||||
|
// as separate arguments later
|
||||||
|
for (const attr of openingPath.get("attributes")) {
|
||||||
|
if (attr.isJSXAttribute() && t.isJSXIdentifier(attr.node.name)) {
|
||||||
|
const { name } = attr.node.name;
|
||||||
|
switch (name) {
|
||||||
|
case "__source":
|
||||||
|
case "__self":
|
||||||
|
if (extracted[name]) throw sourceSelfError(path, name);
|
||||||
|
/* falls through */
|
||||||
|
case "key":
|
||||||
|
extracted[name] = convertAttributeValue(attr.node.value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
attribs.push(attr.node);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
attribs.push(attr.node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const children = t.react.buildChildren(path.node);
|
||||||
|
|
||||||
|
if (attribs.length || children.length) {
|
||||||
|
attribs = buildJSXOpeningElementAttributes(attribs, file, children);
|
||||||
|
} else {
|
||||||
|
// attributes should never be null
|
||||||
|
attribs = t.objectExpression([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push(attribs);
|
||||||
|
|
||||||
|
if (development) {
|
||||||
|
// isStaticChildren, __source, and __self are only used in development
|
||||||
|
// automatically include __source and __self in this plugin
|
||||||
|
// so we can eliminate the need for separate Babel plugins in Babel 8
|
||||||
|
args.push(
|
||||||
|
extracted.key ?? path.scope.buildUndefinedNode(),
|
||||||
|
t.booleanLiteral(children.length > 1),
|
||||||
|
extracted.__source ?? path.scope.buildUndefinedNode(),
|
||||||
|
extracted.__self ?? t.thisExpression(),
|
||||||
|
);
|
||||||
|
} else if (extracted.key !== undefined) {
|
||||||
|
args.push(extracted.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return call(file, children.length > 1 ? "jsxs" : "jsx", args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builds props for React.jsx. This function adds children into the props
|
||||||
|
// and ensures that props is always an object
|
||||||
|
function buildJSXOpeningElementAttributes(attribs, file, children) {
|
||||||
|
const props = attribs.map(convertAttribute);
|
||||||
|
|
||||||
|
// In React.jsx, children is no longer a separate argument, but passed in
|
||||||
|
// through the argument object
|
||||||
|
if (children?.length > 0) {
|
||||||
|
props.push(buildChildrenProperty(children));
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.objectExpression(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builds JSX Fragment <></> into
|
||||||
|
// Production: React.jsx(type, arguments)
|
||||||
|
// Development: React.jsxDEV(type, { children })
|
||||||
|
function buildJSXFragmentCall(path, file) {
|
||||||
|
const args = [get(file, "id/fragment")()];
|
||||||
|
|
||||||
|
const children = t.react.buildChildren(path.node);
|
||||||
|
|
||||||
|
args.push(
|
||||||
|
t.objectExpression(
|
||||||
|
children.length > 0 ? [buildChildrenProperty(children)] : [],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (development) {
|
||||||
|
args.push(
|
||||||
|
path.scope.buildUndefinedNode(),
|
||||||
|
t.booleanLiteral(children.length > 1),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return call(file, children.length > 1 ? "jsxs" : "jsx", args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builds JSX Fragment <></> into
|
||||||
|
// React.createElement(React.Fragment, null, ...children)
|
||||||
|
function buildCreateElementFragmentCall(path, file) {
|
||||||
|
if (filter && !filter(path.node, file)) return;
|
||||||
|
|
||||||
|
return call(file, "createElement", [
|
||||||
|
get(file, "id/fragment")(),
|
||||||
|
t.nullLiteral(),
|
||||||
|
...t.react.buildChildren(path.node),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builds JSX into:
|
||||||
|
// Production: React.createElement(type, arguments, children)
|
||||||
|
// Development: React.createElement(type, arguments, children, source, self)
|
||||||
|
function buildCreateElementCall(path, file) {
|
||||||
|
const openingPath = path.get("openingElement");
|
||||||
|
|
||||||
|
return call(file, "createElement", [
|
||||||
|
getTag(openingPath),
|
||||||
|
buildCreateElementOpeningElementAttributes(
|
||||||
|
file,
|
||||||
|
path,
|
||||||
|
openingPath.node.attributes,
|
||||||
|
),
|
||||||
|
...t.react.buildChildren(path.node),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTag(openingPath) {
|
||||||
|
const tagExpr = convertJSXIdentifier(
|
||||||
|
openingPath.node.name,
|
||||||
|
openingPath.node,
|
||||||
|
);
|
||||||
|
|
||||||
|
let tagName;
|
||||||
|
if (t.isIdentifier(tagExpr)) {
|
||||||
|
tagName = tagExpr.name;
|
||||||
|
} else if (t.isLiteral(tagExpr)) {
|
||||||
|
tagName = tagExpr.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.react.isCompatTag(tagName)) {
|
||||||
|
return t.stringLiteral(tagName);
|
||||||
|
} else {
|
||||||
|
return tagExpr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The logic for this is quite terse. It's because we need to
|
||||||
|
* support spread elements. We loop over all attributes,
|
||||||
|
* breaking on spreads, we then push a new object containing
|
||||||
|
* all prior attributes to an array for later processing.
|
||||||
|
*/
|
||||||
|
function buildCreateElementOpeningElementAttributes(file, path, attribs) {
|
||||||
|
// TODO (Babel 8): Only leave this branch of the code and remove the rest
|
||||||
|
if (
|
||||||
|
RUNTIME_DEFAULT === "automatic" ||
|
||||||
|
get(file, "runtime") === "automatic"
|
||||||
|
) {
|
||||||
|
const props = [];
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
props.push(convertAttribute(attr));
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.length > 0 ? t.objectExpression(props) : t.nullLiteral();
|
||||||
|
}
|
||||||
|
|
||||||
|
let props = [];
|
||||||
|
const objs = [];
|
||||||
|
|
||||||
|
for (const attr of attribs) {
|
||||||
|
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 getSource(source, importName) {
|
||||||
|
switch (importName) {
|
||||||
|
case "Fragment":
|
||||||
|
return `${source}/${development ? "jsx-dev-runtime" : "jsx-runtime"}`;
|
||||||
|
case "jsxDEV":
|
||||||
|
return `${source}/jsx-dev-runtime`;
|
||||||
|
case "jsx":
|
||||||
|
case "jsxs":
|
||||||
|
return `${source}/jsx-runtime`;
|
||||||
|
case "createElement":
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createImportLazily(pass, path, importName, source) {
|
||||||
|
return () => {
|
||||||
|
const actualSource = getSource(source, importName);
|
||||||
|
if (isModule(path)) {
|
||||||
|
let reference = get(pass, `imports/${importName}`);
|
||||||
|
if (reference) return t.cloneNode(reference);
|
||||||
|
|
||||||
|
reference = addNamed(path, importName, actualSource, {
|
||||||
|
importedInterop: "uncompiled",
|
||||||
|
});
|
||||||
|
set(pass, `imports/${importName}`, reference);
|
||||||
|
|
||||||
|
return reference;
|
||||||
|
} else {
|
||||||
|
let reference = get(pass, `requires/${actualSource}`);
|
||||||
|
if (reference) {
|
||||||
|
reference = t.cloneNode(reference);
|
||||||
|
} else {
|
||||||
|
reference = addNamespace(path, actualSource, {
|
||||||
|
importedInterop: "uncompiled",
|
||||||
|
});
|
||||||
|
set(pass, `requires/${actualSource}`, reference);
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.memberExpression(reference, t.identifier(importName));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toMemberExpression(id) {
|
||||||
|
return id
|
||||||
|
.split(".")
|
||||||
|
.map(name => t.identifier(name))
|
||||||
|
.reduce((object, property) => t.memberExpression(object, property));
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeSource(path, state) {
|
||||||
|
const location = path.node.loc;
|
||||||
|
if (!location) {
|
||||||
|
// the element was generated and doesn't have location information
|
||||||
|
return path.scope.buildUndefinedNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.fileNameIdentifier) {
|
||||||
|
const { filename = "" } = state;
|
||||||
|
|
||||||
|
const fileNameIdentifier = path.scope.generateUidIdentifier("_jsxFileName");
|
||||||
|
const scope = path.hub.getScope();
|
||||||
|
if (scope) {
|
||||||
|
scope.push({
|
||||||
|
id: fileNameIdentifier,
|
||||||
|
init: t.stringLiteral(filename),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
state.fileNameIdentifier = fileNameIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
return makeTrace(
|
||||||
|
t.cloneNode(state.fileNameIdentifier),
|
||||||
|
location.start.line,
|
||||||
|
location.start.column,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeTrace(fileNameIdentifier, lineNumber, column0Based) {
|
||||||
|
const fileLineLiteral =
|
||||||
|
lineNumber != null ? t.numericLiteral(lineNumber) : t.nullLiteral();
|
||||||
|
|
||||||
|
const fileColumnLiteral =
|
||||||
|
column0Based != null ? t.numericLiteral(column0Based + 1) : t.nullLiteral();
|
||||||
|
|
||||||
|
const fileNameProperty = t.objectProperty(
|
||||||
|
t.identifier("fileName"),
|
||||||
|
fileNameIdentifier,
|
||||||
|
);
|
||||||
|
const lineNumberProperty = t.objectProperty(
|
||||||
|
t.identifier("lineNumber"),
|
||||||
|
fileLineLiteral,
|
||||||
|
);
|
||||||
|
const columnNumberProperty = t.objectProperty(
|
||||||
|
t.identifier("columnNumber"),
|
||||||
|
fileColumnLiteral,
|
||||||
|
);
|
||||||
|
return t.objectExpression([
|
||||||
|
fileNameProperty,
|
||||||
|
lineNumberProperty,
|
||||||
|
columnNumberProperty,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sourceSelfError(path, name) {
|
||||||
|
const pluginName = `transform-react-jsx-${name.slice(2)}`;
|
||||||
|
|
||||||
|
return path.buildCodeFrameError(
|
||||||
|
`Duplicate ${name} prop found. You are most likely using the deprecated ${pluginName} Babel plugin. Both __source and __self are automatically set when using the automatic runtime. Please remove transform-react-jsx-source and transform-react-jsx-self from your Babel config.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import createPlugin from "./create-plugin.js";
|
||||||
|
|
||||||
|
export default createPlugin({
|
||||||
|
name: "transform-react-jsx/development",
|
||||||
|
development: true,
|
||||||
|
});
|
||||||
@ -1,61 +1,8 @@
|
|||||||
import jsx from "@babel/plugin-syntax-jsx";
|
/* eslint-disable @babel/development/plugin-name */
|
||||||
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) => {
|
import createPlugin from "./create-plugin.js";
|
||||||
const { runtime = "classic" } = options;
|
|
||||||
const PURE_ANNOTATION = options.pure;
|
|
||||||
|
|
||||||
const visitor = helper(api, {
|
export default createPlugin({
|
||||||
pre(state) {
|
name: "transform-react-jsx",
|
||||||
const tagName = state.tagName;
|
development: false,
|
||||||
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 {
|
|
||||||
const getter = get => ({ enumerable: true, configurable: true, get });
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
// These should actually be functions.
|
|
||||||
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 =
|
|
||||||
PURE_ANNOTATION ??
|
|
||||||
!pass.get("@babel/plugin-react-jsx/importSourceSet");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
...options,
|
|
||||||
development: false,
|
|
||||||
runtime,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: "transform-react-jsx",
|
|
||||||
inherits: jsx,
|
|
||||||
visitor,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -54,15 +54,14 @@ module.exports = function () {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const matches = importee.match(/^@babel\/([^/]+)$/);
|
const matches = importee.match(
|
||||||
|
/^@babel\/(?<pkg>[^/]+)(?:\/lib\/(?<internal>.*?))?$/
|
||||||
|
);
|
||||||
if (!matches) return null;
|
if (!matches) return null;
|
||||||
|
const { pkg, internal } = matches.groups;
|
||||||
|
|
||||||
// resolve babel package names to their src index file
|
// resolve babel package names to their src index file
|
||||||
const packageFolder = path.join(
|
const packageFolder = path.join(dirname, "packages", `babel-${pkg}`);
|
||||||
dirname,
|
|
||||||
"packages",
|
|
||||||
`babel-${matches[1]}`
|
|
||||||
);
|
|
||||||
|
|
||||||
let packageJsonSource;
|
let packageJsonSource;
|
||||||
try {
|
try {
|
||||||
@ -76,10 +75,11 @@ module.exports = function () {
|
|||||||
|
|
||||||
const packageJson = JSON.parse(packageJsonSource);
|
const packageJson = JSON.parse(packageJsonSource);
|
||||||
|
|
||||||
const filename =
|
const filename = internal
|
||||||
typeof packageJson["browser"] === "string"
|
? `src/${internal}`
|
||||||
? packageJson["browser"]
|
: typeof packageJson["browser"] === "string"
|
||||||
: packageJson["main"];
|
? packageJson["browser"]
|
||||||
|
: packageJson["main"];
|
||||||
|
|
||||||
const asJS = path.normalize(
|
const asJS = path.normalize(
|
||||||
path.join(
|
path.join(
|
||||||
|
|||||||
21
yarn.lock
21
yarn.lock
@ -301,16 +301,6 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@babel/helper-builder-react-jsx-experimental@workspace:^7.12.11, @babel/helper-builder-react-jsx-experimental@workspace:packages/babel-helper-builder-react-jsx-experimental":
|
|
||||||
version: 0.0.0-use.local
|
|
||||||
resolution: "@babel/helper-builder-react-jsx-experimental@workspace:packages/babel-helper-builder-react-jsx-experimental"
|
|
||||||
dependencies:
|
|
||||||
"@babel/helper-annotate-as-pure": "workspace:^7.12.10"
|
|
||||||
"@babel/helper-module-imports": "workspace:^7.12.5"
|
|
||||||
"@babel/types": "workspace:^7.12.11"
|
|
||||||
languageName: unknown
|
|
||||||
linkType: soft
|
|
||||||
|
|
||||||
"@babel/helper-builder-react-jsx@workspace:^7.10.4, @babel/helper-builder-react-jsx@workspace:packages/babel-helper-builder-react-jsx":
|
"@babel/helper-builder-react-jsx@workspace:^7.10.4, @babel/helper-builder-react-jsx@workspace:packages/babel-helper-builder-react-jsx":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@babel/helper-builder-react-jsx@workspace:packages/babel-helper-builder-react-jsx"
|
resolution: "@babel/helper-builder-react-jsx@workspace:packages/babel-helper-builder-react-jsx"
|
||||||
@ -2546,10 +2536,8 @@ __metadata:
|
|||||||
resolution: "@babel/plugin-transform-react-jsx-development@workspace:packages/babel-plugin-transform-react-jsx-development"
|
resolution: "@babel/plugin-transform-react-jsx-development@workspace:packages/babel-plugin-transform-react-jsx-development"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core": "workspace:*"
|
"@babel/core": "workspace:*"
|
||||||
"@babel/helper-builder-react-jsx-experimental": "workspace:^7.12.11"
|
|
||||||
"@babel/helper-plugin-test-runner": "workspace:*"
|
"@babel/helper-plugin-test-runner": "workspace:*"
|
||||||
"@babel/helper-plugin-utils": "workspace:^7.10.4"
|
"@babel/plugin-transform-react-jsx": "workspace:^7.12.11"
|
||||||
"@babel/plugin-syntax-jsx": "workspace:^7.12.1"
|
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@babel/core": ^7.0.0-0
|
"@babel/core": ^7.0.0-0
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
@ -2581,16 +2569,17 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@babel/plugin-transform-react-jsx@workspace:*, @babel/plugin-transform-react-jsx@workspace:^7.12.10, @babel/plugin-transform-react-jsx@workspace:packages/babel-plugin-transform-react-jsx":
|
"@babel/plugin-transform-react-jsx@workspace:*, @babel/plugin-transform-react-jsx@workspace:^7.12.10, @babel/plugin-transform-react-jsx@workspace:^7.12.11, @babel/plugin-transform-react-jsx@workspace:packages/babel-plugin-transform-react-jsx":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@babel/plugin-transform-react-jsx@workspace:packages/babel-plugin-transform-react-jsx"
|
resolution: "@babel/plugin-transform-react-jsx@workspace:packages/babel-plugin-transform-react-jsx"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core": "workspace:*"
|
"@babel/core": "workspace:*"
|
||||||
"@babel/helper-builder-react-jsx": "workspace:^7.10.4"
|
"@babel/helper-annotate-as-pure": "workspace:^7.12.10"
|
||||||
"@babel/helper-builder-react-jsx-experimental": "workspace:^7.12.11"
|
"@babel/helper-module-imports": "workspace:^7.12.5"
|
||||||
"@babel/helper-plugin-test-runner": "workspace:*"
|
"@babel/helper-plugin-test-runner": "workspace:*"
|
||||||
"@babel/helper-plugin-utils": "workspace:^7.10.4"
|
"@babel/helper-plugin-utils": "workspace:^7.10.4"
|
||||||
"@babel/plugin-syntax-jsx": "workspace:^7.12.1"
|
"@babel/plugin-syntax-jsx": "workspace:^7.12.1"
|
||||||
|
"@babel/types": "workspace:^7.12.11"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@babel/core": ^7.0.0-0
|
"@babel/core": ^7.0.0-0
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user