react-native-boost 0.7.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -7
- package/dist/plugin/esm/index.mjs +219 -43
- package/dist/plugin/esm/index.mjs.map +1 -1
- package/dist/plugin/index.d.ts +54 -0
- package/dist/plugin/index.js +219 -43
- package/dist/plugin/index.js.map +1 -1
- package/dist/runtime/esm/index.mjs +15 -3
- package/dist/runtime/esm/index.mjs.map +1 -1
- package/dist/runtime/esm/index.web.mjs.map +1 -1
- package/dist/runtime/index.d.ts +49 -3
- package/dist/runtime/index.js +16 -4
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/index.web.d.ts +11 -0
- package/dist/runtime/index.web.js.map +1 -1
- package/package.json +3 -2
- package/src/plugin/index.ts +26 -4
- package/src/plugin/optimizers/text/index.ts +52 -21
- package/src/plugin/optimizers/view/index.ts +57 -10
- package/src/plugin/types/index.ts +61 -21
- package/src/plugin/utils/common/validation.ts +23 -28
- package/src/plugin/utils/generate-test-plugin.ts +7 -1
- package/src/plugin/utils/helpers.ts +15 -0
- package/src/plugin/utils/logger.ts +123 -2
- package/src/runtime/components/native-text.tsx +21 -5
- package/src/runtime/components/native-view.tsx +21 -5
- package/src/runtime/index.ts +20 -0
- package/src/runtime/types/index.ts +5 -0
- package/src/runtime/utils/constants.ts +6 -2
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TextProps, ViewProps, TextStyle } from 'react-native';
|
|
2
|
+
import { ComponentType } from 'react';
|
|
2
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Recursive style prop shape accepted by runtime style helpers.
|
|
6
|
+
*
|
|
7
|
+
* @template T - Style object type.
|
|
8
|
+
*/
|
|
3
9
|
type GenericStyleProp<T> = null | void | T | false | '' | ReadonlyArray<GenericStyleProp<T>>;
|
|
4
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Maps CSS-like `userSelect` values to React Native's `selectable` prop.
|
|
13
|
+
*/
|
|
5
14
|
declare const userSelectToSelectableMap: {
|
|
6
15
|
auto: boolean;
|
|
7
16
|
text: boolean;
|
|
@@ -9,6 +18,9 @@ declare const userSelectToSelectableMap: {
|
|
|
9
18
|
contain: boolean;
|
|
10
19
|
all: boolean;
|
|
11
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* Maps CSS-like `verticalAlign` values to React Native's `textAlignVertical`.
|
|
23
|
+
*/
|
|
12
24
|
declare const verticalAlignToTextAlignVerticalMap: {
|
|
13
25
|
auto: string;
|
|
14
26
|
top: string;
|
|
@@ -16,11 +28,45 @@ declare const verticalAlignToTextAlignVerticalMap: {
|
|
|
16
28
|
middle: string;
|
|
17
29
|
};
|
|
18
30
|
|
|
19
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Native Text component with graceful fallback.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* Uses `unstable_NativeText` on supported native runtimes and falls back to `Text`
|
|
36
|
+
* on web or when the unstable export is unavailable.
|
|
37
|
+
*/
|
|
38
|
+
declare const NativeText: ComponentType<TextProps>;
|
|
20
39
|
|
|
21
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Native View component with graceful fallback.
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* Uses `unstable_NativeView` on supported native runtimes and falls back to `View`
|
|
45
|
+
* on web or when the unstable export is unavailable.
|
|
46
|
+
*/
|
|
47
|
+
declare const NativeView: ComponentType<ViewProps>;
|
|
22
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Normalizes `Text` style values for `NativeText`.
|
|
51
|
+
*
|
|
52
|
+
* @param style - Style prop passed to a text-like component.
|
|
53
|
+
* @returns Native-friendly text props. Returns an empty object when `style` is falsy or cannot be normalized.
|
|
54
|
+
* @remarks
|
|
55
|
+
* - Flattens style arrays via `StyleSheet.flatten`
|
|
56
|
+
* - Converts numeric `fontWeight` values to string values
|
|
57
|
+
* - Maps `userSelect` and `verticalAlign` to native-compatible props
|
|
58
|
+
*/
|
|
23
59
|
declare function processTextStyle(style: GenericStyleProp<TextStyle>): Partial<TextProps>;
|
|
60
|
+
/**
|
|
61
|
+
* Normalizes accessibility and ARIA props for runtime native components.
|
|
62
|
+
*
|
|
63
|
+
* @param props - Accessibility and ARIA props.
|
|
64
|
+
* @returns Props with normalized accessibility fields.
|
|
65
|
+
* @remarks
|
|
66
|
+
* - Merges `aria-label` with `accessibilityLabel`
|
|
67
|
+
* - Merges ARIA state fields into `accessibilityState`
|
|
68
|
+
* - Defaults `accessible` to `true` when omitted
|
|
69
|
+
*/
|
|
24
70
|
declare function processAccessibilityProps(props: Record<string, any>): Record<string, any>;
|
|
25
71
|
|
|
26
72
|
export { NativeText, NativeView, processAccessibilityProps, processTextStyle, userSelectToSelectableMap, verticalAlignToTextAlignVerticalMap };
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var reactNative = require('react-native');
|
|
3
|
+
var reactNative$2 = require('react-native');
|
|
4
4
|
|
|
5
5
|
const userSelectToSelectableMap = {
|
|
6
6
|
auto: true,
|
|
@@ -16,9 +16,21 @@ const verticalAlignToTextAlignVerticalMap = {
|
|
|
16
16
|
middle: "center"
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
const
|
|
19
|
+
const reactNative$1 = require("react-native");
|
|
20
|
+
const isWeb$1 = reactNative$1.Platform.OS === "web";
|
|
21
|
+
let nativeText = reactNative$1.unstable_NativeText;
|
|
22
|
+
if (isWeb$1 || nativeText == null) {
|
|
23
|
+
nativeText = reactNative$1.Text;
|
|
24
|
+
}
|
|
25
|
+
const NativeText = nativeText;
|
|
20
26
|
|
|
21
|
-
const
|
|
27
|
+
const reactNative = require("react-native");
|
|
28
|
+
const isWeb = reactNative.Platform.OS === "web";
|
|
29
|
+
let nativeView = reactNative.unstable_NativeView;
|
|
30
|
+
if (isWeb || nativeView == null) {
|
|
31
|
+
nativeView = reactNative.View;
|
|
32
|
+
}
|
|
33
|
+
const NativeView = nativeView;
|
|
22
34
|
|
|
23
35
|
const propsCache = /* @__PURE__ */ new WeakMap();
|
|
24
36
|
function processTextStyle(style) {
|
|
@@ -27,7 +39,7 @@ function processTextStyle(style) {
|
|
|
27
39
|
if (props) return props;
|
|
28
40
|
props = {};
|
|
29
41
|
propsCache.set(style, props);
|
|
30
|
-
style = reactNative.StyleSheet.flatten(style);
|
|
42
|
+
style = reactNative$2.StyleSheet.flatten(style);
|
|
31
43
|
if (!style) return {};
|
|
32
44
|
if (typeof (style == null ? void 0 : style.fontWeight) === "number") {
|
|
33
45
|
style.fontWeight = style.fontWeight.toString();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../src/runtime/utils/constants.ts","../../src/runtime/components/native-text.tsx","../../src/runtime/components/native-view.tsx","../../src/runtime/index.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/runtime/utils/constants.ts","../../src/runtime/components/native-text.tsx","../../src/runtime/components/native-view.tsx","../../src/runtime/index.ts"],"sourcesContent":["/**\n * Maps CSS-like `userSelect` values to React Native's `selectable` prop.\n */\nexport const userSelectToSelectableMap = {\n auto: true,\n text: true,\n none: false,\n contain: true,\n all: true,\n};\n\n/**\n * Maps CSS-like `verticalAlign` values to React Native's `textAlignVertical`.\n */\nexport const verticalAlignToTextAlignVerticalMap = {\n auto: 'auto',\n top: 'top',\n bottom: 'bottom',\n middle: 'center',\n};\n","/* eslint-disable @typescript-eslint/no-require-imports,unicorn/prefer-module */\n\nimport type { ComponentType } from 'react';\nimport type { TextProps } from 'react-native';\n\nconst reactNative = require('react-native');\nconst isWeb = reactNative.Platform.OS === 'web';\n\nlet nativeText = reactNative.unstable_NativeText;\n\nif (isWeb || nativeText == null) {\n // Fallback to regular Text component if unstable_NativeText is not available or we're on Web\n nativeText = reactNative.Text;\n}\n\n/**\n * Native Text component with graceful fallback.\n *\n * @remarks\n * Uses `unstable_NativeText` on supported native runtimes and falls back to `Text`\n * on web or when the unstable export is unavailable.\n */\nexport const NativeText: ComponentType<TextProps> = nativeText;\n","/* eslint-disable @typescript-eslint/no-require-imports,unicorn/prefer-module */\n\nimport type { ComponentType } from 'react';\nimport type { ViewProps } from 'react-native';\n\nconst reactNative = require('react-native');\nconst isWeb = reactNative.Platform.OS === 'web';\n\nlet nativeView = reactNative.unstable_NativeView;\n\nif (isWeb || nativeView == null) {\n // Fallback to regular View component if unstable_NativeView is not available or we're on Web\n nativeView = reactNative.View;\n}\n\n/**\n * Native View component with graceful fallback.\n *\n * @remarks\n * Uses `unstable_NativeView` on supported native runtimes and falls back to `View`\n * on web or when the unstable export is unavailable.\n */\nexport const NativeView: ComponentType<ViewProps> = nativeView;\n","import { TextProps, TextStyle, StyleSheet } from 'react-native';\nimport { GenericStyleProp } from './types';\nimport { userSelectToSelectableMap, verticalAlignToTextAlignVerticalMap } from './utils/constants';\n\nconst propsCache = new WeakMap();\n\n/**\n * Normalizes `Text` style values for `NativeText`.\n *\n * @param style - Style prop passed to a text-like component.\n * @returns Native-friendly text props. Returns an empty object when `style` is falsy or cannot be normalized.\n * @remarks\n * - Flattens style arrays via `StyleSheet.flatten`\n * - Converts numeric `fontWeight` values to string values\n * - Maps `userSelect` and `verticalAlign` to native-compatible props\n */\nexport function processTextStyle(style: GenericStyleProp<TextStyle>): Partial<TextProps> {\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 = StyleSheet.flatten(style) as TextStyle;\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/**\n * Normalizes accessibility and ARIA props for runtime native components.\n *\n * @param props - Accessibility and ARIA props.\n * @returns Props with normalized accessibility fields.\n * @remarks\n * - Merges `aria-label` with `accessibilityLabel`\n * - Merges ARIA state fields into `accessibilityState`\n * - Defaults `accessible` to `true` when omitted\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function processAccessibilityProps(props: Record<string, any>): Record<string, any> {\n const {\n accessibilityLabel,\n ['aria-label']: ariaLabel,\n accessibilityState,\n ['aria-busy']: ariaBusy,\n ['aria-checked']: ariaChecked,\n ['aria-disabled']: ariaDisabled,\n ['aria-expanded']: ariaExpanded,\n ['aria-selected']: ariaSelected,\n accessible,\n ...restProperties\n } = props;\n\n // Merge label props: prefer the aria-label if defined.\n const normalizedLabel = ariaLabel ?? accessibilityLabel;\n\n // Merge the accessibilityState with any provided ARIA properties.\n let normalizedState = accessibilityState;\n if (ariaBusy != null || ariaChecked != null || ariaDisabled != null || ariaExpanded != null || ariaSelected != null) {\n normalizedState =\n normalizedState == null\n ? {\n busy: ariaBusy,\n checked: ariaChecked,\n disabled: ariaDisabled,\n expanded: ariaExpanded,\n selected: ariaSelected,\n }\n : {\n busy: ariaBusy ?? normalizedState.busy,\n checked: ariaChecked ?? normalizedState.checked,\n disabled: ariaDisabled ?? normalizedState.disabled,\n expanded: ariaExpanded ?? normalizedState.expanded,\n selected: ariaSelected ?? normalizedState.selected,\n };\n }\n\n // For the accessible prop, if not provided, default to `true`\n const normalizedAccessible = accessible == null ? true : accessible;\n\n return {\n ...restProperties,\n accessibilityLabel: normalizedLabel,\n accessibilityState: normalizedState,\n accessible: normalizedAccessible,\n };\n}\n\nexport * from './types';\nexport * from './utils/constants';\nexport * from './components/native-text';\nexport * from './components/native-view';\n"],"names":["reactNative","isWeb","StyleSheet"],"mappings":";;;;AAGO,MAAM,yBAAA,GAA4B;AAAA,EACvC,IAAA,EAAM,IAAA;AAAA,EACN,IAAA,EAAM,IAAA;AAAA,EACN,IAAA,EAAM,KAAA;AAAA,EACN,OAAA,EAAS,IAAA;AAAA,EACT,GAAA,EAAK;AACP;AAKO,MAAM,mCAAA,GAAsC;AAAA,EACjD,IAAA,EAAM,MAAA;AAAA,EACN,GAAA,EAAK,KAAA;AAAA,EACL,MAAA,EAAQ,QAAA;AAAA,EACR,MAAA,EAAQ;AACV;;ACdA,MAAMA,aAAA,GAAc,QAAQ,cAAc,CAAA;AAC1C,MAAMC,OAAA,GAAQD,aAAA,CAAY,QAAA,CAAS,EAAA,KAAO,KAAA;AAE1C,IAAI,aAAaA,aAAA,CAAY,mBAAA;AAE7B,IAAIC,OAAA,IAAS,cAAc,IAAA,EAAM;AAE/B,EAAA,UAAA,GAAaD,aAAA,CAAY,IAAA;AAC3B;AASO,MAAM,UAAA,GAAuC;;ACjBpD,MAAM,WAAA,GAAc,QAAQ,cAAc,CAAA;AAC1C,MAAM,KAAA,GAAQ,WAAA,CAAY,QAAA,CAAS,EAAA,KAAO,KAAA;AAE1C,IAAI,aAAa,WAAA,CAAY,mBAAA;AAE7B,IAAI,KAAA,IAAS,cAAc,IAAA,EAAM;AAE/B,EAAA,UAAA,GAAa,WAAA,CAAY,IAAA;AAC3B;AASO,MAAM,UAAA,GAAuC;;AClBpD,MAAM,UAAA,uBAAiB,OAAA,EAAQ;AAYxB,SAAS,iBAAiB,KAAA,EAAwD;AACvF,EAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AAGpB,EAAA,IAAI,KAAA,GAAQ,UAAA,CAAW,GAAA,CAAI,KAAK,CAAA;AAChC,EAAA,IAAI,OAAO,OAAO,KAAA;AAElB,EAAA,KAAA,GAAQ,EAAC;AACT,EAAA,UAAA,CAAW,GAAA,CAAI,OAAO,KAAK,CAAA;AAE3B,EAAA,KAAA,GAAQE,wBAAA,CAAW,QAAQ,KAAK,CAAA;AAEhC,EAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AAEpB,EAAA,IAAI,QAAO,KAAA,IAAA,IAAA,GAAA,MAAA,GAAA,KAAA,CAAO,UAAA,CAAA,KAAe,QAAA,EAAU;AACzC,IAAA,KAAA,CAAM,UAAA,GAAa,KAAA,CAAM,UAAA,CAAW,QAAA,EAAS;AAAA,EAC/C;AAEA,EAAA,IAAA,CAAI,KAAA,IAAA,IAAA,GAAA,MAAA,GAAA,KAAA,CAAO,eAAc,IAAA,EAAM;AAC7B,IAAA,KAAA,CAAM,UAAA,GAAa,yBAAA,CAA0B,KAAA,CAAM,UAAU,CAAA;AAC7D,IAAA,OAAO,KAAA,CAAM,UAAA;AAAA,EACf;AAEA,EAAA,IAAA,CAAI,KAAA,IAAA,IAAA,GAAA,MAAA,GAAA,KAAA,CAAO,kBAAiB,IAAA,EAAM;AAChC,IAAA,KAAA,CAAM,iBAAA,GAAoB,mCAAA,CACxB,KAAA,CAAM,aACR,CAAA;AACA,IAAA,OAAO,KAAA,CAAM,aAAA;AAAA,EACf;AAEA,EAAA,KAAA,CAAM,KAAA,GAAQ,KAAA;AACd,EAAA,OAAO,KAAA;AACT;AAaO,SAAS,0BAA0B,KAAA,EAAiD;AACzF,EAAA,MAAM;AAAA,IACJ,kBAAA;AAAA,IACA,CAAC,YAAY,GAAG,SAAA;AAAA,IAChB,kBAAA;AAAA,IACA,CAAC,WAAW,GAAG,QAAA;AAAA,IACf,CAAC,cAAc,GAAG,WAAA;AAAA,IAClB,CAAC,eAAe,GAAG,YAAA;AAAA,IACnB,CAAC,eAAe,GAAG,YAAA;AAAA,IACnB,CAAC,eAAe,GAAG,YAAA;AAAA,IACnB,UAAA;AAAA,IACA,GAAG;AAAA,GACL,GAAI,KAAA;AAGJ,EAAA,MAAM,kBAAkB,SAAA,IAAA,IAAA,GAAA,SAAA,GAAa,kBAAA;AAGrC,EAAA,IAAI,eAAA,GAAkB,kBAAA;AACtB,EAAA,IAAI,QAAA,IAAY,QAAQ,WAAA,IAAe,IAAA,IAAQ,gBAAgB,IAAA,IAAQ,YAAA,IAAgB,IAAA,IAAQ,YAAA,IAAgB,IAAA,EAAM;AACnH,IAAA,eAAA,GACE,mBAAmB,IAAA,GACf;AAAA,MACE,IAAA,EAAM,QAAA;AAAA,MACN,OAAA,EAAS,WAAA;AAAA,MACT,QAAA,EAAU,YAAA;AAAA,MACV,QAAA,EAAU,YAAA;AAAA,MACV,QAAA,EAAU;AAAA,KACZ,GACA;AAAA,MACE,IAAA,EAAM,8BAAY,eAAA,CAAgB,IAAA;AAAA,MAClC,OAAA,EAAS,oCAAe,eAAA,CAAgB,OAAA;AAAA,MACxC,QAAA,EAAU,sCAAgB,eAAA,CAAgB,QAAA;AAAA,MAC1C,QAAA,EAAU,sCAAgB,eAAA,CAAgB,QAAA;AAAA,MAC1C,QAAA,EAAU,sCAAgB,eAAA,CAAgB;AAAA,KAC5C;AAAA,EACR;AAGA,EAAA,MAAM,oBAAA,GAAuB,UAAA,IAAc,IAAA,GAAO,IAAA,GAAO,UAAA;AAEzD,EAAA,OAAO;AAAA,IACL,GAAG,cAAA;AAAA,IACH,kBAAA,EAAoB,eAAA;AAAA,IACpB,kBAAA,EAAoB,eAAA;AAAA,IACpB,UAAA,EAAY;AAAA,GACd;AACF;;;;;;;;;"}
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { TextStyle, TextProps } from 'react-native';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Recursive style prop shape accepted by runtime style helpers.
|
|
5
|
+
*
|
|
6
|
+
* @template T - Style object type.
|
|
7
|
+
*/
|
|
3
8
|
type GenericStyleProp<T> = null | void | T | false | '' | ReadonlyArray<GenericStyleProp<T>>;
|
|
4
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Maps CSS-like `userSelect` values to React Native's `selectable` prop.
|
|
12
|
+
*/
|
|
5
13
|
declare const userSelectToSelectableMap: {
|
|
6
14
|
auto: boolean;
|
|
7
15
|
text: boolean;
|
|
@@ -9,6 +17,9 @@ declare const userSelectToSelectableMap: {
|
|
|
9
17
|
contain: boolean;
|
|
10
18
|
all: boolean;
|
|
11
19
|
};
|
|
20
|
+
/**
|
|
21
|
+
* Maps CSS-like `verticalAlign` values to React Native's `textAlignVertical`.
|
|
22
|
+
*/
|
|
12
23
|
declare const verticalAlignToTextAlignVerticalMap: {
|
|
13
24
|
auto: string;
|
|
14
25
|
top: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.web.js","sources":["../../src/runtime/utils/constants.ts","../../src/runtime/index.web.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"index.web.js","sources":["../../src/runtime/utils/constants.ts","../../src/runtime/index.web.ts"],"sourcesContent":["/**\n * Maps CSS-like `userSelect` values to React Native's `selectable` prop.\n */\nexport const userSelectToSelectableMap = {\n auto: true,\n text: true,\n none: false,\n contain: true,\n all: true,\n};\n\n/**\n * Maps CSS-like `verticalAlign` values to React Native's `textAlignVertical`.\n */\nexport const verticalAlignToTextAlignVerticalMap = {\n auto: 'auto',\n top: 'top',\n bottom: 'bottom',\n middle: 'center',\n};\n","// This is a dummy file to ensure that nothing breaks when using the runtime in a web environment.\n\nimport { TextProps, TextStyle } from 'react-native';\nimport { GenericStyleProp } from './types';\n\nexport const processTextStyle = (style: GenericStyleProp<TextStyle>) => ({ style }) as Partial<TextProps>;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function processAccessibilityProps(props: Record<string, any>): Record<string, any> {\n return props;\n}\n\nexport * from './types';\nexport * from './utils/constants';\n\n// On Web, the native components are not available, so we use the standard components that'll be replaced by their DOM\n// equivalents by react-native-web.\n/* eslint-disable @typescript-eslint/no-require-imports,unicorn/prefer-module */\nexport const NativeText = require('react-native').Text;\nexport const NativeView = require('react-native').View;\n/* eslint-enable @typescript-eslint/no-require-imports,unicorn/prefer-module */\n"],"names":[],"mappings":";;AAGO,MAAM,yBAAA,GAA4B;AAAA,EACvC,IAAA,EAAM,IAAA;AAAA,EACN,IAAA,EAAM,IAAA;AAAA,EACN,IAAA,EAAM,KAAA;AAAA,EACN,OAAA,EAAS,IAAA;AAAA,EACT,GAAA,EAAK;AACP;AAKO,MAAM,mCAAA,GAAsC;AAAA,EACjD,IAAA,EAAM,MAAA;AAAA,EACN,GAAA,EAAK,KAAA;AAAA,EACL,MAAA,EAAQ,QAAA;AAAA,EACR,MAAA,EAAQ;AACV;;ACdO,MAAM,gBAAA,GAAmB,CAAC,KAAA,MAAwC,EAAE,KAAA,EAAM;AAG1E,SAAS,0BAA0B,KAAA,EAAiD;AACzF,EAAA,OAAO,KAAA;AACT;AAQO,MAAM,UAAA,GAAa,OAAA,CAAQ,cAAc,CAAA,CAAE;AAC3C,MAAM,UAAA,GAAa,OAAA,CAAQ,cAAc,CAAA,CAAE;;;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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": "
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/esm/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
},
|
|
110
110
|
"peerDependencies": {
|
|
111
111
|
"react": "*",
|
|
112
|
-
"react-native": "
|
|
112
|
+
"react-native": ">=0.83.0"
|
|
113
113
|
},
|
|
114
114
|
"release-it": {
|
|
115
115
|
"git": {
|
|
@@ -118,6 +118,7 @@
|
|
|
118
118
|
},
|
|
119
119
|
"npm": {
|
|
120
120
|
"publish": true,
|
|
121
|
+
"skipChecks": true,
|
|
121
122
|
"versionArgs": [
|
|
122
123
|
"--workspaces-update=false"
|
|
123
124
|
]
|
package/src/plugin/index.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { declare } from '@babel/helper-plugin-utils';
|
|
2
2
|
import { textOptimizer } from './optimizers/text';
|
|
3
|
-
import { PluginOptions } from './types';
|
|
4
|
-
import {
|
|
3
|
+
import { PluginLogger, PluginOptions } from './types';
|
|
4
|
+
import { createLogger } from './utils/logger';
|
|
5
5
|
import { viewOptimizer } from './optimizers/view';
|
|
6
6
|
import { isIgnoredFile } from './utils/common';
|
|
7
7
|
|
|
8
|
+
export type { PluginOptimizationOptions, PluginOptions } from './types';
|
|
9
|
+
|
|
10
|
+
type PluginState = {
|
|
11
|
+
opts?: PluginOptions;
|
|
12
|
+
__reactNativeBoostLogger?: PluginLogger;
|
|
13
|
+
};
|
|
14
|
+
|
|
8
15
|
export default declare((api) => {
|
|
9
16
|
api.assertVersion(7);
|
|
10
17
|
|
|
@@ -12,8 +19,10 @@ export default declare((api) => {
|
|
|
12
19
|
name: 'react-native-boost',
|
|
13
20
|
visitor: {
|
|
14
21
|
JSXOpeningElement(path, state) {
|
|
15
|
-
const
|
|
16
|
-
const
|
|
22
|
+
const pluginState = state as PluginState;
|
|
23
|
+
const options = (pluginState.opts ?? {}) as PluginOptions;
|
|
24
|
+
const logger = getOrCreateLogger(pluginState, options);
|
|
25
|
+
|
|
17
26
|
if (isIgnoredFile(path, options.ignores ?? [])) return;
|
|
18
27
|
if (options.optimizations?.text !== false) textOptimizer(path, logger);
|
|
19
28
|
if (options.optimizations?.view !== false) viewOptimizer(path, logger, options);
|
|
@@ -21,3 +30,16 @@ export default declare((api) => {
|
|
|
21
30
|
},
|
|
22
31
|
};
|
|
23
32
|
});
|
|
33
|
+
|
|
34
|
+
function getOrCreateLogger(state: PluginState, options: PluginOptions): PluginLogger {
|
|
35
|
+
if (state.__reactNativeBoostLogger) {
|
|
36
|
+
return state.__reactNativeBoostLogger;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
state.__reactNativeBoostLogger = createLogger({
|
|
40
|
+
verbose: options.verbose === true,
|
|
41
|
+
silent: options.silent === true,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return state.__reactNativeBoostLogger;
|
|
45
|
+
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { NodePath, types as t } from '@babel/core';
|
|
2
|
-
import { HubFile, Optimizer } from '../../types';
|
|
2
|
+
import { HubFile, Optimizer, PluginLogger } from '../../types';
|
|
3
3
|
import PluginError from '../../utils/plugin-error';
|
|
4
|
+
import { BailoutCheck, getFirstBailoutReason } from '../../utils/helpers';
|
|
4
5
|
import {
|
|
5
6
|
addDefaultProperty,
|
|
6
7
|
addFileImportHint,
|
|
7
8
|
buildPropertiesFromAttributes,
|
|
8
9
|
hasAccessibilityProperty,
|
|
9
10
|
hasBlacklistedProperty,
|
|
11
|
+
isForcedLine,
|
|
10
12
|
isIgnoredLine,
|
|
11
13
|
isValidJSXComponent,
|
|
12
14
|
isReactNativeImport,
|
|
@@ -36,16 +38,48 @@ export const textBlacklistedProperties = new Set([
|
|
|
36
38
|
'selectionColor', // TODO: we can use react-native's internal `processColor` to process this at runtime
|
|
37
39
|
]);
|
|
38
40
|
|
|
39
|
-
export const textOptimizer: Optimizer = (path,
|
|
40
|
-
if (isIgnoredLine(path)) return;
|
|
41
|
+
export const textOptimizer: Optimizer = (path, logger) => {
|
|
41
42
|
if (!isValidJSXComponent(path, 'Text')) return;
|
|
42
43
|
if (!isReactNativeImport(path, 'Text')) return;
|
|
43
|
-
if (hasBlacklistedProperty(path, textBlacklistedProperties)) return;
|
|
44
|
-
if (hasExpoRouterLinkParentWithAsChild(path)) return;
|
|
45
44
|
|
|
46
|
-
// Verify that the Text only has string children
|
|
47
45
|
const parent = path.parent as t.JSXElement;
|
|
48
|
-
|
|
46
|
+
const forced = isForcedLine(path);
|
|
47
|
+
|
|
48
|
+
const overridableChecks: BailoutCheck[] = [
|
|
49
|
+
{
|
|
50
|
+
reason: 'contains blacklisted props',
|
|
51
|
+
shouldBail: () => hasBlacklistedProperty(path, textBlacklistedProperties),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
reason: 'is a direct child of expo-router Link with asChild',
|
|
55
|
+
shouldBail: () => hasExpoRouterLinkParentWithAsChild(path),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
reason: 'contains non-string children',
|
|
59
|
+
shouldBail: () => hasInvalidChildren(path, parent),
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
if (forced) {
|
|
64
|
+
const overriddenReason = getFirstBailoutReason(overridableChecks);
|
|
65
|
+
|
|
66
|
+
if (overriddenReason) {
|
|
67
|
+
logger.forced({ component: 'Text', path, reason: overriddenReason });
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
const skipReason = getFirstBailoutReason([
|
|
71
|
+
{
|
|
72
|
+
reason: 'line is marked with @boost-ignore',
|
|
73
|
+
shouldBail: () => isIgnoredLine(path),
|
|
74
|
+
},
|
|
75
|
+
...overridableChecks,
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
if (skipReason) {
|
|
79
|
+
logger.skipped({ component: 'Text', path, reason: skipReason });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
49
83
|
|
|
50
84
|
const hub = path.hub as unknown;
|
|
51
85
|
const file = typeof hub === 'object' && hub !== null && 'file' in hub ? (hub.file as HubFile) : undefined;
|
|
@@ -54,12 +88,13 @@ export const textOptimizer: Optimizer = (path, log = () => {}) => {
|
|
|
54
88
|
throw new PluginError('No file found in Babel hub');
|
|
55
89
|
}
|
|
56
90
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
91
|
+
logger.optimized({
|
|
92
|
+
component: 'Text',
|
|
93
|
+
path,
|
|
94
|
+
});
|
|
60
95
|
|
|
61
96
|
// Process props
|
|
62
|
-
fixNegativeNumberOfLines({ path,
|
|
97
|
+
fixNegativeNumberOfLines({ path, logger });
|
|
63
98
|
addDefaultProperty(path, 'allowFontScaling', t.booleanLiteral(true));
|
|
64
99
|
addDefaultProperty(path, 'ellipsizeMode', t.stringLiteral('tail'));
|
|
65
100
|
processProps(path, file);
|
|
@@ -98,13 +133,7 @@ function hasInvalidChildren(path: NodePath<t.JSXOpeningElement>, parent: t.JSXEl
|
|
|
98
133
|
/**
|
|
99
134
|
* Fixes negative numberOfLines values by setting them to 0.
|
|
100
135
|
*/
|
|
101
|
-
function fixNegativeNumberOfLines({
|
|
102
|
-
path,
|
|
103
|
-
log,
|
|
104
|
-
}: {
|
|
105
|
-
path: NodePath<t.JSXOpeningElement>;
|
|
106
|
-
log: (message: string) => void;
|
|
107
|
-
}) {
|
|
136
|
+
function fixNegativeNumberOfLines({ path, logger }: { path: NodePath<t.JSXOpeningElement>; logger: PluginLogger }) {
|
|
108
137
|
for (const attribute of path.node.attributes) {
|
|
109
138
|
if (
|
|
110
139
|
t.isJSXAttribute(attribute) &&
|
|
@@ -123,9 +152,11 @@ function fixNegativeNumberOfLines({
|
|
|
123
152
|
originalValue = -attribute.value.expression.argument.value;
|
|
124
153
|
}
|
|
125
154
|
if (originalValue !== undefined && originalValue < 0) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
155
|
+
logger.warning({
|
|
156
|
+
component: 'Text',
|
|
157
|
+
path,
|
|
158
|
+
message: `'numberOfLines' must be a non-negative number, received: ${originalValue}. The value will be set to 0.`,
|
|
159
|
+
});
|
|
129
160
|
attribute.value.expression = t.numericLiteral(0);
|
|
130
161
|
}
|
|
131
162
|
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { types as t } from '@babel/core';
|
|
2
2
|
import { HubFile, Optimizer } from '../../types';
|
|
3
3
|
import PluginError from '../../utils/plugin-error';
|
|
4
|
+
import { BailoutCheck, getFirstBailoutReason } from '../../utils/helpers';
|
|
4
5
|
import {
|
|
5
6
|
hasBlacklistedProperty,
|
|
7
|
+
isForcedLine,
|
|
6
8
|
isIgnoredLine,
|
|
7
9
|
isValidJSXComponent,
|
|
8
10
|
isReactNativeImport,
|
|
9
11
|
replaceWithNativeComponent,
|
|
10
|
-
|
|
12
|
+
getViewAncestorClassification,
|
|
13
|
+
ViewAncestorClassification,
|
|
11
14
|
} from '../../utils/common';
|
|
12
15
|
|
|
13
16
|
export const viewBlacklistedProperties = new Set([
|
|
@@ -26,14 +29,58 @@ export const viewBlacklistedProperties = new Set([
|
|
|
26
29
|
'style', // TODO: process style at runtime
|
|
27
30
|
]);
|
|
28
31
|
|
|
29
|
-
export const viewOptimizer: Optimizer = (path,
|
|
30
|
-
if (isIgnoredLine(path)) return;
|
|
32
|
+
export const viewOptimizer: Optimizer = (path, logger, options) => {
|
|
31
33
|
if (!isValidJSXComponent(path, 'View')) return;
|
|
32
34
|
if (!isReactNativeImport(path, 'View')) return;
|
|
33
|
-
if (hasBlacklistedProperty(path, viewBlacklistedProperties)) return;
|
|
34
|
-
if (hasUnsafeViewAncestor(path, options?.dangerouslyOptimizeViewWithUnknownAncestors === true)) return;
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
let ancestorClassification: ViewAncestorClassification | undefined;
|
|
37
|
+
const getAncestorClassification = () => {
|
|
38
|
+
if (!ancestorClassification) {
|
|
39
|
+
ancestorClassification = getViewAncestorClassification(path);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return ancestorClassification;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const forced = isForcedLine(path);
|
|
46
|
+
|
|
47
|
+
const overridableChecks: BailoutCheck[] = [
|
|
48
|
+
{
|
|
49
|
+
reason: 'contains blacklisted props',
|
|
50
|
+
shouldBail: () => hasBlacklistedProperty(path, viewBlacklistedProperties),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
reason: 'has Text ancestor',
|
|
54
|
+
shouldBail: () => getAncestorClassification() === 'text',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
reason: 'has unresolved ancestor and dangerous optimization is disabled',
|
|
58
|
+
shouldBail: () =>
|
|
59
|
+
getAncestorClassification() === 'unknown' && options?.dangerouslyOptimizeViewWithUnknownAncestors !== true,
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
if (forced) {
|
|
64
|
+
const overriddenReason = getFirstBailoutReason(overridableChecks);
|
|
65
|
+
|
|
66
|
+
if (overriddenReason) {
|
|
67
|
+
logger.forced({ component: 'View', path, reason: overriddenReason });
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
const skipReason = getFirstBailoutReason([
|
|
71
|
+
{
|
|
72
|
+
reason: 'line is marked with @boost-ignore',
|
|
73
|
+
shouldBail: () => isIgnoredLine(path),
|
|
74
|
+
},
|
|
75
|
+
...overridableChecks,
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
if (skipReason) {
|
|
79
|
+
logger.skipped({ component: 'View', path, reason: skipReason });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
37
84
|
const hub = path.hub as unknown;
|
|
38
85
|
const file = typeof hub === 'object' && hub !== null && 'file' in hub ? (hub.file as HubFile) : undefined;
|
|
39
86
|
|
|
@@ -41,12 +88,12 @@ export const viewOptimizer: Optimizer = (path, log = () => {}, options) => {
|
|
|
41
88
|
throw new PluginError('No file found in Babel hub');
|
|
42
89
|
}
|
|
43
90
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
91
|
+
logger.optimized({
|
|
92
|
+
component: 'View',
|
|
93
|
+
path,
|
|
94
|
+
});
|
|
47
95
|
|
|
48
96
|
const parent = path.parent as t.JSXElement;
|
|
49
97
|
|
|
50
|
-
// Replace the View component with NativeView
|
|
51
98
|
replaceWithNativeComponent(path, parent, file, 'NativeView');
|
|
52
99
|
};
|
|
@@ -1,44 +1,84 @@
|
|
|
1
1
|
import { NodePath, types as t } from '@babel/core';
|
|
2
2
|
|
|
3
|
+
export interface PluginOptimizationOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Whether to optimize the `Text` component.
|
|
6
|
+
* @default true
|
|
7
|
+
*/
|
|
8
|
+
text?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Whether to optimize the `View` component.
|
|
11
|
+
* @default true
|
|
12
|
+
*/
|
|
13
|
+
view?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
3
16
|
export interface PluginOptions {
|
|
4
17
|
/**
|
|
5
|
-
* Paths to ignore from optimization.
|
|
18
|
+
* Paths to ignore from optimization.
|
|
19
|
+
*
|
|
20
|
+
* Patterns are resolved from Babel's current working directory.
|
|
21
|
+
* In nested monorepo apps, parent segments may be needed, for example `../../node_modules/**`.
|
|
22
|
+
* @default []
|
|
6
23
|
*/
|
|
7
24
|
ignores?: string[];
|
|
8
25
|
/**
|
|
9
|
-
*
|
|
26
|
+
* Enables verbose logging.
|
|
27
|
+
*
|
|
28
|
+
* With `silent: false`, optimized components are logged by default.
|
|
29
|
+
* When enabled, skipped components and their skip reasons are also logged.
|
|
10
30
|
* @default false
|
|
11
31
|
*/
|
|
12
32
|
verbose?: boolean;
|
|
13
33
|
/**
|
|
14
|
-
*
|
|
34
|
+
* Disables all plugin logs.
|
|
35
|
+
*
|
|
36
|
+
* When set to `true`, this overrides `verbose`.
|
|
37
|
+
* @default false
|
|
15
38
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* Whether or not to optimize the View component.
|
|
24
|
-
* @default true
|
|
25
|
-
*/
|
|
26
|
-
view?: boolean;
|
|
27
|
-
};
|
|
39
|
+
silent?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Toggle individual optimizers.
|
|
42
|
+
*
|
|
43
|
+
* If omitted, all available optimizers are enabled.
|
|
44
|
+
*/
|
|
45
|
+
optimizations?: PluginOptimizationOptions;
|
|
28
46
|
/**
|
|
29
47
|
* Opt-in flag that allows View optimization when ancestor components cannot be statically resolved.
|
|
30
48
|
*
|
|
31
|
-
* This
|
|
49
|
+
* This increases optimization coverage, but may introduce behavioral differences
|
|
50
|
+
* when unresolved ancestors render React Native `Text` wrappers.
|
|
51
|
+
* Prefer targeted ignores first, and enable this only after verifying affected screens.
|
|
32
52
|
* @default false
|
|
33
53
|
*/
|
|
34
54
|
dangerouslyOptimizeViewWithUnknownAncestors?: boolean;
|
|
35
55
|
}
|
|
36
56
|
|
|
37
|
-
export type
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
57
|
+
export type OptimizableComponent = 'Text' | 'View';
|
|
58
|
+
|
|
59
|
+
export interface OptimizationLogPayload {
|
|
60
|
+
component: OptimizableComponent;
|
|
61
|
+
path: NodePath<t.JSXOpeningElement>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface SkippedOptimizationLogPayload extends OptimizationLogPayload {
|
|
65
|
+
reason: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface WarningLogPayload {
|
|
69
|
+
message: string;
|
|
70
|
+
component?: OptimizableComponent;
|
|
71
|
+
path?: NodePath<t.JSXOpeningElement>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface PluginLogger {
|
|
75
|
+
optimized: (payload: OptimizationLogPayload) => void;
|
|
76
|
+
skipped: (payload: SkippedOptimizationLogPayload) => void;
|
|
77
|
+
forced: (payload: SkippedOptimizationLogPayload) => void;
|
|
78
|
+
warning: (payload: WarningLogPayload) => void;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type Optimizer = (path: NodePath<t.JSXOpeningElement>, logger: PluginLogger, options?: PluginOptions) => void;
|
|
42
82
|
|
|
43
83
|
export type HubFile = t.File & {
|
|
44
84
|
opts: {
|