react-native-boost 0.5.3 → 0.5.5
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/dist/plugin/esm/index.mjs +217 -161
- package/dist/plugin/esm/index.mjs.map +1 -1
- package/dist/plugin/index.js +217 -161
- package/dist/plugin/index.js.map +1 -1
- package/package.json +1 -1
- package/src/plugin/optimizers/text/index.ts +127 -155
- package/src/plugin/optimizers/view/index.ts +96 -50
- package/src/plugin/utils/common/attributes.ts +144 -0
- package/src/plugin/utils/common/base.ts +82 -0
- package/src/plugin/utils/common/index.ts +4 -0
- package/src/plugin/utils/common/node-types.ts +22 -0
- package/src/plugin/utils/common/validation.ts +181 -0
- package/src/plugin/utils/constants.ts +16 -0
- package/src/plugin/utils/common.ts +0 -296
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { declare } from '@babel/helper-plugin-utils';
|
|
2
2
|
import { types } from '@babel/core';
|
|
3
|
-
import { minimatch } from 'minimatch';
|
|
4
|
-
import path from 'node:path';
|
|
5
3
|
import { addDefault, addNamed } from '@babel/helper-module-imports';
|
|
4
|
+
import { minimatch } from 'minimatch';
|
|
5
|
+
import nodePath from 'node:path';
|
|
6
6
|
|
|
7
7
|
class PluginError extends Error {
|
|
8
8
|
constructor(message) {
|
|
@@ -11,15 +11,23 @@ class PluginError extends Error {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
const RUNTIME_MODULE_NAME = "react-native-boost";
|
|
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
|
+
]);
|
|
18
26
|
|
|
19
27
|
function addFileImportHint({
|
|
20
28
|
file,
|
|
21
29
|
nameHint,
|
|
22
|
-
path
|
|
30
|
+
path,
|
|
23
31
|
importName,
|
|
24
32
|
moduleName,
|
|
25
33
|
importType = "named"
|
|
@@ -27,12 +35,35 @@ function addFileImportHint({
|
|
|
27
35
|
var _a;
|
|
28
36
|
if (!((_a = file.__hasImports) == null ? void 0 : _a[nameHint])) {
|
|
29
37
|
file.__hasImports = file.__hasImports || {};
|
|
30
|
-
file.__hasImports[nameHint] = importType === "default" ? addDefault(
|
|
38
|
+
file.__hasImports[nameHint] = importType === "default" ? addDefault(path, moduleName, { nameHint }) : addNamed(path, importName, moduleName, { nameHint });
|
|
31
39
|
}
|
|
32
40
|
return file.__hasImports[nameHint];
|
|
33
41
|
}
|
|
34
|
-
const
|
|
35
|
-
const
|
|
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
|
+
const ensureArray = (value) => {
|
|
61
|
+
if (Array.isArray(value)) return value;
|
|
62
|
+
return [value];
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const isIgnoredFile = (path, ignores) => {
|
|
66
|
+
const hub = path.hub;
|
|
36
67
|
const file = typeof hub === "object" && hub !== null && "file" in hub ? hub.file : void 0;
|
|
37
68
|
if (!file) {
|
|
38
69
|
throw new PluginError("No file found in Babel hub");
|
|
@@ -40,19 +71,19 @@ const isIgnoredFile = (p, ignores) => {
|
|
|
40
71
|
const fileName = file.opts.filename;
|
|
41
72
|
const baseDirectory = "cwd" in file.opts ? file.opts.cwd : process.cwd();
|
|
42
73
|
for (const pattern of ignores) {
|
|
43
|
-
const absolutePattern =
|
|
74
|
+
const absolutePattern = nodePath.isAbsolute(pattern) ? pattern : nodePath.join(baseDirectory, pattern);
|
|
44
75
|
if (minimatch(fileName, absolutePattern, { dot: true })) {
|
|
45
76
|
return true;
|
|
46
77
|
}
|
|
47
78
|
}
|
|
48
79
|
return false;
|
|
49
80
|
};
|
|
50
|
-
const
|
|
81
|
+
const isIgnoredLine = (path) => {
|
|
51
82
|
var _a, _b, _c;
|
|
52
|
-
if ((_a =
|
|
83
|
+
if ((_a = path.node.leadingComments) == null ? void 0 : _a.some((comment) => comment.value.includes("@boost-ignore"))) {
|
|
53
84
|
return true;
|
|
54
85
|
}
|
|
55
|
-
const jsxElementPath =
|
|
86
|
+
const jsxElementPath = path.parentPath;
|
|
56
87
|
if ((_b = jsxElementPath.node.leadingComments) == null ? void 0 : _b.some((comment) => comment.value.includes("@boost-ignore"))) {
|
|
57
88
|
return true;
|
|
58
89
|
}
|
|
@@ -90,11 +121,51 @@ const shouldIgnoreOptimization = (path2) => {
|
|
|
90
121
|
}
|
|
91
122
|
return false;
|
|
92
123
|
};
|
|
93
|
-
const
|
|
94
|
-
|
|
124
|
+
const isValidJSXComponent = (path, componentName) => {
|
|
125
|
+
if (!types.isJSXIdentifier(path.node.name)) return false;
|
|
126
|
+
const parent = path.parent;
|
|
127
|
+
if (!types.isJSXElement(parent)) return false;
|
|
128
|
+
const componentIdentifier = path.node.name.name;
|
|
129
|
+
const binding = path.scope.getBinding(componentIdentifier);
|
|
130
|
+
if (!binding) return false;
|
|
131
|
+
if (binding.kind === "module" && types.isImportDeclaration(binding.path.parent) && types.isImportSpecifier(binding.path.node)) {
|
|
132
|
+
const imported = binding.path.node.imported;
|
|
133
|
+
if (types.isIdentifier(imported)) {
|
|
134
|
+
return imported.name === componentName;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return path.node.name.name === componentName;
|
|
138
|
+
};
|
|
139
|
+
const isReactNativeImport = (path, expectedImportedName) => {
|
|
140
|
+
if (!types.isJSXIdentifier(path.node.name)) return false;
|
|
141
|
+
const localName = path.node.name.name;
|
|
142
|
+
const binding = path.scope.getBinding(localName);
|
|
143
|
+
if (!binding) return false;
|
|
144
|
+
if (binding.kind === "module") {
|
|
145
|
+
const importDeclaration = binding.path.parent;
|
|
146
|
+
if (!types.isImportDeclaration(importDeclaration)) return false;
|
|
147
|
+
if (importDeclaration.source.value !== "react-native") return false;
|
|
148
|
+
if (types.isImportSpecifier(binding.path.node)) {
|
|
149
|
+
const imported = binding.path.node.imported;
|
|
150
|
+
if (types.isIdentifier(imported)) {
|
|
151
|
+
return imported.name === expectedImportedName;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (types.isImportDefaultSpecifier(binding.path.node)) {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const hasBlacklistedProperty = (path, blacklist) => {
|
|
162
|
+
return path.node.attributes.some((attribute) => {
|
|
163
|
+
if (types.isJSXAttribute(attribute) && types.isJSXIdentifier(attribute.name) && blacklist.has(attribute.name.name)) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
95
166
|
if (types.isJSXSpreadAttribute(attribute)) {
|
|
96
167
|
if (types.isIdentifier(attribute.argument)) {
|
|
97
|
-
const binding =
|
|
168
|
+
const binding = path.scope.getBinding(attribute.argument.name);
|
|
98
169
|
let objectExpression;
|
|
99
170
|
if (binding) {
|
|
100
171
|
if (types.isVariableDeclarator(binding.path.node)) {
|
|
@@ -147,38 +218,27 @@ const buildPropertiesFromAttributes = (attributes) => {
|
|
|
147
218
|
...arguments_
|
|
148
219
|
]);
|
|
149
220
|
};
|
|
150
|
-
const
|
|
151
|
-
"accessibilityLabel",
|
|
152
|
-
"aria-label",
|
|
153
|
-
"accessibilityState",
|
|
154
|
-
"aria-busy",
|
|
155
|
-
"aria-checked",
|
|
156
|
-
"aria-disabled",
|
|
157
|
-
"aria-expanded",
|
|
158
|
-
"aria-selected",
|
|
159
|
-
"accessible"
|
|
160
|
-
]);
|
|
161
|
-
const hasAccessibilityProperty = (path2, attributes) => {
|
|
221
|
+
const hasAccessibilityProperty = (path, attributes) => {
|
|
162
222
|
for (const attribute of attributes) {
|
|
163
223
|
if (types.isJSXAttribute(attribute)) {
|
|
164
224
|
const key = attribute.name.name;
|
|
165
|
-
if (typeof key === "string" &&
|
|
225
|
+
if (typeof key === "string" && ACCESSIBILITY_PROPERTIES.has(key)) {
|
|
166
226
|
return true;
|
|
167
227
|
}
|
|
168
228
|
} else if (types.isJSXSpreadAttribute(attribute)) {
|
|
169
229
|
if (types.isObjectExpression(attribute.argument)) {
|
|
170
230
|
for (const property of attribute.argument.properties) {
|
|
171
|
-
if (types.isObjectProperty(property) && types.isIdentifier(property.key) &&
|
|
231
|
+
if (types.isObjectProperty(property) && types.isIdentifier(property.key) && ACCESSIBILITY_PROPERTIES.has(property.key.name)) {
|
|
172
232
|
return true;
|
|
173
233
|
}
|
|
174
234
|
}
|
|
175
235
|
} else if (types.isIdentifier(attribute.argument)) {
|
|
176
|
-
const binding =
|
|
236
|
+
const binding = path.scope.getBinding(attribute.argument.name);
|
|
177
237
|
if (binding && types.isVariableDeclarator(binding.path.node)) {
|
|
178
238
|
const declarator = binding.path.node;
|
|
179
239
|
if (declarator.init && types.isObjectExpression(declarator.init)) {
|
|
180
240
|
for (const property of declarator.init.properties) {
|
|
181
|
-
if (types.isObjectProperty(property) && types.isIdentifier(property.key) &&
|
|
241
|
+
if (types.isObjectProperty(property) && types.isIdentifier(property.key) && ACCESSIBILITY_PROPERTIES.has(property.key.name)) {
|
|
182
242
|
return true;
|
|
183
243
|
}
|
|
184
244
|
}
|
|
@@ -194,6 +254,22 @@ const hasAccessibilityProperty = (path2, attributes) => {
|
|
|
194
254
|
return false;
|
|
195
255
|
};
|
|
196
256
|
|
|
257
|
+
const isStringNode = (path, child) => {
|
|
258
|
+
if (types.isJSXText(child) || types.isStringLiteral(child)) return true;
|
|
259
|
+
if (types.isJSXExpressionContainer(child)) {
|
|
260
|
+
const expression = child.expression;
|
|
261
|
+
if (types.isIdentifier(expression)) {
|
|
262
|
+
const binding = path.scope.getBinding(expression.name);
|
|
263
|
+
if (binding && binding.path.node && types.isVariableDeclarator(binding.path.node)) {
|
|
264
|
+
return !!binding.path.node.init && types.isStringLiteral(binding.path.node.init);
|
|
265
|
+
}
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
if (types.isStringLiteral(expression)) return true;
|
|
269
|
+
}
|
|
270
|
+
return false;
|
|
271
|
+
};
|
|
272
|
+
|
|
197
273
|
const textBlacklistedProperties = /* @__PURE__ */ new Set([
|
|
198
274
|
"allowFontScaling",
|
|
199
275
|
"ellipsizeMode",
|
|
@@ -217,25 +293,12 @@ const textBlacklistedProperties = /* @__PURE__ */ new Set([
|
|
|
217
293
|
const textOptimizer = (path, log = () => {
|
|
218
294
|
}) => {
|
|
219
295
|
var _a, _b, _c;
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
-
if (!
|
|
223
|
-
const elementName = path.node.name.name;
|
|
224
|
-
if (elementName !== "Text") return;
|
|
225
|
-
if (shouldIgnoreOptimization(path)) {
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
const binding = path.scope.getBinding(elementName);
|
|
229
|
-
if (!binding) return;
|
|
230
|
-
if (binding.kind === "module") {
|
|
231
|
-
const parentNode = binding.path.parent;
|
|
232
|
-
if (!types.isImportDeclaration(parentNode) || parentNode.source.value !== "react-native") {
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
296
|
+
if (isIgnoredLine(path)) return;
|
|
297
|
+
if (!isValidJSXComponent(path, "Text")) return;
|
|
298
|
+
if (!isReactNativeImport(path, "Text")) return;
|
|
236
299
|
if (hasBlacklistedProperty(path, textBlacklistedProperties)) return;
|
|
237
|
-
|
|
238
|
-
if (
|
|
300
|
+
const parent = path.parent;
|
|
301
|
+
if (hasInvalidChildren(path, parent)) return;
|
|
239
302
|
const hub = path.hub;
|
|
240
303
|
const file = typeof hub === "object" && hub !== null && "file" in hub ? hub.file : void 0;
|
|
241
304
|
if (!file) {
|
|
@@ -244,32 +307,69 @@ const textOptimizer = (path, log = () => {
|
|
|
244
307
|
const filename = ((_a = file.opts) == null ? void 0 : _a.filename) || "unknown file";
|
|
245
308
|
const lineNumber = (_c = (_b = path.node.loc) == null ? void 0 : _b.start.line) != null ? _c : "unknown line";
|
|
246
309
|
log(`Optimizing Text component in ${filename}:${lineNumber}`);
|
|
247
|
-
fixNegativeNumberOfLines({ path, log });
|
|
248
310
|
const originalAttributes = [...path.node.attributes];
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
311
|
+
fixNegativeNumberOfLines({ path, log });
|
|
312
|
+
processProps(path, file, originalAttributes);
|
|
313
|
+
replaceWithNativeComponent(path, parent, file, "NativeText");
|
|
314
|
+
};
|
|
315
|
+
function hasInvalidChildren(path, parent) {
|
|
316
|
+
for (const attribute of path.node.attributes) {
|
|
317
|
+
if (types.isJSXSpreadAttribute(attribute)) continue;
|
|
318
|
+
if (types.isJSXIdentifier(attribute.name) && attribute.value && // For a "children" attribute, optimization is allowed only if it is a string
|
|
319
|
+
attribute.name.name === "children" && !isStringNode(path, attribute.value)) {
|
|
320
|
+
return true;
|
|
254
321
|
}
|
|
255
322
|
}
|
|
256
|
-
|
|
257
|
-
|
|
323
|
+
return !parent.children.every((child) => isStringNode(path, child));
|
|
324
|
+
}
|
|
325
|
+
function fixNegativeNumberOfLines({
|
|
326
|
+
path,
|
|
327
|
+
log
|
|
328
|
+
}) {
|
|
329
|
+
for (const attribute of path.node.attributes) {
|
|
330
|
+
if (types.isJSXAttribute(attribute) && types.isJSXIdentifier(attribute.name, { name: "numberOfLines" }) && attribute.value && types.isJSXExpressionContainer(attribute.value)) {
|
|
331
|
+
let originalValue;
|
|
332
|
+
if (types.isNumericLiteral(attribute.value.expression)) {
|
|
333
|
+
originalValue = attribute.value.expression.value;
|
|
334
|
+
} else if (types.isUnaryExpression(attribute.value.expression) && attribute.value.expression.operator === "-" && types.isNumericLiteral(attribute.value.expression.argument)) {
|
|
335
|
+
originalValue = -attribute.value.expression.argument.value;
|
|
336
|
+
}
|
|
337
|
+
if (originalValue !== void 0 && originalValue < 0) {
|
|
338
|
+
log(
|
|
339
|
+
`Warning: 'numberOfLines' in <Text> must be a non-negative number, received: ${originalValue}. The value will be set to 0.`
|
|
340
|
+
);
|
|
341
|
+
attribute.value.expression = types.numericLiteral(0);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function extractStyleAttribute(attributes) {
|
|
347
|
+
for (const attribute of attributes) {
|
|
348
|
+
if (types.isJSXAttribute(attribute) && types.isJSXIdentifier(attribute.name, { name: "style" })) {
|
|
349
|
+
if (attribute.value && types.isJSXExpressionContainer(attribute.value) && !types.isJSXEmptyExpression(attribute.value.expression)) {
|
|
350
|
+
return {
|
|
351
|
+
styleAttribute: attribute,
|
|
352
|
+
styleExpr: attribute.value.expression
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
return { styleAttribute: attribute };
|
|
356
|
+
}
|
|
258
357
|
}
|
|
358
|
+
return {};
|
|
359
|
+
}
|
|
360
|
+
function processProps(path, file, originalAttributes) {
|
|
361
|
+
const { styleExpr } = extractStyleAttribute(originalAttributes);
|
|
259
362
|
const hasA11y = hasAccessibilityProperty(path, originalAttributes);
|
|
260
363
|
if (styleExpr && hasA11y) {
|
|
261
|
-
const accessibilityAttributes = originalAttributes.filter(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
return true;
|
|
266
|
-
});
|
|
364
|
+
const accessibilityAttributes = originalAttributes.filter(
|
|
365
|
+
(attribute) => !(types.isJSXAttribute(attribute) && types.isJSXIdentifier(attribute.name, { name: "style" }))
|
|
366
|
+
);
|
|
267
367
|
const normalizeIdentifier = addFileImportHint({
|
|
268
368
|
file,
|
|
269
369
|
nameHint: "normalizeAccessibilityProps",
|
|
270
370
|
path,
|
|
271
371
|
importName: "normalizeAccessibilityProps",
|
|
272
|
-
moduleName:
|
|
372
|
+
moduleName: RUNTIME_MODULE_NAME
|
|
273
373
|
});
|
|
274
374
|
const accessibilityObject = buildPropertiesFromAttributes(accessibilityAttributes);
|
|
275
375
|
const accessibilityExpr = types.callExpression(types.identifier(normalizeIdentifier.name), [accessibilityObject]);
|
|
@@ -278,7 +378,7 @@ const textOptimizer = (path, log = () => {
|
|
|
278
378
|
nameHint: "flattenTextStyle",
|
|
279
379
|
path,
|
|
280
380
|
importName: "flattenTextStyle",
|
|
281
|
-
moduleName:
|
|
381
|
+
moduleName: RUNTIME_MODULE_NAME
|
|
282
382
|
});
|
|
283
383
|
const flattenedStyleExpr = types.callExpression(types.identifier(flattenIdentifier.name), [styleExpr]);
|
|
284
384
|
path.node.attributes = [types.jsxSpreadAttribute(accessibilityExpr), types.jsxSpreadAttribute(flattenedStyleExpr)];
|
|
@@ -288,7 +388,7 @@ const textOptimizer = (path, log = () => {
|
|
|
288
388
|
nameHint: "flattenTextStyle",
|
|
289
389
|
path,
|
|
290
390
|
importName: "flattenTextStyle",
|
|
291
|
-
moduleName:
|
|
391
|
+
moduleName: RUNTIME_MODULE_NAME
|
|
292
392
|
});
|
|
293
393
|
const flattened = types.callExpression(types.identifier(flattenIdentifier.name), [styleExpr]);
|
|
294
394
|
path.node.attributes = [types.jsxSpreadAttribute(flattened)];
|
|
@@ -298,77 +398,12 @@ const textOptimizer = (path, log = () => {
|
|
|
298
398
|
nameHint: "normalizeAccessibilityProps",
|
|
299
399
|
path,
|
|
300
400
|
importName: "normalizeAccessibilityProps",
|
|
301
|
-
moduleName:
|
|
401
|
+
moduleName: RUNTIME_MODULE_NAME
|
|
302
402
|
});
|
|
303
403
|
const propsObject = buildPropertiesFromAttributes(originalAttributes);
|
|
304
404
|
const normalized = types.callExpression(types.identifier(normalizeIdentifier.name), [propsObject]);
|
|
305
405
|
path.node.attributes = [types.jsxSpreadAttribute(normalized)];
|
|
306
406
|
}
|
|
307
|
-
const nativeTextIdentifier = addFileImportHint({
|
|
308
|
-
file,
|
|
309
|
-
nameHint: "NativeText",
|
|
310
|
-
path,
|
|
311
|
-
importName: "NativeText",
|
|
312
|
-
moduleName: "react-native-boost"
|
|
313
|
-
});
|
|
314
|
-
path.node.name.name = nativeTextIdentifier.name;
|
|
315
|
-
if (!path.node.selfClosing && parent.closingElement && types.isJSXIdentifier(parent.closingElement.name) && parent.closingElement.name.name === "Text") {
|
|
316
|
-
parent.closingElement.name.name = nativeTextIdentifier.name;
|
|
317
|
-
}
|
|
318
|
-
};
|
|
319
|
-
function hasOnlyStringChildren(path, node) {
|
|
320
|
-
return node.children.every((child) => isStringNode(path, child));
|
|
321
|
-
}
|
|
322
|
-
function isStringNode(path, child) {
|
|
323
|
-
if (types.isJSXText(child) || types.isStringLiteral(child)) return true;
|
|
324
|
-
if (types.isJSXExpressionContainer(child)) {
|
|
325
|
-
const expression = child.expression;
|
|
326
|
-
if (types.isIdentifier(expression)) {
|
|
327
|
-
const binding = path.scope.getBinding(expression.name);
|
|
328
|
-
if (binding && binding.path.node && types.isVariableDeclarator(binding.path.node)) {
|
|
329
|
-
return !!binding.path.node.init && types.isStringLiteral(binding.path.node.init);
|
|
330
|
-
}
|
|
331
|
-
return false;
|
|
332
|
-
}
|
|
333
|
-
if (types.isStringLiteral(expression)) return true;
|
|
334
|
-
}
|
|
335
|
-
return false;
|
|
336
|
-
}
|
|
337
|
-
function fixNegativeNumberOfLines({
|
|
338
|
-
path,
|
|
339
|
-
log
|
|
340
|
-
}) {
|
|
341
|
-
for (const attribute of path.node.attributes) {
|
|
342
|
-
if (types.isJSXAttribute(attribute) && types.isJSXIdentifier(attribute.name, { name: "numberOfLines" }) && attribute.value && types.isJSXExpressionContainer(attribute.value)) {
|
|
343
|
-
let originalValue;
|
|
344
|
-
if (types.isNumericLiteral(attribute.value.expression)) {
|
|
345
|
-
originalValue = attribute.value.expression.value;
|
|
346
|
-
} else if (types.isUnaryExpression(attribute.value.expression) && attribute.value.expression.operator === "-" && types.isNumericLiteral(attribute.value.expression.argument)) {
|
|
347
|
-
originalValue = -attribute.value.expression.argument.value;
|
|
348
|
-
}
|
|
349
|
-
if (originalValue !== void 0 && originalValue < 0) {
|
|
350
|
-
log(
|
|
351
|
-
`Warning: 'numberOfLines' in <Text> must be a non-negative number, received: ${originalValue}. The value will be set to 0.`
|
|
352
|
-
);
|
|
353
|
-
attribute.value.expression = types.numericLiteral(0);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
function hasInvalidChildren(path) {
|
|
359
|
-
for (const attribute of path.node.attributes) {
|
|
360
|
-
if (types.isJSXSpreadAttribute(attribute)) continue;
|
|
361
|
-
if (types.isJSXIdentifier(attribute.name) && attribute.value) {
|
|
362
|
-
if (attribute.name.name === "children") {
|
|
363
|
-
if (!isStringNode(path, attribute.value)) {
|
|
364
|
-
return true;
|
|
365
|
-
}
|
|
366
|
-
} else if (textBlacklistedProperties.has(attribute.name.name)) {
|
|
367
|
-
return true;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
return false;
|
|
372
407
|
}
|
|
373
408
|
|
|
374
409
|
const log = (message) => {
|
|
@@ -410,20 +445,9 @@ const viewBlacklistedProperties = /* @__PURE__ */ new Set([
|
|
|
410
445
|
const viewOptimizer = (path, log = () => {
|
|
411
446
|
}) => {
|
|
412
447
|
var _a, _b, _c;
|
|
413
|
-
if (
|
|
414
|
-
|
|
415
|
-
if (!
|
|
416
|
-
const elementName = path.node.name.name;
|
|
417
|
-
if (elementName !== "View") return;
|
|
418
|
-
if (shouldIgnoreOptimization(path)) return;
|
|
419
|
-
const binding = path.scope.getBinding(elementName);
|
|
420
|
-
if (!binding) return;
|
|
421
|
-
if (binding.kind === "module") {
|
|
422
|
-
const parentNode = binding.path.parent;
|
|
423
|
-
if (!types.isImportDeclaration(parentNode) || parentNode.source.value !== "react-native") {
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
448
|
+
if (isIgnoredLine(path)) return;
|
|
449
|
+
if (!isValidJSXComponent(path, "View")) return;
|
|
450
|
+
if (!isReactNativeImport(path, "View")) return;
|
|
427
451
|
if (hasBlacklistedProperty(path, viewBlacklistedProperties)) return;
|
|
428
452
|
if (hasTextAncestor(path)) return;
|
|
429
453
|
const hub = path.hub;
|
|
@@ -434,23 +458,55 @@ const viewOptimizer = (path, log = () => {
|
|
|
434
458
|
const filename = ((_a = file.opts) == null ? void 0 : _a.filename) || "unknown file";
|
|
435
459
|
const lineNumber = (_c = (_b = path.node.loc) == null ? void 0 : _b.start.line) != null ? _c : "unknown line";
|
|
436
460
|
log(`Optimizing View component in ${filename}:${lineNumber}`);
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
path,
|
|
440
|
-
importName: "NativeView",
|
|
441
|
-
moduleName: "react-native-boost",
|
|
442
|
-
importType: "named",
|
|
443
|
-
nameHint: "NativeView"
|
|
444
|
-
});
|
|
445
|
-
path.node.name.name = viewNativeIdentifier.name;
|
|
446
|
-
if (!path.node.selfClosing && parent.closingElement && types.isJSXIdentifier(parent.closingElement.name) && parent.closingElement.name.name === "View") {
|
|
447
|
-
parent.closingElement.name.name = viewNativeIdentifier.name;
|
|
448
|
-
}
|
|
461
|
+
const parent = path.parent;
|
|
462
|
+
replaceWithNativeComponent(path, parent, file, "NativeView");
|
|
449
463
|
};
|
|
450
464
|
function hasTextAncestor(path) {
|
|
451
|
-
|
|
465
|
+
const directTextAncestor = path.findParent((parentPath) => {
|
|
452
466
|
return types.isJSXElement(parentPath.node) && types.isJSXIdentifier(parentPath.node.openingElement.name, { name: "Text" });
|
|
453
467
|
});
|
|
468
|
+
if (directTextAncestor) return true;
|
|
469
|
+
return !!path.findParent((parentPath) => {
|
|
470
|
+
if (!types.isJSXElement(parentPath.node)) return false;
|
|
471
|
+
const openingElement = parentPath.node.openingElement;
|
|
472
|
+
if (!types.isJSXIdentifier(openingElement.name)) return false;
|
|
473
|
+
const componentName = openingElement.name.name;
|
|
474
|
+
if (componentName === "Text" || componentName === "View" || componentName === "Fragment" || componentName[0] === componentName[0].toLowerCase()) {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
const binding = parentPath.scope.getBinding(componentName);
|
|
478
|
+
if (!binding) return false;
|
|
479
|
+
if (types.isVariableDeclarator(binding.path.node)) {
|
|
480
|
+
const init = binding.path.node.init;
|
|
481
|
+
if (types.isArrowFunctionExpression(init) || types.isFunctionExpression(init)) {
|
|
482
|
+
return types.isBlockStatement(init.body) ? hasTextInReturnStatement(init.body) : hasTextInExpression(init.body);
|
|
483
|
+
}
|
|
484
|
+
} else if (types.isFunctionDeclaration(binding.path.node)) {
|
|
485
|
+
return hasTextInReturnStatement(binding.path.node.body);
|
|
486
|
+
}
|
|
487
|
+
return false;
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
function hasTextInReturnStatement(blockStatement) {
|
|
491
|
+
for (const statement of blockStatement.body) {
|
|
492
|
+
if (types.isReturnStatement(statement) && statement.argument && hasTextInExpression(statement.argument)) {
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
function hasTextInExpression(expression) {
|
|
499
|
+
if (types.isJSXElement(expression)) {
|
|
500
|
+
if (types.isJSXIdentifier(expression.openingElement.name, { name: "Text" })) {
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
for (const child of expression.children) {
|
|
504
|
+
if (types.isJSXElement(child) && types.isJSXIdentifier(child.openingElement.name, { name: "Text" })) {
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return false;
|
|
454
510
|
}
|
|
455
511
|
|
|
456
512
|
var index = declare((api) => {
|