Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement plugin-transform-react-pure-annotations and add to preset-react #11428

Merged
merged 2 commits into from May 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,3 @@
src
test
*.log
@@ -0,0 +1,25 @@
{
"name": "@babel/plugin-transform-react-pure-annotations",
"version": "7.9.4",
"description": "Mark top-level React method calls as pure for tree shaking",
"repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-react-pure",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"main": "lib/index.js",
"keywords": [
"babel-plugin"
],
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.8.3",
"@babel/helper-plugin-utils": "^7.8.3"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
},
"devDependencies": {
"@babel/core": "^7.9.0",
"@babel/helper-plugin-test-runner": "^7.8.3"
}
}
@@ -0,0 +1,79 @@
import { declare } from "@babel/helper-plugin-utils";
import annotateAsPure from "@babel/helper-annotate-as-pure";
import { types as t } from "@babel/core";

// Mapping of React top-level methods that are pure.
// This plugin adds a /*#__PURE__#/ annotation to calls to these methods,
// so that terser and other minifiers can safely remove them during dead
// code elimination.
// See https://reactjs.org/docs/react-api.html
const PURE_CALLS = new Map([
[
"react",
[
"cloneElement",
"createElement",
"createFactory",
"createRef",
"forwardRef",
"isValidElement",
"memo",
"lazy",
],
],
["react-dom", ["createPortal"]],
]);

export default declare(api => {
api.assertVersion(7);

return {
name: "transform-react-pure-annotations",
visitor: {
CallExpression(path) {
if (isReactCall(path)) {
annotateAsPure(path);
}
},
},
};
});

function isReactCall(path) {
// If the callee is not a member expression, then check if it matches
// a named import, e.g. `import {forwardRef} from 'react'`.
if (!t.isMemberExpression(path.node.callee)) {
const callee = path.get("callee");
for (const [module, methods] of PURE_CALLS) {
for (const method of methods) {
if (callee.referencesImport(module, method)) {
return true;
}
}
}

return false;
}

// Otherwise, check if the member expression's object matches
// a default import (`import React from 'react'`) or namespace
// import (`import * as React from 'react'), and check if the
// property matches one of the pure methods.
for (const [module, methods] of PURE_CALLS) {
const object = path.get("callee.object");
if (
object.referencesImport(module, "default") ||
object.referencesImport(module, "*")
) {
for (const method of methods) {
if (t.isIdentifier(path.node.callee.property, { name: method })) {
return true;
}
}

return false;
}
}

return false;
}
@@ -0,0 +1,4 @@
import * as React from 'react';
import ReactDOM from 'react-dom';

const Portal = ReactDOM.createPortal(React.createElement('div'), document.getElementById('test'));
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["transform-react-pure-annotations"]
}
@@ -0,0 +1,3 @@
import * as React from 'react';
import ReactDOM from 'react-dom';
const Portal = /*#__PURE__*/ReactDOM.createPortal( /*#__PURE__*/React.createElement('div'), document.getElementById('test'));
@@ -0,0 +1,3 @@
import React from 'react';

React.cloneElement(React.createElement('div'));
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["transform-react-pure-annotations"]
}
@@ -0,0 +1,4 @@
import React from 'react';

/*#__PURE__*/
React.cloneElement( /*#__PURE__*/React.createElement('div'));
@@ -0,0 +1,3 @@
import React from 'react';

React.createElement('div');
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["transform-react-pure-annotations"]
}
@@ -0,0 +1,4 @@
import React from 'react';

/*#__PURE__*/
React.createElement('div');
@@ -0,0 +1,3 @@
import {createFactory} from 'react';

const div = createFactory('div');
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["transform-react-pure-annotations"]
}
@@ -0,0 +1,2 @@
import { createFactory } from 'react';
const div = /*#__PURE__*/createFactory('div');
@@ -0,0 +1,3 @@
import React from 'react';

React.createRef();
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["transform-react-pure-annotations"]
}
@@ -0,0 +1,4 @@
import React from 'react';

/*#__PURE__*/
React.createRef();
@@ -0,0 +1,3 @@
import {forwardRef} from 'react';

const Comp = forwardRef((props, ref) => null);
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["transform-react-pure-annotations"]
}
@@ -0,0 +1,2 @@
import { forwardRef } from 'react';
const Comp = /*#__PURE__*/forwardRef((props, ref) => null);
@@ -0,0 +1,3 @@
import React from 'react';

const isElement = React.isValidElement(React.createElement('div'));
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["transform-react-pure-annotations"]
}
@@ -0,0 +1,2 @@
import React from 'react';
const isElement = /*#__PURE__*/React.isValidElement( /*#__PURE__*/React.createElement('div'));
@@ -0,0 +1,3 @@
import React from 'react';

const SomeComponent = React.lazy(() => import('./SomeComponent'));
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["transform-react-pure-annotations"]
}
@@ -0,0 +1,2 @@
import React from 'react';
const SomeComponent = /*#__PURE__*/React.lazy(() => import('./SomeComponent'));
@@ -0,0 +1,3 @@
import React from 'react';

const Comp = React.memo((props) => null);
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["transform-react-pure-annotations"]
}
@@ -0,0 +1,2 @@
import React from 'react';
const Comp = /*#__PURE__*/React.memo(props => null);
@@ -0,0 +1,3 @@
import runner from "@babel/helper-plugin-test-runner";

runner(__dirname);
3 changes: 2 additions & 1 deletion packages/babel-preset-react/package.json
Expand Up @@ -16,7 +16,8 @@
"@babel/plugin-transform-react-jsx": "^7.9.4",
"@babel/plugin-transform-react-jsx-development": "^7.9.0",
"@babel/plugin-transform-react-jsx-self": "^7.9.0",
"@babel/plugin-transform-react-jsx-source": "^7.9.0"
"@babel/plugin-transform-react-jsx-source": "^7.9.0",
"@babel/plugin-transform-react-pure-annotations": "^7.9.4"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
Expand Down
2 changes: 2 additions & 0 deletions packages/babel-preset-react/src/index.js
Expand Up @@ -4,6 +4,7 @@ import transformReactJSXDevelopment from "@babel/plugin-transform-react-jsx-deve
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) => {
api.assertVersion(7);
Expand Down Expand Up @@ -55,6 +56,7 @@ export default declare((api, opts) => {
},
],
transformReactDisplayName,
pure !== false && transformReactPure,

development && runtime === "classic" && transformReactJSXSource,
development && runtime === "classic" && transformReactJSXSelf,
Expand Down
@@ -0,0 +1,3 @@
import {forwardRef} from 'react';

const Comp = forwardRef((props, ref) => null);
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"presets": [["react", {"pure": false}]]
}
@@ -0,0 +1,2 @@
import { forwardRef } from 'react';
const Comp = forwardRef((props, ref) => null);
@@ -0,0 +1,3 @@
import {forwardRef} from 'react';

const Comp = forwardRef((props, ref) => null);
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"presets": ["react"]
}
@@ -0,0 +1,2 @@
import { forwardRef } from 'react';
const Comp = /*#__PURE__*/forwardRef((props, ref) => null);