From d0161544187ff7f4d55d27c7de6860ca3a24c8b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 25 Oct 2020 09:10:37 +0100 Subject: [PATCH] Stop using old JSX transform --- .../src/index.js | 92 +++++++++++---- .../fixtures/linux/auto-import-dev/output.mjs | 4 +- .../fixtures/linux/classic-runtime/output.js | 20 ++-- .../src/index.js | 71 ++++++++++-- .../src/transform-automatic.js | 52 --------- .../src/transform-classic.js | 106 ------------------ .../input.js | 5 - .../options.json | 3 - .../input.js | 3 - .../options.json | 3 - packages/babel-preset-react/package.json | 2 - packages/babel-preset-react/src/index.js | 16 +-- 12 files changed, 144 insertions(+), 233 deletions(-) delete mode 100644 packages/babel-plugin-transform-react-jsx/src/transform-automatic.js delete mode 100644 packages/babel-plugin-transform-react-jsx/src/transform-classic.js delete mode 100644 packages/babel-plugin-transform-react-jsx/test/fixtures/nextReactClassic/throw-if-pragma-set-but-not-pragmafrag-and-frag-used/input.js delete mode 100644 packages/babel-plugin-transform-react-jsx/test/fixtures/nextReactClassic/throw-if-pragma-set-but-not-pragmafrag-and-frag-used/options.json delete mode 100644 packages/babel-plugin-transform-react-jsx/test/fixtures/react/throw-if-pragma-set-but-not-pragmafrag-and-frag-used/input.js delete mode 100644 packages/babel-plugin-transform-react-jsx/test/fixtures/react/throw-if-pragma-set-but-not-pragmafrag-and-frag-used/options.json diff --git a/packages/babel-helper-builder-react-jsx-experimental/src/index.js b/packages/babel-helper-builder-react-jsx-experimental/src/index.js index a2f47e1f4609..ec198eb0fa5f 100644 --- a/packages/babel-helper-builder-react-jsx-experimental/src/index.js +++ b/packages/babel-helper-builder-react-jsx-experimental/src/index.js @@ -10,6 +10,29 @@ const DEFAULT = { }; export function helper(babel, options) { + const { useSpread = true, useBuiltIns = false } = options; + + if (typeof useSpread !== "boolean") { + throw new Error( + "transform-react-jsx currently only accepts a boolean option for " + + "useSpread (defaults to true)", + ); + } + + 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 +66,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]); }, }; @@ -245,20 +268,6 @@ 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", - ); - } - }, }, }; @@ -781,6 +790,7 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, } const attribs = buildCreateElementOpeningElementAttributes( + file, path, openingPath.node.attributes, ); @@ -804,8 +814,9 @@ 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) { - const props = []; + function buildCreateElementOpeningElementAttributes(file, path, attribs) { + let props = []; + const objs = []; const found = Object.create(null); for (const attr of attribs) { @@ -820,10 +831,45 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, if (!options.development) continue; } - props.push(convertAttribute(attr)); + 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(); } - return props.length > 0 ? t.objectExpression(props) : 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) { diff --git a/packages/babel-plugin-transform-react-jsx-development/test/fixtures/linux/auto-import-dev/output.mjs b/packages/babel-plugin-transform-react-jsx-development/test/fixtures/linux/auto-import-dev/output.mjs index dcdeec458630..823d0e7e88fb 100644 --- a/packages/babel-plugin-transform-react-jsx-development/test/fixtures/linux/auto-import-dev/output.mjs +++ b/packages/babel-plugin-transform-react-jsx-development/test/fixtures/linux/auto-import-dev/output.mjs @@ -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, diff --git a/packages/babel-plugin-transform-react-jsx-development/test/fixtures/linux/classic-runtime/output.js b/packages/babel-plugin-transform-react-jsx-development/test/fixtures/linux/classic-runtime/output.js index ae50081c209c..b73da88e5f72 100644 --- a/packages/babel-plugin-transform-react-jsx-development/test/fixtures/linux/classic-runtime/output.js +++ b/packages/babel-plugin-transform-react-jsx-development/test/fixtures/linux/classic-runtime/output.js @@ -1,42 +1,42 @@ var _jsxFileName = "/packages/babel-plugin-transform-react-jsx-development/test/fixtures/linux/classic-runtime/input.js"; 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, key: "4", + __self: this, __source: { fileName: _jsxFileName, lineNumber: 7, columnNumber: 9 - }, - __self: this + } }))); diff --git a/packages/babel-plugin-transform-react-jsx/src/index.js b/packages/babel-plugin-transform-react-jsx/src/index.js index 4e0c5b69b3bb..99bad1ebc33a 100644 --- a/packages/babel-plugin-transform-react-jsx/src/index.js +++ b/packages/babel-plugin-transform-react-jsx/src/index.js @@ -1,18 +1,67 @@ -/* 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); - } else { - return transformAutomatic(api, options); + if ( + options.useSpread !== undefined && + runtime === "classic" && + typeof options.useSpread !== "boolean" + ) { + throw new Error( + "transform-react-jsx currently only accepts a boolean option for " + + "useSpread (defaults to false)", + ); } + + 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, + runtime, + useSpread: + "useSpread" in options ? options.useSpread : runtime !== "classic", + }); + + return { + name: "transform-react-jsx", + inherits: jsx, + visitor, + }; }); diff --git a/packages/babel-plugin-transform-react-jsx/src/transform-automatic.js b/packages/babel-plugin-transform-react-jsx/src/transform-automatic.js deleted file mode 100644 index 543b9ef07133..000000000000 --- a/packages/babel-plugin-transform-react-jsx/src/transform-automatic.js +++ /dev/null @@ -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, - }; -}); diff --git a/packages/babel-plugin-transform-react-jsx/src/transform-classic.js b/packages/babel-plugin-transform-react-jsx/src/transform-classic.js deleted file mode 100644 index d74b16078fe0..000000000000 --- a/packages/babel-plugin-transform-react-jsx/src/transform-classic.js +++ /dev/null @@ -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)) { - 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, - }; -}); diff --git a/packages/babel-plugin-transform-react-jsx/test/fixtures/nextReactClassic/throw-if-pragma-set-but-not-pragmafrag-and-frag-used/input.js b/packages/babel-plugin-transform-react-jsx/test/fixtures/nextReactClassic/throw-if-pragma-set-but-not-pragmafrag-and-frag-used/input.js deleted file mode 100644 index c361fa6b7ab3..000000000000 --- a/packages/babel-plugin-transform-react-jsx/test/fixtures/nextReactClassic/throw-if-pragma-set-but-not-pragmafrag-and-frag-used/input.js +++ /dev/null @@ -1,5 +0,0 @@ -/** @jsx dom */ -/** @jsxRuntime classic */ - - -<> diff --git a/packages/babel-plugin-transform-react-jsx/test/fixtures/nextReactClassic/throw-if-pragma-set-but-not-pragmafrag-and-frag-used/options.json b/packages/babel-plugin-transform-react-jsx/test/fixtures/nextReactClassic/throw-if-pragma-set-but-not-pragmafrag-and-frag-used/options.json deleted file mode 100644 index 1bfb659b3c98..000000000000 --- a/packages/babel-plugin-transform-react-jsx/test/fixtures/nextReactClassic/throw-if-pragma-set-but-not-pragmafrag-and-frag-used/options.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "throws": "transform-react-jsx: pragma has been set but pragmaFrag has not been set" -} diff --git a/packages/babel-plugin-transform-react-jsx/test/fixtures/react/throw-if-pragma-set-but-not-pragmafrag-and-frag-used/input.js b/packages/babel-plugin-transform-react-jsx/test/fixtures/react/throw-if-pragma-set-but-not-pragmafrag-and-frag-used/input.js deleted file mode 100644 index e78832be43b5..000000000000 --- a/packages/babel-plugin-transform-react-jsx/test/fixtures/react/throw-if-pragma-set-but-not-pragmafrag-and-frag-used/input.js +++ /dev/null @@ -1,3 +0,0 @@ -/** @jsx dom */ - -<> diff --git a/packages/babel-plugin-transform-react-jsx/test/fixtures/react/throw-if-pragma-set-but-not-pragmafrag-and-frag-used/options.json b/packages/babel-plugin-transform-react-jsx/test/fixtures/react/throw-if-pragma-set-but-not-pragmafrag-and-frag-used/options.json deleted file mode 100644 index 1bfb659b3c98..000000000000 --- a/packages/babel-plugin-transform-react-jsx/test/fixtures/react/throw-if-pragma-set-but-not-pragmafrag-and-frag-used/options.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "throws": "transform-react-jsx: pragma has been set but pragmaFrag has not been set" -} diff --git a/packages/babel-preset-react/package.json b/packages/babel-preset-react/package.json index 7b8a02db07e6..76450c204acd 100644 --- a/packages/babel-preset-react/package.json +++ b/packages/babel-preset-react/package.json @@ -19,8 +19,6 @@ "@babel/plugin-transform-react-display-name": "workspace:^7.12.1", "@babel/plugin-transform-react-jsx": "workspace:^7.12.1", "@babel/plugin-transform-react-jsx-development": "workspace:^7.12.1", - "@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": { diff --git a/packages/babel-preset-react/src/index.js b/packages/babel-preset-react/src/index.js index c57cc52ff580..556cbb46c6e9 100644 --- a/packages/babel-preset-react/src/index.js +++ b/packages/babel-preset-react/src/index.js @@ -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; @@ -35,15 +32,10 @@ export default declare((api, opts) => { ); } - const transformReactJSXPlugin = - runtime === "automatic" && development - ? transformReactJSXDevelopment - : transformReactJSX; - return { plugins: [ [ - transformReactJSXPlugin, + development ? transformReactJSXDevelopment : transformReactJSX, { importSource, pragma, @@ -51,15 +43,13 @@ export default declare((api, opts) => { runtime, throwIfNamespace, useBuiltIns, - useSpread, + useSpread: + "useSpread" in opts ? opts.useSpread : runtime !== "classic", pure, }, ], transformReactDisplayName, pure !== false && transformReactPure, - - development && runtime === "classic" && transformReactJSXSource, - development && runtime === "classic" && transformReactJSXSelf, ].filter(Boolean), }; });