react-native-boost 0.3.0 → 0.4.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.
@@ -0,0 +1,114 @@
1
+ import { NodePath, types as t } from '@babel/core';
2
+ import { addDefault } from '@babel/helper-module-imports';
3
+ import { HubFile, Optimizer } from '../../types';
4
+ import PluginError from '../../utils/plugin-error';
5
+ import { hasBlacklistedProperty, shouldIgnoreOptimization } from '../../utils/common';
6
+
7
+ export const viewBlacklistedProperties = new Set([
8
+ 'accessible',
9
+ 'accessibilityLabel',
10
+ 'accessibilityState',
11
+ 'allowFontScaling',
12
+ 'aria-busy',
13
+ 'aria-checked',
14
+ 'aria-disabled',
15
+ 'aria-expanded',
16
+ 'aria-label',
17
+ 'aria-selected',
18
+ 'ellipsizeMode',
19
+ 'disabled',
20
+ 'id',
21
+ 'nativeID',
22
+ 'numberOfLines',
23
+ 'onLongPress',
24
+ 'onPress',
25
+ 'onPressIn',
26
+ 'onPressOut',
27
+ 'onResponderGrant',
28
+ 'onResponderMove',
29
+ 'onResponderRelease',
30
+ 'onResponderTerminate',
31
+ 'onResponderTerminationRequest',
32
+ 'onStartShouldSetResponder',
33
+ 'pressRetentionOffset',
34
+ 'selectable',
35
+ 'selectionColor',
36
+ 'suppressHighlighting',
37
+ 'style',
38
+ ]);
39
+
40
+ export const viewOptimizer: Optimizer = (path, log = () => {}) => {
41
+ // Ensure we're processing a JSX element identifier.
42
+ if (!t.isJSXIdentifier(path.node.name)) return;
43
+
44
+ const parent = path.parent;
45
+ if (!t.isJSXElement(parent)) return;
46
+
47
+ const elementName = path.node.name.name;
48
+ if (elementName !== 'View') return;
49
+
50
+ // Respect comments that disable optimization.
51
+ if (shouldIgnoreOptimization(path)) return;
52
+
53
+ // Ensure the View element comes from react-native.
54
+ const binding = path.scope.getBinding(elementName);
55
+ if (!binding) return;
56
+ if (binding.kind === 'module') {
57
+ const parentNode = binding.path.parent;
58
+ if (!t.isImportDeclaration(parentNode) || parentNode.source.value !== 'react-native') {
59
+ return;
60
+ }
61
+ }
62
+
63
+ // Bail if any blacklisted props are present.
64
+ if (hasBlacklistedProperty(path, viewBlacklistedProperties)) return;
65
+
66
+ // Bail if a <TextAncestor /> component exists as an ancestor.
67
+ if (hasTextAncestor(path)) return;
68
+
69
+ // Extract the file from the Babel hub and add flags for logging & import caching.
70
+ const hub = path.hub as unknown;
71
+ const file = typeof hub === 'object' && hub !== null && 'file' in hub ? (hub.file as HubFile) : undefined;
72
+
73
+ if (!file) {
74
+ throw new PluginError('No file found in Babel hub');
75
+ }
76
+
77
+ const filename = file.opts?.filename || 'unknown file';
78
+ const lineNumber = path.node.loc?.start.line ?? 'unknown line';
79
+ log(`Optimizing View component in ${filename}:${lineNumber}`);
80
+
81
+ // Add ViewNativeComponent import (cached on the file) to prevent duplicate imports.
82
+ if (!file.__hasImports) {
83
+ file.__hasImports = {};
84
+ }
85
+ if (!file.__hasImports.ViewNativeComponent) {
86
+ file.__hasImports.NativeView = addDefault(path, 'react-native/Libraries/Components/View/ViewNativeComponent', {
87
+ nameHint: 'NativeView',
88
+ });
89
+ }
90
+ const viewNativeIdentifier = file.__hasImports.NativeView;
91
+
92
+ // Replace the component with its native counterpart.
93
+ path.node.name.name = viewNativeIdentifier.name;
94
+
95
+ // If the element is not self-closing, update the closing element as well.
96
+ if (
97
+ !path.node.selfClosing &&
98
+ parent.closingElement &&
99
+ t.isJSXIdentifier(parent.closingElement.name) &&
100
+ parent.closingElement.name.name === 'View'
101
+ ) {
102
+ parent.closingElement.name.name = viewNativeIdentifier.name;
103
+ }
104
+ };
105
+
106
+ /**
107
+ * Returns true if any ancestor element is a <Text />.
108
+ * TODO: This is dangerous as we can't resolve custom components and check if they have a <Text /> ancestor in the tree
109
+ */
110
+ function hasTextAncestor(path: NodePath<t.JSXOpeningElement>): boolean {
111
+ return !!path.findParent((parentPath) => {
112
+ return t.isJSXElement(parentPath.node) && t.isJSXIdentifier(parentPath.node.openingElement.name, { name: 'Text' });
113
+ });
114
+ }
@@ -1,6 +1,10 @@
1
1
  import { NodePath, types as t } from '@babel/core';
2
2
 
3
3
  export interface PluginOptions {
4
+ /**
5
+ * Paths to ignore from optimization. Relative to the Babel configuration file.
6
+ */
7
+ ignores?: string[];
4
8
  /**
5
9
  * Whether or not to log optimized files to the console.
6
10
  * @default false
@@ -11,10 +15,15 @@ export interface PluginOptions {
11
15
  */
12
16
  optimizations?: {
13
17
  /**
14
- * Whether or not to optimize the text component.
18
+ * Whether or not to optimize the Text component.
15
19
  * @default true
16
20
  */
17
21
  text?: boolean;
22
+ /**
23
+ * Whether or not to optimize the View component.
24
+ * @default true
25
+ */
26
+ view?: boolean;
18
27
  };
19
28
  }
20
29
 
@@ -1,5 +1,43 @@
1
1
  import { NodePath, types as t } from '@babel/core';
2
2
  import { ensureArray } from './helpers';
3
+ import { HubFile } from '../types';
4
+ import { minimatch } from 'minimatch';
5
+ import path from 'node:path';
6
+ import PluginError from './plugin-error';
7
+
8
+ /**
9
+ * Checks if the file is in the list of ignored files.
10
+ *
11
+ * @param p - The path to the JSXOpeningElement.
12
+ * @param ignores - List of glob paths (absolute or relative to import.meta.dirname).
13
+ * @returns true if the file matches any of the ignore patterns.
14
+ */
15
+ export const isIgnoredFile = (p: NodePath<t.JSXOpeningElement>, ignores: string[]): boolean => {
16
+ const hub = p.hub as unknown;
17
+ const file = typeof hub === 'object' && hub !== null && 'file' in hub ? (hub.file as HubFile) : undefined;
18
+
19
+ if (!file) {
20
+ throw new PluginError('No file found in Babel hub');
21
+ }
22
+
23
+ const fileName = file.opts.filename;
24
+
25
+ // Use the current working directory which typically corresponds to the user's project root.
26
+ const baseDirectory = 'cwd' in file.opts ? (file.opts.cwd as string) : process.cwd();
27
+
28
+ // Iterate through the ignore patterns.
29
+ for (const pattern of ignores) {
30
+ // If the pattern is not absolute, join it with the baseDir
31
+ const absolutePattern = path.isAbsolute(pattern) ? pattern : path.join(baseDirectory, pattern);
32
+
33
+ // Check if the file name matches the glob pattern.
34
+ if (minimatch(fileName, absolutePattern, { dot: true })) {
35
+ return true;
36
+ }
37
+ }
38
+
39
+ return false;
40
+ };
3
41
 
4
42
  /**
5
43
  * Checks if the JSX element should be ignored based on a preceding comment.
@@ -70,3 +108,36 @@ export const shouldIgnoreOptimization = (path: NodePath<t.JSXOpeningElement>): b
70
108
  }
71
109
  return false;
72
110
  };
111
+
112
+ export const hasBlacklistedProperty = (path: NodePath<t.JSXOpeningElement>, blacklist: Set<string>): boolean => {
113
+ return path.node.attributes.some((attribute) => {
114
+ // Check if we can resolve the spread attribute
115
+ if (t.isJSXSpreadAttribute(attribute)) {
116
+ if (t.isIdentifier(attribute.argument)) {
117
+ const binding = path.scope.getBinding(attribute.argument.name);
118
+ let objectExpression: t.ObjectExpression | undefined;
119
+ if (binding) {
120
+ // If the binding node is a VariableDeclarator, use its initializer
121
+ if (t.isVariableDeclarator(binding.path.node)) {
122
+ objectExpression = binding.path.node.init as t.ObjectExpression;
123
+ } else if (t.isObjectExpression(binding.path.node)) {
124
+ objectExpression = binding.path.node;
125
+ }
126
+ }
127
+ if (objectExpression && t.isObjectExpression(objectExpression)) {
128
+ return objectExpression.properties.some((property) => {
129
+ if (t.isObjectProperty(property) && t.isIdentifier(property.key)) {
130
+ return blacklist.has(property.key.name);
131
+ }
132
+ return false;
133
+ });
134
+ }
135
+ }
136
+ // Bail if we can't resolve the spread attribute
137
+ return true;
138
+ }
139
+
140
+ // For other attribute types (e.g. namespaced), assume no blacklisting
141
+ return false;
142
+ });
143
+ };