react-native-boost 0.6.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -3
- package/dist/plugin/esm/index.mjs +709 -199
- package/dist/plugin/esm/index.mjs.map +1 -1
- package/dist/plugin/index.d.ts +54 -0
- package/dist/plugin/index.js +709 -199
- package/dist/plugin/index.js.map +1 -1
- package/dist/runtime/esm/index.mjs +15 -3
- package/dist/runtime/esm/index.mjs.map +1 -1
- package/dist/runtime/esm/index.web.mjs.map +1 -1
- package/dist/runtime/index.d.ts +51 -4
- package/dist/runtime/index.js +16 -4
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/index.web.d.ts +13 -1
- package/dist/runtime/index.web.js.map +1 -1
- package/package.json +13 -14
- package/src/plugin/index.ts +27 -5
- package/src/plugin/optimizers/text/index.ts +116 -92
- package/src/plugin/optimizers/view/index.ts +53 -31
- package/src/plugin/types/index.ts +67 -17
- package/src/plugin/utils/common/attributes.ts +165 -0
- package/src/plugin/utils/common/index.ts +1 -3
- package/src/plugin/utils/common/validation.ts +513 -0
- package/src/plugin/utils/constants.ts +9 -0
- package/src/plugin/utils/format-test-result.ts +29 -0
- package/src/plugin/utils/generate-test-plugin.ts +9 -3
- package/src/plugin/utils/helpers.ts +15 -0
- package/src/plugin/utils/logger.ts +109 -2
- package/src/runtime/components/native-text.tsx +21 -5
- package/src/runtime/components/native-view.tsx +21 -5
- package/src/runtime/index.ts +20 -0
- package/src/runtime/types/index.ts +5 -0
- package/src/runtime/utils/constants.ts +6 -2
- package/src/plugin/utils/common/ancestors.ts +0 -120
- package/src/plugin/utils/common/node-types.ts +0 -22
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { declare } from '@babel/helper-plugin-utils';
|
|
2
2
|
import { types } from '@babel/core';
|
|
3
|
-
import { addDefault, addNamed } from '@babel/helper-module-imports';
|
|
4
3
|
import { minimatch } from 'minimatch';
|
|
5
4
|
import nodePath from 'node:path';
|
|
5
|
+
import { addDefault, addNamed } from '@babel/helper-module-imports';
|
|
6
6
|
|
|
7
7
|
class PluginError extends Error {
|
|
8
8
|
constructor(message) {
|
|
@@ -11,56 +11,18 @@ class PluginError extends Error {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
const RUNTIME_MODULE_NAME = "react-native-boost/runtime";
|
|
15
|
-
const ACCESSIBILITY_PROPERTIES = /* @__PURE__ */ new Set([
|
|
16
|
-
"accessibilityLabel",
|
|
17
|
-
"aria-label",
|
|
18
|
-
"accessibilityState",
|
|
19
|
-
"aria-busy",
|
|
20
|
-
"aria-checked",
|
|
21
|
-
"aria-disabled",
|
|
22
|
-
"aria-expanded",
|
|
23
|
-
"aria-selected",
|
|
24
|
-
"accessible"
|
|
25
|
-
]);
|
|
26
|
-
|
|
27
|
-
function addFileImportHint({
|
|
28
|
-
file,
|
|
29
|
-
nameHint,
|
|
30
|
-
path,
|
|
31
|
-
importName,
|
|
32
|
-
moduleName,
|
|
33
|
-
importType = "named"
|
|
34
|
-
}) {
|
|
35
|
-
var _a;
|
|
36
|
-
if (!((_a = file.__hasImports) == null ? void 0 : _a[nameHint])) {
|
|
37
|
-
file.__hasImports = file.__hasImports || {};
|
|
38
|
-
file.__hasImports[nameHint] = importType === "default" ? addDefault(path, moduleName, { nameHint }) : addNamed(path, importName, moduleName, { nameHint });
|
|
39
|
-
}
|
|
40
|
-
return file.__hasImports[nameHint];
|
|
41
|
-
}
|
|
42
|
-
const replaceWithNativeComponent = (path, parent, file, nativeComponentName) => {
|
|
43
|
-
const nativeIdentifier = addFileImportHint({
|
|
44
|
-
file,
|
|
45
|
-
nameHint: nativeComponentName,
|
|
46
|
-
path,
|
|
47
|
-
importName: nativeComponentName,
|
|
48
|
-
moduleName: RUNTIME_MODULE_NAME,
|
|
49
|
-
importType: "named"
|
|
50
|
-
});
|
|
51
|
-
const currentName = path.node.name.name;
|
|
52
|
-
const jsxName = path.node.name;
|
|
53
|
-
jsxName.name = nativeIdentifier.name;
|
|
54
|
-
if (!path.node.selfClosing && parent.closingElement && types.isJSXIdentifier(parent.closingElement.name) && parent.closingElement.name.name === currentName) {
|
|
55
|
-
parent.closingElement.name.name = nativeIdentifier.name;
|
|
56
|
-
}
|
|
57
|
-
return nativeIdentifier;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
14
|
const ensureArray = (value) => {
|
|
61
15
|
if (Array.isArray(value)) return value;
|
|
62
16
|
return [value];
|
|
63
17
|
};
|
|
18
|
+
const getFirstBailoutReason = (checks) => {
|
|
19
|
+
for (const check of checks) {
|
|
20
|
+
if (check.shouldBail()) {
|
|
21
|
+
return check.reason;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
};
|
|
64
26
|
|
|
65
27
|
const isIgnoredFile = (path, ignores) => {
|
|
66
28
|
const hub = path.hub;
|
|
@@ -157,6 +119,369 @@ const isReactNativeImport = (path, expectedImportedName) => {
|
|
|
157
119
|
}
|
|
158
120
|
return false;
|
|
159
121
|
};
|
|
122
|
+
const getViewAncestorClassification = (path) => {
|
|
123
|
+
return classifyViewAncestors(path);
|
|
124
|
+
};
|
|
125
|
+
function classifyViewAncestors(path) {
|
|
126
|
+
const context = {
|
|
127
|
+
componentCache: /* @__PURE__ */ new WeakMap(),
|
|
128
|
+
componentInProgress: /* @__PURE__ */ new WeakSet(),
|
|
129
|
+
renderExpressionInProgress: /* @__PURE__ */ new WeakSet()
|
|
130
|
+
};
|
|
131
|
+
let classification = "safe";
|
|
132
|
+
let ancestorPath = path.parentPath.parentPath;
|
|
133
|
+
while (ancestorPath) {
|
|
134
|
+
if (ancestorPath.isJSXElement()) {
|
|
135
|
+
const ancestorClassification = classifyJSXElementAsAncestor(ancestorPath, context);
|
|
136
|
+
classification = mergeAncestorClassification(classification, ancestorClassification);
|
|
137
|
+
if (classification === "text") return classification;
|
|
138
|
+
}
|
|
139
|
+
ancestorPath = ancestorPath.parentPath;
|
|
140
|
+
}
|
|
141
|
+
return classification;
|
|
142
|
+
}
|
|
143
|
+
function classifyJSXElementAsAncestor(path, context) {
|
|
144
|
+
const openingElementName = path.node.openingElement.name;
|
|
145
|
+
if (types.isJSXIdentifier(openingElementName)) {
|
|
146
|
+
return classifyJSXIdentifierAsAncestor(path, openingElementName.name, context);
|
|
147
|
+
}
|
|
148
|
+
if (types.isJSXMemberExpression(openingElementName)) {
|
|
149
|
+
return classifyJSXMemberExpressionAsAncestor(path, openingElementName);
|
|
150
|
+
}
|
|
151
|
+
return "unknown";
|
|
152
|
+
}
|
|
153
|
+
function classifyJSXIdentifierAsAncestor(path, identifierName, context) {
|
|
154
|
+
if (identifierName === "Fragment") return "safe";
|
|
155
|
+
const binding = path.scope.getBinding(identifierName);
|
|
156
|
+
if (!binding) return "unknown";
|
|
157
|
+
return classifyBindingAsAncestor(binding, context);
|
|
158
|
+
}
|
|
159
|
+
function classifyJSXMemberExpressionAsAncestor(path, expression) {
|
|
160
|
+
if (!types.isJSXIdentifier(expression.object) || !types.isJSXIdentifier(expression.property)) {
|
|
161
|
+
return "unknown";
|
|
162
|
+
}
|
|
163
|
+
const binding = path.scope.getBinding(expression.object.name);
|
|
164
|
+
if (!binding || binding.kind !== "module" || !types.isImportNamespaceSpecifier(binding.path.node)) {
|
|
165
|
+
return "unknown";
|
|
166
|
+
}
|
|
167
|
+
const importDeclaration = binding.path.parent;
|
|
168
|
+
if (!types.isImportDeclaration(importDeclaration)) return "unknown";
|
|
169
|
+
if (importDeclaration.source.value === "react-native") {
|
|
170
|
+
return expression.property.name === "Text" ? "text" : "safe";
|
|
171
|
+
}
|
|
172
|
+
if (importDeclaration.source.value === "react" && expression.property.name === "Fragment") {
|
|
173
|
+
return "safe";
|
|
174
|
+
}
|
|
175
|
+
return "unknown";
|
|
176
|
+
}
|
|
177
|
+
function classifyBindingAsAncestor(binding, context) {
|
|
178
|
+
if (binding.kind === "module") {
|
|
179
|
+
return classifyModuleBindingAsAncestor(binding);
|
|
180
|
+
}
|
|
181
|
+
return classifyLocalBindingAsAncestor(binding, context);
|
|
182
|
+
}
|
|
183
|
+
function classifyModuleBindingAsAncestor(binding) {
|
|
184
|
+
const importDeclaration = binding.path.parent;
|
|
185
|
+
if (!types.isImportDeclaration(importDeclaration)) return "unknown";
|
|
186
|
+
const source = importDeclaration.source.value;
|
|
187
|
+
if (source === "react-native") {
|
|
188
|
+
if (types.isImportSpecifier(binding.path.node)) {
|
|
189
|
+
const importedName = getImportSpecifierImportedName(binding.path.node);
|
|
190
|
+
if (!importedName) return "unknown";
|
|
191
|
+
return importedName === "Text" ? "text" : "safe";
|
|
192
|
+
}
|
|
193
|
+
if (types.isImportNamespaceSpecifier(binding.path.node)) {
|
|
194
|
+
return "safe";
|
|
195
|
+
}
|
|
196
|
+
return "unknown";
|
|
197
|
+
}
|
|
198
|
+
if (source === "react" && types.isImportSpecifier(binding.path.node)) {
|
|
199
|
+
const importedName = getImportSpecifierImportedName(binding.path.node);
|
|
200
|
+
if (importedName === "Fragment") return "safe";
|
|
201
|
+
}
|
|
202
|
+
return "unknown";
|
|
203
|
+
}
|
|
204
|
+
function classifyLocalBindingAsAncestor(binding, context) {
|
|
205
|
+
const cacheKey = binding.path.node;
|
|
206
|
+
const cached = context.componentCache.get(cacheKey);
|
|
207
|
+
if (cached) return cached;
|
|
208
|
+
if (context.componentInProgress.has(cacheKey)) {
|
|
209
|
+
return "unknown";
|
|
210
|
+
}
|
|
211
|
+
context.componentInProgress.add(cacheKey);
|
|
212
|
+
let classification;
|
|
213
|
+
if (binding.path.isFunctionDeclaration()) {
|
|
214
|
+
classification = analyzeFunctionComponent(binding.path, context);
|
|
215
|
+
} else if (binding.path.isVariableDeclarator()) {
|
|
216
|
+
classification = analyzeVariableDeclaratorComponent(binding.path, context);
|
|
217
|
+
} else {
|
|
218
|
+
classification = "unknown";
|
|
219
|
+
}
|
|
220
|
+
context.componentInProgress.delete(cacheKey);
|
|
221
|
+
context.componentCache.set(cacheKey, classification);
|
|
222
|
+
return classification;
|
|
223
|
+
}
|
|
224
|
+
function analyzeVariableDeclaratorComponent(path, context) {
|
|
225
|
+
const initPath = path.get("init");
|
|
226
|
+
if (!initPath.node) return "unknown";
|
|
227
|
+
if (initPath.isArrowFunctionExpression() || initPath.isFunctionExpression()) {
|
|
228
|
+
return analyzeFunctionComponent(initPath, context);
|
|
229
|
+
}
|
|
230
|
+
if (initPath.isCallExpression()) {
|
|
231
|
+
return analyzeCallWrappedComponent(initPath, context);
|
|
232
|
+
}
|
|
233
|
+
if (initPath.isIdentifier()) {
|
|
234
|
+
const aliasBinding = path.scope.getBinding(initPath.node.name);
|
|
235
|
+
if (!aliasBinding) return "unknown";
|
|
236
|
+
return classifyBindingAsAncestor(aliasBinding, context);
|
|
237
|
+
}
|
|
238
|
+
return "unknown";
|
|
239
|
+
}
|
|
240
|
+
function analyzeCallWrappedComponent(path, context) {
|
|
241
|
+
if (!isReactMemoOrForwardRefCall(path)) return "unknown";
|
|
242
|
+
const [firstArgumentPath] = path.get("arguments");
|
|
243
|
+
if (!(firstArgumentPath == null ? void 0 : firstArgumentPath.node)) return "unknown";
|
|
244
|
+
if (firstArgumentPath.isArrowFunctionExpression() || firstArgumentPath.isFunctionExpression()) {
|
|
245
|
+
return analyzeFunctionComponent(firstArgumentPath, context);
|
|
246
|
+
}
|
|
247
|
+
if (firstArgumentPath.isIdentifier()) {
|
|
248
|
+
const wrappedComponentBinding = path.scope.getBinding(firstArgumentPath.node.name);
|
|
249
|
+
if (!wrappedComponentBinding) return "unknown";
|
|
250
|
+
return classifyBindingAsAncestor(wrappedComponentBinding, context);
|
|
251
|
+
}
|
|
252
|
+
if (firstArgumentPath.isCallExpression()) {
|
|
253
|
+
return analyzeCallWrappedComponent(firstArgumentPath, context);
|
|
254
|
+
}
|
|
255
|
+
return "unknown";
|
|
256
|
+
}
|
|
257
|
+
function isReactMemoOrForwardRefCall(path) {
|
|
258
|
+
const calleePath = path.get("callee");
|
|
259
|
+
if (calleePath.isIdentifier()) {
|
|
260
|
+
if (!isMemoOrForwardRefName(calleePath.node.name)) return false;
|
|
261
|
+
const binding = path.scope.getBinding(calleePath.node.name);
|
|
262
|
+
return isReactImportBinding(binding);
|
|
263
|
+
}
|
|
264
|
+
if (calleePath.isMemberExpression()) {
|
|
265
|
+
const objectPath = calleePath.get("object");
|
|
266
|
+
const propertyPath = calleePath.get("property");
|
|
267
|
+
if (!objectPath.isIdentifier() || !propertyPath.isIdentifier()) return false;
|
|
268
|
+
if (!isMemoOrForwardRefName(propertyPath.node.name)) return false;
|
|
269
|
+
const objectBinding = path.scope.getBinding(objectPath.node.name);
|
|
270
|
+
return isReactImportBinding(objectBinding);
|
|
271
|
+
}
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
function isMemoOrForwardRefName(name) {
|
|
275
|
+
return name === "memo" || name === "forwardRef";
|
|
276
|
+
}
|
|
277
|
+
function isReactImportBinding(binding) {
|
|
278
|
+
if (!binding || binding.kind !== "module") return false;
|
|
279
|
+
const importDeclaration = binding.path.parent;
|
|
280
|
+
return types.isImportDeclaration(importDeclaration) && importDeclaration.source.value === "react";
|
|
281
|
+
}
|
|
282
|
+
function analyzeFunctionComponent(path, context) {
|
|
283
|
+
const bodyPath = path.get("body");
|
|
284
|
+
if (!bodyPath.isBlockStatement()) {
|
|
285
|
+
return analyzeRenderExpression(bodyPath, context);
|
|
286
|
+
}
|
|
287
|
+
let classification = "safe";
|
|
288
|
+
for (const statementPath of bodyPath.get("body")) {
|
|
289
|
+
if (!statementPath.isReturnStatement()) continue;
|
|
290
|
+
const argumentPath = statementPath.get("argument");
|
|
291
|
+
if (!argumentPath.node) continue;
|
|
292
|
+
const returnClassification = analyzeRenderExpression(argumentPath, context);
|
|
293
|
+
classification = mergeAncestorClassification(classification, returnClassification);
|
|
294
|
+
if (classification === "text") return classification;
|
|
295
|
+
}
|
|
296
|
+
return classification;
|
|
297
|
+
}
|
|
298
|
+
function analyzeRenderExpression(path, context) {
|
|
299
|
+
if (path.isJSXFragment()) {
|
|
300
|
+
return analyzeJSXChildren(path.get("children"), context);
|
|
301
|
+
}
|
|
302
|
+
let classification = "safe";
|
|
303
|
+
let hasJSX = false;
|
|
304
|
+
path.traverse({
|
|
305
|
+
JSXOpeningElement(jsxPath) {
|
|
306
|
+
hasJSX = true;
|
|
307
|
+
const jsxElementPath = jsxPath.parentPath;
|
|
308
|
+
if (!jsxElementPath.isJSXElement()) {
|
|
309
|
+
classification = mergeAncestorClassification(classification, "unknown");
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const jsxClassification = classifyJSXElementAsAncestor(jsxElementPath, context);
|
|
313
|
+
classification = mergeAncestorClassification(classification, jsxClassification);
|
|
314
|
+
if (classification === "text") {
|
|
315
|
+
jsxPath.stop();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
if (hasJSX) return classification;
|
|
320
|
+
if (path.isIdentifier()) {
|
|
321
|
+
return analyzeIdentifierRenderExpression(path, context);
|
|
322
|
+
}
|
|
323
|
+
if (path.isMemberExpression() && isPropsChildrenMemberExpression(path.node)) {
|
|
324
|
+
return "safe";
|
|
325
|
+
}
|
|
326
|
+
if (path.isNullLiteral() || path.isBooleanLiteral() || path.isNumericLiteral() || path.isStringLiteral() || path.isBigIntLiteral()) {
|
|
327
|
+
return "safe";
|
|
328
|
+
}
|
|
329
|
+
return "unknown";
|
|
330
|
+
}
|
|
331
|
+
function analyzeJSXChildren(children, context) {
|
|
332
|
+
let classification = "safe";
|
|
333
|
+
for (const childPath of children) {
|
|
334
|
+
if (childPath.isJSXElement()) {
|
|
335
|
+
const childClassification = classifyJSXElementAsAncestor(childPath, context);
|
|
336
|
+
classification = mergeAncestorClassification(classification, childClassification);
|
|
337
|
+
} else if (childPath.isJSXFragment()) {
|
|
338
|
+
const fragmentClassification = analyzeJSXChildren(childPath.get("children"), context);
|
|
339
|
+
classification = mergeAncestorClassification(classification, fragmentClassification);
|
|
340
|
+
} else if (childPath.isJSXExpressionContainer()) {
|
|
341
|
+
const expressionPath = childPath.get("expression");
|
|
342
|
+
if (!expressionPath.node || expressionPath.isJSXEmptyExpression()) continue;
|
|
343
|
+
const expressionClassification = analyzeRenderExpression(expressionPath, context);
|
|
344
|
+
classification = mergeAncestorClassification(classification, expressionClassification);
|
|
345
|
+
} else if (childPath.isJSXSpreadChild()) {
|
|
346
|
+
classification = mergeAncestorClassification(classification, "unknown");
|
|
347
|
+
}
|
|
348
|
+
if (classification === "text") {
|
|
349
|
+
return classification;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return classification;
|
|
353
|
+
}
|
|
354
|
+
function analyzeIdentifierRenderExpression(path, context) {
|
|
355
|
+
if (path.node.name === "children") return "safe";
|
|
356
|
+
const binding = path.scope.getBinding(path.node.name);
|
|
357
|
+
if (!binding) return "unknown";
|
|
358
|
+
if (binding.kind === "param") {
|
|
359
|
+
return binding.identifier.name === "children" ? "safe" : "unknown";
|
|
360
|
+
}
|
|
361
|
+
if (!binding.path.isVariableDeclarator()) return "unknown";
|
|
362
|
+
const cacheKey = binding.path.node;
|
|
363
|
+
if (context.renderExpressionInProgress.has(cacheKey)) {
|
|
364
|
+
return "unknown";
|
|
365
|
+
}
|
|
366
|
+
const initPath = binding.path.get("init");
|
|
367
|
+
if (!initPath.node) return "unknown";
|
|
368
|
+
context.renderExpressionInProgress.add(cacheKey);
|
|
369
|
+
const classification = analyzeRenderExpression(initPath, context);
|
|
370
|
+
context.renderExpressionInProgress.delete(cacheKey);
|
|
371
|
+
return classification;
|
|
372
|
+
}
|
|
373
|
+
function isPropsChildrenMemberExpression(expression) {
|
|
374
|
+
if (!types.isIdentifier(expression.object, { name: "props" })) return false;
|
|
375
|
+
if (!types.isIdentifier(expression.property, { name: "children" })) return false;
|
|
376
|
+
return !expression.computed;
|
|
377
|
+
}
|
|
378
|
+
function mergeAncestorClassification(current, next) {
|
|
379
|
+
if (current === "text" || next === "text") return "text";
|
|
380
|
+
if (current === "unknown" || next === "unknown") return "unknown";
|
|
381
|
+
return "safe";
|
|
382
|
+
}
|
|
383
|
+
function getImportSpecifierImportedName(specifier) {
|
|
384
|
+
if (types.isIdentifier(specifier.imported)) {
|
|
385
|
+
return specifier.imported.name;
|
|
386
|
+
}
|
|
387
|
+
if (types.isStringLiteral(specifier.imported)) {
|
|
388
|
+
return specifier.imported.value;
|
|
389
|
+
}
|
|
390
|
+
return void 0;
|
|
391
|
+
}
|
|
392
|
+
const hasExpoRouterLinkParentWithAsChild = (path) => {
|
|
393
|
+
const textElementPath = path.parentPath;
|
|
394
|
+
if (!textElementPath.isJSXElement()) return false;
|
|
395
|
+
let ancestorPath = textElementPath.parentPath;
|
|
396
|
+
while (ancestorPath) {
|
|
397
|
+
if (ancestorPath.isJSXElement()) {
|
|
398
|
+
if (!isExpoRouterLinkElement(ancestorPath)) return false;
|
|
399
|
+
return hasTruthyAsChildAttribute(ancestorPath.node.openingElement.attributes);
|
|
400
|
+
}
|
|
401
|
+
ancestorPath = ancestorPath.parentPath;
|
|
402
|
+
}
|
|
403
|
+
return false;
|
|
404
|
+
};
|
|
405
|
+
function isExpoRouterLinkElement(path) {
|
|
406
|
+
const openingElementName = path.node.openingElement.name;
|
|
407
|
+
if (types.isJSXIdentifier(openingElementName)) {
|
|
408
|
+
const binding = path.scope.getBinding(openingElementName.name);
|
|
409
|
+
if (!binding || binding.kind !== "module") return false;
|
|
410
|
+
if (!types.isImportSpecifier(binding.path.node)) return false;
|
|
411
|
+
const importDeclaration = binding.path.parent;
|
|
412
|
+
if (!types.isImportDeclaration(importDeclaration) || importDeclaration.source.value !== "expo-router") return false;
|
|
413
|
+
const imported = binding.path.node.imported;
|
|
414
|
+
return types.isIdentifier(imported, { name: "Link" }) || types.isStringLiteral(imported) && imported.value === "Link";
|
|
415
|
+
}
|
|
416
|
+
if (types.isJSXMemberExpression(openingElementName)) {
|
|
417
|
+
if (!types.isJSXIdentifier(openingElementName.object)) return false;
|
|
418
|
+
if (!types.isJSXIdentifier(openingElementName.property, { name: "Link" })) return false;
|
|
419
|
+
const namespaceBinding = path.scope.getBinding(openingElementName.object.name);
|
|
420
|
+
if (!namespaceBinding || namespaceBinding.kind !== "module") return false;
|
|
421
|
+
if (!types.isImportNamespaceSpecifier(namespaceBinding.path.node)) return false;
|
|
422
|
+
const importDeclaration = namespaceBinding.path.parent;
|
|
423
|
+
return types.isImportDeclaration(importDeclaration) && importDeclaration.source.value === "expo-router";
|
|
424
|
+
}
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
function hasTruthyAsChildAttribute(attributes) {
|
|
428
|
+
let asChildAttribute;
|
|
429
|
+
for (const attribute of attributes) {
|
|
430
|
+
if (types.isJSXAttribute(attribute) && types.isJSXIdentifier(attribute.name, { name: "asChild" })) {
|
|
431
|
+
asChildAttribute = attribute;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (!asChildAttribute) return false;
|
|
435
|
+
return isJSXAttributeValueTruthy(asChildAttribute.value);
|
|
436
|
+
}
|
|
437
|
+
function isJSXAttributeValueTruthy(value) {
|
|
438
|
+
if (!value) return true;
|
|
439
|
+
if (types.isStringLiteral(value)) return value.value.length > 0;
|
|
440
|
+
if (types.isJSXElement(value) || types.isJSXFragment(value)) return true;
|
|
441
|
+
if (types.isJSXExpressionContainer(value)) {
|
|
442
|
+
const staticTruthiness = getStaticExpressionTruthiness(value.expression);
|
|
443
|
+
return staticTruthiness != null ? staticTruthiness : true;
|
|
444
|
+
}
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
function getStaticExpressionTruthiness(expression) {
|
|
448
|
+
var _a, _b;
|
|
449
|
+
if (types.isJSXEmptyExpression(expression)) return false;
|
|
450
|
+
if (types.isBooleanLiteral(expression)) return expression.value;
|
|
451
|
+
if (types.isNullLiteral(expression)) return false;
|
|
452
|
+
if (types.isStringLiteral(expression)) return expression.value.length > 0;
|
|
453
|
+
if (types.isNumericLiteral(expression)) return expression.value !== 0 && !Number.isNaN(expression.value);
|
|
454
|
+
if (types.isBigIntLiteral(expression)) return expression.value !== "0";
|
|
455
|
+
if (types.isIdentifier(expression, { name: "undefined" })) return false;
|
|
456
|
+
if (types.isTemplateLiteral(expression) && expression.expressions.length === 0) {
|
|
457
|
+
return ((_b = (_a = expression.quasis[0]) == null ? void 0 : _a.value.cooked) != null ? _b : "").length > 0;
|
|
458
|
+
}
|
|
459
|
+
if (types.isUnaryExpression(expression, { operator: "!" })) {
|
|
460
|
+
const staticTruthiness = getStaticExpressionTruthiness(expression.argument);
|
|
461
|
+
return staticTruthiness === void 0 ? void 0 : !staticTruthiness;
|
|
462
|
+
}
|
|
463
|
+
return void 0;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const RUNTIME_MODULE_NAME = "react-native-boost/runtime";
|
|
467
|
+
const ACCESSIBILITY_PROPERTIES = /* @__PURE__ */ new Set([
|
|
468
|
+
"accessibilityLabel",
|
|
469
|
+
"aria-label",
|
|
470
|
+
"accessibilityState",
|
|
471
|
+
"aria-busy",
|
|
472
|
+
"aria-checked",
|
|
473
|
+
"aria-disabled",
|
|
474
|
+
"aria-expanded",
|
|
475
|
+
"aria-selected",
|
|
476
|
+
"accessible"
|
|
477
|
+
]);
|
|
478
|
+
const USER_SELECT_STYLE_TO_SELECTABLE_PROP = {
|
|
479
|
+
auto: true,
|
|
480
|
+
text: true,
|
|
481
|
+
none: false,
|
|
482
|
+
contain: true,
|
|
483
|
+
all: true
|
|
484
|
+
};
|
|
160
485
|
|
|
161
486
|
const hasBlacklistedProperty = (path, blacklist) => {
|
|
162
487
|
return path.node.attributes.some((attribute) => {
|
|
@@ -188,6 +513,47 @@ const hasBlacklistedProperty = (path, blacklist) => {
|
|
|
188
513
|
return false;
|
|
189
514
|
});
|
|
190
515
|
};
|
|
516
|
+
const addDefaultProperty = (path, key, value) => {
|
|
517
|
+
let propertyIsFound = false;
|
|
518
|
+
let hasUnresolvableSpread = false;
|
|
519
|
+
for (const attribute of path.node.attributes) {
|
|
520
|
+
if (types.isJSXAttribute(attribute) && attribute.name.name === key) {
|
|
521
|
+
propertyIsFound = true;
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
if (types.isJSXSpreadAttribute(attribute)) {
|
|
525
|
+
if (types.isObjectExpression(attribute.argument)) {
|
|
526
|
+
const propertyInSpread = attribute.argument.properties.some(
|
|
527
|
+
(p) => types.isObjectProperty(p) && types.isIdentifier(p.key) && p.key.name === key || types.isObjectProperty(p) && types.isStringLiteral(p.key) && p.key.value === key
|
|
528
|
+
);
|
|
529
|
+
if (propertyInSpread) {
|
|
530
|
+
propertyIsFound = true;
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
} else if (types.isIdentifier(attribute.argument)) {
|
|
534
|
+
const binding = path.scope.getBinding(attribute.argument.name);
|
|
535
|
+
if ((binding == null ? void 0 : binding.path.node) && types.isVariableDeclarator(binding.path.node) && types.isObjectExpression(binding.path.node.init)) {
|
|
536
|
+
const propertyInSpread = binding.path.node.init.properties.some(
|
|
537
|
+
(p) => types.isObjectProperty(p) && types.isIdentifier(p.key) && p.key.name === key || types.isObjectProperty(p) && types.isStringLiteral(p.key) && p.key.value === key
|
|
538
|
+
);
|
|
539
|
+
if (propertyInSpread) {
|
|
540
|
+
propertyIsFound = true;
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
} else {
|
|
544
|
+
hasUnresolvableSpread = true;
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
} else {
|
|
548
|
+
hasUnresolvableSpread = true;
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (!propertyIsFound && !hasUnresolvableSpread) {
|
|
554
|
+
path.node.attributes.push(types.jsxAttribute(types.jsxIdentifier(key), types.jsxExpressionContainer(value)));
|
|
555
|
+
}
|
|
556
|
+
};
|
|
191
557
|
const buildPropertiesFromAttributes = (attributes) => {
|
|
192
558
|
const arguments_ = [];
|
|
193
559
|
for (const attribute of attributes) {
|
|
@@ -253,7 +619,54 @@ const hasAccessibilityProperty = (path, attributes) => {
|
|
|
253
619
|
}
|
|
254
620
|
return false;
|
|
255
621
|
};
|
|
256
|
-
|
|
622
|
+
function extractStyleAttribute(attributes) {
|
|
623
|
+
for (const attribute of attributes) {
|
|
624
|
+
if (types.isJSXAttribute(attribute) && types.isJSXIdentifier(attribute.name, { name: "style" })) {
|
|
625
|
+
if (attribute.value && types.isJSXExpressionContainer(attribute.value) && !types.isJSXEmptyExpression(attribute.value.expression)) {
|
|
626
|
+
return {
|
|
627
|
+
styleAttribute: attribute,
|
|
628
|
+
styleExpr: attribute.value.expression
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
return { styleAttribute: attribute };
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return {};
|
|
635
|
+
}
|
|
636
|
+
function extractSelectableAndUpdateStyle(styleExpr) {
|
|
637
|
+
const handleObjectExpression = (objectExpr) => {
|
|
638
|
+
let selectableValue;
|
|
639
|
+
objectExpr.properties = objectExpr.properties.filter((property) => {
|
|
640
|
+
if (!types.isObjectProperty(property) || !types.isIdentifier(property.key, { name: "userSelect" }) && !(types.isStringLiteral(property.key) && property.key.value === "userSelect")) {
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
643
|
+
if (types.isStringLiteral(property.value)) {
|
|
644
|
+
const mapped = USER_SELECT_STYLE_TO_SELECTABLE_PROP[property.value.value];
|
|
645
|
+
if (mapped !== void 0) {
|
|
646
|
+
selectableValue = mapped;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return false;
|
|
650
|
+
});
|
|
651
|
+
return selectableValue;
|
|
652
|
+
};
|
|
653
|
+
if (types.isObjectExpression(styleExpr)) {
|
|
654
|
+
return handleObjectExpression(styleExpr);
|
|
655
|
+
}
|
|
656
|
+
if (types.isArrayExpression(styleExpr)) {
|
|
657
|
+
let selectableValue;
|
|
658
|
+
for (const element of styleExpr.elements) {
|
|
659
|
+
if (element && types.isObjectExpression(element)) {
|
|
660
|
+
const value = handleObjectExpression(element);
|
|
661
|
+
if (value !== void 0) {
|
|
662
|
+
selectableValue = value;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return selectableValue;
|
|
667
|
+
}
|
|
668
|
+
return void 0;
|
|
669
|
+
}
|
|
257
670
|
const isStringNode = (path, child) => {
|
|
258
671
|
if (types.isJSXText(child) || types.isStringLiteral(child)) return true;
|
|
259
672
|
if (types.isJSXExpressionContainer(child)) {
|
|
@@ -270,63 +683,40 @@ const isStringNode = (path, child) => {
|
|
|
270
683
|
return false;
|
|
271
684
|
};
|
|
272
685
|
|
|
273
|
-
function
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
if (skipComponents.includes(ancestorComponentName)) {
|
|
287
|
-
return false;
|
|
288
|
-
}
|
|
289
|
-
if (ancestorComponentName[0] === ancestorComponentName[0].toLowerCase()) {
|
|
290
|
-
return false;
|
|
291
|
-
}
|
|
292
|
-
const binding = parentPath.scope.getBinding(ancestorComponentName);
|
|
293
|
-
if (!binding) return false;
|
|
294
|
-
if (types.isVariableDeclarator(binding.path.node)) {
|
|
295
|
-
const init = binding.path.node.init;
|
|
296
|
-
if (types.isArrowFunctionExpression(init) || types.isFunctionExpression(init)) {
|
|
297
|
-
return types.isBlockStatement(init.body) ? hasComponentInReturnStatement(init.body, componentName) : hasComponentInExpression(init.body, componentName);
|
|
298
|
-
}
|
|
299
|
-
} else if (types.isFunctionDeclaration(binding.path.node)) {
|
|
300
|
-
return hasComponentInReturnStatement(binding.path.node.body, componentName);
|
|
301
|
-
}
|
|
302
|
-
return false;
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
function hasComponentInReturnStatement(blockStatement, componentName) {
|
|
306
|
-
for (const statement of blockStatement.body) {
|
|
307
|
-
if (types.isReturnStatement(statement) && statement.argument && hasComponentInExpression(statement.argument, componentName)) {
|
|
308
|
-
return true;
|
|
309
|
-
}
|
|
686
|
+
function addFileImportHint({
|
|
687
|
+
file,
|
|
688
|
+
nameHint,
|
|
689
|
+
path,
|
|
690
|
+
importName,
|
|
691
|
+
moduleName,
|
|
692
|
+
importType = "named"
|
|
693
|
+
}) {
|
|
694
|
+
var _a;
|
|
695
|
+
if (!((_a = file.__hasImports) == null ? void 0 : _a[nameHint])) {
|
|
696
|
+
file.__hasImports = file.__hasImports || {};
|
|
697
|
+
file.__hasImports[nameHint] = importType === "default" ? addDefault(path, moduleName, { nameHint }) : addNamed(path, importName, moduleName, { nameHint });
|
|
310
698
|
}
|
|
311
|
-
return
|
|
699
|
+
return file.__hasImports[nameHint];
|
|
312
700
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
701
|
+
const replaceWithNativeComponent = (path, parent, file, nativeComponentName) => {
|
|
702
|
+
const nativeIdentifier = addFileImportHint({
|
|
703
|
+
file,
|
|
704
|
+
nameHint: nativeComponentName,
|
|
705
|
+
path,
|
|
706
|
+
importName: nativeComponentName,
|
|
707
|
+
moduleName: RUNTIME_MODULE_NAME,
|
|
708
|
+
importType: "named"
|
|
709
|
+
});
|
|
710
|
+
const currentName = path.node.name.name;
|
|
711
|
+
const jsxName = path.node.name;
|
|
712
|
+
jsxName.name = nativeIdentifier.name;
|
|
713
|
+
if (!path.node.selfClosing && parent.closingElement && types.isJSXIdentifier(parent.closingElement.name) && parent.closingElement.name.name === currentName) {
|
|
714
|
+
parent.closingElement.name.name = nativeIdentifier.name;
|
|
323
715
|
}
|
|
324
|
-
return
|
|
325
|
-
}
|
|
716
|
+
return nativeIdentifier;
|
|
717
|
+
};
|
|
326
718
|
|
|
327
719
|
const textBlacklistedProperties = /* @__PURE__ */ new Set([
|
|
328
|
-
"allowFontScaling",
|
|
329
|
-
"ellipsizeMode",
|
|
330
720
|
"id",
|
|
331
721
|
"nativeID",
|
|
332
722
|
"onLongPress",
|
|
@@ -341,29 +731,55 @@ const textBlacklistedProperties = /* @__PURE__ */ new Set([
|
|
|
341
731
|
"onStartShouldSetResponder",
|
|
342
732
|
"pressRetentionOffset",
|
|
343
733
|
"suppressHighlighting",
|
|
344
|
-
"selectable",
|
|
345
734
|
"selectionColor"
|
|
735
|
+
// TODO: we can use react-native's internal `processColor` to process this at runtime
|
|
346
736
|
]);
|
|
347
|
-
const textOptimizer = (path,
|
|
348
|
-
}) => {
|
|
349
|
-
var _a, _b, _c;
|
|
350
|
-
if (isIgnoredLine(path)) return;
|
|
737
|
+
const textOptimizer = (path, logger) => {
|
|
351
738
|
if (!isValidJSXComponent(path, "Text")) return;
|
|
352
|
-
if (!isReactNativeImport(path, "Text")) return;
|
|
353
|
-
if (hasBlacklistedProperty(path, textBlacklistedProperties)) return;
|
|
354
739
|
const parent = path.parent;
|
|
355
|
-
|
|
740
|
+
const skipReason = getFirstBailoutReason([
|
|
741
|
+
{
|
|
742
|
+
reason: "line is marked with @boost-ignore",
|
|
743
|
+
shouldBail: () => isIgnoredLine(path)
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
reason: "Text is not imported from react-native",
|
|
747
|
+
shouldBail: () => !isReactNativeImport(path, "Text")
|
|
748
|
+
},
|
|
749
|
+
{
|
|
750
|
+
reason: "contains blacklisted props",
|
|
751
|
+
shouldBail: () => hasBlacklistedProperty(path, textBlacklistedProperties)
|
|
752
|
+
},
|
|
753
|
+
{
|
|
754
|
+
reason: "is a direct child of expo-router Link with asChild",
|
|
755
|
+
shouldBail: () => hasExpoRouterLinkParentWithAsChild(path)
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
reason: "contains non-string children",
|
|
759
|
+
shouldBail: () => hasInvalidChildren(path, parent)
|
|
760
|
+
}
|
|
761
|
+
]);
|
|
762
|
+
if (skipReason) {
|
|
763
|
+
logger.skipped({
|
|
764
|
+
component: "Text",
|
|
765
|
+
path,
|
|
766
|
+
reason: skipReason
|
|
767
|
+
});
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
356
770
|
const hub = path.hub;
|
|
357
771
|
const file = typeof hub === "object" && hub !== null && "file" in hub ? hub.file : void 0;
|
|
358
772
|
if (!file) {
|
|
359
773
|
throw new PluginError("No file found in Babel hub");
|
|
360
774
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
fixNegativeNumberOfLines({ path,
|
|
366
|
-
|
|
775
|
+
logger.optimized({
|
|
776
|
+
component: "Text",
|
|
777
|
+
path
|
|
778
|
+
});
|
|
779
|
+
fixNegativeNumberOfLines({ path, logger });
|
|
780
|
+
addDefaultProperty(path, "allowFontScaling", types.booleanLiteral(true));
|
|
781
|
+
addDefaultProperty(path, "ellipsizeMode", types.stringLiteral("tail"));
|
|
782
|
+
processProps(path, file);
|
|
367
783
|
replaceWithNativeComponent(path, parent, file, "NativeText");
|
|
368
784
|
};
|
|
369
785
|
function hasInvalidChildren(path, parent) {
|
|
@@ -376,10 +792,7 @@ function hasInvalidChildren(path, parent) {
|
|
|
376
792
|
}
|
|
377
793
|
return !parent.children.every((child) => isStringNode(path, child));
|
|
378
794
|
}
|
|
379
|
-
function fixNegativeNumberOfLines({
|
|
380
|
-
path,
|
|
381
|
-
log
|
|
382
|
-
}) {
|
|
795
|
+
function fixNegativeNumberOfLines({ path, logger }) {
|
|
383
796
|
for (const attribute of path.node.attributes) {
|
|
384
797
|
if (types.isJSXAttribute(attribute) && types.isJSXIdentifier(attribute.name, { name: "numberOfLines" }) && attribute.value && types.isJSXExpressionContainer(attribute.value)) {
|
|
385
798
|
let originalValue;
|
|
@@ -389,35 +802,26 @@ function fixNegativeNumberOfLines({
|
|
|
389
802
|
originalValue = -attribute.value.expression.argument.value;
|
|
390
803
|
}
|
|
391
804
|
if (originalValue !== void 0 && originalValue < 0) {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
805
|
+
logger.warning({
|
|
806
|
+
component: "Text",
|
|
807
|
+
path,
|
|
808
|
+
message: `'numberOfLines' must be a non-negative number, received: ${originalValue}. The value will be set to 0.`
|
|
809
|
+
});
|
|
395
810
|
attribute.value.expression = types.numericLiteral(0);
|
|
396
811
|
}
|
|
397
812
|
}
|
|
398
813
|
}
|
|
399
814
|
}
|
|
400
|
-
function
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
return {};
|
|
413
|
-
}
|
|
414
|
-
function processProps(path, file, originalAttributes) {
|
|
415
|
-
const { styleExpr } = extractStyleAttribute(originalAttributes);
|
|
416
|
-
const hasA11y = hasAccessibilityProperty(path, originalAttributes);
|
|
417
|
-
if (styleExpr && hasA11y) {
|
|
418
|
-
const accessibilityAttributes = originalAttributes.filter(
|
|
419
|
-
(attribute) => !(types.isJSXAttribute(attribute) && types.isJSXIdentifier(attribute.name, { name: "style" }))
|
|
420
|
-
);
|
|
815
|
+
function processProps(path, file) {
|
|
816
|
+
const currentAttributes = [...path.node.attributes];
|
|
817
|
+
const { styleExpr, styleAttribute } = extractStyleAttribute(currentAttributes);
|
|
818
|
+
const hasA11y = hasAccessibilityProperty(path, currentAttributes);
|
|
819
|
+
const spreadAttributes = [];
|
|
820
|
+
if (hasA11y) {
|
|
821
|
+
const accessibilityAttributes = currentAttributes.filter((attribute) => {
|
|
822
|
+
if (!types.isJSXAttribute(attribute)) return false;
|
|
823
|
+
return types.isJSXIdentifier(attribute.name) && ACCESSIBILITY_PROPERTIES.has(attribute.name.name);
|
|
824
|
+
});
|
|
421
825
|
const normalizeIdentifier = addFileImportHint({
|
|
422
826
|
file,
|
|
423
827
|
nameHint: "processAccessibilityProps",
|
|
@@ -427,6 +831,17 @@ function processProps(path, file, originalAttributes) {
|
|
|
427
831
|
});
|
|
428
832
|
const accessibilityObject = buildPropertiesFromAttributes(accessibilityAttributes);
|
|
429
833
|
const accessibilityExpr = types.callExpression(types.identifier(normalizeIdentifier.name), [accessibilityObject]);
|
|
834
|
+
spreadAttributes.push(types.jsxSpreadAttribute(accessibilityExpr));
|
|
835
|
+
}
|
|
836
|
+
let selectableAttribute;
|
|
837
|
+
if (styleExpr) {
|
|
838
|
+
const selectableValue = extractSelectableAndUpdateStyle(styleExpr);
|
|
839
|
+
if (selectableValue != null) {
|
|
840
|
+
selectableAttribute = types.jsxAttribute(
|
|
841
|
+
types.jsxIdentifier("selectable"),
|
|
842
|
+
types.jsxExpressionContainer(types.booleanLiteral(selectableValue))
|
|
843
|
+
);
|
|
844
|
+
}
|
|
430
845
|
const flattenIdentifier = addFileImportHint({
|
|
431
846
|
file,
|
|
432
847
|
nameHint: "processTextStyle",
|
|
@@ -435,84 +850,169 @@ function processProps(path, file, originalAttributes) {
|
|
|
435
850
|
moduleName: RUNTIME_MODULE_NAME
|
|
436
851
|
});
|
|
437
852
|
const flattenedStyleExpr = types.callExpression(types.identifier(flattenIdentifier.name), [styleExpr]);
|
|
438
|
-
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const flattened = types.callExpression(types.identifier(flattenIdentifier.name), [styleExpr]);
|
|
448
|
-
path.node.attributes = [types.jsxSpreadAttribute(flattened)];
|
|
449
|
-
} else if (hasA11y) {
|
|
450
|
-
const normalizeIdentifier = addFileImportHint({
|
|
451
|
-
file,
|
|
452
|
-
nameHint: "processAccessibilityProps",
|
|
453
|
-
path,
|
|
454
|
-
importName: "processAccessibilityProps",
|
|
455
|
-
moduleName: RUNTIME_MODULE_NAME
|
|
456
|
-
});
|
|
457
|
-
const propsObject = buildPropertiesFromAttributes(originalAttributes);
|
|
458
|
-
const normalized = types.callExpression(types.identifier(normalizeIdentifier.name), [propsObject]);
|
|
459
|
-
path.node.attributes = [types.jsxSpreadAttribute(normalized)];
|
|
853
|
+
spreadAttributes.push(types.jsxSpreadAttribute(flattenedStyleExpr));
|
|
854
|
+
}
|
|
855
|
+
const remainingAttributes = [];
|
|
856
|
+
for (const attribute of currentAttributes) {
|
|
857
|
+
if (styleAttribute && attribute === styleAttribute) continue;
|
|
858
|
+
if (hasA11y && types.isJSXAttribute(attribute) && types.isJSXIdentifier(attribute.name) && ACCESSIBILITY_PROPERTIES.has(attribute.name.name)) {
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
remainingAttributes.push(attribute);
|
|
460
862
|
}
|
|
863
|
+
path.node.attributes = [...spreadAttributes, selectableAttribute, ...remainingAttributes].filter(
|
|
864
|
+
(attribute) => attribute !== void 0
|
|
865
|
+
);
|
|
461
866
|
}
|
|
462
867
|
|
|
463
|
-
const
|
|
464
|
-
|
|
868
|
+
const LOG_PREFIX = "[react-native-boost]";
|
|
869
|
+
const ANSI_RESET = "\x1B[0m";
|
|
870
|
+
const ANSI_GREEN = "\x1B[32m";
|
|
871
|
+
const ANSI_YELLOW = "\x1B[33m";
|
|
872
|
+
const ANSI_MAGENTA = "\x1B[35m";
|
|
873
|
+
const noopLogger = {
|
|
874
|
+
optimized() {
|
|
875
|
+
},
|
|
876
|
+
skipped() {
|
|
877
|
+
},
|
|
878
|
+
warning() {
|
|
879
|
+
}
|
|
465
880
|
};
|
|
881
|
+
const createLogger = ({ verbose, silent }) => {
|
|
882
|
+
if (silent) return noopLogger;
|
|
883
|
+
return {
|
|
884
|
+
optimized(payload) {
|
|
885
|
+
writeLog("optimized", `Optimized ${payload.component} in ${formatPathLocation(payload.path)}`);
|
|
886
|
+
},
|
|
887
|
+
skipped(payload) {
|
|
888
|
+
if (!verbose) return;
|
|
889
|
+
writeLog("skipped", `Skipped ${payload.component} in ${formatPathLocation(payload.path)} (${payload.reason})`);
|
|
890
|
+
},
|
|
891
|
+
warning(payload) {
|
|
892
|
+
const context = formatWarningContext(payload);
|
|
893
|
+
const message = context.length > 0 ? `${context}: ${payload.message}` : payload.message;
|
|
894
|
+
writeLog("warning", message);
|
|
895
|
+
}
|
|
896
|
+
};
|
|
897
|
+
};
|
|
898
|
+
function formatWarningContext(payload) {
|
|
899
|
+
const location = formatPathLocation(payload.path);
|
|
900
|
+
if (payload.component && location.length > 0) {
|
|
901
|
+
return `${payload.component} in ${location}`;
|
|
902
|
+
}
|
|
903
|
+
if (payload.component) {
|
|
904
|
+
return payload.component;
|
|
905
|
+
}
|
|
906
|
+
return location;
|
|
907
|
+
}
|
|
908
|
+
function writeLog(level, message) {
|
|
909
|
+
const levelTag = formatLevel(level);
|
|
910
|
+
console.log(`${LOG_PREFIX} ${levelTag} ${message}`);
|
|
911
|
+
}
|
|
912
|
+
function formatLevel(level) {
|
|
913
|
+
if (level === "optimized") {
|
|
914
|
+
return colorize("[optimized]", ANSI_GREEN);
|
|
915
|
+
}
|
|
916
|
+
if (level === "skipped") {
|
|
917
|
+
return colorize("[skipped]", ANSI_YELLOW);
|
|
918
|
+
}
|
|
919
|
+
return colorize("[warning]", ANSI_MAGENTA);
|
|
920
|
+
}
|
|
921
|
+
function colorize(value, colorCode) {
|
|
922
|
+
if (!shouldUseColor()) return value;
|
|
923
|
+
return `${colorCode}${value}${ANSI_RESET}`;
|
|
924
|
+
}
|
|
925
|
+
function shouldUseColor() {
|
|
926
|
+
var _a, _b;
|
|
927
|
+
if (process.env.NO_COLOR != null) return false;
|
|
928
|
+
if (process.env.FORCE_COLOR === "0") return false;
|
|
929
|
+
if (process.env.FORCE_COLOR != null) return true;
|
|
930
|
+
if (process.env.CLICOLOR === "0") return false;
|
|
931
|
+
if (process.env.CLICOLOR_FORCE != null && process.env.CLICOLOR_FORCE !== "0") return true;
|
|
932
|
+
if (((_a = process.stdout) == null ? void 0 : _a.isTTY) === true || ((_b = process.stderr) == null ? void 0 : _b.isTTY) === true) {
|
|
933
|
+
return true;
|
|
934
|
+
}
|
|
935
|
+
const colorTerm = process.env.COLORTERM;
|
|
936
|
+
if (colorTerm != null && colorTerm !== "") {
|
|
937
|
+
return true;
|
|
938
|
+
}
|
|
939
|
+
const term = process.env.TERM;
|
|
940
|
+
return term != null && term !== "" && term.toLowerCase() !== "dumb";
|
|
941
|
+
}
|
|
942
|
+
function formatPathLocation(payloadPath) {
|
|
943
|
+
var _a, _b, _c, _d;
|
|
944
|
+
if (!payloadPath) return "unknown file:unknown line";
|
|
945
|
+
const hub = payloadPath.hub;
|
|
946
|
+
const file = typeof hub === "object" && hub !== null && "file" in hub ? hub.file : void 0;
|
|
947
|
+
const filename = (_b = (_a = file == null ? void 0 : file.opts) == null ? void 0 : _a.filename) != null ? _b : "unknown file";
|
|
948
|
+
const lineNumber = (_d = (_c = payloadPath.node.loc) == null ? void 0 : _c.start.line) != null ? _d : "unknown line";
|
|
949
|
+
return `${filename}:${lineNumber}`;
|
|
950
|
+
}
|
|
466
951
|
|
|
467
952
|
const viewBlacklistedProperties = /* @__PURE__ */ new Set([
|
|
953
|
+
// TODO: process a11y props at runtime
|
|
468
954
|
"accessible",
|
|
469
955
|
"accessibilityLabel",
|
|
470
956
|
"accessibilityState",
|
|
471
|
-
"allowFontScaling",
|
|
472
957
|
"aria-busy",
|
|
473
958
|
"aria-checked",
|
|
474
959
|
"aria-disabled",
|
|
475
960
|
"aria-expanded",
|
|
476
961
|
"aria-label",
|
|
477
962
|
"aria-selected",
|
|
478
|
-
"ellipsizeMode",
|
|
479
|
-
"disabled",
|
|
480
963
|
"id",
|
|
481
964
|
"nativeID",
|
|
482
|
-
"numberOfLines",
|
|
483
|
-
"onLongPress",
|
|
484
|
-
"onPress",
|
|
485
|
-
"onPressIn",
|
|
486
|
-
"onPressOut",
|
|
487
|
-
"onResponderGrant",
|
|
488
|
-
"onResponderMove",
|
|
489
|
-
"onResponderRelease",
|
|
490
|
-
"onResponderTerminate",
|
|
491
|
-
"onResponderTerminationRequest",
|
|
492
|
-
"onStartShouldSetResponder",
|
|
493
|
-
"pressRetentionOffset",
|
|
494
|
-
"selectable",
|
|
495
|
-
"selectionColor",
|
|
496
|
-
"suppressHighlighting",
|
|
497
965
|
"style"
|
|
966
|
+
// TODO: process style at runtime
|
|
498
967
|
]);
|
|
499
|
-
const
|
|
500
|
-
const viewOptimizer = (path, log = () => {
|
|
501
|
-
}) => {
|
|
502
|
-
var _a, _b, _c;
|
|
503
|
-
if (isIgnoredLine(path)) return;
|
|
968
|
+
const viewOptimizer = (path, logger, options) => {
|
|
504
969
|
if (!isValidJSXComponent(path, "View")) return;
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
970
|
+
let ancestorClassification;
|
|
971
|
+
const getAncestorClassification = () => {
|
|
972
|
+
if (!ancestorClassification) {
|
|
973
|
+
ancestorClassification = getViewAncestorClassification(path);
|
|
974
|
+
}
|
|
975
|
+
return ancestorClassification;
|
|
976
|
+
};
|
|
977
|
+
const skipReason = getFirstBailoutReason([
|
|
978
|
+
{
|
|
979
|
+
reason: "line is marked with @boost-ignore",
|
|
980
|
+
shouldBail: () => isIgnoredLine(path)
|
|
981
|
+
},
|
|
982
|
+
{
|
|
983
|
+
reason: "View is not imported from react-native",
|
|
984
|
+
shouldBail: () => !isReactNativeImport(path, "View")
|
|
985
|
+
},
|
|
986
|
+
{
|
|
987
|
+
reason: "contains blacklisted props",
|
|
988
|
+
shouldBail: () => hasBlacklistedProperty(path, viewBlacklistedProperties)
|
|
989
|
+
},
|
|
990
|
+
{
|
|
991
|
+
reason: "has Text ancestor",
|
|
992
|
+
shouldBail: () => getAncestorClassification() === "text"
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
reason: "has unresolved ancestor and dangerous optimization is disabled",
|
|
996
|
+
shouldBail: () => getAncestorClassification() === "unknown" && (options == null ? void 0 : options.dangerouslyOptimizeViewWithUnknownAncestors) !== true
|
|
997
|
+
}
|
|
998
|
+
]);
|
|
999
|
+
if (skipReason) {
|
|
1000
|
+
logger.skipped({
|
|
1001
|
+
component: "View",
|
|
1002
|
+
path,
|
|
1003
|
+
reason: skipReason
|
|
1004
|
+
});
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
508
1007
|
const hub = path.hub;
|
|
509
1008
|
const file = typeof hub === "object" && hub !== null && "file" in hub ? hub.file : void 0;
|
|
510
1009
|
if (!file) {
|
|
511
1010
|
throw new PluginError("No file found in Babel hub");
|
|
512
1011
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
1012
|
+
logger.optimized({
|
|
1013
|
+
component: "View",
|
|
1014
|
+
path
|
|
1015
|
+
});
|
|
516
1016
|
const parent = path.parent;
|
|
517
1017
|
replaceWithNativeComponent(path, parent, file, "NativeView");
|
|
518
1018
|
};
|
|
@@ -524,16 +1024,26 @@ var index = declare((api) => {
|
|
|
524
1024
|
visitor: {
|
|
525
1025
|
JSXOpeningElement(path, state) {
|
|
526
1026
|
var _a, _b, _c, _d;
|
|
527
|
-
const
|
|
528
|
-
const
|
|
529
|
-
|
|
1027
|
+
const pluginState = state;
|
|
1028
|
+
const options = (_a = pluginState.opts) != null ? _a : {};
|
|
1029
|
+
const logger = getOrCreateLogger(pluginState, options);
|
|
530
1030
|
if (isIgnoredFile(path, (_b = options.ignores) != null ? _b : [])) return;
|
|
531
1031
|
if (((_c = options.optimizations) == null ? void 0 : _c.text) !== false) textOptimizer(path, logger);
|
|
532
|
-
if (((_d = options.optimizations) == null ? void 0 : _d.view) !== false) viewOptimizer(path, logger);
|
|
1032
|
+
if (((_d = options.optimizations) == null ? void 0 : _d.view) !== false) viewOptimizer(path, logger, options);
|
|
533
1033
|
}
|
|
534
1034
|
}
|
|
535
1035
|
};
|
|
536
1036
|
});
|
|
1037
|
+
function getOrCreateLogger(state, options) {
|
|
1038
|
+
if (state.__reactNativeBoostLogger) {
|
|
1039
|
+
return state.__reactNativeBoostLogger;
|
|
1040
|
+
}
|
|
1041
|
+
state.__reactNativeBoostLogger = createLogger({
|
|
1042
|
+
verbose: options.verbose === true,
|
|
1043
|
+
silent: options.silent === true
|
|
1044
|
+
});
|
|
1045
|
+
return state.__reactNativeBoostLogger;
|
|
1046
|
+
}
|
|
537
1047
|
|
|
538
1048
|
export { index as default };
|
|
539
1049
|
//# sourceMappingURL=index.mjs.map
|