react-native-boost 0.6.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -3
- package/dist/plugin/esm/index.mjs +709 -199
- package/dist/plugin/esm/index.mjs.map +1 -1
- package/dist/plugin/index.d.ts +54 -0
- package/dist/plugin/index.js +709 -199
- package/dist/plugin/index.js.map +1 -1
- package/dist/runtime/esm/index.mjs +15 -3
- package/dist/runtime/esm/index.mjs.map +1 -1
- package/dist/runtime/esm/index.web.mjs.map +1 -1
- package/dist/runtime/index.d.ts +51 -4
- package/dist/runtime/index.js +16 -4
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/index.web.d.ts +13 -1
- package/dist/runtime/index.web.js.map +1 -1
- package/package.json +13 -14
- package/src/plugin/index.ts +27 -5
- package/src/plugin/optimizers/text/index.ts +116 -92
- package/src/plugin/optimizers/view/index.ts +53 -31
- package/src/plugin/types/index.ts +67 -17
- package/src/plugin/utils/common/attributes.ts +165 -0
- package/src/plugin/utils/common/index.ts +1 -3
- package/src/plugin/utils/common/validation.ts +513 -0
- package/src/plugin/utils/constants.ts +9 -0
- package/src/plugin/utils/format-test-result.ts +29 -0
- package/src/plugin/utils/generate-test-plugin.ts +9 -3
- package/src/plugin/utils/helpers.ts +15 -0
- package/src/plugin/utils/logger.ts +109 -2
- package/src/runtime/components/native-text.tsx +21 -5
- package/src/runtime/components/native-view.tsx +21 -5
- package/src/runtime/index.ts +20 -0
- package/src/runtime/types/index.ts +5 -0
- package/src/runtime/utils/constants.ts +6 -2
- package/src/plugin/utils/common/ancestors.ts +0 -120
- package/src/plugin/utils/common/node-types.ts +0 -22
|
@@ -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.
|
|
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
|
-
*
|
|
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
|
-
*
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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>,
|
|
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
|
+
};
|