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 +14 -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 +209 -0
- package/dist/plugin/esm/index.mjs.map +1 -0
- package/dist/plugin/index.d.ts +12 -0
- package/dist/plugin/index.js +211 -0
- package/dist/plugin/index.js.map +1 -0
- package/package.json +33 -12
- package/src/{optimizers → plugin/optimizers}/text/index.ts +37 -5
- package/src/{types → plugin/types}/index.ts +1 -1
- package/src/plugin/utils/common.ts +53 -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 -70508
- /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
|
@@ -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 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","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.1.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,26 +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": "^
|
|
84
|
+
"esbuild-node-externals": "^1.18.0",
|
|
62
85
|
"globals": "^16.0.0",
|
|
86
|
+
"react-native": "0.76.7",
|
|
63
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",
|
|
64
91
|
"typescript": "^5.7.3",
|
|
65
92
|
"vitest": "^3.0.6"
|
|
66
93
|
},
|
|
67
94
|
"peerDependencies": {
|
|
68
|
-
"expo": "*",
|
|
69
95
|
"react": "*",
|
|
70
96
|
"react-native": "*"
|
|
71
97
|
},
|
|
72
|
-
"peerDependenciesMeta": {
|
|
73
|
-
"expo": {
|
|
74
|
-
"optional": true
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
98
|
"release-it": {
|
|
78
99
|
"git": {
|
|
79
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;
|
|
@@ -43,11 +48,17 @@ export const textOptimizer: Optimizer = (path, log = () => {}) => {
|
|
|
43
48
|
file.__optimized = true;
|
|
44
49
|
}
|
|
45
50
|
|
|
51
|
+
// Optimize props
|
|
52
|
+
optimizeStyleTag({ path, file });
|
|
53
|
+
|
|
46
54
|
// Add TextNativeComponent import (cached on file) so we only add it once per file
|
|
47
|
-
if (!file.
|
|
48
|
-
file.
|
|
55
|
+
if (!file.__hasImports) {
|
|
56
|
+
file.__hasImports = {};
|
|
57
|
+
}
|
|
58
|
+
if (!file.__hasImports.NativeText) {
|
|
59
|
+
file.__hasImports.NativeText = addNamed(path, 'NativeText', 'react-native/Libraries/Text/TextNativeComponent');
|
|
49
60
|
}
|
|
50
|
-
const nativeTextIdentifier = file.
|
|
61
|
+
const nativeTextIdentifier = file.__hasImports.NativeText;
|
|
51
62
|
path.node.name.name = nativeTextIdentifier.name;
|
|
52
63
|
|
|
53
64
|
// If the element is not self-closing, update the closing element as well
|
|
@@ -107,9 +118,30 @@ const blacklistedProperties = new Set([
|
|
|
107
118
|
'onStartShouldSetResponder',
|
|
108
119
|
'pressRetentionOffset',
|
|
109
120
|
'suppressHighlighting',
|
|
110
|
-
'style',
|
|
111
121
|
]);
|
|
112
122
|
|
|
123
|
+
function optimizeStyleTag({ path, file }: { path: NodePath<t.JSXOpeningElement>; file: HubFile }) {
|
|
124
|
+
let shouldImportFlattenTextStyle = false;
|
|
125
|
+
const nameHint = '_flattenTextStyle';
|
|
126
|
+
|
|
127
|
+
for (const [index, attribute] of path.node.attributes.entries()) {
|
|
128
|
+
if (t.isJSXAttribute(attribute) && t.isJSXIdentifier(attribute.name, { name: 'style' })) {
|
|
129
|
+
shouldImportFlattenTextStyle = true;
|
|
130
|
+
|
|
131
|
+
if (t.isJSXExpressionContainer(attribute.value) && !t.isJSXEmptyExpression(attribute.value.expression)) {
|
|
132
|
+
path.node.attributes[index] = t.jsxSpreadAttribute(
|
|
133
|
+
t.callExpression(t.identifier(nameHint), [attribute.value.expression])
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (shouldImportFlattenTextStyle && !file.__hasImports?.flattenTextStyle) {
|
|
140
|
+
if (!file.__hasImports) file.__hasImports = {};
|
|
141
|
+
file.__hasImports.flattenTextStyle = addNamed(path, 'flattenTextStyle', 'react-native-boost', { nameHint });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
113
145
|
function hasBlacklistedProperties(path: NodePath<t.JSXOpeningElement>): boolean {
|
|
114
146
|
return path.node.attributes.some((attribute) => {
|
|
115
147
|
// Check if we can resolve the spread attribute
|
|
@@ -0,0 +1,53 @@
|
|
|
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 parent container's children, finds the current
|
|
8
|
+
* JSX element, and then (ignoring whitespace-only nodes) inspects the previous
|
|
9
|
+
* sibling for a comment that includes "@boost-ignore".
|
|
10
|
+
*/
|
|
11
|
+
export const shouldIgnoreOptimization = (path: NodePath<t.JSXOpeningElement>): boolean => {
|
|
12
|
+
// Get the JSX element by going up one level (opening element -> JSXElement)
|
|
13
|
+
const jsxElementPath = path.parentPath;
|
|
14
|
+
if (!jsxElementPath.parentPath) return false;
|
|
15
|
+
|
|
16
|
+
// Get the container that holds this element (for example, a JSX fragment or JSX element)
|
|
17
|
+
const containerPath = jsxElementPath.parentPath;
|
|
18
|
+
const siblings = ensureArray(containerPath.get('children'));
|
|
19
|
+
const index = siblings.findIndex((sibling) => sibling.node === jsxElementPath.node);
|
|
20
|
+
if (index === -1) return false;
|
|
21
|
+
|
|
22
|
+
// Look backward from the current element for a non-empty node.
|
|
23
|
+
for (let index_ = index - 1; index_ >= 0; index_--) {
|
|
24
|
+
const sibling = siblings[index_];
|
|
25
|
+
// Skip over any whitespace (only in JSXText nodes)
|
|
26
|
+
if (sibling.isJSXText() && sibling.node.value.trim() === '') {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
// If the sibling is a JSX expression container, check its empty expression's comments.
|
|
30
|
+
if (sibling.isJSXExpressionContainer()) {
|
|
31
|
+
const expression = sibling.get('expression');
|
|
32
|
+
if (expression && expression.node) {
|
|
33
|
+
const comments = [
|
|
34
|
+
...(expression.node.leadingComments || []),
|
|
35
|
+
...(expression.node.trailingComments || []),
|
|
36
|
+
...(expression.node.innerComments || []),
|
|
37
|
+
].map((comment) => comment.value.trim());
|
|
38
|
+
if (comments.some((comment) => comment.includes('@boost-ignore'))) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Also check if the node itself carries a leadingComments property.
|
|
44
|
+
if (
|
|
45
|
+
sibling.node.leadingComments &&
|
|
46
|
+
sibling.node.leadingComments.some((comment) => comment.value.includes('@boost-ignore'))
|
|
47
|
+
) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
break; // if the immediate non-whitespace node is not our ignore marker, stop
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
};
|
|
@@ -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>>;
|