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