Implement @babel/plugin-transform-react-pure-annotations (#11428)

The new plugin is also enabled in `@babel/preset-react`
This commit is contained in:
Devon Govett 2020-05-24 13:55:29 -07:00 committed by GitHub
parent 93a50056ca
commit 6ba1f0dd22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 221 additions and 1 deletions

View File

@ -0,0 +1,3 @@
src
test
*.log

View File

@ -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"
}
}

View File

@ -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;
}

View File

@ -0,0 +1,4 @@
import * as React from 'react';
import ReactDOM from 'react-dom';
const Portal = ReactDOM.createPortal(React.createElement('div'), document.getElementById('test'));

View File

@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["transform-react-pure-annotations"]
}

View File

@ -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'));

View File

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

View File

@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["transform-react-pure-annotations"]
}

View File

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

View File

@ -0,0 +1,3 @@
import React from 'react';
React.createElement('div');

View File

@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["transform-react-pure-annotations"]
}

View File

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

View File

@ -0,0 +1,3 @@
import {createFactory} from 'react';
const div = createFactory('div');

View File

@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["transform-react-pure-annotations"]
}

View File

@ -0,0 +1,2 @@
import { createFactory } from 'react';
const div = /*#__PURE__*/createFactory('div');

View File

@ -0,0 +1,3 @@
import React from 'react';
React.createRef();

View File

@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["transform-react-pure-annotations"]
}

View File

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

View File

@ -0,0 +1,3 @@
import {forwardRef} from 'react';
const Comp = forwardRef((props, ref) => null);

View File

@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["transform-react-pure-annotations"]
}

View File

@ -0,0 +1,2 @@
import { forwardRef } from 'react';
const Comp = /*#__PURE__*/forwardRef((props, ref) => null);

View File

@ -0,0 +1,3 @@
import React from 'react';
const isElement = React.isValidElement(React.createElement('div'));

View File

@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["transform-react-pure-annotations"]
}

View File

@ -0,0 +1,2 @@
import React from 'react';
const isElement = /*#__PURE__*/React.isValidElement( /*#__PURE__*/React.createElement('div'));

View File

@ -0,0 +1,3 @@
import React from 'react';
const SomeComponent = React.lazy(() => import('./SomeComponent'));

View File

@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["transform-react-pure-annotations"]
}

View File

@ -0,0 +1,2 @@
import React from 'react';
const SomeComponent = /*#__PURE__*/React.lazy(() => import('./SomeComponent'));

View File

@ -0,0 +1,3 @@
import React from 'react';
const Comp = React.memo((props) => null);

View File

@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["transform-react-pure-annotations"]
}

View File

@ -0,0 +1,2 @@
import React from 'react';
const Comp = /*#__PURE__*/React.memo(props => null);

View File

@ -0,0 +1,3 @@
import runner from "@babel/helper-plugin-test-runner";
runner(__dirname);

View File

@ -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"

View File

@ -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);
@ -55,6 +56,7 @@ export default declare((api, opts) => {
},
],
transformReactDisplayName,
pure !== false && transformReactPure,
development && runtime === "classic" && transformReactJSXSource,
development && runtime === "classic" && transformReactJSXSelf,

View File

@ -0,0 +1,3 @@
import {forwardRef} from 'react';
const Comp = forwardRef((props, ref) => null);

View File

@ -0,0 +1,4 @@
{
"sourceType": "module",
"presets": [["react", {"pure": false}]]
}

View File

@ -0,0 +1,2 @@
import { forwardRef } from 'react';
const Comp = forwardRef((props, ref) => null);

View File

@ -0,0 +1,3 @@
import {forwardRef} from 'react';
const Comp = forwardRef((props, ref) => null);

View File

@ -0,0 +1,4 @@
{
"sourceType": "module",
"presets": ["react"]
}

View File

@ -0,0 +1,2 @@
import { forwardRef } from 'react';
const Comp = /*#__PURE__*/forwardRef((props, ref) => null);