react-native-boost 0.5.3 → 0.5.4

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