UNPKG

4.79 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.default = void 0;
7
8var _helperPluginUtils = require("@babel/helper-plugin-utils");
9
10var _core = require("@babel/core");
11
12var _default = (0, _helperPluginUtils.declare)((api, options) => {
13 api.assertVersion(7);
14 const {
15 allowMutablePropsOnTags
16 } = options;
17
18 if (allowMutablePropsOnTags != null && !Array.isArray(allowMutablePropsOnTags)) {
19 throw new Error(".allowMutablePropsOnTags must be an array, null, or undefined.");
20 }
21
22 const HOISTED = new WeakMap();
23
24 function declares(node, scope) {
25 if (_core.types.isJSXIdentifier(node, {
26 name: "this"
27 }) || _core.types.isJSXIdentifier(node, {
28 name: "arguments"
29 }) || _core.types.isJSXIdentifier(node, {
30 name: "super"
31 }) || _core.types.isJSXIdentifier(node, {
32 name: "new"
33 })) {
34 const {
35 path
36 } = scope;
37 return path.isFunctionParent() && !path.isArrowFunctionExpression();
38 }
39
40 return scope.hasOwnBinding(node.name);
41 }
42
43 function isHoistingScope({
44 path
45 }) {
46 return path.isFunctionParent() || path.isLoop() || path.isProgram();
47 }
48
49 function getHoistingScope(scope) {
50 while (!isHoistingScope(scope)) scope = scope.parent;
51
52 return scope;
53 }
54
55 const analyzer = {
56 enter(path, state) {
57 const stop = () => {
58 state.isImmutable = false;
59 path.stop();
60 };
61
62 if (path.isJSXClosingElement()) {
63 path.skip();
64 return;
65 }
66
67 if (path.isJSXIdentifier({
68 name: "ref"
69 }) && path.parentPath.isJSXAttribute({
70 name: path.node
71 })) {
72 return stop();
73 }
74
75 if (path.isJSXIdentifier() || path.isJSXMemberExpression() || path.isJSXNamespacedName()) {
76 return;
77 }
78
79 if (path.isIdentifier()) {
80 const binding = path.scope.getBinding(path.node.name);
81 if (binding && binding.constant) return;
82 }
83
84 if (!path.isImmutable()) {
85 if (path.isPure()) {
86 const expressionResult = path.evaluate();
87
88 if (expressionResult.confident) {
89 const {
90 value
91 } = expressionResult;
92 const isMutable = !state.mutablePropsAllowed && value && typeof value === "object" || typeof value === "function";
93
94 if (!isMutable) {
95 path.skip();
96 return;
97 }
98 } else if (_core.types.isIdentifier(expressionResult.deopt)) {
99 return;
100 }
101 }
102
103 stop();
104 }
105 },
106
107 ReferencedIdentifier(path, state) {
108 const {
109 node
110 } = path;
111 let {
112 scope
113 } = path;
114
115 while (scope) {
116 if (scope === state.targetScope) return;
117 if (declares(node, scope)) break;
118 scope = scope.parent;
119 }
120
121 state.targetScope = getHoistingScope(scope);
122 }
123
124 };
125 return {
126 name: "transform-react-constant-elements",
127 visitor: {
128 JSXElement(path) {
129 var _jsxScope;
130
131 if (HOISTED.has(path.node)) return;
132 HOISTED.set(path.node, path.scope);
133 const name = path.node.openingElement.name;
134 let mutablePropsAllowed = false;
135
136 if (allowMutablePropsOnTags != null) {
137 let lastSegment = name;
138
139 while (_core.types.isJSXMemberExpression(lastSegment)) {
140 lastSegment = lastSegment.property;
141 }
142
143 const elementName = lastSegment.name;
144 mutablePropsAllowed = allowMutablePropsOnTags.includes(elementName);
145 }
146
147 const state = {
148 isImmutable: true,
149 mutablePropsAllowed,
150 targetScope: path.scope.getProgramParent()
151 };
152 path.traverse(analyzer, state);
153 if (!state.isImmutable) return;
154 const {
155 targetScope
156 } = state;
157 HOISTED.set(path.node, targetScope);
158 let jsxScope;
159 let current = path;
160
161 while (!jsxScope && current.parentPath.isJSX()) {
162 current = current.parentPath;
163 jsxScope = HOISTED.get(current.node);
164 }
165
166 (_jsxScope = jsxScope) != null ? _jsxScope : jsxScope = getHoistingScope(path.scope);
167 if (targetScope === jsxScope) return;
168 const id = path.scope.generateUidBasedOnNode(name);
169 targetScope.push({
170 id: _core.types.identifier(id)
171 });
172 let replacement = _core.template.expression.ast`
173 ${_core.types.identifier(id)} || (${_core.types.identifier(id)} = ${path.node})
174 `;
175
176 if (path.parentPath.isJSXElement() || path.parentPath.isJSXAttribute()) {
177 replacement = _core.types.jsxExpressionContainer(replacement);
178 }
179
180 path.replaceWith(replacement);
181 }
182
183 }
184 };
185});
186
187exports.default = _default;
\No newline at end of file