1 | "use strict";
|
2 |
|
3 | Object.defineProperty(exports, "__esModule", {
|
4 | value: true
|
5 | });
|
6 | exports.default = void 0;
|
7 |
|
8 | var _helperPluginUtils = require("@babel/helper-plugin-utils");
|
9 |
|
10 | var _core = require("@babel/core");
|
11 |
|
12 | var _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 |
|
187 | exports.default = _default; |
\ | No newline at end of file |