Skip to content

Commit

Permalink
Implement @babel/plugin-transform-react-pure-annotations (#11428)
Browse files Browse the repository at this point in the history
The new plugin is also enabled in `@babel/preset-react`
  • Loading branch information
devongovett committed May 24, 2020
1 parent 93a5005 commit 6ba1f0d
Show file tree
Hide file tree
Showing 39 changed files with 221 additions and 1 deletion.
@@ -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);

0 comments on commit 6ba1f0d

Please sign in to comment.