react-native-boost 0.0.4 → 0.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 CHANGED
@@ -9,17 +9,19 @@ 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
- - 🎯 Zero runtime overhead - all optimizations happen during build time
12
+ - 🎯 Virtually zero runtime overhead
13
13
  - 📱 Cross-platform compatible
14
14
  - 🧪 Works seamlessly with Expo
15
15
  - 🎨 Configurable optimization strategies
16
16
 
17
17
  ## Installation
18
18
 
19
+ 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.
20
+
19
21
  ```sh
20
- npm install --save-dev react-native-boost
22
+ npm install react-native-boost
21
23
  # or
22
- yarn add --dev react-native-boost
24
+ yarn add react-native-boost
23
25
  ```
24
26
 
25
27
  Then, add the plugin to your Babel configuration (`babel.config.js`):
@@ -30,6 +32,8 @@ module.exports = {
30
32
  };
31
33
  ```
32
34
 
35
+ That's it! No imports in your code, pod installing, or anything else is required.
36
+
33
37
  Optionally, you can configure the plugin to log optimized files to the console and disable specific optimizations:
34
38
 
35
39
  ```js
@@ -48,6 +52,13 @@ module.exports = {
48
52
  };
49
53
  ```
50
54
 
55
+ You can also skip optimization for a specific comment using a decorator comment above the component's opening tag:
56
+
57
+ ```jsx
58
+ // @boost-ignore
59
+ <Text>This will not be optimized.</Text>
60
+ ```
61
+
51
62
  ## How It Works
52
63
 
53
64
  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;;;;"}
@@ -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,209 @@
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
+ const jsxElementPath = path.parentPath;
19
+ if (!jsxElementPath.parentPath) return false;
20
+ const containerPath = jsxElementPath.parentPath;
21
+ const siblings = ensureArray(containerPath.get("children"));
22
+ const index = siblings.findIndex((sibling) => sibling.node === jsxElementPath.node);
23
+ if (index === -1) return false;
24
+ for (let index_ = index - 1; index_ >= 0; index_--) {
25
+ const sibling = siblings[index_];
26
+ if (sibling.isJSXText() && sibling.node.value.trim() === "") {
27
+ continue;
28
+ }
29
+ if (sibling.isJSXExpressionContainer()) {
30
+ const expression = sibling.get("expression");
31
+ if (expression && expression.node) {
32
+ const comments = [
33
+ ...expression.node.leadingComments || [],
34
+ ...expression.node.trailingComments || [],
35
+ ...expression.node.innerComments || []
36
+ ].map((comment) => comment.value.trim());
37
+ if (comments.some((comment) => comment.includes("@boost-ignore"))) {
38
+ return true;
39
+ }
40
+ }
41
+ }
42
+ if (sibling.node.leadingComments && sibling.node.leadingComments.some((comment) => comment.value.includes("@boost-ignore"))) {
43
+ return true;
44
+ }
45
+ break;
46
+ }
47
+ return false;
48
+ };
49
+
50
+ const textOptimizer = (path, log = () => {
51
+ }) => {
52
+ var _a;
53
+ if (!types.isJSXIdentifier(path.node.name)) return;
54
+ const parent = path.parent;
55
+ if (!types.isJSXElement(parent)) return;
56
+ const elementName = path.node.name.name;
57
+ if (elementName !== "Text") return;
58
+ if (shouldIgnoreOptimization(path)) {
59
+ return;
60
+ }
61
+ const binding = path.scope.getBinding(elementName);
62
+ if (!binding) return;
63
+ if (binding.kind === "module") {
64
+ const parentNode = binding.path.parent;
65
+ if (!types.isImportDeclaration(parentNode) || parentNode.source.value !== "react-native") {
66
+ return;
67
+ }
68
+ }
69
+ if (hasBlacklistedProperties(path)) return;
70
+ if (!hasOnlyStringChildren(path, parent)) return;
71
+ const hub = path.hub;
72
+ const file = typeof hub === "object" && hub !== null && "file" in hub ? hub.file : void 0;
73
+ if (!file) {
74
+ throw new PluginError("No file found in Babel hub");
75
+ }
76
+ if (!file.__optimized) {
77
+ const filename = ((_a = file.opts) == null ? void 0 : _a.filename) || "unknown file";
78
+ log(`Optimizing file: ${filename}`);
79
+ file.__optimized = true;
80
+ }
81
+ optimizeStyleTag({ path, file });
82
+ if (!file.__hasImports) {
83
+ file.__hasImports = {};
84
+ }
85
+ if (!file.__hasImports.NativeText) {
86
+ file.__hasImports.NativeText = addNamed(path, "NativeText", "react-native/Libraries/Text/TextNativeComponent");
87
+ }
88
+ const nativeTextIdentifier = file.__hasImports.NativeText;
89
+ path.node.name.name = nativeTextIdentifier.name;
90
+ if (!path.node.selfClosing && parent.closingElement && types.isJSXIdentifier(parent.closingElement.name) && parent.closingElement.name.name === "Text") {
91
+ parent.closingElement.name.name = nativeTextIdentifier.name;
92
+ }
93
+ };
94
+ function hasOnlyStringChildren(path, node) {
95
+ return node.children.every((child) => isStringNode(path, child));
96
+ }
97
+ function isStringNode(path, child) {
98
+ if (types.isJSXText(child)) return true;
99
+ if (types.isJSXExpressionContainer(child)) {
100
+ const expression = child.expression;
101
+ if (types.isIdentifier(expression)) {
102
+ const binding = path.scope.getBinding(expression.name);
103
+ return binding ? types.isStringLiteral(binding.path.node) : false;
104
+ }
105
+ }
106
+ return false;
107
+ }
108
+ const blacklistedProperties = /* @__PURE__ */ new Set([
109
+ "accessible",
110
+ "accessibilityLabel",
111
+ "accessibilityState",
112
+ "allowFontScaling",
113
+ "aria-busy",
114
+ "aria-checked",
115
+ "aria-disabled",
116
+ "aria-expanded",
117
+ "aria-label",
118
+ "aria-selected",
119
+ "ellipsizeMode",
120
+ "id",
121
+ "nativeID",
122
+ "onLongPress",
123
+ "onPress",
124
+ "onPressIn",
125
+ "onPressOut",
126
+ "onResponderGrant",
127
+ "onResponderMove",
128
+ "onResponderRelease",
129
+ "onResponderTerminate",
130
+ "onResponderTerminationRequest",
131
+ "onStartShouldSetResponder",
132
+ "pressRetentionOffset",
133
+ "suppressHighlighting"
134
+ ]);
135
+ function optimizeStyleTag({ path, file }) {
136
+ var _a;
137
+ let shouldImportFlattenTextStyle = false;
138
+ const nameHint = "_flattenTextStyle";
139
+ for (const [index, attribute] of path.node.attributes.entries()) {
140
+ if (types.isJSXAttribute(attribute) && types.isJSXIdentifier(attribute.name, { name: "style" })) {
141
+ shouldImportFlattenTextStyle = true;
142
+ if (types.isJSXExpressionContainer(attribute.value) && !types.isJSXEmptyExpression(attribute.value.expression)) {
143
+ path.node.attributes[index] = types.jsxSpreadAttribute(
144
+ types.callExpression(types.identifier(nameHint), [attribute.value.expression])
145
+ );
146
+ }
147
+ }
148
+ }
149
+ if (shouldImportFlattenTextStyle && !((_a = file.__hasImports) == null ? void 0 : _a.flattenTextStyle)) {
150
+ if (!file.__hasImports) file.__hasImports = {};
151
+ file.__hasImports.flattenTextStyle = addNamed(path, "flattenTextStyle", "react-native-boost", { nameHint });
152
+ }
153
+ }
154
+ function hasBlacklistedProperties(path) {
155
+ return path.node.attributes.some((attribute) => {
156
+ if (types.isJSXSpreadAttribute(attribute)) {
157
+ if (types.isIdentifier(attribute.argument)) {
158
+ const binding = path.scope.getBinding(attribute.argument.name);
159
+ let objectExpression;
160
+ if (binding) {
161
+ if (types.isVariableDeclarator(binding.path.node)) {
162
+ objectExpression = binding.path.node.init;
163
+ } else if (types.isObjectExpression(binding.path.node)) {
164
+ objectExpression = binding.path.node;
165
+ }
166
+ }
167
+ if (objectExpression && types.isObjectExpression(objectExpression)) {
168
+ return objectExpression.properties.some((property) => {
169
+ if (types.isObjectProperty(property) && types.isIdentifier(property.key)) {
170
+ return blacklistedProperties.has(property.key.name);
171
+ }
172
+ return false;
173
+ });
174
+ }
175
+ }
176
+ return true;
177
+ }
178
+ if (types.isJSXIdentifier(attribute.name) && attribute.value) {
179
+ if (attribute.name.name === "children") {
180
+ return isStringNode(path, attribute.value);
181
+ }
182
+ return blacklistedProperties.has(attribute.name.name);
183
+ }
184
+ return false;
185
+ });
186
+ }
187
+
188
+ const log = (message) => {
189
+ console.log(`[react-native-boost] ${message}`);
190
+ };
191
+
192
+ var index = declare((api) => {
193
+ api.assertVersion(7);
194
+ return {
195
+ name: "react-native-boost",
196
+ visitor: {
197
+ JSXOpeningElement(path, state) {
198
+ var _a, _b;
199
+ const options = (_a = state.opts) != null ? _a : {};
200
+ const logger = options.verbose ? log : () => {
201
+ };
202
+ if (((_b = options.optimizations) == null ? void 0 : _b.text) !== false) textOptimizer(path, logger);
203
+ }
204
+ }
205
+ };
206
+ });
207
+
208
+ export { index as default };
209
+ //# 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 parent container's children, finds the current\n * JSX element, and then (ignoring whitespace-only nodes) inspects the previous\n * sibling for a comment that includes \"@boost-ignore\".\n */\nexport const shouldIgnoreOptimization = (path: NodePath<t.JSXOpeningElement>): boolean => {\n // Get the JSX element by going up one level (opening element -> JSXElement)\n const jsxElementPath = path.parentPath;\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 // Log the file's optimized status only once\n if (!file.__optimized) {\n const filename = file.opts?.filename || 'unknown file';\n log(`Optimizing file: ${filename}`);\n file.__optimized = true;\n }\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,211 @@
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
+ const jsxElementPath = path.parentPath;
21
+ if (!jsxElementPath.parentPath) return false;
22
+ const containerPath = jsxElementPath.parentPath;
23
+ const siblings = ensureArray(containerPath.get("children"));
24
+ const index = siblings.findIndex((sibling) => sibling.node === jsxElementPath.node);
25
+ if (index === -1) return false;
26
+ for (let index_ = index - 1; index_ >= 0; index_--) {
27
+ const sibling = siblings[index_];
28
+ if (sibling.isJSXText() && sibling.node.value.trim() === "") {
29
+ continue;
30
+ }
31
+ if (sibling.isJSXExpressionContainer()) {
32
+ const expression = sibling.get("expression");
33
+ if (expression && expression.node) {
34
+ const comments = [
35
+ ...expression.node.leadingComments || [],
36
+ ...expression.node.trailingComments || [],
37
+ ...expression.node.innerComments || []
38
+ ].map((comment) => comment.value.trim());
39
+ if (comments.some((comment) => comment.includes("@boost-ignore"))) {
40
+ return true;
41
+ }
42
+ }
43
+ }
44
+ if (sibling.node.leadingComments && sibling.node.leadingComments.some((comment) => comment.value.includes("@boost-ignore"))) {
45
+ return true;
46
+ }
47
+ break;
48
+ }
49
+ return false;
50
+ };
51
+
52
+ const textOptimizer = (path, log = () => {
53
+ }) => {
54
+ var _a;
55
+ if (!core.types.isJSXIdentifier(path.node.name)) return;
56
+ const parent = path.parent;
57
+ if (!core.types.isJSXElement(parent)) return;
58
+ const elementName = path.node.name.name;
59
+ if (elementName !== "Text") return;
60
+ if (shouldIgnoreOptimization(path)) {
61
+ return;
62
+ }
63
+ const binding = path.scope.getBinding(elementName);
64
+ if (!binding) return;
65
+ if (binding.kind === "module") {
66
+ const parentNode = binding.path.parent;
67
+ if (!core.types.isImportDeclaration(parentNode) || parentNode.source.value !== "react-native") {
68
+ return;
69
+ }
70
+ }
71
+ if (hasBlacklistedProperties(path)) return;
72
+ if (!hasOnlyStringChildren(path, parent)) return;
73
+ const hub = path.hub;
74
+ const file = typeof hub === "object" && hub !== null && "file" in hub ? hub.file : void 0;
75
+ if (!file) {
76
+ throw new PluginError("No file found in Babel hub");
77
+ }
78
+ if (!file.__optimized) {
79
+ const filename = ((_a = file.opts) == null ? void 0 : _a.filename) || "unknown file";
80
+ log(`Optimizing file: ${filename}`);
81
+ file.__optimized = true;
82
+ }
83
+ optimizeStyleTag({ path, file });
84
+ if (!file.__hasImports) {
85
+ file.__hasImports = {};
86
+ }
87
+ if (!file.__hasImports.NativeText) {
88
+ file.__hasImports.NativeText = helperModuleImports.addNamed(path, "NativeText", "react-native/Libraries/Text/TextNativeComponent");
89
+ }
90
+ const nativeTextIdentifier = file.__hasImports.NativeText;
91
+ path.node.name.name = nativeTextIdentifier.name;
92
+ if (!path.node.selfClosing && parent.closingElement && core.types.isJSXIdentifier(parent.closingElement.name) && parent.closingElement.name.name === "Text") {
93
+ parent.closingElement.name.name = nativeTextIdentifier.name;
94
+ }
95
+ };
96
+ function hasOnlyStringChildren(path, node) {
97
+ return node.children.every((child) => isStringNode(path, child));
98
+ }
99
+ function isStringNode(path, child) {
100
+ if (core.types.isJSXText(child)) return true;
101
+ if (core.types.isJSXExpressionContainer(child)) {
102
+ const expression = child.expression;
103
+ if (core.types.isIdentifier(expression)) {
104
+ const binding = path.scope.getBinding(expression.name);
105
+ return binding ? core.types.isStringLiteral(binding.path.node) : false;
106
+ }
107
+ }
108
+ return false;
109
+ }
110
+ const blacklistedProperties = /* @__PURE__ */ new Set([
111
+ "accessible",
112
+ "accessibilityLabel",
113
+ "accessibilityState",
114
+ "allowFontScaling",
115
+ "aria-busy",
116
+ "aria-checked",
117
+ "aria-disabled",
118
+ "aria-expanded",
119
+ "aria-label",
120
+ "aria-selected",
121
+ "ellipsizeMode",
122
+ "id",
123
+ "nativeID",
124
+ "onLongPress",
125
+ "onPress",
126
+ "onPressIn",
127
+ "onPressOut",
128
+ "onResponderGrant",
129
+ "onResponderMove",
130
+ "onResponderRelease",
131
+ "onResponderTerminate",
132
+ "onResponderTerminationRequest",
133
+ "onStartShouldSetResponder",
134
+ "pressRetentionOffset",
135
+ "suppressHighlighting"
136
+ ]);
137
+ function optimizeStyleTag({ path, file }) {
138
+ var _a;
139
+ let shouldImportFlattenTextStyle = false;
140
+ const nameHint = "_flattenTextStyle";
141
+ for (const [index, attribute] of path.node.attributes.entries()) {
142
+ if (core.types.isJSXAttribute(attribute) && core.types.isJSXIdentifier(attribute.name, { name: "style" })) {
143
+ shouldImportFlattenTextStyle = true;
144
+ if (core.types.isJSXExpressionContainer(attribute.value) && !core.types.isJSXEmptyExpression(attribute.value.expression)) {
145
+ path.node.attributes[index] = core.types.jsxSpreadAttribute(
146
+ core.types.callExpression(core.types.identifier(nameHint), [attribute.value.expression])
147
+ );
148
+ }
149
+ }
150
+ }
151
+ if (shouldImportFlattenTextStyle && !((_a = file.__hasImports) == null ? void 0 : _a.flattenTextStyle)) {
152
+ if (!file.__hasImports) file.__hasImports = {};
153
+ file.__hasImports.flattenTextStyle = helperModuleImports.addNamed(path, "flattenTextStyle", "react-native-boost", { nameHint });
154
+ }
155
+ }
156
+ function hasBlacklistedProperties(path) {
157
+ return path.node.attributes.some((attribute) => {
158
+ if (core.types.isJSXSpreadAttribute(attribute)) {
159
+ if (core.types.isIdentifier(attribute.argument)) {
160
+ const binding = path.scope.getBinding(attribute.argument.name);
161
+ let objectExpression;
162
+ if (binding) {
163
+ if (core.types.isVariableDeclarator(binding.path.node)) {
164
+ objectExpression = binding.path.node.init;
165
+ } else if (core.types.isObjectExpression(binding.path.node)) {
166
+ objectExpression = binding.path.node;
167
+ }
168
+ }
169
+ if (objectExpression && core.types.isObjectExpression(objectExpression)) {
170
+ return objectExpression.properties.some((property) => {
171
+ if (core.types.isObjectProperty(property) && core.types.isIdentifier(property.key)) {
172
+ return blacklistedProperties.has(property.key.name);
173
+ }
174
+ return false;
175
+ });
176
+ }
177
+ }
178
+ return true;
179
+ }
180
+ if (core.types.isJSXIdentifier(attribute.name) && attribute.value) {
181
+ if (attribute.name.name === "children") {
182
+ return isStringNode(path, attribute.value);
183
+ }
184
+ return blacklistedProperties.has(attribute.name.name);
185
+ }
186
+ return false;
187
+ });
188
+ }
189
+
190
+ const log = (message) => {
191
+ console.log(`[react-native-boost] ${message}`);
192
+ };
193
+
194
+ var index = helperPluginUtils.declare((api) => {
195
+ api.assertVersion(7);
196
+ return {
197
+ name: "react-native-boost",
198
+ visitor: {
199
+ JSXOpeningElement(path, state) {
200
+ var _a, _b;
201
+ const options = (_a = state.opts) != null ? _a : {};
202
+ const logger = options.verbose ? log : () => {
203
+ };
204
+ if (((_b = options.optimizations) == null ? void 0 : _b.text) !== false) textOptimizer(path, logger);
205
+ }
206
+ }
207
+ };
208
+ });
209
+
210
+ module.exports = index;
211
+ //# sourceMappingURL=index.js.map