react-native-boost 0.0.1

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.
Files changed (40) hide show
  1. package/.github/FUNDING.yml +1 -0
  2. package/.github/actions/setup/action.yml +28 -0
  3. package/.github/workflows/release.yml +87 -0
  4. package/.github/workflows/stale.yml +37 -0
  5. package/.github/workflows/test.yml +54 -0
  6. package/.husky/pre-commit +2 -0
  7. package/.lintstagedrc +3 -0
  8. package/.nvmrc +1 -0
  9. package/.prettierignore +4 -0
  10. package/.prettierrc +14 -0
  11. package/.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs +541 -0
  12. package/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs +28 -0
  13. package/.yarn/releases/yarn-3.6.1.cjs +874 -0
  14. package/.yarnrc.yml +2 -0
  15. package/CODE_OF_CONDUCT.md +132 -0
  16. package/CONTRIBUTING.md +122 -0
  17. package/LICENSE +20 -0
  18. package/README.md +72 -0
  19. package/commitlint.config.mjs +1 -0
  20. package/dist/plugin.js +70473 -0
  21. package/dist/plugin.js.map +7 -0
  22. package/eslint.config.mjs +14 -0
  23. package/package.json +70 -0
  24. package/src/optimizers/text/__tests__/fixtures/basic/code.js +2 -0
  25. package/src/optimizers/text/__tests__/fixtures/basic/options.js +4 -0
  26. package/src/optimizers/text/__tests__/fixtures/basic/output.js +3 -0
  27. package/src/optimizers/text/__tests__/fixtures/text-content/code.js +2 -0
  28. package/src/optimizers/text/__tests__/fixtures/text-content/options.js +4 -0
  29. package/src/optimizers/text/__tests__/fixtures/text-content/output.js +3 -0
  30. package/src/optimizers/text/__tests__/fixtures/text-content-as-explicit-child-prop/code.js +2 -0
  31. package/src/optimizers/text/__tests__/fixtures/text-content-as-explicit-child-prop/options.js +4 -0
  32. package/src/optimizers/text/__tests__/fixtures/text-content-as-explicit-child-prop/output.js +3 -0
  33. package/src/optimizers/text/__tests__/fixtures/two-components/code.js +3 -0
  34. package/src/optimizers/text/__tests__/fixtures/two-components/options.js +4 -0
  35. package/src/optimizers/text/__tests__/fixtures/two-components/output.js +4 -0
  36. package/src/optimizers/text/__tests__/index.test.ts +12 -0
  37. package/src/optimizers/text/index.ts +128 -0
  38. package/src/plugin.ts +15 -0
  39. package/tsconfig.json +14 -0
  40. package/vitest.config.ts +8 -0
@@ -0,0 +1,14 @@
1
+ import globals from 'globals';
2
+ import pluginJs from '@eslint/js';
3
+ import tseslint from 'typescript-eslint';
4
+ import eslintPluginUnicorn from 'eslint-plugin-unicorn';
5
+
6
+ /** @type {import('eslint').Linter.Config[]} */
7
+ export default [
8
+ { files: ['**/*.{js,mjs,cjs,ts}'] },
9
+ { languageOptions: { globals: globals.node } },
10
+ { ignores: ['**/fixtures', '**/*.config.{js,mjs,cjs}'] },
11
+ pluginJs.configs.recommended,
12
+ ...tseslint.configs.recommended,
13
+ eslintPluginUnicorn.configs.recommended,
14
+ ];
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "react-native-boost",
3
+ "description": "🚀 Boost your React Native app's performance with a single line of code",
4
+ "version": "0.0.1",
5
+ "main": "dist/plugin.js",
6
+ "scripts": {
7
+ "clean": "rm -rf dist",
8
+ "build": "yarn clean && esbuild src/plugin.ts --bundle --outfile=dist/plugin.js --sourcemap --platform=node",
9
+ "test": "vitest",
10
+ "typecheck": "tsc --noEmit",
11
+ "lint": "eslint src/**/*.ts",
12
+ "format": "prettier --write .",
13
+ "prepare": "husky",
14
+ "release": "release-it"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/kuatsu/react-native-boost.git"
19
+ },
20
+ "author": "Kuatsu App Agency <hello@kuatsu.de>",
21
+ "license": "MIT",
22
+ "bugs": {
23
+ "url": "https://github.com/kuatsu/react-native-boost/issues"
24
+ },
25
+ "homepage": "https://github.com/kuatsu/react-native-boost#readme",
26
+ "packageManager": "yarn@3.6.1",
27
+ "devDependencies": {
28
+ "@babel/core": "^7.25.0",
29
+ "@babel/helper-module-imports": "^7.25.0",
30
+ "@babel/helper-plugin-utils": "^7.25.0",
31
+ "@babel/plugin-syntax-jsx": "^7.25.0",
32
+ "@babel/preset-typescript": "^7.25.0",
33
+ "@commitlint/cli": "^19.7.1",
34
+ "@commitlint/config-conventional": "^17.0.2",
35
+ "@eslint/js": "^9.21.0",
36
+ "@release-it/conventional-changelog": "^5.0.0",
37
+ "@types/babel__helper-module-imports": "^7.0.0",
38
+ "@types/babel__helper-plugin-utils": "^7.0.0",
39
+ "@types/node": "^20",
40
+ "babel-plugin-tester": "^11.0.4",
41
+ "esbuild": "^0.25.0",
42
+ "eslint": "^9.21.0",
43
+ "eslint-plugin-unicorn": "^57.0.0",
44
+ "globals": "^16.0.0",
45
+ "husky": "^9.1.7",
46
+ "lint-staged": "^15.4.3",
47
+ "prettier": "^3.5.2",
48
+ "release-it": "^18.1.2",
49
+ "typescript": "^5.7.3",
50
+ "typescript-eslint": "^8.24.1",
51
+ "vitest": "^3.0.6"
52
+ },
53
+ "release-it": {
54
+ "git": {
55
+ "commitMessage": "chore: release ${version}",
56
+ "tagName": "v${version}"
57
+ },
58
+ "npm": {
59
+ "publish": true
60
+ },
61
+ "github": {
62
+ "release": true
63
+ },
64
+ "plugins": {
65
+ "@release-it/conventional-changelog": {
66
+ "preset": "angular"
67
+ }
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,2 @@
1
+ import { Text } from "react-native";
2
+ <Text />;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @type {import('babel-plugin-tester').TestObject}
3
+ */
4
+ module.exports = {};
@@ -0,0 +1,3 @@
1
+ import { NativeText as _NativeText } from "react-native/Libraries/Text/TextNativeComponent";
2
+ import { Text } from "react-native";
3
+ <_NativeText />;
@@ -0,0 +1,2 @@
1
+ import { Text } from "react-native";
2
+ <Text>Hello, world!</Text>;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @type {import('babel-plugin-tester').TestObject}
3
+ */
4
+ module.exports = {};
@@ -0,0 +1,3 @@
1
+ import { NativeText as _NativeText } from "react-native/Libraries/Text/TextNativeComponent";
2
+ import { Text } from "react-native";
3
+ <_NativeText>Hello, world!</_NativeText>;
@@ -0,0 +1,2 @@
1
+ import { Text } from "react-native";
2
+ <Text children="Hello, world!" />;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @type {import('babel-plugin-tester').TestObject}
3
+ */
4
+ module.exports = {};
@@ -0,0 +1,3 @@
1
+ import { NativeText as _NativeText } from "react-native/Libraries/Text/TextNativeComponent";
2
+ import { Text } from "react-native";
3
+ <_NativeText children="Hello, world!" />;
@@ -0,0 +1,3 @@
1
+ import { Text } from "react-native";
2
+ <Text />;
3
+ <Text></Text>;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @type {import('babel-plugin-tester').TestObject}
3
+ */
4
+ module.exports = {};
@@ -0,0 +1,4 @@
1
+ import { NativeText as _NativeText } from "react-native/Libraries/Text/TextNativeComponent";
2
+ import { Text } from "react-native";
3
+ <_NativeText />;
4
+ <_NativeText></_NativeText>;
@@ -0,0 +1,12 @@
1
+ import path from 'node:path';
2
+ import { pluginTester } from 'babel-plugin-tester/pure';
3
+ import plugin from '../../../plugin';
4
+
5
+ pluginTester({
6
+ plugin,
7
+ title: 'text',
8
+ fixtures: path.resolve(import.meta.dirname, 'fixtures'),
9
+ babelOptions: {
10
+ plugins: ['@babel/plugin-syntax-jsx'],
11
+ },
12
+ });
@@ -0,0 +1,128 @@
1
+ import { NodePath, types as t } from '@babel/core';
2
+ import { addNamed } from '@babel/helper-module-imports';
3
+
4
+ export function textOptimizer(path: NodePath<t.JSXOpeningElement>): void {
5
+ // Ensure we're processing a JSX element
6
+ if (!t.isJSXIdentifier(path.node.name)) return;
7
+
8
+ const parent = path.parent;
9
+ if (!t.isJSXElement(parent)) return;
10
+
11
+ const elementName = path.node.name.name;
12
+ if (elementName !== 'Text') return;
13
+
14
+ // Ensure Text element comes from react-native
15
+ const binding = path.scope.getBinding(elementName);
16
+ if (!binding) return;
17
+ if (binding.kind === 'module') {
18
+ const parentNode = binding.path.parent;
19
+ if (!t.isImportDeclaration(parentNode) || parentNode.source.value !== 'react-native') {
20
+ return;
21
+ }
22
+ }
23
+
24
+ // Bail if the element has any blacklisted properties or non-string children props
25
+ if (hasBlacklistedProperties(path)) return;
26
+ if (!hasOnlyStringChildren(path, parent)) return;
27
+ // TODO: Don't bail if the element has a style prop
28
+
29
+ // Add NativeTextComponent import (cached on file) so we only add it once per file
30
+ const file = (path.hub as unknown as { file: t.File }).file as t.File & {
31
+ __nativeTextImport?: t.Identifier;
32
+ };
33
+ if (!file.__nativeTextImport) {
34
+ file.__nativeTextImport = addNamed(path, 'NativeText', 'react-native/Libraries/Text/TextNativeComponent');
35
+ }
36
+ const nativeTextIdentifier = file.__nativeTextImport;
37
+ path.node.name.name = nativeTextIdentifier.name;
38
+
39
+ // If the element is not self-closing, update the closing element as well
40
+ if (
41
+ !path.node.selfClosing &&
42
+ parent.closingElement &&
43
+ t.isJSXIdentifier(parent.closingElement.name) &&
44
+ parent.closingElement.name.name === 'Text'
45
+ ) {
46
+ parent.closingElement.name.name = nativeTextIdentifier.name;
47
+ }
48
+ }
49
+
50
+ function hasOnlyStringChildren(path: NodePath<t.JSXOpeningElement>, node: t.JSXElement): boolean {
51
+ return node.children.every((child) => isStringNode(path, child));
52
+ }
53
+
54
+ function isStringNode(path: NodePath<t.JSXOpeningElement>, child: t.Node): boolean {
55
+ if (t.isJSXText(child)) return true;
56
+
57
+ // Check for JSX expressions
58
+ if (t.isJSXExpressionContainer(child)) {
59
+ const expression = child.expression;
60
+
61
+ // If the expression is an identifier, look it up in the current scope
62
+ if (t.isIdentifier(expression)) {
63
+ const binding = path.scope.getBinding(expression.name);
64
+ return binding ? t.isStringLiteral(binding.path.node) : false;
65
+ }
66
+ }
67
+ return false;
68
+ }
69
+
70
+ const blacklistedProperties = new Set([
71
+ 'accessible',
72
+ 'accessibilityLabel',
73
+ 'accessibilityState',
74
+ 'allowFontScaling',
75
+ 'aria-busy',
76
+ 'aria-checked',
77
+ 'aria-disabled',
78
+ 'aria-expanded',
79
+ 'aria-label',
80
+ 'aria-selected',
81
+ 'ellipsizeMode',
82
+ 'id',
83
+ 'nativeID',
84
+ 'onLongPress',
85
+ 'onPress',
86
+ 'onPressIn',
87
+ 'onPressOut',
88
+ 'onResponderGrant',
89
+ 'onResponderMove',
90
+ 'onResponderRelease',
91
+ 'onResponderTerminate',
92
+ 'onResponderTerminationRequest',
93
+ 'onStartShouldSetResponder',
94
+ 'pressRetentionOffset',
95
+ 'suppressHighlighting',
96
+ 'style',
97
+ ]);
98
+
99
+ function hasBlacklistedProperties(path: NodePath<t.JSXOpeningElement>): boolean {
100
+ return path.node.attributes.some((attribute) => {
101
+ if (t.isJSXSpreadAttribute(attribute)) {
102
+ if (t.isIdentifier(attribute.argument)) {
103
+ const binding = path.scope.getBinding(attribute.argument.name);
104
+ if (binding && t.isObjectExpression(binding.path.node)) {
105
+ return binding.path.node.properties.some((property) => {
106
+ if (t.isObjectProperty(property) && t.isIdentifier(property.key)) {
107
+ return blacklistedProperties.has(property.key.name);
108
+ }
109
+ return false;
110
+ });
111
+ }
112
+ }
113
+ // Bail if we can't resolve the spread attribute
114
+ return true;
115
+ }
116
+
117
+ if (t.isJSXIdentifier(attribute.name) && attribute.value) {
118
+ // For a "children" attribute, optimization is allowed only if it is a string
119
+ if (attribute.name.name === 'children') {
120
+ return isStringNode(path, attribute.value);
121
+ }
122
+ return blacklistedProperties.has(attribute.name.name);
123
+ }
124
+
125
+ // For other attribute types (e.g. namespaced), assume no blacklisting
126
+ return false;
127
+ });
128
+ }
package/src/plugin.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { declare } from '@babel/helper-plugin-utils';
2
+ import { textOptimizer } from './optimizers/text';
3
+
4
+ export default declare((api) => {
5
+ api.assertVersion(7);
6
+
7
+ return {
8
+ name: 'react-native-boost/text',
9
+ visitor: {
10
+ JSXOpeningElement(path) {
11
+ textOptimizer(path);
12
+ },
13
+ },
14
+ };
15
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["ESNext"],
4
+ "module": "esnext",
5
+ "moduleResolution": "bundler",
6
+ "target": "es2019",
7
+ "declaration": true,
8
+ "outDir": "./dist",
9
+ "types": ["node"],
10
+ "skipLibCheck": true
11
+ },
12
+ "include": ["src/**/*"],
13
+ "exclude": ["node_modules", "dist", "fixtures"]
14
+ }
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ // babel-plugin-tester requires it and describe to be set globally
6
+ globals: true,
7
+ },
8
+ });