react-native-boost 1.0.0 → 1.1.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 +13 -4
- package/dist/plugin/esm/index.mjs +66 -40
- package/dist/plugin/esm/index.mjs.map +1 -1
- package/dist/plugin/index.js +66 -40
- package/dist/plugin/index.js.map +1 -1
- package/package.json +2 -1
- package/src/plugin/optimizers/text/index.ts +25 -19
- package/src/plugin/optimizers/view/index.ts +25 -20
- package/src/plugin/types/index.ts +1 -0
- package/src/plugin/utils/common/validation.ts +20 -23
- package/src/plugin/utils/logger.ts +16 -2
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { types as t } from '@babel/core';
|
|
2
2
|
import { HubFile, Optimizer } from '../../types';
|
|
3
3
|
import PluginError from '../../utils/plugin-error';
|
|
4
|
-
import { getFirstBailoutReason } from '../../utils/helpers';
|
|
4
|
+
import { BailoutCheck, getFirstBailoutReason } from '../../utils/helpers';
|
|
5
5
|
import {
|
|
6
6
|
hasBlacklistedProperty,
|
|
7
|
+
isForcedLine,
|
|
7
8
|
isIgnoredLine,
|
|
8
9
|
isValidJSXComponent,
|
|
9
10
|
isReactNativeImport,
|
|
@@ -30,6 +31,7 @@ export const viewBlacklistedProperties = new Set([
|
|
|
30
31
|
|
|
31
32
|
export const viewOptimizer: Optimizer = (path, logger, options) => {
|
|
32
33
|
if (!isValidJSXComponent(path, 'View')) return;
|
|
34
|
+
if (!isReactNativeImport(path, 'View')) return;
|
|
33
35
|
|
|
34
36
|
let ancestorClassification: ViewAncestorClassification | undefined;
|
|
35
37
|
const getAncestorClassification = () => {
|
|
@@ -40,15 +42,9 @@ export const viewOptimizer: Optimizer = (path, logger, options) => {
|
|
|
40
42
|
return ancestorClassification;
|
|
41
43
|
};
|
|
42
44
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
shouldBail: () => isIgnoredLine(path),
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
reason: 'View is not imported from react-native',
|
|
50
|
-
shouldBail: () => !isReactNativeImport(path, 'View'),
|
|
51
|
-
},
|
|
45
|
+
const forced = isForcedLine(path);
|
|
46
|
+
|
|
47
|
+
const overridableChecks: BailoutCheck[] = [
|
|
52
48
|
{
|
|
53
49
|
reason: 'contains blacklisted props',
|
|
54
50
|
shouldBail: () => hasBlacklistedProperty(path, viewBlacklistedProperties),
|
|
@@ -62,19 +58,29 @@ export const viewOptimizer: Optimizer = (path, logger, options) => {
|
|
|
62
58
|
shouldBail: () =>
|
|
63
59
|
getAncestorClassification() === 'unknown' && options?.dangerouslyOptimizeViewWithUnknownAncestors !== true,
|
|
64
60
|
},
|
|
65
|
-
]
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
if (forced) {
|
|
64
|
+
const overriddenReason = getFirstBailoutReason(overridableChecks);
|
|
66
65
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
if (overriddenReason) {
|
|
67
|
+
logger.forced({ component: 'View', path, reason: overriddenReason });
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
const skipReason = getFirstBailoutReason([
|
|
71
|
+
{
|
|
72
|
+
reason: 'line is marked with @boost-ignore',
|
|
73
|
+
shouldBail: () => isIgnoredLine(path),
|
|
74
|
+
},
|
|
75
|
+
...overridableChecks,
|
|
76
|
+
]);
|
|
73
77
|
|
|
74
|
-
|
|
78
|
+
if (skipReason) {
|
|
79
|
+
logger.skipped({ component: 'View', path, reason: skipReason });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
75
82
|
}
|
|
76
83
|
|
|
77
|
-
// Extract the file from the Babel hub
|
|
78
84
|
const hub = path.hub as unknown;
|
|
79
85
|
const file = typeof hub === 'object' && hub !== null && 'file' in hub ? (hub.file as HubFile) : undefined;
|
|
80
86
|
|
|
@@ -89,6 +95,5 @@ export const viewOptimizer: Optimizer = (path, logger, options) => {
|
|
|
89
95
|
|
|
90
96
|
const parent = path.parent as t.JSXElement;
|
|
91
97
|
|
|
92
|
-
// Replace the View component with NativeView
|
|
93
98
|
replaceWithNativeComponent(path, parent, file, 'NativeView');
|
|
94
99
|
};
|
|
@@ -74,6 +74,7 @@ export interface WarningLogPayload {
|
|
|
74
74
|
export interface PluginLogger {
|
|
75
75
|
optimized: (payload: OptimizationLogPayload) => void;
|
|
76
76
|
skipped: (payload: SkippedOptimizationLogPayload) => void;
|
|
77
|
+
forced: (payload: SkippedOptimizationLogPayload) => void;
|
|
77
78
|
warning: (payload: WarningLogPayload) => void;
|
|
78
79
|
}
|
|
79
80
|
|
|
@@ -39,54 +39,52 @@ export const isIgnoredFile = (path: NodePath<t.JSXOpeningElement>, ignores: stri
|
|
|
39
39
|
return false;
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
+
export const isForcedLine = (path: NodePath<t.JSXOpeningElement>): boolean => {
|
|
43
|
+
return hasDecoratorComment(path, '@boost-force');
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const isIgnoredLine = (path: NodePath<t.JSXOpeningElement>): boolean => {
|
|
47
|
+
return hasDecoratorComment(path, '@boost-ignore');
|
|
48
|
+
};
|
|
49
|
+
|
|
42
50
|
/**
|
|
43
|
-
* Checks if the JSX element
|
|
51
|
+
* Checks if the JSX element has a preceding comment containing the given decorator string.
|
|
44
52
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* @param path - The path to the JSXOpeningElement.
|
|
49
|
-
* @returns true if the JSX element should be ignored.
|
|
53
|
+
* Scans the JSXOpeningElement's own leading comments, the parent element's comments,
|
|
54
|
+
* ObjectProperty containers, and backward siblings.
|
|
50
55
|
*/
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (path.node.leadingComments?.some((comment) => comment.value.includes('@boost-ignore'))) {
|
|
56
|
+
function hasDecoratorComment(path: NodePath<t.JSXOpeningElement>, decorator: string): boolean {
|
|
57
|
+
if (path.node.leadingComments?.some((comment) => comment.value.includes(decorator))) {
|
|
54
58
|
return true;
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
// Check for @boost-ignore in the leading comments on the parent JSX element.
|
|
58
61
|
const jsxElementPath = path.parentPath;
|
|
59
|
-
if (jsxElementPath.node.leadingComments?.some((comment) => comment.value.includes(
|
|
62
|
+
if (jsxElementPath.node.leadingComments?.some((comment) => comment.value.includes(decorator))) {
|
|
60
63
|
return true;
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
//
|
|
64
|
-
// This handles cases where the JSX element is used as a value inside an object literal.
|
|
66
|
+
// Check leading comments on the ObjectProperty (if the JSX element is a value inside an object literal).
|
|
65
67
|
const propertyPath = jsxElementPath.parentPath;
|
|
66
68
|
if (
|
|
67
69
|
propertyPath &&
|
|
68
70
|
propertyPath.isObjectProperty() &&
|
|
69
|
-
propertyPath.node.leadingComments?.some((comment) => comment.value.includes(
|
|
71
|
+
propertyPath.node.leadingComments?.some((comment) => comment.value.includes(decorator))
|
|
70
72
|
) {
|
|
71
73
|
return true;
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
if (!jsxElementPath.parentPath) return false;
|
|
75
77
|
|
|
76
|
-
// Get the container that holds this element (for example, a JSX fragment or JSX element)
|
|
77
78
|
const containerPath = jsxElementPath.parentPath;
|
|
78
79
|
const siblings = ensureArray(containerPath.get('children'));
|
|
79
80
|
const index = siblings.findIndex((sibling) => sibling.node === jsxElementPath.node);
|
|
80
81
|
if (index === -1) return false;
|
|
81
82
|
|
|
82
|
-
// Look backward from the current element for a non-empty node.
|
|
83
83
|
for (let index_ = index - 1; index_ >= 0; index_--) {
|
|
84
84
|
const sibling = siblings[index_];
|
|
85
|
-
// Skip over any whitespace (only in JSXText nodes)
|
|
86
85
|
if (sibling.isJSXText() && sibling.node.value.trim() === '') {
|
|
87
86
|
continue;
|
|
88
87
|
}
|
|
89
|
-
// If the sibling is a JSX expression container, check its empty expression's comments.
|
|
90
88
|
if (sibling.isJSXExpressionContainer()) {
|
|
91
89
|
const expression = sibling.get('expression');
|
|
92
90
|
if (expression && expression.node) {
|
|
@@ -95,22 +93,21 @@ export const isIgnoredLine = (path: NodePath<t.JSXOpeningElement>): boolean => {
|
|
|
95
93
|
...(expression.node.trailingComments || []),
|
|
96
94
|
...(expression.node.innerComments || []),
|
|
97
95
|
].map((comment) => comment.value.trim());
|
|
98
|
-
if (comments.some((comment) => comment.includes(
|
|
96
|
+
if (comments.some((comment) => comment.includes(decorator))) {
|
|
99
97
|
return true;
|
|
100
98
|
}
|
|
101
99
|
}
|
|
102
100
|
}
|
|
103
|
-
// Also check if the node itself carries a leadingComments property.
|
|
104
101
|
if (
|
|
105
102
|
sibling.node.leadingComments &&
|
|
106
|
-
sibling.node.leadingComments.some((comment) => comment.value.includes(
|
|
103
|
+
sibling.node.leadingComments.some((comment) => comment.value.includes(decorator))
|
|
107
104
|
) {
|
|
108
105
|
return true;
|
|
109
106
|
}
|
|
110
|
-
break;
|
|
107
|
+
break;
|
|
111
108
|
}
|
|
112
109
|
return false;
|
|
113
|
-
}
|
|
110
|
+
}
|
|
114
111
|
|
|
115
112
|
/**
|
|
116
113
|
* Checks if the path represents a valid JSX component with the specified name.
|
|
@@ -12,10 +12,12 @@ const ANSI_RESET = '\u001B[0m';
|
|
|
12
12
|
const ANSI_GREEN = '\u001B[32m';
|
|
13
13
|
const ANSI_YELLOW = '\u001B[33m';
|
|
14
14
|
const ANSI_MAGENTA = '\u001B[35m';
|
|
15
|
+
const ANSI_RED = '\u001B[31m';
|
|
15
16
|
|
|
16
17
|
export const noopLogger: PluginLogger = {
|
|
17
18
|
optimized() {},
|
|
18
19
|
skipped() {},
|
|
20
|
+
forced() {},
|
|
19
21
|
warning() {},
|
|
20
22
|
};
|
|
21
23
|
|
|
@@ -30,6 +32,12 @@ export const createLogger = ({ verbose, silent }: { verbose: boolean; silent: bo
|
|
|
30
32
|
if (!verbose) return;
|
|
31
33
|
writeLog('skipped', `Skipped ${payload.component} in ${formatPathLocation(payload.path)} (${payload.reason})`);
|
|
32
34
|
},
|
|
35
|
+
forced(payload) {
|
|
36
|
+
writeLog(
|
|
37
|
+
'forced',
|
|
38
|
+
`Force-optimized ${payload.component} in ${formatPathLocation(payload.path)} (skipped bailout: ${payload.reason})`
|
|
39
|
+
);
|
|
40
|
+
},
|
|
33
41
|
warning(payload) {
|
|
34
42
|
const context = formatWarningContext(payload);
|
|
35
43
|
const message = context.length > 0 ? `${context}: ${payload.message}` : payload.message;
|
|
@@ -52,12 +60,14 @@ function formatWarningContext(payload: WarningLogPayload): string {
|
|
|
52
60
|
return location;
|
|
53
61
|
}
|
|
54
62
|
|
|
55
|
-
|
|
63
|
+
type LogLevel = 'optimized' | 'skipped' | 'forced' | 'warning';
|
|
64
|
+
|
|
65
|
+
function writeLog(level: LogLevel, message: string): void {
|
|
56
66
|
const levelTag = formatLevel(level);
|
|
57
67
|
console.log(`${LOG_PREFIX} ${levelTag} ${message}`);
|
|
58
68
|
}
|
|
59
69
|
|
|
60
|
-
function formatLevel(level:
|
|
70
|
+
function formatLevel(level: LogLevel): string {
|
|
61
71
|
if (level === 'optimized') {
|
|
62
72
|
return colorize('[optimized]', ANSI_GREEN);
|
|
63
73
|
}
|
|
@@ -66,6 +76,10 @@ function formatLevel(level: 'optimized' | 'skipped' | 'warning'): string {
|
|
|
66
76
|
return colorize('[skipped]', ANSI_YELLOW);
|
|
67
77
|
}
|
|
68
78
|
|
|
79
|
+
if (level === 'forced') {
|
|
80
|
+
return colorize('[forced]', ANSI_RED);
|
|
81
|
+
}
|
|
82
|
+
|
|
69
83
|
return colorize('[warning]', ANSI_MAGENTA);
|
|
70
84
|
}
|
|
71
85
|
|