babel-preset-expo 54.0.9 → 54.0.10

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.
@@ -5,6 +5,8 @@
5
5
  * This source code is licensed under the MIT license found in the
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
- import type { ConfigAPI, PluginObj } from '@babel/core';
9
- declare function definePlugin({ types: t }: ConfigAPI & typeof import('@babel/core')): PluginObj;
8
+ import type { ConfigAPI, PluginObj, PluginPass } from '@babel/core';
9
+ declare function definePlugin({ types: t, }: ConfigAPI & typeof import('@babel/core')): PluginObj<PluginPass & {
10
+ opts: Record<string, null | boolean | string>;
11
+ }>;
10
12
  export default definePlugin;
@@ -7,30 +7,8 @@
7
7
  * LICENSE file in the root directory of this source tree.
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- /**
11
- * Checks if the given identifier is an ES module import
12
- * @param {babelNode} identifierNodePath The node to check
13
- * @return {boolean} Indicates if the provided node is an import specifier or references one
14
- */
15
- const isImportIdentifier = (identifierNodePath) => {
16
- if (identifierNodePath.container &&
17
- !Array.isArray(identifierNodePath.container) &&
18
- 'type' in identifierNodePath.container) {
19
- return (identifierNodePath.container.type === 'ImportDefaultSpecifier' ||
20
- identifierNodePath.container.type === 'ImportSpecifier');
21
- }
22
- return false;
23
- };
24
- const memberExpressionComparator = (nodePath, value) => nodePath.matchesPattern(value);
25
- const identifierComparator = (nodePath, value) => 'name' in nodePath.node && nodePath.node.name === value;
26
- const unaryExpressionComparator = (nodePath, value) => {
27
- if ('argument' in nodePath.node && nodePath.node.argument && 'name' in nodePath.node?.argument) {
28
- return nodePath.node.argument.name === value;
29
- }
30
- return false;
31
- };
32
10
  const TYPEOF_PREFIX = 'typeof ';
33
- function definePlugin({ types: t }) {
11
+ function definePlugin({ types: t, }) {
34
12
  /**
35
13
  * Replace a node with a given value. If the replacement results in a BinaryExpression, it will be
36
14
  * evaluated. For example, if the result of the replacement is `var x = "production" === "production"`
@@ -38,79 +16,120 @@ function definePlugin({ types: t }) {
38
16
  */
39
17
  function replaceAndEvaluateNode(nodePath, replacement) {
40
18
  nodePath.replaceWith(t.valueToNode(replacement));
41
- if (nodePath.parentPath && nodePath.parentPath.isBinaryExpression()) {
19
+ if (nodePath.parentPath?.isBinaryExpression()) {
42
20
  const result = nodePath.parentPath.evaluate();
43
21
  if (result.confident) {
44
22
  nodePath.parentPath.replaceWith(t.valueToNode(result.value));
45
23
  }
46
24
  }
47
25
  }
48
- const isLeftHandSideOfAssignmentExpression = (node, parent) => t.isAssignmentExpression(parent) && parent.left === node;
49
- const processNode = (replacements, nodePath, comparator) => {
50
- const replacementKey = Object.keys(replacements).find((value) => comparator(nodePath, value));
51
- if (typeof replacementKey === 'string' &&
52
- replacements != null &&
53
- replacementKey in replacements) {
54
- replaceAndEvaluateNode(nodePath, replacements[replacementKey]);
26
+ /** Get the root identifier name from a member expression (e.g., "process" from "process.env.NODE_ENV") */
27
+ function getMemberExpressionRoot(node) {
28
+ let current = node;
29
+ while (t.isMemberExpression(current)) {
30
+ current = current.object;
55
31
  }
56
- };
32
+ return t.isIdentifier(current) ? current.name : null;
33
+ }
57
34
  return {
58
35
  name: 'expo-define-globals',
36
+ pre() {
37
+ // Pre-process replacements once per file
38
+ const identifiers = new Map();
39
+ const memberPatterns = [];
40
+ const typeofValues = new Map();
41
+ const memberRoots = new Set();
42
+ for (const key of Object.keys(this.opts)) {
43
+ const value = this.opts[key];
44
+ if (key.startsWith(TYPEOF_PREFIX)) {
45
+ // "typeof window" -> typeofValues["window"]
46
+ typeofValues.set(key.slice(TYPEOF_PREFIX.length), value);
47
+ }
48
+ else if (key.includes('.')) {
49
+ // "process.env.NODE_ENV" -> memberPatterns, extract "process" as root
50
+ memberPatterns.push([key, value]);
51
+ const root = key.split('.')[0];
52
+ memberRoots.add(root);
53
+ }
54
+ else {
55
+ // "__DEV__" -> identifiers
56
+ identifiers.set(key, value);
57
+ }
58
+ }
59
+ this.processed = {
60
+ identifiers,
61
+ memberPatterns,
62
+ typeofValues,
63
+ memberRoots,
64
+ };
65
+ },
59
66
  visitor: {
60
67
  // process.env.NODE_ENV;
61
68
  MemberExpression(nodePath, state) {
62
- if (
63
69
  // Prevent rewriting if the member expression is on the left-hand side of an assignment
64
- isLeftHandSideOfAssignmentExpression(nodePath.node, nodePath.parent)) {
70
+ if (t.isAssignmentExpression(nodePath.parent) && nodePath.parent.left === nodePath.node) {
65
71
  return;
66
72
  }
67
- const replacements = state.opts;
68
- assertOptions(replacements);
69
- processNode(replacements, nodePath, memberExpressionComparator);
73
+ const { memberPatterns, memberRoots } = state.processed;
74
+ if (memberPatterns.length === 0)
75
+ return;
76
+ // Quick filter: check if root matches any known pattern root
77
+ const root = getMemberExpressionRoot(nodePath.node);
78
+ if (!root || !memberRoots.has(root))
79
+ return;
80
+ // Check against patterns
81
+ for (const [pattern, replacement] of memberPatterns) {
82
+ if (nodePath.matchesPattern(pattern)) {
83
+ replaceAndEvaluateNode(nodePath, replacement);
84
+ return;
85
+ }
86
+ }
70
87
  },
71
88
  // const x = { version: VERSION };
72
89
  ReferencedIdentifier(nodePath, state) {
73
- const binding = nodePath.scope?.getBinding(nodePath.node.name);
74
- if (binding ||
75
- // Don't transform import identifiers. This is meant to mimic webpack's
76
- // DefinePlugin behavior.
77
- isImportIdentifier(nodePath) ||
78
- // Do not transform Object keys / properties unless they are computed like {[key]: value}
79
- (nodePath.key === 'key' &&
80
- nodePath.parent &&
81
- 'computed' in nodePath.parent &&
82
- nodePath.parent.computed === false) ||
83
- (nodePath.key === 'property' &&
84
- nodePath.parent &&
85
- 'computed' in nodePath.parent &&
86
- nodePath.parent.computed === false)) {
90
+ const { identifiers } = state.processed;
91
+ if (identifiers.size === 0)
92
+ return;
93
+ const name = nodePath.node.name;
94
+ // Quick check: is this identifier in our replacements?
95
+ if (!identifiers.has(name))
96
+ return;
97
+ // Check for binding (locally defined variable shadows replacement)
98
+ if (nodePath.scope?.getBinding(name))
99
+ return;
100
+ // Don't transform import identifiers (mimics webpack's DefinePlugin behavior)
101
+ const container = nodePath.container;
102
+ if (container &&
103
+ !Array.isArray(container) &&
104
+ 'type' in container &&
105
+ (container.type === 'ImportDefaultSpecifier' || container.type === 'ImportSpecifier')) {
106
+ return;
107
+ }
108
+ // Do not transform Object keys / properties unless they are computed like {[key]: value}
109
+ if ((nodePath.key === 'key' || nodePath.key === 'property') &&
110
+ nodePath.parent &&
111
+ 'computed' in nodePath.parent &&
112
+ nodePath.parent.computed === false) {
87
113
  return;
88
114
  }
89
- const replacements = state.opts;
90
- assertOptions(replacements);
91
- processNode(replacements, nodePath, identifierComparator);
115
+ replaceAndEvaluateNode(nodePath, identifiers.get(name));
92
116
  },
93
117
  // typeof window
94
118
  UnaryExpression(nodePath, state) {
95
- if (nodePath.node.operator !== 'typeof') {
119
+ if (nodePath.node.operator !== 'typeof')
120
+ return;
121
+ const { typeofValues } = state.processed;
122
+ if (typeofValues.size === 0)
123
+ return;
124
+ const argument = nodePath.node.argument;
125
+ if (!t.isIdentifier(argument))
96
126
  return;
127
+ const replacement = typeofValues.get(argument.name);
128
+ if (replacement !== undefined) {
129
+ replaceAndEvaluateNode(nodePath, replacement);
97
130
  }
98
- const replacements = state.opts;
99
- assertOptions(replacements);
100
- const typeofValues = {};
101
- Object.keys(replacements).forEach((key) => {
102
- if (key.substring(0, TYPEOF_PREFIX.length) === TYPEOF_PREFIX) {
103
- typeofValues[key.substring(TYPEOF_PREFIX.length)] = replacements[key];
104
- }
105
- });
106
- processNode(typeofValues, nodePath, unaryExpressionComparator);
107
131
  },
108
132
  },
109
133
  };
110
134
  }
111
- function assertOptions(opts) {
112
- if (opts == null || typeof opts !== 'object') {
113
- throw new Error('define plugin expects an object as options');
114
- }
115
- }
116
135
  exports.default = definePlugin;
@@ -1,2 +1,6 @@
1
- import type { ConfigAPI, PluginObj } from '@babel/core';
2
- export declare function expoInlineManifestPlugin(api: ConfigAPI & typeof import('@babel/core')): PluginObj;
1
+ import type { ConfigAPI, PluginObj, PluginPass } from '@babel/core';
2
+ interface InlineManifestState extends PluginPass {
3
+ projectRoot: string;
4
+ }
5
+ export declare function expoInlineManifestPlugin(api: ConfigAPI & typeof import('@babel/core')): PluginObj<InlineManifestState>;
6
+ export {};
@@ -4,7 +4,7 @@ exports.expoInlineManifestPlugin = expoInlineManifestPlugin;
4
4
  const common_1 = require("./common");
5
5
  const debug = require('debug')('expo:babel:inline-manifest');
6
6
  // Convert expo value to PWA value
7
- function ensurePWAorientation(orientation) {
7
+ function ensurePWAOrientation(orientation) {
8
8
  if (orientation) {
9
9
  const webOrientation = orientation.toLowerCase();
10
10
  if (webOrientation !== 'default') {
@@ -52,7 +52,7 @@ function applyWebDefaults({ config, appName, webName }) {
52
52
  const startUrl = webManifest.startUrl;
53
53
  const { scope, crossorigin } = webManifest;
54
54
  const barStyle = webManifest.barStyle;
55
- const orientation = ensurePWAorientation(webManifest.orientation || appJSON.orientation);
55
+ const orientation = ensurePWAOrientation(webManifest.orientation || appJSON.orientation);
56
56
  /**
57
57
  * **Splash screen background color**
58
58
  * `https://developers.google.com/web/fundamentals/web-app-manifest/#splash-screen`
@@ -144,34 +144,46 @@ function expoInlineManifestPlugin(api) {
144
144
  const platform = api.caller(common_1.getPlatform);
145
145
  const possibleProjectRoot = api.caller(common_1.getPossibleProjectRoot);
146
146
  const shouldInline = platform === 'web' || isReactServer;
147
+ // Early exit: return a no-op plugin if we're not going to inline anything
148
+ if (!shouldInline) {
149
+ return {
150
+ name: 'expo-inline-manifest-plugin',
151
+ visitor: {},
152
+ };
153
+ }
147
154
  return {
148
155
  name: 'expo-inline-manifest-plugin',
156
+ pre() {
157
+ this.projectRoot = possibleProjectRoot || this.file.opts.root || '';
158
+ },
149
159
  visitor: {
150
160
  MemberExpression(path, state) {
151
- // Web-only/React Server-only feature: the native manifest is provided dynamically by the client.
152
- if (!shouldInline) {
161
+ // We're looking for: process.env.APP_MANIFEST
162
+ // This visitor is called on every MemberExpression, so we need fast checks
163
+ // Quick check: the property we're looking for is 'APP_MANIFEST'
164
+ // The parent must be a MemberExpression with property 'APP_MANIFEST'
165
+ const parent = path.parentPath;
166
+ if (!parent?.isMemberExpression())
153
167
  return;
154
- }
155
- if (!t.isIdentifier(path.node.object, { name: 'process' }) ||
156
- !t.isIdentifier(path.node.property, { name: 'env' })) {
168
+ // Check if parent's property is APP_MANIFEST (most selective check first)
169
+ const parentProp = parent.node.property;
170
+ if (!t.isIdentifier(parentProp) || parentProp.name !== 'APP_MANIFEST')
157
171
  return;
158
- }
159
- const parent = path.parentPath;
160
- if (!t.isMemberExpression(parent.node)) {
172
+ // Now verify this is process.env
173
+ const obj = path.node.object;
174
+ const prop = path.node.property;
175
+ if (!t.isIdentifier(obj) || obj.name !== 'process')
176
+ return;
177
+ if (!t.isIdentifier(prop) || prop.name !== 'env')
178
+ return;
179
+ // Skip if this is an assignment target
180
+ if (parent.parentPath?.isAssignmentExpression())
161
181
  return;
162
- }
163
- const projectRoot = possibleProjectRoot || state.file.opts.root || '';
164
- if (
165
182
  // Surfaces the `app.json` (config) as an environment variable which is then parsed by
166
183
  // `expo-constants` https://docs.expo.dev/versions/latest/sdk/constants/
167
- t.isIdentifier(parent.node.property, {
168
- name: 'APP_MANIFEST',
169
- }) &&
170
- !parent.parentPath.isAssignmentExpression()) {
171
- const manifest = getExpoAppManifest(projectRoot);
172
- if (manifest !== null) {
173
- parent.replaceWith(t.stringLiteral(manifest));
174
- }
184
+ const manifest = getExpoAppManifest(state.projectRoot);
185
+ if (manifest !== null) {
186
+ parent.replaceWith(t.stringLiteral(manifest));
175
187
  }
176
188
  },
177
189
  },
@@ -5,14 +5,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.expoRouterBabelPlugin = expoRouterBabelPlugin;
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
- const resolve_from_1 = __importDefault(require("resolve-from"));
9
8
  const common_1 = require("./common");
10
9
  const debug = require('debug')('expo:babel:router');
11
- function getExpoRouterAppRoot(projectRoot, appFolder) {
12
- // TODO: We should have cache invalidation if the expo-router/entry file location changes.
13
- const routerEntry = (0, resolve_from_1.default)(projectRoot, 'expo-router/entry');
14
- const appRoot = node_path_1.default.relative(node_path_1.default.dirname(routerEntry), appFolder);
15
- debug('routerEntry', routerEntry, appFolder, appRoot);
10
+ /**
11
+ * Compute the relative path from the current file to the app folder.
12
+ *
13
+ * Previously this was computed relative to `expo-router/entry`, but that breaks
14
+ * when packages are hoisted to unexpected locations (e.g., with Bun in monorepos).
15
+ * By using the actual file being transformed, the relative path is always correct
16
+ * regardless of where the package is installed.
17
+ */
18
+ function getExpoRouterAppRoot(currentFile, appFolder) {
19
+ const appRoot = node_path_1.default.relative(node_path_1.default.dirname(currentFile), appFolder);
20
+ debug('getExpoRouterAppRoot', currentFile, appFolder, appRoot);
16
21
  return appRoot;
17
22
  }
18
23
  /**
@@ -28,35 +33,61 @@ function expoRouterBabelPlugin(api) {
28
33
  const possibleProjectRoot = api.caller(common_1.getPossibleProjectRoot);
29
34
  const asyncRoutes = api.caller(common_1.getAsyncRoutes);
30
35
  const routerAbsoluteRoot = api.caller(common_1.getExpoRouterAbsoluteAppRoot);
31
- function isFirstInAssign(path) {
32
- return t.isAssignmentExpression(path.parent) && path.parent.left === path.node;
33
- }
36
+ const importMode = asyncRoutes ? 'lazy' : 'sync';
34
37
  return {
35
38
  name: 'expo-router',
39
+ pre() {
40
+ const state = this;
41
+ state.projectRoot = possibleProjectRoot || this.file.opts.root || '';
42
+ // Check test env at transform time, not module load time
43
+ state.isTestEnv = process.env.NODE_ENV === 'test';
44
+ },
36
45
  visitor: {
37
46
  MemberExpression(path, state) {
38
- const projectRoot = possibleProjectRoot || state.file.opts.root || '';
39
- if (path.get('object').matchesPattern('process.env')) {
40
- const key = path.toComputedKey();
41
- if (t.isStringLiteral(key) && !isFirstInAssign(path)) {
42
- // Used for log box on web.
43
- if (key.value.startsWith('EXPO_PROJECT_ROOT')) {
44
- path.replaceWith(t.stringLiteral(projectRoot));
45
- }
46
- else if (key.value.startsWith('EXPO_ROUTER_IMPORT_MODE')) {
47
- path.replaceWith(t.stringLiteral(asyncRoutes ? 'lazy' : 'sync'));
48
- }
49
- if (
50
- // Skip loading the app root in tests.
51
- // This is handled by the testing-library utils
52
- process.env.NODE_ENV !== 'test') {
53
- if (key.value.startsWith('EXPO_ROUTER_ABS_APP_ROOT')) {
54
- path.replaceWith(t.stringLiteral(routerAbsoluteRoot));
55
- }
56
- else if (key.value.startsWith('EXPO_ROUTER_APP_ROOT')) {
57
- path.replaceWith(t.stringLiteral(getExpoRouterAppRoot(projectRoot, routerAbsoluteRoot)));
58
- }
47
+ // Quick check: skip if not accessing something on an object named 'process'
48
+ const object = path.node.object;
49
+ if (!t.isMemberExpression(object))
50
+ return;
51
+ const objectOfObject = object.object;
52
+ if (!t.isIdentifier(objectOfObject) || objectOfObject.name !== 'process')
53
+ return;
54
+ // Now check if it's process.env
55
+ if (!t.isIdentifier(object.property) || object.property.name !== 'env')
56
+ return;
57
+ // Skip if this is an assignment target
58
+ if (t.isAssignmentExpression(path.parent) && path.parent.left === path.node)
59
+ return;
60
+ // Get the property key
61
+ const key = path.toComputedKey();
62
+ if (!t.isStringLiteral(key))
63
+ return;
64
+ const keyValue = key.value;
65
+ // Check each possible env var
66
+ switch (keyValue) {
67
+ case 'EXPO_PROJECT_ROOT':
68
+ path.replaceWith(t.stringLiteral(state.projectRoot));
69
+ return;
70
+ case 'EXPO_ROUTER_IMPORT_MODE':
71
+ path.replaceWith(t.stringLiteral(importMode));
72
+ return;
73
+ default:
74
+ break;
75
+ }
76
+ // Skip app root transforms in tests (handled by testing-library utils)
77
+ if (state.isTestEnv)
78
+ return;
79
+ switch (keyValue) {
80
+ case 'EXPO_ROUTER_ABS_APP_ROOT':
81
+ path.replaceWith(t.stringLiteral(routerAbsoluteRoot));
82
+ return;
83
+ case 'EXPO_ROUTER_APP_ROOT': {
84
+ // Use the actual file being transformed to compute the relative path.
85
+ // This ensures the path is correct regardless of package hoisting.
86
+ const filename = state.filename || state.file.opts.filename;
87
+ if (!filename) {
88
+ throw new Error('babel-preset-expo: Unable to determine filename for EXPO_ROUTER_APP_ROOT transformation');
59
89
  }
90
+ path.replaceWith(t.stringLiteral(getExpoRouterAppRoot(filename, routerAbsoluteRoot)));
60
91
  }
61
92
  }
62
93
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "babel-preset-expo",
3
- "version": "54.0.9",
3
+ "version": "54.0.10",
4
4
  "description": "The Babel preset for Expo projects",
5
5
  "main": "build/index.js",
6
6
  "files": [
@@ -85,5 +85,5 @@
85
85
  "jest": "^29.2.1",
86
86
  "react-refresh": "^0.14.2"
87
87
  },
88
- "gitHead": "309f8dc09d24203fb80eca0ad534106c5f73c4b7"
88
+ "gitHead": "a68247841f460700a3066c7bc7954a72591b26bc"
89
89
  }