Skip to content

Commit

Permalink
Mark transpiled JSX elements as pure (#11126)
Browse files Browse the repository at this point in the history
* Mark transpiled JSX elements as pure

* Avoid duble annotation

* Add "pure" option to the React preset

* Fix generator indentation

* Update tests

* Add tests for the "pure" option

* Update windows fixtures
  • Loading branch information
nicolo-ribaudo committed Mar 19, 2020
1 parent fa7ec81 commit f3912ac
Show file tree
Hide file tree
Showing 246 changed files with 613 additions and 314 deletions.
2 changes: 2 additions & 0 deletions packages/babel-generator/src/printer.js
Expand Up @@ -262,6 +262,8 @@ export default class Printer {
if (i + 1 === str.length) return;
const chaPost = str[i + 1];
if (chaPost !== "/" && chaPost !== "*") return;
// We don't print newlines aroung /*#__PURE__*/ annotations
if (PURE_ANNOTATION_RE.test(str.slice(i + 2, str.length - 2))) return;
}
this.token("(");
this.indent();
Expand Down
Expand Up @@ -9,6 +9,7 @@
},
"main": "lib/index.js",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.8.3",
"@babel/helper-module-imports": "^7.8.3",
"@babel/types": "^7.8.3",
"esutils": "^2.0.0"
Expand Down
64 changes: 50 additions & 14 deletions packages/babel-helper-builder-react-jsx-experimental/src/index.js
@@ -1,6 +1,14 @@
import esutils from "esutils";
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) {
const FILE_NAME_VAR = "_jsxFileName";
Expand All @@ -17,10 +25,10 @@ export function helper(babel, options) {
const IMPORT_NAME_SIZE = options.development ? 3 : 4;

const {
importSource: IMPORT_SOURCE_DEFAULT = "react",
runtime: RUNTIME_DEFAULT = "automatic",
pragma: PRAGMA_DEFAULT = "React.createElement",
pragmaFrag: PRAGMA_FRAG_DEFAULT = "React.Fragment",
importSource: IMPORT_SOURCE_DEFAULT = DEFAULT.importSource,
runtime: RUNTIME_DEFAULT = DEFAULT.runtime,
pragma: PRAGMA_DEFAULT = DEFAULT.pragma,
pragmaFrag: PRAGMA_FRAG_DEFAULT = DEFAULT.pragmaFrag,
} = options;

return {
Expand Down Expand Up @@ -142,8 +150,14 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
createIdentifierParser(pragmaFrag),
);
state.set("@babel/plugin-react-jsx/usedFragment", false);
state.set("@babel/plugin-react-jsx/pragmaSet", pragmaSet);
state.set("@babel/plugin-react-jsx/pragmaFragSet", pragmaFragSet);
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(
Expand Down Expand Up @@ -190,6 +204,11 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
createIdentifierName(path, "Fragment", importName),
),
);

state.set(
"@babel/plugin-react-jsx/importSourceSet",
source !== DEFAULT.importSource,
);
} else {
throw path.buildCodeFrameError(
`Runtime must be either "classic" or "automatic".`,
Expand Down Expand Up @@ -505,6 +524,7 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
tagExpr: tagExpr,
tagName: tagName,
args: args,
pure: false,
};

if (options.pre) {
Expand Down Expand Up @@ -577,13 +597,15 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
options.post(state, file);
}

return (
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
Expand Down Expand Up @@ -633,6 +655,7 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
tagExpr: tagExpr,
tagName: tagName,
args: args,
pure: false,
};

if (options.pre) {
Expand Down Expand Up @@ -667,13 +690,15 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
options.post(state, file);
}

return (
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) {
Expand All @@ -692,6 +717,7 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
tagExpr: tagExpr,
tagName: tagName,
args: args,
pure: false,
};

if (options.pre) {
Expand All @@ -706,7 +732,12 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
}

file.set("@babel/plugin-react-jsx/usedFragment", true);
return state.call || t.callExpression(state.createElementCallee, args);

const call =
state.call || t.callExpression(state.createElementCallee, args);
if (state.pure) annotateAsPure(call);

return call;
}

// Builds JSX into:
Expand All @@ -733,6 +764,7 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
tagExpr: tagExpr,
tagName: tagName,
args: args,
pure: false,
};

if (options.pre) {
Expand All @@ -751,7 +783,11 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
options.post(state, file);
}

return state.call || t.callExpression(state.createElementCallee, args);
const call =
state.call || t.callExpression(state.createElementCallee, args);
if (state.pure) annotateAsPure(call);

return call;
}

/**
Expand Down
1 change: 1 addition & 0 deletions packages/babel-helper-builder-react-jsx/package.json
Expand Up @@ -9,6 +9,7 @@
},
"main": "lib/index.js",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.8.3",
"@babel/types": "^7.8.3",
"esutils": "^2.0.0"
}
Expand Down
15 changes: 13 additions & 2 deletions packages/babel-helper-builder-react-jsx/src/index.js
@@ -1,11 +1,13 @@
import esutils from "esutils";
import * as t from "@babel/types";
import annotateAsPure from "@babel/helper-annotate-as-pure";

type ElementState = {
tagExpr: Object, // tag node
tagName: ?string, // raw string tag name
args: Array<Object>, // array of call arguments
call?: Object, // optional call property that can be set to override the call expression returned
pure: boolean, // true if the element can be marked with a #__PURE__ annotation
};

export default function(opts) {
Expand Down Expand Up @@ -134,6 +136,7 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
tagExpr: tagExpr,
tagName: tagName,
args: args,
pure: false,
};

if (opts.pre) {
Expand All @@ -153,7 +156,10 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
opts.post(state, file);
}

return state.call || t.callExpression(state.callee, args);
const call = state.call || t.callExpression(state.callee, args);
if (state.pure) annotateAsPure(call);

return call;
}

function pushProps(_props, objs) {
Expand Down Expand Up @@ -248,6 +254,7 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
tagExpr: tagExpr,
tagName: tagName,
args: args,
pure: false,
};

if (opts.pre) {
Expand All @@ -262,6 +269,10 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
}

file.set("usedFragment", true);
return state.call || t.callExpression(state.callee, args);

const call = state.call || t.callExpression(state.callee, args);
if (state.pure) annotateAsPure(call);

return call;
}
}
Expand Up @@ -2,5 +2,5 @@
var arr = [];

for (var i = 0; i < 4; ++i) {
arr.push(React.createElement("i", null));
arr.push( /*#__PURE__*/React.createElement("i", null));
}
Expand Up @@ -24,9 +24,9 @@ var RandomComponent = /*#__PURE__*/function (_Component) {
babelHelpers.createClass(RandomComponent, [{
key: "render",
value: function render() {
return _react["default"].createElement("div", {
return /*#__PURE__*/_react["default"].createElement("div", {
className: "sui-RandomComponent"
}, _react["default"].createElement("h2", null, "Hi there!"));
}, /*#__PURE__*/_react["default"].createElement("h2", null, "Hi there!"));
}
}]);
return RandomComponent;
Expand Down
Expand Up @@ -6,16 +6,15 @@ Object.defineProperty(exports, "__esModule", {
exports["default"] = _default;

function _default() {
return (/*#__PURE__*/function () {
function Select() {
babelHelpers.classCallCheck(this, Select);
}
return /*#__PURE__*/function () {
function Select() {
babelHelpers.classCallCheck(this, Select);
}

babelHelpers.createClass(Select, [{
key: "query",
value: function query(_query) {}
}]);
return Select;
}()
);
babelHelpers.createClass(Select, [{
key: "query",
value: function query(_query) {}
}]);
return Select;
}();
}
Expand Up @@ -7,7 +7,7 @@ exports["default"] = void 0;

var _default = function _default(_ref) {
var _onClick = _ref.onClick;
return React.createElement("div", {
return /*#__PURE__*/React.createElement("div", {
onClick: function onClick() {
return _onClick();
}
Expand Down
Expand Up @@ -12,7 +12,6 @@
"babel-plugin"
],
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.8.3",
"@babel/helper-plugin-utils": "^7.8.3"
},
"peerDependencies": {
Expand Down
@@ -1,6 +1,5 @@
import { declare } from "@babel/helper-plugin-utils";
import { types as t } from "@babel/core";
import annotateAsPure from "@babel/helper-annotate-as-pure";

export default declare((api, options) => {
api.assertVersion(7);
Expand Down Expand Up @@ -107,13 +106,7 @@ export default declare((api, options) => {
// Traverse all props passed to this element for immutability.
path.traverse(immutabilityVisitor, state);

if (state.isImmutable) {
const hoisted = path.hoist();

if (hoisted) {
annotateAsPure(hoisted);
}
}
if (state.isImmutable) path.hoist();
},
},
};
Expand Down
@@ -1,10 +1,10 @@
var _ref = /*#__PURE__*/<div>child</div>;
var _ref = <div>child</div>;

const AppItem = () => {
return _ref;
};

var _ref2 = /*#__PURE__*/<div>
var _ref2 = <div>
<p>Parent</p>
<AppItem />
</div>;
Expand Down
@@ -1,6 +1,6 @@
var _ref2 = /*#__PURE__*/<div>child</div>;
var _ref2 = <div>child</div>;

var _ref3 = /*#__PURE__*/<p>Parent</p>;
var _ref3 = <p>Parent</p>;

(function () {
class App extends React.Component {
Expand All @@ -13,7 +13,7 @@ var _ref3 = /*#__PURE__*/<p>Parent</p>;
const AppItem = () => {
return _ref2;
},
_ref = /*#__PURE__*/<div>
_ref = <div>
{_ref3}
<AppItem />
</div>;
Expand Down
@@ -1,13 +1,13 @@
var _ref = /*#__PURE__*/<div>child</div>;
var _ref = <div>child</div>;

var _ref3 = /*#__PURE__*/<p>Parent</p>;
var _ref3 = <p>Parent</p>;

(function () {
const AppItem = () => {
return _ref;
};

var _ref2 = /*#__PURE__*/<div>
var _ref2 = <div>
{_ref3}
<AppItem />
</div>;
Expand Down
Expand Up @@ -5,12 +5,12 @@ export default class App extends React.Component {

}

var _ref2 = /*#__PURE__*/<div>child</div>;
var _ref2 = <div>child</div>;

const AppItem = () => {
return _ref2;
},
_ref = /*#__PURE__*/<div>
_ref = <div>
<p>Parent</p>
<AppItem />
</div>;
@@ -1,4 +1,4 @@
var _ref = /*#__PURE__*/<span />;
var _ref = <span />;

var Foo = React.createClass({
render: function () {
Expand Down
@@ -1,6 +1,6 @@
import React from 'react'; // Regression test for https://github.com/babel/babel/issues/5552

var _ref = /*#__PURE__*/<div />;
var _ref = <div />;

class BugReport extends React.Component {
constructor(...args) {
Expand Down

0 comments on commit f3912ac

Please sign in to comment.