react-native-boost 0.5.5 → 0.5.7
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 +21 -5
- package/dist/plugin/esm/index.mjs +57 -49
- package/dist/plugin/esm/index.mjs.map +1 -1
- package/dist/plugin/index.js +57 -49
- package/dist/plugin/index.js.map +1 -1
- package/dist/runtime/esm/index.mjs +84 -0
- package/dist/runtime/esm/index.mjs.map +1 -0
- package/dist/runtime/index.d.ts +31 -0
- package/dist/runtime/index.js +91 -0
- package/dist/runtime/index.js.map +1 -0
- package/package.json +27 -4
- package/plugin.d.ts +1 -0
- package/plugin.js +1 -0
- package/plugin.mjs +1 -0
- package/runtime.d.ts +1 -0
- package/runtime.js +1 -0
- package/runtime.mjs +1 -0
- package/src/plugin/optimizers/view/index.ts +8 -94
- package/src/plugin/types/index.ts +1 -1
- package/src/plugin/utils/common/ancestors.ts +120 -0
- package/src/plugin/utils/common/base.ts +1 -1
- package/src/plugin/utils/common/index.ts +1 -0
- package/src/plugin/utils/constants.ts +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { types as t } from '@babel/core';
|
|
2
2
|
import { HubFile, Optimizer } from '../../types';
|
|
3
3
|
import PluginError from '../../utils/plugin-error';
|
|
4
4
|
import {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
isValidJSXComponent,
|
|
8
8
|
isReactNativeImport,
|
|
9
9
|
replaceWithNativeComponent,
|
|
10
|
+
hasComponentAncestor,
|
|
10
11
|
} from '../../utils/common';
|
|
11
12
|
|
|
12
13
|
export const viewBlacklistedProperties = new Set([
|
|
@@ -42,14 +43,17 @@ export const viewBlacklistedProperties = new Set([
|
|
|
42
43
|
'style',
|
|
43
44
|
]);
|
|
44
45
|
|
|
46
|
+
// Components to skip when checking for indirect Text ancestors
|
|
47
|
+
const skipComponents = ['View', 'Fragment', 'ScrollView', 'FlatList'];
|
|
48
|
+
|
|
45
49
|
export const viewOptimizer: Optimizer = (path, log = () => {}) => {
|
|
46
50
|
if (isIgnoredLine(path)) return;
|
|
47
51
|
if (!isValidJSXComponent(path, 'View')) return;
|
|
48
52
|
if (!isReactNativeImport(path, 'View')) return;
|
|
49
53
|
if (hasBlacklistedProperty(path, viewBlacklistedProperties)) return;
|
|
50
|
-
if (
|
|
54
|
+
if (hasComponentAncestor(path, 'Text', skipComponents)) return;
|
|
51
55
|
|
|
52
|
-
// Extract the file from the Babel hub
|
|
56
|
+
// Extract the file from the Babel hub
|
|
53
57
|
const hub = path.hub as unknown;
|
|
54
58
|
const file = typeof hub === 'object' && hub !== null && 'file' in hub ? (hub.file as HubFile) : undefined;
|
|
55
59
|
|
|
@@ -63,96 +67,6 @@ export const viewOptimizer: Optimizer = (path, log = () => {}) => {
|
|
|
63
67
|
|
|
64
68
|
const parent = path.parent as t.JSXElement;
|
|
65
69
|
|
|
66
|
-
// Replace the
|
|
70
|
+
// Replace the View component with NativeView
|
|
67
71
|
replaceWithNativeComponent(path, parent, file, 'NativeView');
|
|
68
72
|
};
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Returns true if any ancestor element is a <Text /> or contains a <Text />.
|
|
72
|
-
* This function handles both direct Text ancestors and custom components that may contain Text.
|
|
73
|
-
* TODO: We can't test across file boundaries within the Babel plugin
|
|
74
|
-
*/
|
|
75
|
-
function hasTextAncestor(path: NodePath<t.JSXOpeningElement>): boolean {
|
|
76
|
-
// Check for direct Text ancestors (no custom components)
|
|
77
|
-
const directTextAncestor = path.findParent((parentPath) => {
|
|
78
|
-
return t.isJSXElement(parentPath.node) && t.isJSXIdentifier(parentPath.node.openingElement.name, { name: 'Text' });
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
if (directTextAncestor) return true;
|
|
82
|
-
|
|
83
|
-
// Check for indirect Text ancestors (custom components that contain Text)
|
|
84
|
-
return !!path.findParent((parentPath) => {
|
|
85
|
-
// Only check JSX elements
|
|
86
|
-
if (!t.isJSXElement(parentPath.node)) return false;
|
|
87
|
-
|
|
88
|
-
// Get the component name
|
|
89
|
-
const openingElement = parentPath.node.openingElement;
|
|
90
|
-
if (!t.isJSXIdentifier(openingElement.name)) return false;
|
|
91
|
-
|
|
92
|
-
const componentName = openingElement.name.name;
|
|
93
|
-
|
|
94
|
-
// Skip built-in components and already checked Text component
|
|
95
|
-
if (
|
|
96
|
-
componentName === 'Text' ||
|
|
97
|
-
componentName === 'View' ||
|
|
98
|
-
componentName === 'Fragment' ||
|
|
99
|
-
componentName[0] === componentName[0].toLowerCase()
|
|
100
|
-
) {
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Try to find the component definition through variable binding
|
|
105
|
-
const binding = parentPath.scope.getBinding(componentName);
|
|
106
|
-
if (!binding) return false;
|
|
107
|
-
|
|
108
|
-
// Now check the component definition for Text elements
|
|
109
|
-
if (t.isVariableDeclarator(binding.path.node)) {
|
|
110
|
-
const init = binding.path.node.init;
|
|
111
|
-
|
|
112
|
-
// Handle arrow functions or function expressions
|
|
113
|
-
if (t.isArrowFunctionExpression(init) || t.isFunctionExpression(init)) {
|
|
114
|
-
// Check the function body for Text elements
|
|
115
|
-
return t.isBlockStatement(init.body) ? hasTextInReturnStatement(init.body) : hasTextInExpression(init.body);
|
|
116
|
-
}
|
|
117
|
-
} else if (t.isFunctionDeclaration(binding.path.node)) {
|
|
118
|
-
// Handle function declarations
|
|
119
|
-
return hasTextInReturnStatement(binding.path.node.body);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return false;
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Check if a block statement contains a return statement with a Text element
|
|
128
|
-
*/
|
|
129
|
-
function hasTextInReturnStatement(blockStatement: t.BlockStatement): boolean {
|
|
130
|
-
for (const statement of blockStatement.body) {
|
|
131
|
-
if (t.isReturnStatement(statement) && statement.argument && hasTextInExpression(statement.argument)) {
|
|
132
|
-
return true;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Check if an expression contains a Text element
|
|
140
|
-
*/
|
|
141
|
-
function hasTextInExpression(expression: t.Expression): boolean {
|
|
142
|
-
// If directly returning a JSX element
|
|
143
|
-
if (t.isJSXElement(expression)) {
|
|
144
|
-
// Check if it's a Text element
|
|
145
|
-
if (t.isJSXIdentifier(expression.openingElement.name, { name: 'Text' })) {
|
|
146
|
-
return true;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Check if any children are Text elements
|
|
150
|
-
for (const child of expression.children) {
|
|
151
|
-
if (t.isJSXElement(child) && t.isJSXIdentifier(child.openingElement.name, { name: 'Text' })) {
|
|
152
|
-
return true;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
@@ -50,7 +50,7 @@ export interface FileImportOptions {
|
|
|
50
50
|
* The named import string (e.g. 'normalizeAccessibilityProps'). Ignored if importType is "default".
|
|
51
51
|
*/
|
|
52
52
|
importName: string;
|
|
53
|
-
/** The module to import from (e.g. 'react-native-boost') */
|
|
53
|
+
/** The module to import from (e.g. 'react-native-boost/runtime') */
|
|
54
54
|
moduleName: string;
|
|
55
55
|
/**
|
|
56
56
|
* Determines which helper to use:
|
|
@@ -0,0 +1,120 @@
|
|
|
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
|
+
}
|
|
@@ -11,7 +11,7 @@ import { RUNTIME_MODULE_NAME } from '../constants';
|
|
|
11
11
|
* - nameHint: The name hint which also acts as the cache key to ensure the import is only added once (e.g. 'normalizeAccessibilityProps')
|
|
12
12
|
* - path: The current Babel NodePath
|
|
13
13
|
* - importName: The named import string (e.g. 'normalizeAccessibilityProps'), used when importType is 'named'
|
|
14
|
-
* - moduleName: The module to import from (e.g. 'react-native-boost')
|
|
14
|
+
* - moduleName: The module to import from (e.g. 'react-native-boost/runtime')
|
|
15
15
|
* - importType: Either 'named' (default) or 'default' to determine the type of import to use.
|
|
16
16
|
*
|
|
17
17
|
* @returns The identifier returned by addNamed or addDefault.
|