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,3 +1,110 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
HubFile,
|
|
3
|
+
OptimizationLogPayload,
|
|
4
|
+
PluginLogger,
|
|
5
|
+
SkippedOptimizationLogPayload,
|
|
6
|
+
WarningLogPayload,
|
|
7
|
+
} from '../types';
|
|
8
|
+
|
|
9
|
+
const LOG_PREFIX = '[react-native-boost]';
|
|
10
|
+
|
|
11
|
+
const ANSI_RESET = '\u001B[0m';
|
|
12
|
+
const ANSI_GREEN = '\u001B[32m';
|
|
13
|
+
const ANSI_YELLOW = '\u001B[33m';
|
|
14
|
+
const ANSI_MAGENTA = '\u001B[35m';
|
|
15
|
+
|
|
16
|
+
export const noopLogger: PluginLogger = {
|
|
17
|
+
optimized() {},
|
|
18
|
+
skipped() {},
|
|
19
|
+
warning() {},
|
|
3
20
|
};
|
|
21
|
+
|
|
22
|
+
export const createLogger = ({ verbose, silent }: { verbose: boolean; silent: boolean }): PluginLogger => {
|
|
23
|
+
if (silent) return noopLogger;
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
optimized(payload) {
|
|
27
|
+
writeLog('optimized', `Optimized ${payload.component} in ${formatPathLocation(payload.path)}`);
|
|
28
|
+
},
|
|
29
|
+
skipped(payload) {
|
|
30
|
+
if (!verbose) return;
|
|
31
|
+
writeLog('skipped', `Skipped ${payload.component} in ${formatPathLocation(payload.path)} (${payload.reason})`);
|
|
32
|
+
},
|
|
33
|
+
warning(payload) {
|
|
34
|
+
const context = formatWarningContext(payload);
|
|
35
|
+
const message = context.length > 0 ? `${context}: ${payload.message}` : payload.message;
|
|
36
|
+
writeLog('warning', message);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function formatWarningContext(payload: WarningLogPayload): string {
|
|
42
|
+
const location = formatPathLocation(payload.path);
|
|
43
|
+
|
|
44
|
+
if (payload.component && location.length > 0) {
|
|
45
|
+
return `${payload.component} in ${location}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (payload.component) {
|
|
49
|
+
return payload.component;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return location;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function writeLog(level: 'optimized' | 'skipped' | 'warning', message: string): void {
|
|
56
|
+
const levelTag = formatLevel(level);
|
|
57
|
+
console.log(`${LOG_PREFIX} ${levelTag} ${message}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function formatLevel(level: 'optimized' | 'skipped' | 'warning'): string {
|
|
61
|
+
if (level === 'optimized') {
|
|
62
|
+
return colorize('[optimized]', ANSI_GREEN);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (level === 'skipped') {
|
|
66
|
+
return colorize('[skipped]', ANSI_YELLOW);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return colorize('[warning]', ANSI_MAGENTA);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function colorize(value: string, colorCode: string): string {
|
|
73
|
+
if (!shouldUseColor()) return value;
|
|
74
|
+
return `${colorCode}${value}${ANSI_RESET}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function shouldUseColor(): boolean {
|
|
78
|
+
if (process.env.NO_COLOR != null) return false;
|
|
79
|
+
|
|
80
|
+
if (process.env.FORCE_COLOR === '0') return false;
|
|
81
|
+
if (process.env.FORCE_COLOR != null) return true;
|
|
82
|
+
|
|
83
|
+
if (process.env.CLICOLOR === '0') return false;
|
|
84
|
+
if (process.env.CLICOLOR_FORCE != null && process.env.CLICOLOR_FORCE !== '0') return true;
|
|
85
|
+
|
|
86
|
+
if (process.stdout?.isTTY === true || process.stderr?.isTTY === true) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const colorTerm = process.env.COLORTERM;
|
|
91
|
+
if (colorTerm != null && colorTerm !== '') {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const term = process.env.TERM;
|
|
96
|
+
return term != null && term !== '' && term.toLowerCase() !== 'dumb';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function formatPathLocation(
|
|
100
|
+
payloadPath: OptimizationLogPayload['path'] | SkippedOptimizationLogPayload['path'] | undefined
|
|
101
|
+
): string {
|
|
102
|
+
if (!payloadPath) return 'unknown file:unknown line';
|
|
103
|
+
|
|
104
|
+
const hub = payloadPath.hub as unknown;
|
|
105
|
+
const file = typeof hub === 'object' && hub !== null && 'file' in hub ? (hub.file as HubFile) : undefined;
|
|
106
|
+
const filename = file?.opts?.filename ?? 'unknown file';
|
|
107
|
+
const lineNumber = payloadPath.node.loc?.start.line ?? 'unknown line';
|
|
108
|
+
|
|
109
|
+
return `${filename}:${lineNumber}`;
|
|
110
|
+
}
|
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-require-imports,unicorn/prefer-module */
|
|
2
|
-
import { Platform } from 'react-native';
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
import type { ComponentType } from 'react';
|
|
4
|
+
import type { TextProps } from 'react-native';
|
|
5
|
+
|
|
6
|
+
const reactNative = require('react-native');
|
|
7
|
+
const isWeb = reactNative.Platform.OS === 'web';
|
|
8
|
+
|
|
9
|
+
let nativeText = reactNative.unstable_NativeText;
|
|
10
|
+
|
|
11
|
+
if (isWeb || nativeText == null) {
|
|
12
|
+
// Fallback to regular Text component if unstable_NativeText is not available or we're on Web
|
|
13
|
+
nativeText = reactNative.Text;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Native Text component with graceful fallback.
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* Uses `unstable_NativeText` on supported native runtimes and falls back to `Text`
|
|
21
|
+
* on web or when the unstable export is unavailable.
|
|
22
|
+
*/
|
|
23
|
+
export const NativeText: ComponentType<TextProps> = nativeText;
|
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-require-imports,unicorn/prefer-module */
|
|
2
|
-
import { Platform } from 'react-native';
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
import type { ComponentType } from 'react';
|
|
4
|
+
import type { ViewProps } from 'react-native';
|
|
5
|
+
|
|
6
|
+
const reactNative = require('react-native');
|
|
7
|
+
const isWeb = reactNative.Platform.OS === 'web';
|
|
8
|
+
|
|
9
|
+
let nativeView = reactNative.unstable_NativeView;
|
|
10
|
+
|
|
11
|
+
if (isWeb || nativeView == null) {
|
|
12
|
+
// Fallback to regular View component if unstable_NativeView is not available or we're on Web
|
|
13
|
+
nativeView = reactNative.View;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Native View component with graceful fallback.
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* Uses `unstable_NativeView` on supported native runtimes and falls back to `View`
|
|
21
|
+
* on web or when the unstable export is unavailable.
|
|
22
|
+
*/
|
|
23
|
+
export const NativeView: ComponentType<ViewProps> = nativeView;
|
package/src/runtime/index.ts
CHANGED
|
@@ -4,6 +4,16 @@ import { userSelectToSelectableMap, verticalAlignToTextAlignVerticalMap } from '
|
|
|
4
4
|
|
|
5
5
|
const propsCache = new WeakMap();
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Normalizes `Text` style values for `NativeText`.
|
|
9
|
+
*
|
|
10
|
+
* @param style - Style prop passed to a text-like component.
|
|
11
|
+
* @returns Native-friendly text props. Returns an empty object when `style` is falsy or cannot be normalized.
|
|
12
|
+
* @remarks
|
|
13
|
+
* - Flattens style arrays via `StyleSheet.flatten`
|
|
14
|
+
* - Converts numeric `fontWeight` values to string values
|
|
15
|
+
* - Maps `userSelect` and `verticalAlign` to native-compatible props
|
|
16
|
+
*/
|
|
7
17
|
export function processTextStyle(style: GenericStyleProp<TextStyle>): Partial<TextProps> {
|
|
8
18
|
if (!style) return {};
|
|
9
19
|
|
|
@@ -38,6 +48,16 @@ export function processTextStyle(style: GenericStyleProp<TextStyle>): Partial<Te
|
|
|
38
48
|
return props;
|
|
39
49
|
}
|
|
40
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Normalizes accessibility and ARIA props for runtime native components.
|
|
53
|
+
*
|
|
54
|
+
* @param props - Accessibility and ARIA props.
|
|
55
|
+
* @returns Props with normalized accessibility fields.
|
|
56
|
+
* @remarks
|
|
57
|
+
* - Merges `aria-label` with `accessibilityLabel`
|
|
58
|
+
* - Merges ARIA state fields into `accessibilityState`
|
|
59
|
+
* - Defaults `accessible` to `true` when omitted
|
|
60
|
+
*/
|
|
41
61
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
42
62
|
export function processAccessibilityProps(props: Record<string, any>): Record<string, any> {
|
|
43
63
|
const {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Maps CSS-like `userSelect` values to React Native's `selectable` prop.
|
|
3
|
+
*/
|
|
2
4
|
export const userSelectToSelectableMap = {
|
|
3
5
|
auto: true,
|
|
4
6
|
text: true,
|
|
@@ -7,7 +9,9 @@ export const userSelectToSelectableMap = {
|
|
|
7
9
|
all: true,
|
|
8
10
|
};
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Maps CSS-like `verticalAlign` values to React Native's `textAlignVertical`.
|
|
14
|
+
*/
|
|
11
15
|
export const verticalAlignToTextAlignVerticalMap = {
|
|
12
16
|
auto: 'auto',
|
|
13
17
|
top: 'top',
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { NodePath, types as t } from '@babel/core';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Checks if any ancestor element is of the specified component type or contains that component type.
|
|
5
|
-
* This function handles both direct ancestors and custom components that may contain the specified component.
|
|
6
|
-
*
|
|
7
|
-
* @param path - The path to the JSXOpeningElement.
|
|
8
|
-
* @param componentName - The name of the component to check for in ancestors.
|
|
9
|
-
* @param skipComponents - Optional array of component names to skip when checking ancestors.
|
|
10
|
-
* @returns true if any ancestor is or contains the specified component.
|
|
11
|
-
*/
|
|
12
|
-
export function hasComponentAncestor(
|
|
13
|
-
path: NodePath<t.JSXOpeningElement>,
|
|
14
|
-
componentName: string,
|
|
15
|
-
skipComponents: string[] = ['Fragment']
|
|
16
|
-
): boolean {
|
|
17
|
-
// Check for direct ancestors of the specified component type
|
|
18
|
-
const directAncestor = path.findParent((parentPath) => {
|
|
19
|
-
return (
|
|
20
|
-
t.isJSXElement(parentPath.node) && t.isJSXIdentifier(parentPath.node.openingElement.name, { name: componentName })
|
|
21
|
-
);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
if (directAncestor) return true;
|
|
25
|
-
|
|
26
|
-
// Check for indirect ancestors (custom components that contain the specified component)
|
|
27
|
-
return !!path.findParent((parentPath) => {
|
|
28
|
-
// Only check JSX elements
|
|
29
|
-
if (!t.isJSXElement(parentPath.node)) return false;
|
|
30
|
-
|
|
31
|
-
// Get the component name
|
|
32
|
-
const openingElement = parentPath.node.openingElement;
|
|
33
|
-
if (!t.isJSXIdentifier(openingElement.name)) return false;
|
|
34
|
-
|
|
35
|
-
const ancestorComponentName = openingElement.name.name;
|
|
36
|
-
|
|
37
|
-
// Skip the component we're looking for
|
|
38
|
-
if (ancestorComponentName === componentName) {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Skip components in the skipComponents list
|
|
43
|
-
if (skipComponents.includes(ancestorComponentName)) {
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Skip lowercase components (built-in HTML elements)
|
|
48
|
-
if (ancestorComponentName[0] === ancestorComponentName[0].toLowerCase()) {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Try to find the component definition through variable binding
|
|
53
|
-
const binding = parentPath.scope.getBinding(ancestorComponentName);
|
|
54
|
-
if (!binding) return false;
|
|
55
|
-
|
|
56
|
-
// Now check the component definition for the specified component
|
|
57
|
-
if (t.isVariableDeclarator(binding.path.node)) {
|
|
58
|
-
const init = binding.path.node.init;
|
|
59
|
-
|
|
60
|
-
// Handle arrow functions or function expressions
|
|
61
|
-
if (t.isArrowFunctionExpression(init) || t.isFunctionExpression(init)) {
|
|
62
|
-
// Check the function body for the specified component
|
|
63
|
-
return t.isBlockStatement(init.body)
|
|
64
|
-
? hasComponentInReturnStatement(init.body, componentName)
|
|
65
|
-
: hasComponentInExpression(init.body, componentName);
|
|
66
|
-
}
|
|
67
|
-
} else if (t.isFunctionDeclaration(binding.path.node)) {
|
|
68
|
-
// Handle function declarations
|
|
69
|
-
return hasComponentInReturnStatement(binding.path.node.body, componentName);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return false;
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Check if a block statement contains a return statement with the specified component
|
|
78
|
-
*
|
|
79
|
-
* @param blockStatement - The block statement to check
|
|
80
|
-
* @param componentName - The name of the component to look for
|
|
81
|
-
* @returns true if the block statement contains a return with the specified component
|
|
82
|
-
*/
|
|
83
|
-
function hasComponentInReturnStatement(blockStatement: t.BlockStatement, componentName: string): boolean {
|
|
84
|
-
for (const statement of blockStatement.body) {
|
|
85
|
-
if (
|
|
86
|
-
t.isReturnStatement(statement) &&
|
|
87
|
-
statement.argument &&
|
|
88
|
-
hasComponentInExpression(statement.argument, componentName)
|
|
89
|
-
) {
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Check if an expression contains the specified component
|
|
98
|
-
*
|
|
99
|
-
* @param expression - The expression to check
|
|
100
|
-
* @param componentName - The name of the component to look for
|
|
101
|
-
* @returns true if the expression contains the specified component
|
|
102
|
-
*/
|
|
103
|
-
function hasComponentInExpression(expression: t.Expression, componentName: string): boolean {
|
|
104
|
-
// If directly returning a JSX element
|
|
105
|
-
if (t.isJSXElement(expression)) {
|
|
106
|
-
// Check if it's the specified component
|
|
107
|
-
if (t.isJSXIdentifier(expression.openingElement.name, { name: componentName })) {
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Check if any children are the specified component
|
|
112
|
-
for (const child of expression.children) {
|
|
113
|
-
if (t.isJSXElement(child) && t.isJSXIdentifier(child.openingElement.name, { name: componentName })) {
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { NodePath, types as t } from '@babel/core';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Checks if a node represents a string value.
|
|
5
|
-
*/
|
|
6
|
-
export const isStringNode = (path: NodePath<t.JSXOpeningElement>, child: t.Node): boolean => {
|
|
7
|
-
if (t.isJSXText(child) || t.isStringLiteral(child)) return true;
|
|
8
|
-
|
|
9
|
-
// Check for JSX expressions
|
|
10
|
-
if (t.isJSXExpressionContainer(child)) {
|
|
11
|
-
const expression = child.expression;
|
|
12
|
-
if (t.isIdentifier(expression)) {
|
|
13
|
-
const binding = path.scope.getBinding(expression.name);
|
|
14
|
-
if (binding && binding.path.node && t.isVariableDeclarator(binding.path.node)) {
|
|
15
|
-
return !!binding.path.node.init && t.isStringLiteral(binding.path.node.init);
|
|
16
|
-
}
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
if (t.isStringLiteral(expression)) return true;
|
|
20
|
-
}
|
|
21
|
-
return false;
|
|
22
|
-
};
|