react-native-boost 0.0.5 → 0.2.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 +28 -3
- package/dist/esm/index.mjs +41 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin/esm/index.mjs +218 -0
- package/dist/plugin/esm/index.mjs.map +1 -0
- package/dist/plugin/index.d.ts +12 -0
- package/dist/plugin/index.js +220 -0
- package/dist/plugin/index.js.map +1 -0
- package/package.json +32 -12
- package/src/{optimizers → plugin/optimizers}/text/index.ts +40 -11
- package/src/{types → plugin/types}/index.ts +1 -1
- package/src/plugin/utils/common.ts +72 -0
- package/src/plugin/utils/helpers.ts +4 -0
- package/src/runtime/index.ts +58 -0
- package/src/runtime/types/index.ts +1 -0
- package/src/runtime/types/react-native.d.ts +5 -0
- package/plugin/index.js +0 -1
- /package/src/{plugin.ts → plugin/index.ts} +0 -0
- /package/src/{utils → plugin/utils}/generate-test-plugin.ts +0 -0
- /package/src/{utils → plugin/utils}/logger.ts +0 -0
- /package/src/{utils → plugin/utils}/plugin-error.ts +0 -0
package/README.md
CHANGED
|
@@ -9,17 +9,33 @@ A powerful Babel plugin that automatically optimizes React Native apps through s
|
|
|
9
9
|
|
|
10
10
|
- ⚡ Automatic performance optimization through source code analysis
|
|
11
11
|
- 🔒 Safe optimizations that don't break your app
|
|
12
|
-
- 🎯
|
|
12
|
+
- 🎯 Virtually zero runtime overhead
|
|
13
13
|
- 📱 Cross-platform compatible
|
|
14
14
|
- 🧪 Works seamlessly with Expo
|
|
15
15
|
- 🎨 Configurable optimization strategies
|
|
16
16
|
|
|
17
|
+
## Benchmark
|
|
18
|
+
|
|
19
|
+
The example app in the `apps/example` directory is a benchmark for the performance of the plugin.
|
|
20
|
+
|
|
21
|
+
<div align="center">
|
|
22
|
+
<img src="./packages/react-native-boost/img/benchmark.png" width="500" />
|
|
23
|
+
<p>
|
|
24
|
+
<b>1,000 Text components</b>: Render time of 1,000 Text components with and without React Native Boost.<br/>
|
|
25
|
+
Measured in milliseconds on an iPhone 16 Pro in development mode, lower is better.
|
|
26
|
+
</p>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
The data is averaged over 10 runs on an iPhone 16 Pro Simulator running the app in development mode.
|
|
30
|
+
|
|
17
31
|
## Installation
|
|
18
32
|
|
|
33
|
+
Install the package using your favorite package manager. Please **do not** install the package as a dev dependency. While the Babel plugin itself would work as a dev dependency, some optimizations import minimal helpers into your code, which requires the package to be installed as a regular dependency.
|
|
34
|
+
|
|
19
35
|
```sh
|
|
20
|
-
npm install
|
|
36
|
+
npm install react-native-boost
|
|
21
37
|
# or
|
|
22
|
-
yarn add
|
|
38
|
+
yarn add react-native-boost
|
|
23
39
|
```
|
|
24
40
|
|
|
25
41
|
Then, add the plugin to your Babel configuration (`babel.config.js`):
|
|
@@ -30,6 +46,8 @@ module.exports = {
|
|
|
30
46
|
};
|
|
31
47
|
```
|
|
32
48
|
|
|
49
|
+
That's it! No imports in your code, pod installing, or anything else is required.
|
|
50
|
+
|
|
33
51
|
Optionally, you can configure the plugin to log optimized files to the console and disable specific optimizations:
|
|
34
52
|
|
|
35
53
|
```js
|
|
@@ -48,6 +66,13 @@ module.exports = {
|
|
|
48
66
|
};
|
|
49
67
|
```
|
|
50
68
|
|
|
69
|
+
You can also skip optimization for a specific comment using a decorator comment above the component's opening tag:
|
|
70
|
+
|
|
71
|
+
```jsx
|
|
72
|
+
// @boost-ignore
|
|
73
|
+
<Text>This will not be optimized.</Text>
|
|
74
|
+
```
|
|
75
|
+
|
|
51
76
|
## How It Works
|
|
52
77
|
|
|
53
78
|
Several standard components in React Native are actually wrappers around their native counterparts. These wrappers often only handle edge cases and aren't needed in most cases. However, they add additional runtime overhead and depth to the component tree, which can lead to performance bottlenecks.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { flattenStyle } from 'react-native/Libraries/StyleSheet/flattenStyle';
|
|
2
|
+
|
|
3
|
+
const propsCache = /* @__PURE__ */ new WeakMap();
|
|
4
|
+
function flattenTextStyle(style) {
|
|
5
|
+
if (!style) return {};
|
|
6
|
+
let props = propsCache.get(style);
|
|
7
|
+
if (props) return props;
|
|
8
|
+
props = {};
|
|
9
|
+
propsCache.set(style, props);
|
|
10
|
+
style = flattenStyle(style);
|
|
11
|
+
if (!style) return {};
|
|
12
|
+
if (typeof (style == null ? void 0 : style.fontWeight) === "number") {
|
|
13
|
+
style.fontWeight = style.fontWeight.toString();
|
|
14
|
+
}
|
|
15
|
+
if ((style == null ? void 0 : style.userSelect) != null) {
|
|
16
|
+
props.selectable = userSelectToSelectableMap[style.userSelect];
|
|
17
|
+
delete style.userSelect;
|
|
18
|
+
}
|
|
19
|
+
if ((style == null ? void 0 : style.verticalAlign) != null) {
|
|
20
|
+
style.textAlignVertical = verticalAlignToTextAlignVerticalMap[style.verticalAlign];
|
|
21
|
+
delete style.verticalAlign;
|
|
22
|
+
}
|
|
23
|
+
props.style = style;
|
|
24
|
+
return props;
|
|
25
|
+
}
|
|
26
|
+
const userSelectToSelectableMap = {
|
|
27
|
+
auto: true,
|
|
28
|
+
text: true,
|
|
29
|
+
none: false,
|
|
30
|
+
contain: true,
|
|
31
|
+
all: true
|
|
32
|
+
};
|
|
33
|
+
const verticalAlignToTextAlignVerticalMap = {
|
|
34
|
+
auto: "auto",
|
|
35
|
+
top: "top",
|
|
36
|
+
bottom: "bottom",
|
|
37
|
+
middle: "center"
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export { flattenTextStyle, userSelectToSelectableMap, verticalAlignToTextAlignVerticalMap };
|
|
41
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../src/runtime/index.ts"],"sourcesContent":["import { TextStyle } from 'react-native';\nimport { flattenStyle } from 'react-native/Libraries/StyleSheet/flattenStyle';\nimport { GenericStyleProp } from './types';\n\nconst propsCache = new WeakMap();\n\nexport function flattenTextStyle(style: GenericStyleProp<TextStyle>) {\n if (!style) return {};\n\n // Cache the computed props\n let props = propsCache.get(style);\n if (props) return props;\n\n props = {};\n propsCache.set(style, props);\n\n style = flattenStyle(style);\n\n if (!style) return {};\n\n if (typeof style?.fontWeight === 'number') {\n style.fontWeight = style.fontWeight.toString() as TextStyle['fontWeight'];\n }\n\n if (style?.userSelect != null) {\n props.selectable = userSelectToSelectableMap[style.userSelect];\n delete style.userSelect;\n }\n\n if (style?.verticalAlign != null) {\n style.textAlignVertical = verticalAlignToTextAlignVerticalMap[\n style.verticalAlign\n ] as TextStyle['textAlignVertical'];\n delete style.verticalAlign;\n }\n\n props.style = style;\n return props;\n}\n\n// Maps the `userSelect` prop to the native `selectable` prop\nexport const userSelectToSelectableMap = {\n auto: true,\n text: true,\n none: false,\n contain: true,\n all: true,\n};\n\n// Maps the `verticalAlign` prop to the native `textAlignVertical` prop\nexport const verticalAlignToTextAlignVerticalMap = {\n auto: 'auto',\n top: 'top',\n bottom: 'bottom',\n middle: 'center',\n};\n\nexport * from './types';\n"],"names":[],"mappings":";;AAIA,MAAM,UAAA,uBAAiB,OAAQ,EAAA;AAExB,SAAS,iBAAiB,KAAoC,EAAA;AACnE,EAAI,IAAA,CAAC,KAAO,EAAA,OAAO,EAAC;AAGpB,EAAI,IAAA,KAAA,GAAQ,UAAW,CAAA,GAAA,CAAI,KAAK,CAAA;AAChC,EAAA,IAAI,OAAc,OAAA,KAAA;AAElB,EAAA,KAAA,GAAQ,EAAC;AACT,EAAW,UAAA,CAAA,GAAA,CAAI,OAAO,KAAK,CAAA;AAE3B,EAAA,KAAA,GAAQ,aAAa,KAAK,CAAA;AAE1B,EAAI,IAAA,CAAC,KAAO,EAAA,OAAO,EAAC;AAEpB,EAAI,IAAA,QAAO,KAAO,IAAA,IAAA,GAAA,MAAA,GAAA,KAAA,CAAA,UAAA,CAAA,KAAe,QAAU,EAAA;AACzC,IAAM,KAAA,CAAA,UAAA,GAAa,KAAM,CAAA,UAAA,CAAW,QAAS,EAAA;AAAA;AAG/C,EAAI,IAAA,CAAA,KAAA,IAAA,IAAA,GAAA,MAAA,GAAA,KAAA,CAAO,eAAc,IAAM,EAAA;AAC7B,IAAM,KAAA,CAAA,UAAA,GAAa,yBAA0B,CAAA,KAAA,CAAM,UAAU,CAAA;AAC7D,IAAA,OAAO,KAAM,CAAA,UAAA;AAAA;AAGf,EAAI,IAAA,CAAA,KAAA,IAAA,IAAA,GAAA,MAAA,GAAA,KAAA,CAAO,kBAAiB,IAAM,EAAA;AAChC,IAAM,KAAA,CAAA,iBAAA,GAAoB,mCACxB,CAAA,KAAA,CAAM,aACR,CAAA;AACA,IAAA,OAAO,KAAM,CAAA,aAAA;AAAA;AAGf,EAAA,KAAA,CAAM,KAAQ,GAAA,KAAA;AACd,EAAO,OAAA,KAAA;AACT;AAGO,MAAM,yBAA4B,GAAA;AAAA,EACvC,IAAM,EAAA,IAAA;AAAA,EACN,IAAM,EAAA,IAAA;AAAA,EACN,IAAM,EAAA,KAAA;AAAA,EACN,OAAS,EAAA,IAAA;AAAA,EACT,GAAK,EAAA;AACP;AAGO,MAAM,mCAAsC,GAAA;AAAA,EACjD,IAAM,EAAA,MAAA;AAAA,EACN,GAAK,EAAA,KAAA;AAAA,EACL,MAAQ,EAAA,QAAA;AAAA,EACR,MAAQ,EAAA;AACV;;;;"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { TextStyle } from 'react-native';
|
|
2
|
+
|
|
3
|
+
type GenericStyleProp<T> = null | void | T | false | '' | ReadonlyArray<GenericStyleProp<T>>;
|
|
4
|
+
|
|
5
|
+
declare function flattenTextStyle(style: GenericStyleProp<TextStyle>): any;
|
|
6
|
+
declare const userSelectToSelectableMap: {
|
|
7
|
+
auto: boolean;
|
|
8
|
+
text: boolean;
|
|
9
|
+
none: boolean;
|
|
10
|
+
contain: boolean;
|
|
11
|
+
all: boolean;
|
|
12
|
+
};
|
|
13
|
+
declare const verticalAlignToTextAlignVerticalMap: {
|
|
14
|
+
auto: string;
|
|
15
|
+
top: string;
|
|
16
|
+
bottom: string;
|
|
17
|
+
middle: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export { type GenericStyleProp, flattenTextStyle, userSelectToSelectableMap, verticalAlignToTextAlignVerticalMap };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var flattenStyle = require('react-native/Libraries/StyleSheet/flattenStyle');
|
|
4
|
+
|
|
5
|
+
const propsCache = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
function flattenTextStyle(style) {
|
|
7
|
+
if (!style) return {};
|
|
8
|
+
let props = propsCache.get(style);
|
|
9
|
+
if (props) return props;
|
|
10
|
+
props = {};
|
|
11
|
+
propsCache.set(style, props);
|
|
12
|
+
style = flattenStyle.flattenStyle(style);
|
|
13
|
+
if (!style) return {};
|
|
14
|
+
if (typeof (style == null ? void 0 : style.fontWeight) === "number") {
|
|
15
|
+
style.fontWeight = style.fontWeight.toString();
|
|
16
|
+
}
|
|
17
|
+
if ((style == null ? void 0 : style.userSelect) != null) {
|
|
18
|
+
props.selectable = userSelectToSelectableMap[style.userSelect];
|
|
19
|
+
delete style.userSelect;
|
|
20
|
+
}
|
|
21
|
+
if ((style == null ? void 0 : style.verticalAlign) != null) {
|
|
22
|
+
style.textAlignVertical = verticalAlignToTextAlignVerticalMap[style.verticalAlign];
|
|
23
|
+
delete style.verticalAlign;
|
|
24
|
+
}
|
|
25
|
+
props.style = style;
|
|
26
|
+
return props;
|
|
27
|
+
}
|
|
28
|
+
const userSelectToSelectableMap = {
|
|
29
|
+
auto: true,
|
|
30
|
+
text: true,
|
|
31
|
+
none: false,
|
|
32
|
+
contain: true,
|
|
33
|
+
all: true
|
|
34
|
+
};
|
|
35
|
+
const verticalAlignToTextAlignVerticalMap = {
|
|
36
|
+
auto: "auto",
|
|
37
|
+
top: "top",
|
|
38
|
+
bottom: "bottom",
|
|
39
|
+
middle: "center"
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
exports.flattenTextStyle = flattenTextStyle;
|
|
43
|
+
exports.userSelectToSelectableMap = userSelectToSelectableMap;
|
|
44
|
+
exports.verticalAlignToTextAlignVerticalMap = verticalAlignToTextAlignVerticalMap;
|
|
45
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/runtime/index.ts"],"sourcesContent":["import { TextStyle } from 'react-native';\nimport { flattenStyle } from 'react-native/Libraries/StyleSheet/flattenStyle';\nimport { GenericStyleProp } from './types';\n\nconst propsCache = new WeakMap();\n\nexport function flattenTextStyle(style: GenericStyleProp<TextStyle>) {\n if (!style) return {};\n\n // Cache the computed props\n let props = propsCache.get(style);\n if (props) return props;\n\n props = {};\n propsCache.set(style, props);\n\n style = flattenStyle(style);\n\n if (!style) return {};\n\n if (typeof style?.fontWeight === 'number') {\n style.fontWeight = style.fontWeight.toString() as TextStyle['fontWeight'];\n }\n\n if (style?.userSelect != null) {\n props.selectable = userSelectToSelectableMap[style.userSelect];\n delete style.userSelect;\n }\n\n if (style?.verticalAlign != null) {\n style.textAlignVertical = verticalAlignToTextAlignVerticalMap[\n style.verticalAlign\n ] as TextStyle['textAlignVertical'];\n delete style.verticalAlign;\n }\n\n props.style = style;\n return props;\n}\n\n// Maps the `userSelect` prop to the native `selectable` prop\nexport const userSelectToSelectableMap = {\n auto: true,\n text: true,\n none: false,\n contain: true,\n all: true,\n};\n\n// Maps the `verticalAlign` prop to the native `textAlignVertical` prop\nexport const verticalAlignToTextAlignVerticalMap = {\n auto: 'auto',\n top: 'top',\n bottom: 'bottom',\n middle: 'center',\n};\n\nexport * from './types';\n"],"names":["flattenStyle"],"mappings":";;;;AAIA,MAAM,UAAA,uBAAiB,OAAQ,EAAA;AAExB,SAAS,iBAAiB,KAAoC,EAAA;AACnE,EAAI,IAAA,CAAC,KAAO,EAAA,OAAO,EAAC;AAGpB,EAAI,IAAA,KAAA,GAAQ,UAAW,CAAA,GAAA,CAAI,KAAK,CAAA;AAChC,EAAA,IAAI,OAAc,OAAA,KAAA;AAElB,EAAA,KAAA,GAAQ,EAAC;AACT,EAAW,UAAA,CAAA,GAAA,CAAI,OAAO,KAAK,CAAA;AAE3B,EAAA,KAAA,GAAQA,0BAAa,KAAK,CAAA;AAE1B,EAAI,IAAA,CAAC,KAAO,EAAA,OAAO,EAAC;AAEpB,EAAI,IAAA,QAAO,KAAO,IAAA,IAAA,GAAA,MAAA,GAAA,KAAA,CAAA,UAAA,CAAA,KAAe,QAAU,EAAA;AACzC,IAAM,KAAA,CAAA,UAAA,GAAa,KAAM,CAAA,UAAA,CAAW,QAAS,EAAA;AAAA;AAG/C,EAAI,IAAA,CAAA,KAAA,IAAA,IAAA,GAAA,MAAA,GAAA,KAAA,CAAO,eAAc,IAAM,EAAA;AAC7B,IAAM,KAAA,CAAA,UAAA,GAAa,yBAA0B,CAAA,KAAA,CAAM,UAAU,CAAA;AAC7D,IAAA,OAAO,KAAM,CAAA,UAAA;AAAA;AAGf,EAAI,IAAA,CAAA,KAAA,IAAA,IAAA,GAAA,MAAA,GAAA,KAAA,CAAO,kBAAiB,IAAM,EAAA;AAChC,IAAM,KAAA,CAAA,iBAAA,GAAoB,mCACxB,CAAA,KAAA,CAAM,aACR,CAAA;AACA,IAAA,OAAO,KAAM,CAAA,aAAA;AAAA;AAGf,EAAA,KAAA,CAAM,KAAQ,GAAA,KAAA;AACd,EAAO,OAAA,KAAA;AACT;AAGO,MAAM,yBAA4B,GAAA;AAAA,EACvC,IAAM,EAAA,IAAA;AAAA,EACN,IAAM,EAAA,IAAA;AAAA,EACN,IAAM,EAAA,KAAA;AAAA,EACN,OAAS,EAAA,IAAA;AAAA,EACT,GAAK,EAAA;AACP;AAGO,MAAM,mCAAsC,GAAA;AAAA,EACjD,IAAM,EAAA,MAAA;AAAA,EACN,GAAK,EAAA,KAAA;AAAA,EACL,MAAQ,EAAA,QAAA;AAAA,EACR,MAAQ,EAAA;AACV;;;;;;"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { declare } from '@babel/helper-plugin-utils';
|
|
2
|
+
import { types } from '@babel/core';
|
|
3
|
+
import { addNamed } from '@babel/helper-module-imports';
|
|
4
|
+
|
|
5
|
+
class PluginError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(`[react-native-boost] Babel plugin exception: ${message}`);
|
|
8
|
+
this.name = "PluginError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const ensureArray = (value) => {
|
|
13
|
+
if (Array.isArray(value)) return value;
|
|
14
|
+
return [value];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const shouldIgnoreOptimization = (path) => {
|
|
18
|
+
var _a, _b, _c;
|
|
19
|
+
if ((_a = path.node.leadingComments) == null ? void 0 : _a.some((comment) => comment.value.includes("@boost-ignore"))) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
const jsxElementPath = path.parentPath;
|
|
23
|
+
if ((_b = jsxElementPath.node.leadingComments) == null ? void 0 : _b.some((comment) => comment.value.includes("@boost-ignore"))) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
const propertyPath = jsxElementPath.parentPath;
|
|
27
|
+
if (propertyPath && propertyPath.isObjectProperty() && ((_c = propertyPath.node.leadingComments) == null ? void 0 : _c.some((comment) => comment.value.includes("@boost-ignore")))) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
if (!jsxElementPath.parentPath) return false;
|
|
31
|
+
const containerPath = jsxElementPath.parentPath;
|
|
32
|
+
const siblings = ensureArray(containerPath.get("children"));
|
|
33
|
+
const index = siblings.findIndex((sibling) => sibling.node === jsxElementPath.node);
|
|
34
|
+
if (index === -1) return false;
|
|
35
|
+
for (let index_ = index - 1; index_ >= 0; index_--) {
|
|
36
|
+
const sibling = siblings[index_];
|
|
37
|
+
if (sibling.isJSXText() && sibling.node.value.trim() === "") {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (sibling.isJSXExpressionContainer()) {
|
|
41
|
+
const expression = sibling.get("expression");
|
|
42
|
+
if (expression && expression.node) {
|
|
43
|
+
const comments = [
|
|
44
|
+
...expression.node.leadingComments || [],
|
|
45
|
+
...expression.node.trailingComments || [],
|
|
46
|
+
...expression.node.innerComments || []
|
|
47
|
+
].map((comment) => comment.value.trim());
|
|
48
|
+
if (comments.some((comment) => comment.includes("@boost-ignore"))) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (sibling.node.leadingComments && sibling.node.leadingComments.some((comment) => comment.value.includes("@boost-ignore"))) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const textOptimizer = (path, log = () => {
|
|
62
|
+
}) => {
|
|
63
|
+
var _a, _b, _c;
|
|
64
|
+
if (!types.isJSXIdentifier(path.node.name)) return;
|
|
65
|
+
const parent = path.parent;
|
|
66
|
+
if (!types.isJSXElement(parent)) return;
|
|
67
|
+
const elementName = path.node.name.name;
|
|
68
|
+
if (elementName !== "Text") return;
|
|
69
|
+
if (shouldIgnoreOptimization(path)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const binding = path.scope.getBinding(elementName);
|
|
73
|
+
if (!binding) return;
|
|
74
|
+
if (binding.kind === "module") {
|
|
75
|
+
const parentNode = binding.path.parent;
|
|
76
|
+
if (!types.isImportDeclaration(parentNode) || parentNode.source.value !== "react-native") {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (hasBlacklistedProperties(path)) return;
|
|
81
|
+
if (!hasOnlyStringChildren(path, parent)) return;
|
|
82
|
+
const hub = path.hub;
|
|
83
|
+
const file = typeof hub === "object" && hub !== null && "file" in hub ? hub.file : void 0;
|
|
84
|
+
if (!file) {
|
|
85
|
+
throw new PluginError("No file found in Babel hub");
|
|
86
|
+
}
|
|
87
|
+
const filename = ((_a = file.opts) == null ? void 0 : _a.filename) || "unknown file";
|
|
88
|
+
const lineNumber = (_c = (_b = path.node.loc) == null ? void 0 : _b.start.line) != null ? _c : "unknown line";
|
|
89
|
+
log(`Optimizing Text component in ${filename}:${lineNumber}`);
|
|
90
|
+
optimizeStyleTag({ path, file });
|
|
91
|
+
if (!file.__hasImports) {
|
|
92
|
+
file.__hasImports = {};
|
|
93
|
+
}
|
|
94
|
+
if (!file.__hasImports.NativeText) {
|
|
95
|
+
file.__hasImports.NativeText = addNamed(path, "NativeText", "react-native/Libraries/Text/TextNativeComponent");
|
|
96
|
+
}
|
|
97
|
+
const nativeTextIdentifier = file.__hasImports.NativeText;
|
|
98
|
+
path.node.name.name = nativeTextIdentifier.name;
|
|
99
|
+
if (!path.node.selfClosing && parent.closingElement && types.isJSXIdentifier(parent.closingElement.name) && parent.closingElement.name.name === "Text") {
|
|
100
|
+
parent.closingElement.name.name = nativeTextIdentifier.name;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
function hasOnlyStringChildren(path, node) {
|
|
104
|
+
return node.children.every((child) => isStringNode(path, child));
|
|
105
|
+
}
|
|
106
|
+
function isStringNode(path, child) {
|
|
107
|
+
if (types.isJSXText(child)) return true;
|
|
108
|
+
if (types.isJSXExpressionContainer(child)) {
|
|
109
|
+
const expression = child.expression;
|
|
110
|
+
if (types.isIdentifier(expression)) {
|
|
111
|
+
const binding = path.scope.getBinding(expression.name);
|
|
112
|
+
return binding ? types.isStringLiteral(binding.path.node) : false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
const blacklistedProperties = /* @__PURE__ */ new Set([
|
|
118
|
+
"accessible",
|
|
119
|
+
"accessibilityLabel",
|
|
120
|
+
"accessibilityState",
|
|
121
|
+
"allowFontScaling",
|
|
122
|
+
"aria-busy",
|
|
123
|
+
"aria-checked",
|
|
124
|
+
"aria-disabled",
|
|
125
|
+
"aria-expanded",
|
|
126
|
+
"aria-label",
|
|
127
|
+
"aria-selected",
|
|
128
|
+
"ellipsizeMode",
|
|
129
|
+
"id",
|
|
130
|
+
"nativeID",
|
|
131
|
+
"onLongPress",
|
|
132
|
+
"onPress",
|
|
133
|
+
"onPressIn",
|
|
134
|
+
"onPressOut",
|
|
135
|
+
"onResponderGrant",
|
|
136
|
+
"onResponderMove",
|
|
137
|
+
"onResponderRelease",
|
|
138
|
+
"onResponderTerminate",
|
|
139
|
+
"onResponderTerminationRequest",
|
|
140
|
+
"onStartShouldSetResponder",
|
|
141
|
+
"pressRetentionOffset",
|
|
142
|
+
"suppressHighlighting"
|
|
143
|
+
]);
|
|
144
|
+
function optimizeStyleTag({ path, file }) {
|
|
145
|
+
var _a;
|
|
146
|
+
let shouldImportFlattenTextStyle = false;
|
|
147
|
+
const nameHint = "_flattenTextStyle";
|
|
148
|
+
for (const [index, attribute] of path.node.attributes.entries()) {
|
|
149
|
+
if (types.isJSXAttribute(attribute) && types.isJSXIdentifier(attribute.name, { name: "style" })) {
|
|
150
|
+
shouldImportFlattenTextStyle = true;
|
|
151
|
+
if (types.isJSXExpressionContainer(attribute.value) && !types.isJSXEmptyExpression(attribute.value.expression)) {
|
|
152
|
+
path.node.attributes[index] = types.jsxSpreadAttribute(
|
|
153
|
+
types.callExpression(types.identifier(nameHint), [attribute.value.expression])
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (shouldImportFlattenTextStyle && !((_a = file.__hasImports) == null ? void 0 : _a.flattenTextStyle)) {
|
|
159
|
+
if (!file.__hasImports) file.__hasImports = {};
|
|
160
|
+
file.__hasImports.flattenTextStyle = addNamed(path, "flattenTextStyle", "react-native-boost", { nameHint });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function hasBlacklistedProperties(path) {
|
|
164
|
+
return path.node.attributes.some((attribute) => {
|
|
165
|
+
if (types.isJSXSpreadAttribute(attribute)) {
|
|
166
|
+
if (types.isIdentifier(attribute.argument)) {
|
|
167
|
+
const binding = path.scope.getBinding(attribute.argument.name);
|
|
168
|
+
let objectExpression;
|
|
169
|
+
if (binding) {
|
|
170
|
+
if (types.isVariableDeclarator(binding.path.node)) {
|
|
171
|
+
objectExpression = binding.path.node.init;
|
|
172
|
+
} else if (types.isObjectExpression(binding.path.node)) {
|
|
173
|
+
objectExpression = binding.path.node;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (objectExpression && types.isObjectExpression(objectExpression)) {
|
|
177
|
+
return objectExpression.properties.some((property) => {
|
|
178
|
+
if (types.isObjectProperty(property) && types.isIdentifier(property.key)) {
|
|
179
|
+
return blacklistedProperties.has(property.key.name);
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
if (types.isJSXIdentifier(attribute.name) && attribute.value) {
|
|
188
|
+
if (attribute.name.name === "children") {
|
|
189
|
+
return isStringNode(path, attribute.value);
|
|
190
|
+
}
|
|
191
|
+
return blacklistedProperties.has(attribute.name.name);
|
|
192
|
+
}
|
|
193
|
+
return false;
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const log = (message) => {
|
|
198
|
+
console.log(`[react-native-boost] ${message}`);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
var index = declare((api) => {
|
|
202
|
+
api.assertVersion(7);
|
|
203
|
+
return {
|
|
204
|
+
name: "react-native-boost",
|
|
205
|
+
visitor: {
|
|
206
|
+
JSXOpeningElement(path, state) {
|
|
207
|
+
var _a, _b;
|
|
208
|
+
const options = (_a = state.opts) != null ? _a : {};
|
|
209
|
+
const logger = options.verbose ? log : () => {
|
|
210
|
+
};
|
|
211
|
+
if (((_b = options.optimizations) == null ? void 0 : _b.text) !== false) textOptimizer(path, logger);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
export { index as default };
|
|
218
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../../src/plugin/utils/plugin-error.ts","../../../src/plugin/utils/helpers.ts","../../../src/plugin/utils/common.ts","../../../src/plugin/optimizers/text/index.ts","../../../src/plugin/utils/logger.ts","../../../src/plugin/index.ts"],"sourcesContent":["export default class PluginError extends Error {\n constructor(message: string) {\n super(`[react-native-boost] Babel plugin exception: ${message}`);\n this.name = 'PluginError';\n }\n}\n","export const ensureArray = <T>(value: T | T[]): T[] => {\n if (Array.isArray(value)) return value;\n return [value];\n};\n","import { NodePath, types as t } from '@babel/core';\nimport { ensureArray } from './helpers';\n\n/**\n * Checks if the JSX element should be ignored based on a preceding comment.\n *\n * The function looks up the JSXOpeningElement's own leading comments as well as\n * the parent element's comments before falling back to inspect siblings.\n */\nexport const shouldIgnoreOptimization = (path: NodePath<t.JSXOpeningElement>): boolean => {\n // Check for @boost-ignore in the leading comments on the JSX opening element.\n if (path.node.leadingComments?.some((comment) => comment.value.includes('@boost-ignore'))) {\n return true;\n }\n\n // Check for @boost-ignore in the leading comments on the parent JSX element.\n const jsxElementPath = path.parentPath;\n if (jsxElementPath.node.leadingComments?.some((comment) => comment.value.includes('@boost-ignore'))) {\n return true;\n }\n\n // NEW: Check for @boost-ignore in the leading comments on the ObjectProperty (if it exists)\n // This handles cases where the JSX element is used as a value inside an object literal.\n const propertyPath = jsxElementPath.parentPath;\n if (\n propertyPath &&\n propertyPath.isObjectProperty() &&\n propertyPath.node.leadingComments?.some((comment) => comment.value.includes('@boost-ignore'))\n ) {\n return true;\n }\n\n if (!jsxElementPath.parentPath) return false;\n\n // Get the container that holds this element (for example, a JSX fragment or JSX element)\n const containerPath = jsxElementPath.parentPath;\n const siblings = ensureArray(containerPath.get('children'));\n const index = siblings.findIndex((sibling) => sibling.node === jsxElementPath.node);\n if (index === -1) return false;\n\n // Look backward from the current element for a non-empty node.\n for (let index_ = index - 1; index_ >= 0; index_--) {\n const sibling = siblings[index_];\n // Skip over any whitespace (only in JSXText nodes)\n if (sibling.isJSXText() && sibling.node.value.trim() === '') {\n continue;\n }\n // If the sibling is a JSX expression container, check its empty expression's comments.\n if (sibling.isJSXExpressionContainer()) {\n const expression = sibling.get('expression');\n if (expression && expression.node) {\n const comments = [\n ...(expression.node.leadingComments || []),\n ...(expression.node.trailingComments || []),\n ...(expression.node.innerComments || []),\n ].map((comment) => comment.value.trim());\n if (comments.some((comment) => comment.includes('@boost-ignore'))) {\n return true;\n }\n }\n }\n // Also check if the node itself carries a leadingComments property.\n if (\n sibling.node.leadingComments &&\n sibling.node.leadingComments.some((comment) => comment.value.includes('@boost-ignore'))\n ) {\n return true;\n }\n break; // if the immediate non-whitespace node is not our ignore marker, stop\n }\n return false;\n};\n","import { NodePath, types as t } from '@babel/core';\nimport { addNamed } from '@babel/helper-module-imports';\nimport { HubFile, Optimizer } from '../../types';\nimport PluginError from '../../utils/plugin-error';\nimport { shouldIgnoreOptimization } from '../../utils/common';\n\nexport const textOptimizer: Optimizer = (path, log = () => {}) => {\n // Ensure we're processing a JSX Text element\n if (!t.isJSXIdentifier(path.node.name)) return;\n\n const parent = path.parent;\n if (!t.isJSXElement(parent)) return;\n\n const elementName = path.node.name.name;\n if (elementName !== 'Text') return;\n\n // If the component is preceded by an ignore comment, do not optimize.\n if (shouldIgnoreOptimization(path)) {\n return;\n }\n\n // Ensure Text element comes from react-native\n const binding = path.scope.getBinding(elementName);\n if (!binding) return;\n if (binding.kind === 'module') {\n const parentNode = binding.path.parent;\n if (!t.isImportDeclaration(parentNode) || parentNode.source.value !== 'react-native') {\n return;\n }\n }\n\n // Bail if the element has any blacklisted properties or non-string children props\n if (hasBlacklistedProperties(path)) return;\n if (!hasOnlyStringChildren(path, parent)) return;\n\n // Extract the file from the Babel hub and add flags for logging & import caching\n const hub = path.hub as unknown;\n const file = typeof hub === 'object' && hub !== null && 'file' in hub ? (hub.file as HubFile) : undefined;\n\n if (!file) {\n throw new PluginError('No file found in Babel hub');\n }\n\n const filename = file.opts?.filename || 'unknown file';\n const lineNumber = path.node.loc?.start.line ?? 'unknown line';\n log(`Optimizing Text component in ${filename}:${lineNumber}`);\n\n // Optimize props\n optimizeStyleTag({ path, file });\n\n // Add TextNativeComponent import (cached on file) so we only add it once per file\n if (!file.__hasImports) {\n file.__hasImports = {};\n }\n if (!file.__hasImports.NativeText) {\n file.__hasImports.NativeText = addNamed(path, 'NativeText', 'react-native/Libraries/Text/TextNativeComponent');\n }\n const nativeTextIdentifier = file.__hasImports.NativeText;\n path.node.name.name = nativeTextIdentifier.name;\n\n // If the element is not self-closing, update the closing element as well\n if (\n !path.node.selfClosing &&\n parent.closingElement &&\n t.isJSXIdentifier(parent.closingElement.name) &&\n parent.closingElement.name.name === 'Text'\n ) {\n parent.closingElement.name.name = nativeTextIdentifier.name;\n }\n};\n\nfunction hasOnlyStringChildren(path: NodePath<t.JSXOpeningElement>, node: t.JSXElement): boolean {\n return node.children.every((child) => isStringNode(path, child));\n}\n\nfunction isStringNode(path: NodePath<t.JSXOpeningElement>, child: t.Node): boolean {\n if (t.isJSXText(child)) return true;\n\n // Check for JSX expressions\n if (t.isJSXExpressionContainer(child)) {\n const expression = child.expression;\n\n // If the expression is an identifier, look it up in the current scope\n if (t.isIdentifier(expression)) {\n const binding = path.scope.getBinding(expression.name);\n return binding ? t.isStringLiteral(binding.path.node) : false;\n }\n }\n return false;\n}\n\nconst blacklistedProperties = new Set([\n 'accessible',\n 'accessibilityLabel',\n 'accessibilityState',\n 'allowFontScaling',\n 'aria-busy',\n 'aria-checked',\n 'aria-disabled',\n 'aria-expanded',\n 'aria-label',\n 'aria-selected',\n 'ellipsizeMode',\n 'id',\n 'nativeID',\n 'onLongPress',\n 'onPress',\n 'onPressIn',\n 'onPressOut',\n 'onResponderGrant',\n 'onResponderMove',\n 'onResponderRelease',\n 'onResponderTerminate',\n 'onResponderTerminationRequest',\n 'onStartShouldSetResponder',\n 'pressRetentionOffset',\n 'suppressHighlighting',\n]);\n\nfunction optimizeStyleTag({ path, file }: { path: NodePath<t.JSXOpeningElement>; file: HubFile }) {\n let shouldImportFlattenTextStyle = false;\n const nameHint = '_flattenTextStyle';\n\n for (const [index, attribute] of path.node.attributes.entries()) {\n if (t.isJSXAttribute(attribute) && t.isJSXIdentifier(attribute.name, { name: 'style' })) {\n shouldImportFlattenTextStyle = true;\n\n if (t.isJSXExpressionContainer(attribute.value) && !t.isJSXEmptyExpression(attribute.value.expression)) {\n path.node.attributes[index] = t.jsxSpreadAttribute(\n t.callExpression(t.identifier(nameHint), [attribute.value.expression])\n );\n }\n }\n }\n\n if (shouldImportFlattenTextStyle && !file.__hasImports?.flattenTextStyle) {\n if (!file.__hasImports) file.__hasImports = {};\n file.__hasImports.flattenTextStyle = addNamed(path, 'flattenTextStyle', 'react-native-boost', { nameHint });\n }\n}\n\nfunction hasBlacklistedProperties(path: NodePath<t.JSXOpeningElement>): boolean {\n return path.node.attributes.some((attribute) => {\n // Check if we can resolve the spread attribute\n if (t.isJSXSpreadAttribute(attribute)) {\n if (t.isIdentifier(attribute.argument)) {\n const binding = path.scope.getBinding(attribute.argument.name);\n let objectExpression: t.ObjectExpression | undefined;\n if (binding) {\n // If the binding node is a VariableDeclarator, use its initializer\n if (t.isVariableDeclarator(binding.path.node)) {\n objectExpression = binding.path.node.init as t.ObjectExpression;\n } else if (t.isObjectExpression(binding.path.node)) {\n objectExpression = binding.path.node;\n }\n }\n if (objectExpression && t.isObjectExpression(objectExpression)) {\n return objectExpression.properties.some((property) => {\n if (t.isObjectProperty(property) && t.isIdentifier(property.key)) {\n return blacklistedProperties.has(property.key.name);\n }\n return false;\n });\n }\n }\n // Bail if we can't resolve the spread attribute\n return true;\n }\n\n if (t.isJSXIdentifier(attribute.name) && attribute.value) {\n // For a \"children\" attribute, optimization is allowed only if it is a string\n if (attribute.name.name === 'children') {\n return isStringNode(path, attribute.value);\n }\n return blacklistedProperties.has(attribute.name.name);\n }\n\n // For other attribute types (e.g. namespaced), assume no blacklisting\n return false;\n });\n}\n","export const log = (message: string) => {\n console.log(`[react-native-boost] ${message}`);\n};\n","import { declare } from '@babel/helper-plugin-utils';\nimport { textOptimizer } from './optimizers/text';\nimport { PluginOptions } from './types';\nimport { log } from './utils/logger';\n\nexport default declare((api) => {\n api.assertVersion(7);\n\n return {\n name: 'react-native-boost',\n visitor: {\n JSXOpeningElement(path, state) {\n const options = (state.opts ?? {}) as PluginOptions;\n const logger = options.verbose ? log : () => {};\n if (options.optimizations?.text !== false) textOptimizer(path, logger);\n },\n },\n };\n});\n"],"names":["t"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as _babel_traverse from '@babel/traverse';
|
|
2
|
+
import * as _babel_types from '@babel/types';
|
|
3
|
+
import * as _babel_core from '@babel/core';
|
|
4
|
+
|
|
5
|
+
declare const _default: (api: object, options: Record<string, any> | null | undefined, dirname: string) => {
|
|
6
|
+
name: string;
|
|
7
|
+
visitor: {
|
|
8
|
+
JSXOpeningElement(this: _babel_core.PluginPass, path: _babel_traverse.NodePath<_babel_types.JSXOpeningElement>, state: _babel_core.PluginPass): void;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export { _default as default };
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var helperPluginUtils = require('@babel/helper-plugin-utils');
|
|
4
|
+
var core = require('@babel/core');
|
|
5
|
+
var helperModuleImports = require('@babel/helper-module-imports');
|
|
6
|
+
|
|
7
|
+
class PluginError extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(`[react-native-boost] Babel plugin exception: ${message}`);
|
|
10
|
+
this.name = "PluginError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const ensureArray = (value) => {
|
|
15
|
+
if (Array.isArray(value)) return value;
|
|
16
|
+
return [value];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const shouldIgnoreOptimization = (path) => {
|
|
20
|
+
var _a, _b, _c;
|
|
21
|
+
if ((_a = path.node.leadingComments) == null ? void 0 : _a.some((comment) => comment.value.includes("@boost-ignore"))) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
const jsxElementPath = path.parentPath;
|
|
25
|
+
if ((_b = jsxElementPath.node.leadingComments) == null ? void 0 : _b.some((comment) => comment.value.includes("@boost-ignore"))) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
const propertyPath = jsxElementPath.parentPath;
|
|
29
|
+
if (propertyPath && propertyPath.isObjectProperty() && ((_c = propertyPath.node.leadingComments) == null ? void 0 : _c.some((comment) => comment.value.includes("@boost-ignore")))) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
if (!jsxElementPath.parentPath) return false;
|
|
33
|
+
const containerPath = jsxElementPath.parentPath;
|
|
34
|
+
const siblings = ensureArray(containerPath.get("children"));
|
|
35
|
+
const index = siblings.findIndex((sibling) => sibling.node === jsxElementPath.node);
|
|
36
|
+
if (index === -1) return false;
|
|
37
|
+
for (let index_ = index - 1; index_ >= 0; index_--) {
|
|
38
|
+
const sibling = siblings[index_];
|
|
39
|
+
if (sibling.isJSXText() && sibling.node.value.trim() === "") {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (sibling.isJSXExpressionContainer()) {
|
|
43
|
+
const expression = sibling.get("expression");
|
|
44
|
+
if (expression && expression.node) {
|
|
45
|
+
const comments = [
|
|
46
|
+
...expression.node.leadingComments || [],
|
|
47
|
+
...expression.node.trailingComments || [],
|
|
48
|
+
...expression.node.innerComments || []
|
|
49
|
+
].map((comment) => comment.value.trim());
|
|
50
|
+
if (comments.some((comment) => comment.includes("@boost-ignore"))) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (sibling.node.leadingComments && sibling.node.leadingComments.some((comment) => comment.value.includes("@boost-ignore"))) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const textOptimizer = (path, log = () => {
|
|
64
|
+
}) => {
|
|
65
|
+
var _a, _b, _c;
|
|
66
|
+
if (!core.types.isJSXIdentifier(path.node.name)) return;
|
|
67
|
+
const parent = path.parent;
|
|
68
|
+
if (!core.types.isJSXElement(parent)) return;
|
|
69
|
+
const elementName = path.node.name.name;
|
|
70
|
+
if (elementName !== "Text") return;
|
|
71
|
+
if (shouldIgnoreOptimization(path)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const binding = path.scope.getBinding(elementName);
|
|
75
|
+
if (!binding) return;
|
|
76
|
+
if (binding.kind === "module") {
|
|
77
|
+
const parentNode = binding.path.parent;
|
|
78
|
+
if (!core.types.isImportDeclaration(parentNode) || parentNode.source.value !== "react-native") {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (hasBlacklistedProperties(path)) return;
|
|
83
|
+
if (!hasOnlyStringChildren(path, parent)) return;
|
|
84
|
+
const hub = path.hub;
|
|
85
|
+
const file = typeof hub === "object" && hub !== null && "file" in hub ? hub.file : void 0;
|
|
86
|
+
if (!file) {
|
|
87
|
+
throw new PluginError("No file found in Babel hub");
|
|
88
|
+
}
|
|
89
|
+
const filename = ((_a = file.opts) == null ? void 0 : _a.filename) || "unknown file";
|
|
90
|
+
const lineNumber = (_c = (_b = path.node.loc) == null ? void 0 : _b.start.line) != null ? _c : "unknown line";
|
|
91
|
+
log(`Optimizing Text component in ${filename}:${lineNumber}`);
|
|
92
|
+
optimizeStyleTag({ path, file });
|
|
93
|
+
if (!file.__hasImports) {
|
|
94
|
+
file.__hasImports = {};
|
|
95
|
+
}
|
|
96
|
+
if (!file.__hasImports.NativeText) {
|
|
97
|
+
file.__hasImports.NativeText = helperModuleImports.addNamed(path, "NativeText", "react-native/Libraries/Text/TextNativeComponent");
|
|
98
|
+
}
|
|
99
|
+
const nativeTextIdentifier = file.__hasImports.NativeText;
|
|
100
|
+
path.node.name.name = nativeTextIdentifier.name;
|
|
101
|
+
if (!path.node.selfClosing && parent.closingElement && core.types.isJSXIdentifier(parent.closingElement.name) && parent.closingElement.name.name === "Text") {
|
|
102
|
+
parent.closingElement.name.name = nativeTextIdentifier.name;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
function hasOnlyStringChildren(path, node) {
|
|
106
|
+
return node.children.every((child) => isStringNode(path, child));
|
|
107
|
+
}
|
|
108
|
+
function isStringNode(path, child) {
|
|
109
|
+
if (core.types.isJSXText(child)) return true;
|
|
110
|
+
if (core.types.isJSXExpressionContainer(child)) {
|
|
111
|
+
const expression = child.expression;
|
|
112
|
+
if (core.types.isIdentifier(expression)) {
|
|
113
|
+
const binding = path.scope.getBinding(expression.name);
|
|
114
|
+
return binding ? core.types.isStringLiteral(binding.path.node) : false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
const blacklistedProperties = /* @__PURE__ */ new Set([
|
|
120
|
+
"accessible",
|
|
121
|
+
"accessibilityLabel",
|
|
122
|
+
"accessibilityState",
|
|
123
|
+
"allowFontScaling",
|
|
124
|
+
"aria-busy",
|
|
125
|
+
"aria-checked",
|
|
126
|
+
"aria-disabled",
|
|
127
|
+
"aria-expanded",
|
|
128
|
+
"aria-label",
|
|
129
|
+
"aria-selected",
|
|
130
|
+
"ellipsizeMode",
|
|
131
|
+
"id",
|
|
132
|
+
"nativeID",
|
|
133
|
+
"onLongPress",
|
|
134
|
+
"onPress",
|
|
135
|
+
"onPressIn",
|
|
136
|
+
"onPressOut",
|
|
137
|
+
"onResponderGrant",
|
|
138
|
+
"onResponderMove",
|
|
139
|
+
"onResponderRelease",
|
|
140
|
+
"onResponderTerminate",
|
|
141
|
+
"onResponderTerminationRequest",
|
|
142
|
+
"onStartShouldSetResponder",
|
|
143
|
+
"pressRetentionOffset",
|
|
144
|
+
"suppressHighlighting"
|
|
145
|
+
]);
|
|
146
|
+
function optimizeStyleTag({ path, file }) {
|
|
147
|
+
var _a;
|
|
148
|
+
let shouldImportFlattenTextStyle = false;
|
|
149
|
+
const nameHint = "_flattenTextStyle";
|
|
150
|
+
for (const [index, attribute] of path.node.attributes.entries()) {
|
|
151
|
+
if (core.types.isJSXAttribute(attribute) && core.types.isJSXIdentifier(attribute.name, { name: "style" })) {
|
|
152
|
+
shouldImportFlattenTextStyle = true;
|
|
153
|
+
if (core.types.isJSXExpressionContainer(attribute.value) && !core.types.isJSXEmptyExpression(attribute.value.expression)) {
|
|
154
|
+
path.node.attributes[index] = core.types.jsxSpreadAttribute(
|
|
155
|
+
core.types.callExpression(core.types.identifier(nameHint), [attribute.value.expression])
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (shouldImportFlattenTextStyle && !((_a = file.__hasImports) == null ? void 0 : _a.flattenTextStyle)) {
|
|
161
|
+
if (!file.__hasImports) file.__hasImports = {};
|
|
162
|
+
file.__hasImports.flattenTextStyle = helperModuleImports.addNamed(path, "flattenTextStyle", "react-native-boost", { nameHint });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function hasBlacklistedProperties(path) {
|
|
166
|
+
return path.node.attributes.some((attribute) => {
|
|
167
|
+
if (core.types.isJSXSpreadAttribute(attribute)) {
|
|
168
|
+
if (core.types.isIdentifier(attribute.argument)) {
|
|
169
|
+
const binding = path.scope.getBinding(attribute.argument.name);
|
|
170
|
+
let objectExpression;
|
|
171
|
+
if (binding) {
|
|
172
|
+
if (core.types.isVariableDeclarator(binding.path.node)) {
|
|
173
|
+
objectExpression = binding.path.node.init;
|
|
174
|
+
} else if (core.types.isObjectExpression(binding.path.node)) {
|
|
175
|
+
objectExpression = binding.path.node;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (objectExpression && core.types.isObjectExpression(objectExpression)) {
|
|
179
|
+
return objectExpression.properties.some((property) => {
|
|
180
|
+
if (core.types.isObjectProperty(property) && core.types.isIdentifier(property.key)) {
|
|
181
|
+
return blacklistedProperties.has(property.key.name);
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
if (core.types.isJSXIdentifier(attribute.name) && attribute.value) {
|
|
190
|
+
if (attribute.name.name === "children") {
|
|
191
|
+
return isStringNode(path, attribute.value);
|
|
192
|
+
}
|
|
193
|
+
return blacklistedProperties.has(attribute.name.name);
|
|
194
|
+
}
|
|
195
|
+
return false;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const log = (message) => {
|
|
200
|
+
console.log(`[react-native-boost] ${message}`);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
var index = helperPluginUtils.declare((api) => {
|
|
204
|
+
api.assertVersion(7);
|
|
205
|
+
return {
|
|
206
|
+
name: "react-native-boost",
|
|
207
|
+
visitor: {
|
|
208
|
+
JSXOpeningElement(path, state) {
|
|
209
|
+
var _a, _b;
|
|
210
|
+
const options = (_a = state.opts) != null ? _a : {};
|
|
211
|
+
const logger = options.verbose ? log : () => {
|
|
212
|
+
};
|
|
213
|
+
if (((_b = options.optimizations) == null ? void 0 : _b.text) !== false) textOptimizer(path, logger);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
module.exports = index;
|
|
220
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/plugin/utils/plugin-error.ts","../../src/plugin/utils/helpers.ts","../../src/plugin/utils/common.ts","../../src/plugin/optimizers/text/index.ts","../../src/plugin/utils/logger.ts","../../src/plugin/index.ts"],"sourcesContent":["export default class PluginError extends Error {\n constructor(message: string) {\n super(`[react-native-boost] Babel plugin exception: ${message}`);\n this.name = 'PluginError';\n }\n}\n","export const ensureArray = <T>(value: T | T[]): T[] => {\n if (Array.isArray(value)) return value;\n return [value];\n};\n","import { NodePath, types as t } from '@babel/core';\nimport { ensureArray } from './helpers';\n\n/**\n * Checks if the JSX element should be ignored based on a preceding comment.\n *\n * The function looks up the JSXOpeningElement's own leading comments as well as\n * the parent element's comments before falling back to inspect siblings.\n */\nexport const shouldIgnoreOptimization = (path: NodePath<t.JSXOpeningElement>): boolean => {\n // Check for @boost-ignore in the leading comments on the JSX opening element.\n if (path.node.leadingComments?.some((comment) => comment.value.includes('@boost-ignore'))) {\n return true;\n }\n\n // Check for @boost-ignore in the leading comments on the parent JSX element.\n const jsxElementPath = path.parentPath;\n if (jsxElementPath.node.leadingComments?.some((comment) => comment.value.includes('@boost-ignore'))) {\n return true;\n }\n\n // NEW: Check for @boost-ignore in the leading comments on the ObjectProperty (if it exists)\n // This handles cases where the JSX element is used as a value inside an object literal.\n const propertyPath = jsxElementPath.parentPath;\n if (\n propertyPath &&\n propertyPath.isObjectProperty() &&\n propertyPath.node.leadingComments?.some((comment) => comment.value.includes('@boost-ignore'))\n ) {\n return true;\n }\n\n if (!jsxElementPath.parentPath) return false;\n\n // Get the container that holds this element (for example, a JSX fragment or JSX element)\n const containerPath = jsxElementPath.parentPath;\n const siblings = ensureArray(containerPath.get('children'));\n const index = siblings.findIndex((sibling) => sibling.node === jsxElementPath.node);\n if (index === -1) return false;\n\n // Look backward from the current element for a non-empty node.\n for (let index_ = index - 1; index_ >= 0; index_--) {\n const sibling = siblings[index_];\n // Skip over any whitespace (only in JSXText nodes)\n if (sibling.isJSXText() && sibling.node.value.trim() === '') {\n continue;\n }\n // If the sibling is a JSX expression container, check its empty expression's comments.\n if (sibling.isJSXExpressionContainer()) {\n const expression = sibling.get('expression');\n if (expression && expression.node) {\n const comments = [\n ...(expression.node.leadingComments || []),\n ...(expression.node.trailingComments || []),\n ...(expression.node.innerComments || []),\n ].map((comment) => comment.value.trim());\n if (comments.some((comment) => comment.includes('@boost-ignore'))) {\n return true;\n }\n }\n }\n // Also check if the node itself carries a leadingComments property.\n if (\n sibling.node.leadingComments &&\n sibling.node.leadingComments.some((comment) => comment.value.includes('@boost-ignore'))\n ) {\n return true;\n }\n break; // if the immediate non-whitespace node is not our ignore marker, stop\n }\n return false;\n};\n","import { NodePath, types as t } from '@babel/core';\nimport { addNamed } from '@babel/helper-module-imports';\nimport { HubFile, Optimizer } from '../../types';\nimport PluginError from '../../utils/plugin-error';\nimport { shouldIgnoreOptimization } from '../../utils/common';\n\nexport const textOptimizer: Optimizer = (path, log = () => {}) => {\n // Ensure we're processing a JSX Text element\n if (!t.isJSXIdentifier(path.node.name)) return;\n\n const parent = path.parent;\n if (!t.isJSXElement(parent)) return;\n\n const elementName = path.node.name.name;\n if (elementName !== 'Text') return;\n\n // If the component is preceded by an ignore comment, do not optimize.\n if (shouldIgnoreOptimization(path)) {\n return;\n }\n\n // Ensure Text element comes from react-native\n const binding = path.scope.getBinding(elementName);\n if (!binding) return;\n if (binding.kind === 'module') {\n const parentNode = binding.path.parent;\n if (!t.isImportDeclaration(parentNode) || parentNode.source.value !== 'react-native') {\n return;\n }\n }\n\n // Bail if the element has any blacklisted properties or non-string children props\n if (hasBlacklistedProperties(path)) return;\n if (!hasOnlyStringChildren(path, parent)) return;\n\n // Extract the file from the Babel hub and add flags for logging & import caching\n const hub = path.hub as unknown;\n const file = typeof hub === 'object' && hub !== null && 'file' in hub ? (hub.file as HubFile) : undefined;\n\n if (!file) {\n throw new PluginError('No file found in Babel hub');\n }\n\n const filename = file.opts?.filename || 'unknown file';\n const lineNumber = path.node.loc?.start.line ?? 'unknown line';\n log(`Optimizing Text component in ${filename}:${lineNumber}`);\n\n // Optimize props\n optimizeStyleTag({ path, file });\n\n // Add TextNativeComponent import (cached on file) so we only add it once per file\n if (!file.__hasImports) {\n file.__hasImports = {};\n }\n if (!file.__hasImports.NativeText) {\n file.__hasImports.NativeText = addNamed(path, 'NativeText', 'react-native/Libraries/Text/TextNativeComponent');\n }\n const nativeTextIdentifier = file.__hasImports.NativeText;\n path.node.name.name = nativeTextIdentifier.name;\n\n // If the element is not self-closing, update the closing element as well\n if (\n !path.node.selfClosing &&\n parent.closingElement &&\n t.isJSXIdentifier(parent.closingElement.name) &&\n parent.closingElement.name.name === 'Text'\n ) {\n parent.closingElement.name.name = nativeTextIdentifier.name;\n }\n};\n\nfunction hasOnlyStringChildren(path: NodePath<t.JSXOpeningElement>, node: t.JSXElement): boolean {\n return node.children.every((child) => isStringNode(path, child));\n}\n\nfunction isStringNode(path: NodePath<t.JSXOpeningElement>, child: t.Node): boolean {\n if (t.isJSXText(child)) return true;\n\n // Check for JSX expressions\n if (t.isJSXExpressionContainer(child)) {\n const expression = child.expression;\n\n // If the expression is an identifier, look it up in the current scope\n if (t.isIdentifier(expression)) {\n const binding = path.scope.getBinding(expression.name);\n return binding ? t.isStringLiteral(binding.path.node) : false;\n }\n }\n return false;\n}\n\nconst blacklistedProperties = new Set([\n 'accessible',\n 'accessibilityLabel',\n 'accessibilityState',\n 'allowFontScaling',\n 'aria-busy',\n 'aria-checked',\n 'aria-disabled',\n 'aria-expanded',\n 'aria-label',\n 'aria-selected',\n 'ellipsizeMode',\n 'id',\n 'nativeID',\n 'onLongPress',\n 'onPress',\n 'onPressIn',\n 'onPressOut',\n 'onResponderGrant',\n 'onResponderMove',\n 'onResponderRelease',\n 'onResponderTerminate',\n 'onResponderTerminationRequest',\n 'onStartShouldSetResponder',\n 'pressRetentionOffset',\n 'suppressHighlighting',\n]);\n\nfunction optimizeStyleTag({ path, file }: { path: NodePath<t.JSXOpeningElement>; file: HubFile }) {\n let shouldImportFlattenTextStyle = false;\n const nameHint = '_flattenTextStyle';\n\n for (const [index, attribute] of path.node.attributes.entries()) {\n if (t.isJSXAttribute(attribute) && t.isJSXIdentifier(attribute.name, { name: 'style' })) {\n shouldImportFlattenTextStyle = true;\n\n if (t.isJSXExpressionContainer(attribute.value) && !t.isJSXEmptyExpression(attribute.value.expression)) {\n path.node.attributes[index] = t.jsxSpreadAttribute(\n t.callExpression(t.identifier(nameHint), [attribute.value.expression])\n );\n }\n }\n }\n\n if (shouldImportFlattenTextStyle && !file.__hasImports?.flattenTextStyle) {\n if (!file.__hasImports) file.__hasImports = {};\n file.__hasImports.flattenTextStyle = addNamed(path, 'flattenTextStyle', 'react-native-boost', { nameHint });\n }\n}\n\nfunction hasBlacklistedProperties(path: NodePath<t.JSXOpeningElement>): boolean {\n return path.node.attributes.some((attribute) => {\n // Check if we can resolve the spread attribute\n if (t.isJSXSpreadAttribute(attribute)) {\n if (t.isIdentifier(attribute.argument)) {\n const binding = path.scope.getBinding(attribute.argument.name);\n let objectExpression: t.ObjectExpression | undefined;\n if (binding) {\n // If the binding node is a VariableDeclarator, use its initializer\n if (t.isVariableDeclarator(binding.path.node)) {\n objectExpression = binding.path.node.init as t.ObjectExpression;\n } else if (t.isObjectExpression(binding.path.node)) {\n objectExpression = binding.path.node;\n }\n }\n if (objectExpression && t.isObjectExpression(objectExpression)) {\n return objectExpression.properties.some((property) => {\n if (t.isObjectProperty(property) && t.isIdentifier(property.key)) {\n return blacklistedProperties.has(property.key.name);\n }\n return false;\n });\n }\n }\n // Bail if we can't resolve the spread attribute\n return true;\n }\n\n if (t.isJSXIdentifier(attribute.name) && attribute.value) {\n // For a \"children\" attribute, optimization is allowed only if it is a string\n if (attribute.name.name === 'children') {\n return isStringNode(path, attribute.value);\n }\n return blacklistedProperties.has(attribute.name.name);\n }\n\n // For other attribute types (e.g. namespaced), assume no blacklisting\n return false;\n });\n}\n","export const log = (message: string) => {\n console.log(`[react-native-boost] ${message}`);\n};\n","import { declare } from '@babel/helper-plugin-utils';\nimport { textOptimizer } from './optimizers/text';\nimport { PluginOptions } from './types';\nimport { log } from './utils/logger';\n\nexport default declare((api) => {\n api.assertVersion(7);\n\n return {\n name: 'react-native-boost',\n visitor: {\n JSXOpeningElement(path, state) {\n const options = (state.opts ?? {}) as PluginOptions;\n const logger = options.verbose ? log : () => {};\n if (options.optimizations?.text !== false) textOptimizer(path, logger);\n },\n },\n };\n});\n"],"names":["t","addNamed","declare"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-boost",
|
|
3
3
|
"description": "🚀 Boost your React Native app's performance with a single line of code",
|
|
4
|
-
"version": "0.0
|
|
5
|
-
"main": "dist/
|
|
4
|
+
"version": "0.2.0",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/esm/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
"./package.json": "./package.json",
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"default": "./dist/esm/index.mjs",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./plugin": {
|
|
18
|
+
"import": {
|
|
19
|
+
"default": "./dist/plugin/esm/index.mjs",
|
|
20
|
+
"types": "./dist/plugin/index.d.ts"
|
|
21
|
+
},
|
|
22
|
+
"default": "./dist/plugin/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
6
25
|
"keywords": [
|
|
7
26
|
"react-native",
|
|
8
27
|
"ios",
|
|
@@ -13,8 +32,8 @@
|
|
|
13
32
|
"optimize"
|
|
14
33
|
],
|
|
15
34
|
"scripts": {
|
|
16
|
-
"clean": "rm -rf
|
|
17
|
-
"build": "yarn clean &&
|
|
35
|
+
"clean": "rm -rf dist",
|
|
36
|
+
"build": "yarn clean && rollup -c",
|
|
18
37
|
"test": "vitest",
|
|
19
38
|
"typecheck": "tsc --noEmit",
|
|
20
39
|
"lint": "eslint src/**/*.ts",
|
|
@@ -25,7 +44,7 @@
|
|
|
25
44
|
},
|
|
26
45
|
"files": [
|
|
27
46
|
"src",
|
|
28
|
-
"
|
|
47
|
+
"dist",
|
|
29
48
|
"!**/__tests__",
|
|
30
49
|
"!**/__fixtures__",
|
|
31
50
|
"!**/__mocks__",
|
|
@@ -54,27 +73,28 @@
|
|
|
54
73
|
"@babel/plugin-syntax-jsx": "^7.25.0",
|
|
55
74
|
"@babel/preset-typescript": "^7.25.0",
|
|
56
75
|
"@release-it/conventional-changelog": "^10.0.0",
|
|
76
|
+
"@rollup/plugin-alias": "^5.1.1",
|
|
77
|
+
"@rollup/plugin-node-resolve": "^16.0.0",
|
|
78
|
+
"@rollup/plugin-replace": "^6.0.2",
|
|
79
|
+
"@rollup/plugin-typescript": "^12.1.2",
|
|
57
80
|
"@types/babel__helper-module-imports": "^7.0.0",
|
|
58
81
|
"@types/babel__helper-plugin-utils": "^7.0.0",
|
|
59
82
|
"@types/node": "^20",
|
|
60
83
|
"babel-plugin-tester": "^11.0.4",
|
|
61
|
-
"esbuild": "^0.25.0",
|
|
62
84
|
"esbuild-node-externals": "^1.18.0",
|
|
63
85
|
"globals": "^16.0.0",
|
|
86
|
+
"react-native": "0.76.7",
|
|
64
87
|
"release-it": "^18.1.2",
|
|
88
|
+
"rollup": "^4.34.8",
|
|
89
|
+
"rollup-plugin-dts": "^6.1.1",
|
|
90
|
+
"rollup-plugin-esbuild": "^6.2.0",
|
|
65
91
|
"typescript": "^5.7.3",
|
|
66
92
|
"vitest": "^3.0.6"
|
|
67
93
|
},
|
|
68
94
|
"peerDependencies": {
|
|
69
|
-
"expo": "*",
|
|
70
95
|
"react": "*",
|
|
71
96
|
"react-native": "*"
|
|
72
97
|
},
|
|
73
|
-
"peerDependenciesMeta": {
|
|
74
|
-
"expo": {
|
|
75
|
-
"optional": true
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
98
|
"release-it": {
|
|
79
99
|
"git": {
|
|
80
100
|
"commitMessage": "chore: release ${version}",
|
|
@@ -2,6 +2,7 @@ import { NodePath, types as t } from '@babel/core';
|
|
|
2
2
|
import { addNamed } from '@babel/helper-module-imports';
|
|
3
3
|
import { HubFile, Optimizer } from '../../types';
|
|
4
4
|
import PluginError from '../../utils/plugin-error';
|
|
5
|
+
import { shouldIgnoreOptimization } from '../../utils/common';
|
|
5
6
|
|
|
6
7
|
export const textOptimizer: Optimizer = (path, log = () => {}) => {
|
|
7
8
|
// Ensure we're processing a JSX Text element
|
|
@@ -13,6 +14,11 @@ export const textOptimizer: Optimizer = (path, log = () => {}) => {
|
|
|
13
14
|
const elementName = path.node.name.name;
|
|
14
15
|
if (elementName !== 'Text') return;
|
|
15
16
|
|
|
17
|
+
// If the component is preceded by an ignore comment, do not optimize.
|
|
18
|
+
if (shouldIgnoreOptimization(path)) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
// Ensure Text element comes from react-native
|
|
17
23
|
const binding = path.scope.getBinding(elementName);
|
|
18
24
|
if (!binding) return;
|
|
@@ -26,7 +32,6 @@ export const textOptimizer: Optimizer = (path, log = () => {}) => {
|
|
|
26
32
|
// Bail if the element has any blacklisted properties or non-string children props
|
|
27
33
|
if (hasBlacklistedProperties(path)) return;
|
|
28
34
|
if (!hasOnlyStringChildren(path, parent)) return;
|
|
29
|
-
// TODO: Don't bail if the element has a style prop
|
|
30
35
|
|
|
31
36
|
// Extract the file from the Babel hub and add flags for logging & import caching
|
|
32
37
|
const hub = path.hub as unknown;
|
|
@@ -36,18 +41,21 @@ export const textOptimizer: Optimizer = (path, log = () => {}) => {
|
|
|
36
41
|
throw new PluginError('No file found in Babel hub');
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
44
|
+
const filename = file.opts?.filename || 'unknown file';
|
|
45
|
+
const lineNumber = path.node.loc?.start.line ?? 'unknown line';
|
|
46
|
+
log(`Optimizing Text component in ${filename}:${lineNumber}`);
|
|
47
|
+
|
|
48
|
+
// Optimize props
|
|
49
|
+
optimizeStyleTag({ path, file });
|
|
45
50
|
|
|
46
51
|
// Add TextNativeComponent import (cached on file) so we only add it once per file
|
|
47
|
-
if (!file.
|
|
48
|
-
file.
|
|
52
|
+
if (!file.__hasImports) {
|
|
53
|
+
file.__hasImports = {};
|
|
49
54
|
}
|
|
50
|
-
|
|
55
|
+
if (!file.__hasImports.NativeText) {
|
|
56
|
+
file.__hasImports.NativeText = addNamed(path, 'NativeText', 'react-native/Libraries/Text/TextNativeComponent');
|
|
57
|
+
}
|
|
58
|
+
const nativeTextIdentifier = file.__hasImports.NativeText;
|
|
51
59
|
path.node.name.name = nativeTextIdentifier.name;
|
|
52
60
|
|
|
53
61
|
// If the element is not self-closing, update the closing element as well
|
|
@@ -107,9 +115,30 @@ const blacklistedProperties = new Set([
|
|
|
107
115
|
'onStartShouldSetResponder',
|
|
108
116
|
'pressRetentionOffset',
|
|
109
117
|
'suppressHighlighting',
|
|
110
|
-
'style',
|
|
111
118
|
]);
|
|
112
119
|
|
|
120
|
+
function optimizeStyleTag({ path, file }: { path: NodePath<t.JSXOpeningElement>; file: HubFile }) {
|
|
121
|
+
let shouldImportFlattenTextStyle = false;
|
|
122
|
+
const nameHint = '_flattenTextStyle';
|
|
123
|
+
|
|
124
|
+
for (const [index, attribute] of path.node.attributes.entries()) {
|
|
125
|
+
if (t.isJSXAttribute(attribute) && t.isJSXIdentifier(attribute.name, { name: 'style' })) {
|
|
126
|
+
shouldImportFlattenTextStyle = true;
|
|
127
|
+
|
|
128
|
+
if (t.isJSXExpressionContainer(attribute.value) && !t.isJSXEmptyExpression(attribute.value.expression)) {
|
|
129
|
+
path.node.attributes[index] = t.jsxSpreadAttribute(
|
|
130
|
+
t.callExpression(t.identifier(nameHint), [attribute.value.expression])
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (shouldImportFlattenTextStyle && !file.__hasImports?.flattenTextStyle) {
|
|
137
|
+
if (!file.__hasImports) file.__hasImports = {};
|
|
138
|
+
file.__hasImports.flattenTextStyle = addNamed(path, 'flattenTextStyle', 'react-native-boost', { nameHint });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
113
142
|
function hasBlacklistedProperties(path: NodePath<t.JSXOpeningElement>): boolean {
|
|
114
143
|
return path.node.attributes.some((attribute) => {
|
|
115
144
|
// Check if we can resolve the spread attribute
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { NodePath, types as t } from '@babel/core';
|
|
2
|
+
import { ensureArray } from './helpers';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Checks if the JSX element should be ignored based on a preceding comment.
|
|
6
|
+
*
|
|
7
|
+
* The function looks up the JSXOpeningElement's own leading comments as well as
|
|
8
|
+
* the parent element's comments before falling back to inspect siblings.
|
|
9
|
+
*/
|
|
10
|
+
export const shouldIgnoreOptimization = (path: NodePath<t.JSXOpeningElement>): boolean => {
|
|
11
|
+
// Check for @boost-ignore in the leading comments on the JSX opening element.
|
|
12
|
+
if (path.node.leadingComments?.some((comment) => comment.value.includes('@boost-ignore'))) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Check for @boost-ignore in the leading comments on the parent JSX element.
|
|
17
|
+
const jsxElementPath = path.parentPath;
|
|
18
|
+
if (jsxElementPath.node.leadingComments?.some((comment) => comment.value.includes('@boost-ignore'))) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// NEW: Check for @boost-ignore in the leading comments on the ObjectProperty (if it exists)
|
|
23
|
+
// This handles cases where the JSX element is used as a value inside an object literal.
|
|
24
|
+
const propertyPath = jsxElementPath.parentPath;
|
|
25
|
+
if (
|
|
26
|
+
propertyPath &&
|
|
27
|
+
propertyPath.isObjectProperty() &&
|
|
28
|
+
propertyPath.node.leadingComments?.some((comment) => comment.value.includes('@boost-ignore'))
|
|
29
|
+
) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!jsxElementPath.parentPath) return false;
|
|
34
|
+
|
|
35
|
+
// Get the container that holds this element (for example, a JSX fragment or JSX element)
|
|
36
|
+
const containerPath = jsxElementPath.parentPath;
|
|
37
|
+
const siblings = ensureArray(containerPath.get('children'));
|
|
38
|
+
const index = siblings.findIndex((sibling) => sibling.node === jsxElementPath.node);
|
|
39
|
+
if (index === -1) return false;
|
|
40
|
+
|
|
41
|
+
// Look backward from the current element for a non-empty node.
|
|
42
|
+
for (let index_ = index - 1; index_ >= 0; index_--) {
|
|
43
|
+
const sibling = siblings[index_];
|
|
44
|
+
// Skip over any whitespace (only in JSXText nodes)
|
|
45
|
+
if (sibling.isJSXText() && sibling.node.value.trim() === '') {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
// If the sibling is a JSX expression container, check its empty expression's comments.
|
|
49
|
+
if (sibling.isJSXExpressionContainer()) {
|
|
50
|
+
const expression = sibling.get('expression');
|
|
51
|
+
if (expression && expression.node) {
|
|
52
|
+
const comments = [
|
|
53
|
+
...(expression.node.leadingComments || []),
|
|
54
|
+
...(expression.node.trailingComments || []),
|
|
55
|
+
...(expression.node.innerComments || []),
|
|
56
|
+
].map((comment) => comment.value.trim());
|
|
57
|
+
if (comments.some((comment) => comment.includes('@boost-ignore'))) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Also check if the node itself carries a leadingComments property.
|
|
63
|
+
if (
|
|
64
|
+
sibling.node.leadingComments &&
|
|
65
|
+
sibling.node.leadingComments.some((comment) => comment.value.includes('@boost-ignore'))
|
|
66
|
+
) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
break; // if the immediate non-whitespace node is not our ignore marker, stop
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { TextStyle } from 'react-native';
|
|
2
|
+
import { flattenStyle } from 'react-native/Libraries/StyleSheet/flattenStyle';
|
|
3
|
+
import { GenericStyleProp } from './types';
|
|
4
|
+
|
|
5
|
+
const propsCache = new WeakMap();
|
|
6
|
+
|
|
7
|
+
export function flattenTextStyle(style: GenericStyleProp<TextStyle>) {
|
|
8
|
+
if (!style) return {};
|
|
9
|
+
|
|
10
|
+
// Cache the computed props
|
|
11
|
+
let props = propsCache.get(style);
|
|
12
|
+
if (props) return props;
|
|
13
|
+
|
|
14
|
+
props = {};
|
|
15
|
+
propsCache.set(style, props);
|
|
16
|
+
|
|
17
|
+
style = flattenStyle(style);
|
|
18
|
+
|
|
19
|
+
if (!style) return {};
|
|
20
|
+
|
|
21
|
+
if (typeof style?.fontWeight === 'number') {
|
|
22
|
+
style.fontWeight = style.fontWeight.toString() as TextStyle['fontWeight'];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (style?.userSelect != null) {
|
|
26
|
+
props.selectable = userSelectToSelectableMap[style.userSelect];
|
|
27
|
+
delete style.userSelect;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (style?.verticalAlign != null) {
|
|
31
|
+
style.textAlignVertical = verticalAlignToTextAlignVerticalMap[
|
|
32
|
+
style.verticalAlign
|
|
33
|
+
] as TextStyle['textAlignVertical'];
|
|
34
|
+
delete style.verticalAlign;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
props.style = style;
|
|
38
|
+
return props;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Maps the `userSelect` prop to the native `selectable` prop
|
|
42
|
+
export const userSelectToSelectableMap = {
|
|
43
|
+
auto: true,
|
|
44
|
+
text: true,
|
|
45
|
+
none: false,
|
|
46
|
+
contain: true,
|
|
47
|
+
all: true,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Maps the `verticalAlign` prop to the native `textAlignVertical` prop
|
|
51
|
+
export const verticalAlignToTextAlignVerticalMap = {
|
|
52
|
+
auto: 'auto',
|
|
53
|
+
top: 'top',
|
|
54
|
+
bottom: 'bottom',
|
|
55
|
+
middle: 'center',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export * from './types';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type GenericStyleProp<T> = null | void | T | false | '' | ReadonlyArray<GenericStyleProp<T>>;
|
package/plugin/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";var d=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var O=Object.getOwnPropertyNames;var E=Object.prototype.hasOwnProperty;var P=(e,n)=>{for(var i in n)d(e,i,{get:n[i],enumerable:!0})},h=(e,n,i,t)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of O(n))!E.call(e,r)&&r!==i&&d(e,r,{get:()=>n[r],enumerable:!(t=v(n,r))||t.enumerable});return e};var I=e=>h(d({},"__esModule",{value:!0}),e);var X={};P(X,{default:()=>T});module.exports=I(X);var S=require("@babel/helper-plugin-utils");var o=require("@babel/core"),u=require("@babel/helper-module-imports");var l=class extends Error{constructor(n){super(`[react-native-boost] Babel plugin exception: ${n}`),this.name="PluginError"}};var g=(e,n=()=>{})=>{var p;if(!o.types.isJSXIdentifier(e.node.name))return;let i=e.parent;if(!o.types.isJSXElement(i))return;let t=e.node.name.name;if(t!=="Text")return;let r=e.scope.getBinding(t);if(!r)return;if(r.kind==="module"){let m=r.path.parent;if(!o.types.isImportDeclaration(m)||m.source.value!=="react-native")return}if(N(e)||!J(e,i))return;let s=e.hub,a=typeof s=="object"&&s!==null&&"file"in s?s.file:void 0;if(!a)throw new l("No file found in Babel hub");if(!a.__optimized){let m=((p=a.opts)==null?void 0:p.filename)||"unknown file";n(`Optimizing file: ${m}`),a.__optimized=!0}a.__nativeTextImport||(a.__nativeTextImport=(0,u.addNamed)(e,"NativeText","react-native/Libraries/Text/TextNativeComponent"));let f=a.__nativeTextImport;e.node.name.name=f.name,!e.node.selfClosing&&i.closingElement&&o.types.isJSXIdentifier(i.closingElement.name)&&i.closingElement.name.name==="Text"&&(i.closingElement.name.name=f.name)};function J(e,n){return n.children.every(i=>b(e,i))}function b(e,n){if(o.types.isJSXText(n))return!0;if(o.types.isJSXExpressionContainer(n)){let i=n.expression;if(o.types.isIdentifier(i)){let t=e.scope.getBinding(i.name);return t?o.types.isStringLiteral(t.path.node):!1}}return!1}var c=new Set(["accessible","accessibilityLabel","accessibilityState","allowFontScaling","aria-busy","aria-checked","aria-disabled","aria-expanded","aria-label","aria-selected","ellipsizeMode","id","nativeID","onLongPress","onPress","onPressIn","onPressOut","onResponderGrant","onResponderMove","onResponderRelease","onResponderTerminate","onResponderTerminationRequest","onStartShouldSetResponder","pressRetentionOffset","suppressHighlighting","style"]);function N(e){return e.node.attributes.some(n=>{if(o.types.isJSXSpreadAttribute(n)){if(o.types.isIdentifier(n.argument)){let i=e.scope.getBinding(n.argument.name),t;if(i&&(o.types.isVariableDeclarator(i.path.node)?t=i.path.node.init:o.types.isObjectExpression(i.path.node)&&(t=i.path.node)),t&&o.types.isObjectExpression(t))return t.properties.some(r=>o.types.isObjectProperty(r)&&o.types.isIdentifier(r.key)?c.has(r.key.name):!1)}return!0}return o.types.isJSXIdentifier(n.name)&&n.value?n.name.name==="children"?b(e,n.value):c.has(n.name.name):!1})}var x=e=>{console.log(`[react-native-boost] ${e}`)};var T=(0,S.declare)(e=>(e.assertVersion(7),{name:"react-native-boost",visitor:{JSXOpeningElement(n,i){var s;let t=i.opts??{},r=t.verbose?x:()=>{};((s=t.optimizations)==null?void 0:s.text)!==!1&&g(n,r)}}}));
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|