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,33 +1,83 @@
1
1
  import { NodePath, types as t } from '@babel/core';
2
2
 
3
+ export interface PluginOptimizationOptions {
4
+ /**
5
+ * Whether to optimize the `Text` component.
6
+ * @default true
7
+ */
8
+ text?: boolean;
9
+ /**
10
+ * Whether to optimize the `View` component.
11
+ * @default true
12
+ */
13
+ view?: boolean;
14
+ }
15
+
3
16
  export interface PluginOptions {
4
17
  /**
5
- * Paths to ignore from optimization. Relative to the Babel configuration file.
18
+ * Paths to ignore from optimization.
19
+ *
20
+ * Patterns are resolved from Babel's current working directory.
21
+ * In nested monorepo apps, parent segments may be needed, for example `../../node_modules/**`.
22
+ * @default []
6
23
  */
7
24
  ignores?: string[];
8
25
  /**
9
- * Whether or not to log optimized files to the console.
26
+ * Enables verbose logging.
27
+ *
28
+ * With `silent: false`, optimized components are logged by default.
29
+ * When enabled, skipped components and their skip reasons are also logged.
10
30
  * @default false
11
31
  */
12
32
  verbose?: boolean;
13
33
  /**
14
- * The optimizations to apply to the plugin.
15
- */
16
- optimizations?: {
17
- /**
18
- * Whether or not to optimize the Text component.
19
- * @default true
20
- */
21
- text?: boolean;
22
- /**
23
- * Whether or not to optimize the View component.
24
- * @default true
25
- */
26
- view?: boolean;
27
- };
34
+ * Disables all plugin logs.
35
+ *
36
+ * When set to `true`, this overrides `verbose`.
37
+ * @default false
38
+ */
39
+ silent?: boolean;
40
+ /**
41
+ * Toggle individual optimizers.
42
+ *
43
+ * If omitted, all available optimizers are enabled.
44
+ */
45
+ optimizations?: PluginOptimizationOptions;
46
+ /**
47
+ * Opt-in flag that allows View optimization when ancestor components cannot be statically resolved.
48
+ *
49
+ * This increases optimization coverage, but may introduce behavioral differences
50
+ * when unresolved ancestors render React Native `Text` wrappers.
51
+ * Prefer targeted ignores first, and enable this only after verifying affected screens.
52
+ * @default false
53
+ */
54
+ dangerouslyOptimizeViewWithUnknownAncestors?: boolean;
55
+ }
56
+
57
+ export type OptimizableComponent = 'Text' | 'View';
58
+
59
+ export interface OptimizationLogPayload {
60
+ component: OptimizableComponent;
61
+ path: NodePath<t.JSXOpeningElement>;
62
+ }
63
+
64
+ export interface SkippedOptimizationLogPayload extends OptimizationLogPayload {
65
+ reason: string;
66
+ }
67
+
68
+ export interface WarningLogPayload {
69
+ message: string;
70
+ component?: OptimizableComponent;
71
+ path?: NodePath<t.JSXOpeningElement>;
72
+ }
73
+
74
+ export interface PluginLogger {
75
+ optimized: (payload: OptimizationLogPayload) => void;
76
+ skipped: (payload: SkippedOptimizationLogPayload) => void;
77
+ warning: (payload: WarningLogPayload) => void;
28
78
  }
29
79
 
30
- export type Optimizer = (path: NodePath<t.JSXOpeningElement>, log?: (message: string) => void) => void;
80
+ export type Optimizer = (path: NodePath<t.JSXOpeningElement>, logger: PluginLogger, options?: PluginOptions) => void;
31
81
 
32
82
  export type HubFile = t.File & {
33
83
  opts: {
@@ -1,5 +1,6 @@
1
1
  import { NodePath, types as t } from '@babel/core';
2
2
  import { ACCESSIBILITY_PROPERTIES } from '../constants';
3
+ import { USER_SELECT_STYLE_TO_SELECTABLE_PROP } from '../constants';
3
4
 
4
5
  /**
5
6
  * Checks if the JSX element has a blacklisted property.
@@ -46,6 +47,67 @@ export const hasBlacklistedProperty = (path: NodePath<t.JSXOpeningElement>, blac
46
47
  });
47
48
  };
48
49
 
50
+ /**
51
+ * Adds a default property to a JSX element if it's not already defined. It avoids adding a default
52
+ * if it cannot statically determine whether the property is already set.
53
+ *
54
+ * @param path - The path to the JSXOpeningElement.
55
+ * @param key - The property key.
56
+ * @param value - The default value expression.
57
+ */
58
+ export const addDefaultProperty = (path: NodePath<t.JSXOpeningElement>, key: string, value: t.Expression) => {
59
+ let propertyIsFound = false;
60
+ let hasUnresolvableSpread = false;
61
+
62
+ for (const attribute of path.node.attributes) {
63
+ if (t.isJSXAttribute(attribute) && attribute.name.name === key) {
64
+ propertyIsFound = true;
65
+ break;
66
+ }
67
+
68
+ if (t.isJSXSpreadAttribute(attribute)) {
69
+ if (t.isObjectExpression(attribute.argument)) {
70
+ const propertyInSpread = attribute.argument.properties.some(
71
+ (p) =>
72
+ (t.isObjectProperty(p) && t.isIdentifier(p.key) && p.key.name === key) ||
73
+ (t.isObjectProperty(p) && t.isStringLiteral(p.key) && p.key.value === key)
74
+ );
75
+ if (propertyInSpread) {
76
+ propertyIsFound = true;
77
+ break;
78
+ }
79
+ } else if (t.isIdentifier(attribute.argument)) {
80
+ const binding = path.scope.getBinding(attribute.argument.name);
81
+ if (
82
+ binding?.path.node &&
83
+ t.isVariableDeclarator(binding.path.node) &&
84
+ t.isObjectExpression(binding.path.node.init)
85
+ ) {
86
+ const propertyInSpread = binding.path.node.init.properties.some(
87
+ (p) =>
88
+ (t.isObjectProperty(p) && t.isIdentifier(p.key) && p.key.name === key) ||
89
+ (t.isObjectProperty(p) && t.isStringLiteral(p.key) && p.key.value === key)
90
+ );
91
+ if (propertyInSpread) {
92
+ propertyIsFound = true;
93
+ break;
94
+ }
95
+ } else {
96
+ hasUnresolvableSpread = true;
97
+ break;
98
+ }
99
+ } else {
100
+ hasUnresolvableSpread = true;
101
+ break;
102
+ }
103
+ }
104
+ }
105
+
106
+ if (!propertyIsFound && !hasUnresolvableSpread) {
107
+ path.node.attributes.push(t.jsxAttribute(t.jsxIdentifier(key), t.jsxExpressionContainer(value)));
108
+ }
109
+ };
110
+
49
111
  /**
50
112
  * Helper that builds an Object.assign expression out of the existing JSX attributes.
51
113
  * It handles both plain JSXAttributes and spread attributes.
@@ -142,3 +204,106 @@ export const hasAccessibilityProperty = (
142
204
  }
143
205
  return false;
144
206
  };
207
+
208
+ /**
209
+ * Extracts the `style` attribute from a JSX attributes list.
210
+ *
211
+ * @returns An object containing the attribute node itself (if found) and the expression inside
212
+ */
213
+ export function extractStyleAttribute(attributes: Array<t.JSXAttribute | t.JSXSpreadAttribute>): {
214
+ styleAttribute?: t.JSXAttribute;
215
+ styleExpr?: t.Expression;
216
+ } {
217
+ for (const attribute of attributes) {
218
+ if (t.isJSXAttribute(attribute) && t.isJSXIdentifier(attribute.name, { name: 'style' })) {
219
+ if (
220
+ attribute.value &&
221
+ t.isJSXExpressionContainer(attribute.value) &&
222
+ !t.isJSXEmptyExpression(attribute.value.expression)
223
+ ) {
224
+ return {
225
+ styleAttribute: attribute,
226
+ styleExpr: attribute.value.expression,
227
+ };
228
+ }
229
+ return { styleAttribute: attribute };
230
+ }
231
+ }
232
+ return {};
233
+ }
234
+
235
+ /**
236
+ * Attempts to statically extract the `userSelect` style property from a style expression.
237
+ *
238
+ * If the `userSelect` value can be resolved at compile-time, the property is removed from the
239
+ * object literal (or array element) and its mapped boolean value for the native `selectable`
240
+ * prop is returned. When the value is unknown or the expression is not statically analysable,
241
+ * `undefined` is returned and no modification is made.
242
+ */
243
+ export function extractSelectableAndUpdateStyle(styleExpr: t.Expression): boolean | undefined {
244
+ // Helper to process a single ObjectExpression
245
+ const handleObjectExpression = (objectExpr: t.ObjectExpression): boolean | undefined => {
246
+ let selectableValue: boolean | undefined;
247
+
248
+ objectExpr.properties = objectExpr.properties.filter((property) => {
249
+ if (
250
+ !t.isObjectProperty(property) ||
251
+ (!t.isIdentifier(property.key, { name: 'userSelect' }) &&
252
+ !(t.isStringLiteral(property.key) && property.key.value === 'userSelect'))
253
+ ) {
254
+ return true; // keep property
255
+ }
256
+
257
+ if (t.isStringLiteral(property.value)) {
258
+ const mapped = USER_SELECT_STYLE_TO_SELECTABLE_PROP[property.value.value];
259
+ if (mapped !== undefined) {
260
+ selectableValue = mapped;
261
+ }
262
+ }
263
+
264
+ // Remove the `userSelect` property
265
+ return false;
266
+ });
267
+
268
+ return selectableValue;
269
+ };
270
+
271
+ if (t.isObjectExpression(styleExpr)) {
272
+ return handleObjectExpression(styleExpr);
273
+ }
274
+
275
+ if (t.isArrayExpression(styleExpr)) {
276
+ let selectableValue: boolean | undefined;
277
+ for (const element of styleExpr.elements) {
278
+ if (element && t.isObjectExpression(element)) {
279
+ const value = handleObjectExpression(element);
280
+ if (value !== undefined) {
281
+ selectableValue = value; // prefer last defined value
282
+ }
283
+ }
284
+ }
285
+ return selectableValue;
286
+ }
287
+
288
+ return undefined; // not statically analysable
289
+ }
290
+
291
+ /**
292
+ * Checks if a node represents a string value.
293
+ */
294
+ export const isStringNode = (path: NodePath<t.JSXOpeningElement>, child: t.Node): boolean => {
295
+ if (t.isJSXText(child) || t.isStringLiteral(child)) return true;
296
+
297
+ if (t.isJSXExpressionContainer(child)) {
298
+ const expression = child.expression;
299
+ if (t.isIdentifier(expression)) {
300
+ const binding = path.scope.getBinding(expression.name);
301
+ if (binding && binding.path.node && t.isVariableDeclarator(binding.path.node)) {
302
+ return !!binding.path.node.init && t.isStringLiteral(binding.path.node.init);
303
+ }
304
+ return false;
305
+ }
306
+ if (t.isStringLiteral(expression)) return true;
307
+ }
308
+ return false;
309
+ };
@@ -1,5 +1,3 @@
1
- export * from './base';
2
1
  export * from './validation';
3
2
  export * from './attributes';
4
- export * from './node-types';
5
- export * from './ancestors';
3
+ export * from './base';