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