react-native-boost 0.6.1 → 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.
Files changed (35) hide show
  1. package/README.md +0 -3
  2. package/dist/plugin/esm/index.mjs +709 -199
  3. package/dist/plugin/esm/index.mjs.map +1 -1
  4. package/dist/plugin/index.d.ts +54 -0
  5. package/dist/plugin/index.js +709 -199
  6. package/dist/plugin/index.js.map +1 -1
  7. package/dist/runtime/esm/index.mjs +16 -5
  8. package/dist/runtime/esm/index.mjs.map +1 -1
  9. package/dist/runtime/esm/index.web.mjs.map +1 -1
  10. package/dist/runtime/index.d.ts +51 -4
  11. package/dist/runtime/index.js +16 -5
  12. package/dist/runtime/index.js.map +1 -1
  13. package/dist/runtime/index.web.d.ts +13 -1
  14. package/dist/runtime/index.web.js.map +1 -1
  15. package/package.json +13 -21
  16. package/src/plugin/index.ts +27 -5
  17. package/src/plugin/optimizers/text/index.ts +116 -92
  18. package/src/plugin/optimizers/view/index.ts +53 -31
  19. package/src/plugin/types/index.ts +67 -17
  20. package/src/plugin/utils/common/attributes.ts +165 -0
  21. package/src/plugin/utils/common/index.ts +1 -3
  22. package/src/plugin/utils/common/validation.ts +513 -0
  23. package/src/plugin/utils/constants.ts +9 -0
  24. package/src/plugin/utils/format-test-result.ts +29 -0
  25. package/src/plugin/utils/generate-test-plugin.ts +9 -3
  26. package/src/plugin/utils/helpers.ts +15 -0
  27. package/src/plugin/utils/logger.ts +109 -2
  28. package/src/runtime/components/native-text.tsx +21 -5
  29. package/src/runtime/components/native-view.tsx +21 -5
  30. package/src/runtime/index.ts +22 -3
  31. package/src/runtime/types/index.ts +5 -0
  32. package/src/runtime/types/react-native.d.ts +0 -6
  33. package/src/runtime/utils/constants.ts +6 -2
  34. package/src/plugin/utils/common/ancestors.ts +0 -120
  35. package/src/plugin/utils/common/node-types.ts +0 -22
@@ -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 hasComponentAncestor(path, componentName, skipComponents = ["Fragment"]) {
276
- const directAncestor = path.findParent((parentPath) => {
277
- return core.types.isJSXElement(parentPath.node) && core.types.isJSXIdentifier(parentPath.node.openingElement.name, { name: componentName });
278
- });
279
- if (directAncestor) return true;
280
- return !!path.findParent((parentPath) => {
281
- if (!core.types.isJSXElement(parentPath.node)) return false;
282
- const openingElement = parentPath.node.openingElement;
283
- if (!core.types.isJSXIdentifier(openingElement.name)) return false;
284
- const ancestorComponentName = openingElement.name.name;
285
- if (ancestorComponentName === componentName) {
286
- return false;
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 false;
701
+ return file.__hasImports[nameHint];
314
702
  }
315
- function hasComponentInExpression(expression, componentName) {
316
- if (core.types.isJSXElement(expression)) {
317
- if (core.types.isJSXIdentifier(expression.openingElement.name, { name: componentName })) {
318
- return true;
319
- }
320
- for (const child of expression.children) {
321
- if (core.types.isJSXElement(child) && core.types.isJSXIdentifier(child.openingElement.name, { name: componentName })) {
322
- return true;
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 false;
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, log = () => {
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
- if (hasInvalidChildren(path, parent)) return;
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
- const filename = ((_a = file.opts) == null ? void 0 : _a.filename) || "unknown file";
364
- const lineNumber = (_c = (_b = path.node.loc) == null ? void 0 : _b.start.line) != null ? _c : "unknown line";
365
- log(`Optimizing Text component in ${filename}:${lineNumber}`);
366
- const originalAttributes = [...path.node.attributes];
367
- fixNegativeNumberOfLines({ path, log });
368
- processProps(path, file, originalAttributes);
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
- log(
395
- `Warning: 'numberOfLines' in <Text> must be a non-negative number, received: ${originalValue}. The value will be set to 0.`
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 extractStyleAttribute(attributes) {
403
- for (const attribute of attributes) {
404
- if (core.types.isJSXAttribute(attribute) && core.types.isJSXIdentifier(attribute.name, { name: "style" })) {
405
- if (attribute.value && core.types.isJSXExpressionContainer(attribute.value) && !core.types.isJSXEmptyExpression(attribute.value.expression)) {
406
- return {
407
- styleAttribute: attribute,
408
- styleExpr: attribute.value.expression
409
- };
410
- }
411
- return { styleAttribute: attribute };
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
- path.node.attributes = [core.types.jsxSpreadAttribute(accessibilityExpr), core.types.jsxSpreadAttribute(flattenedStyleExpr)];
441
- } else if (styleExpr) {
442
- const flattenIdentifier = addFileImportHint({
443
- file,
444
- nameHint: "processTextStyle",
445
- path,
446
- importName: "processTextStyle",
447
- moduleName: RUNTIME_MODULE_NAME
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 log = (message) => {
466
- console.log(`[react-native-boost] ${message}`);
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 skipComponents = ["View", "Fragment", "ScrollView", "FlatList"];
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
- if (!isReactNativeImport(path, "View")) return;
508
- if (hasBlacklistedProperty(path, viewBlacklistedProperties)) return;
509
- if (hasComponentAncestor(path, "Text", skipComponents)) return;
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
- const filename = ((_a = file.opts) == null ? void 0 : _a.filename) || "unknown file";
516
- const lineNumber = (_c = (_b = path.node.loc) == null ? void 0 : _b.start.line) != null ? _c : "unknown line";
517
- log(`Optimizing View component in ${filename}:${lineNumber}`);
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 options = (_a = state.opts) != null ? _a : {};
530
- const logger = options.verbose ? log : () => {
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