babel-preset-expo 54.1.0-canary-20260105-6b962e6 → 54.1.0-canary-20260114-d8e19f5
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/build/common.d.ts +1 -0
- package/build/common.js +5 -0
- package/build/define-plugin.d.ts +4 -2
- package/build/define-plugin.js +88 -69
- package/build/expo-inline-manifest-plugin.d.ts +6 -2
- package/build/expo-inline-manifest-plugin.js +33 -21
- package/build/expo-router-plugin.js +53 -26
- package/build/index.js +5 -2
- package/build/server-data-loaders-plugin.js +73 -14
- package/package.json +3 -3
package/build/common.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export declare function getIsNodeModule(caller?: any): boolean;
|
|
|
13
13
|
export declare function getBaseUrl(caller?: any): string;
|
|
14
14
|
export declare function getReactCompiler(caller?: any): boolean;
|
|
15
15
|
export declare function getIsServer(caller?: any): boolean;
|
|
16
|
+
export declare function getIsLoaderBundle(caller?: any): boolean;
|
|
16
17
|
export declare function getMetroSourceType(caller?: any): "script" | "module" | "asset" | undefined;
|
|
17
18
|
export declare function getBabelRuntimeVersion(caller?: any): string;
|
|
18
19
|
export declare function getExpoRouterAbsoluteAppRoot(caller?: any): string;
|
package/build/common.js
CHANGED
|
@@ -15,6 +15,7 @@ exports.getIsNodeModule = getIsNodeModule;
|
|
|
15
15
|
exports.getBaseUrl = getBaseUrl;
|
|
16
16
|
exports.getReactCompiler = getReactCompiler;
|
|
17
17
|
exports.getIsServer = getIsServer;
|
|
18
|
+
exports.getIsLoaderBundle = getIsLoaderBundle;
|
|
18
19
|
exports.getMetroSourceType = getMetroSourceType;
|
|
19
20
|
exports.getBabelRuntimeVersion = getBabelRuntimeVersion;
|
|
20
21
|
exports.getExpoRouterAbsoluteAppRoot = getExpoRouterAbsoluteAppRoot;
|
|
@@ -118,6 +119,10 @@ function getIsServer(caller) {
|
|
|
118
119
|
assertExpoBabelCaller(caller);
|
|
119
120
|
return caller?.isServer ?? false;
|
|
120
121
|
}
|
|
122
|
+
function getIsLoaderBundle(caller) {
|
|
123
|
+
assertExpoBabelCaller(caller);
|
|
124
|
+
return caller?.isLoaderBundle ?? false;
|
|
125
|
+
}
|
|
121
126
|
function getMetroSourceType(caller) {
|
|
122
127
|
assertExpoBabelCaller(caller);
|
|
123
128
|
return caller?.metroSourceType;
|
package/build/define-plugin.d.ts
CHANGED
|
@@ -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;
|
package/build/define-plugin.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
70
|
+
if (t.isAssignmentExpression(nodePath.parent) && nodePath.parent.left === nodePath.node) {
|
|
65
71
|
return;
|
|
66
72
|
}
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
//
|
|
152
|
-
|
|
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
|
-
|
|
156
|
-
|
|
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
|
|
160
|
-
|
|
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
|
-
|
|
168
|
-
|
|
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
|
},
|
|
@@ -8,11 +8,19 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
8
8
|
const resolve_from_1 = __importDefault(require("resolve-from"));
|
|
9
9
|
const common_1 = require("./common");
|
|
10
10
|
const debug = require('debug')('expo:babel:router');
|
|
11
|
+
// Cache for getExpoRouterAppRoot results (projectRoot -> appRoot)
|
|
12
|
+
const appRootCache = new Map();
|
|
11
13
|
function getExpoRouterAppRoot(projectRoot, appFolder) {
|
|
14
|
+
const cacheKey = `${projectRoot}:${appFolder}`;
|
|
15
|
+
const cached = appRootCache.get(cacheKey);
|
|
16
|
+
if (cached !== undefined) {
|
|
17
|
+
return cached;
|
|
18
|
+
}
|
|
12
19
|
// TODO: We should have cache invalidation if the expo-router/entry file location changes.
|
|
13
20
|
const routerEntry = (0, resolve_from_1.default)(projectRoot, 'expo-router/entry');
|
|
14
21
|
const appRoot = node_path_1.default.relative(node_path_1.default.dirname(routerEntry), appFolder);
|
|
15
22
|
debug('routerEntry', routerEntry, appFolder, appRoot);
|
|
23
|
+
appRootCache.set(cacheKey, appRoot);
|
|
16
24
|
return appRoot;
|
|
17
25
|
}
|
|
18
26
|
/**
|
|
@@ -28,36 +36,55 @@ function expoRouterBabelPlugin(api) {
|
|
|
28
36
|
const possibleProjectRoot = api.caller(common_1.getPossibleProjectRoot);
|
|
29
37
|
const asyncRoutes = api.caller(common_1.getAsyncRoutes);
|
|
30
38
|
const routerAbsoluteRoot = api.caller(common_1.getExpoRouterAbsoluteAppRoot);
|
|
31
|
-
|
|
32
|
-
return t.isAssignmentExpression(path.parent) && path.parent.left === path.node;
|
|
33
|
-
}
|
|
39
|
+
const importMode = asyncRoutes ? 'lazy' : 'sync';
|
|
34
40
|
return {
|
|
35
41
|
name: 'expo-router',
|
|
42
|
+
pre() {
|
|
43
|
+
const state = this;
|
|
44
|
+
state.projectRoot = possibleProjectRoot || this.file.opts.root || '';
|
|
45
|
+
// Check test env at transform time, not module load time
|
|
46
|
+
state.isTestEnv = process.env.NODE_ENV === 'test';
|
|
47
|
+
},
|
|
36
48
|
visitor: {
|
|
37
49
|
MemberExpression(path, state) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
50
|
+
// Quick check: skip if not accessing something on an object named 'process'
|
|
51
|
+
const object = path.node.object;
|
|
52
|
+
if (!t.isMemberExpression(object))
|
|
53
|
+
return;
|
|
54
|
+
const objectOfObject = object.object;
|
|
55
|
+
if (!t.isIdentifier(objectOfObject) || objectOfObject.name !== 'process')
|
|
56
|
+
return;
|
|
57
|
+
// Now check if it's process.env
|
|
58
|
+
if (!t.isIdentifier(object.property) || object.property.name !== 'env')
|
|
59
|
+
return;
|
|
60
|
+
// Skip if this is an assignment target
|
|
61
|
+
if (t.isAssignmentExpression(path.parent) && path.parent.left === path.node)
|
|
62
|
+
return;
|
|
63
|
+
// Get the property key
|
|
64
|
+
const key = path.toComputedKey();
|
|
65
|
+
if (!t.isStringLiteral(key))
|
|
66
|
+
return;
|
|
67
|
+
const keyValue = key.value;
|
|
68
|
+
// Check each possible env var
|
|
69
|
+
switch (keyValue) {
|
|
70
|
+
case 'EXPO_PROJECT_ROOT':
|
|
71
|
+
path.replaceWith(t.stringLiteral(state.projectRoot));
|
|
72
|
+
return;
|
|
73
|
+
case 'EXPO_ROUTER_IMPORT_MODE':
|
|
74
|
+
path.replaceWith(t.stringLiteral(importMode));
|
|
75
|
+
return;
|
|
76
|
+
default:
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
// Skip app root transforms in tests (handled by testing-library utils)
|
|
80
|
+
if (state.isTestEnv)
|
|
81
|
+
return;
|
|
82
|
+
switch (keyValue) {
|
|
83
|
+
case 'EXPO_ROUTER_ABS_APP_ROOT':
|
|
84
|
+
path.replaceWith(t.stringLiteral(routerAbsoluteRoot));
|
|
85
|
+
return;
|
|
86
|
+
case 'EXPO_ROUTER_APP_ROOT':
|
|
87
|
+
path.replaceWith(t.stringLiteral(getExpoRouterAppRoot(state.projectRoot, routerAbsoluteRoot)));
|
|
61
88
|
}
|
|
62
89
|
},
|
|
63
90
|
},
|
package/build/index.js
CHANGED
|
@@ -163,8 +163,11 @@ function babelPresetExpo(api, options = {}) {
|
|
|
163
163
|
}
|
|
164
164
|
if ((0, common_1.hasModule)('expo-router')) {
|
|
165
165
|
extraPlugins.push(expo_router_plugin_1.expoRouterBabelPlugin);
|
|
166
|
-
//
|
|
167
|
-
|
|
166
|
+
// Process `loader()` functions for client, loader and server bundles (excluding RSC)
|
|
167
|
+
// - Client bundles: Remove loader exports, they run on server only
|
|
168
|
+
// - Server bundles: Keep loader exports (needed for SSG)
|
|
169
|
+
// - Loader-only bundles: Keep only loader exports, remove everything else
|
|
170
|
+
if (!isReactServer) {
|
|
168
171
|
extraPlugins.push(server_data_loaders_plugin_1.serverDataLoadersPlugin);
|
|
169
172
|
}
|
|
170
173
|
}
|
|
@@ -8,11 +8,27 @@ function serverDataLoadersPlugin(api) {
|
|
|
8
8
|
const { types: t } = api;
|
|
9
9
|
const routerAbsoluteRoot = api.caller(common_1.getExpoRouterAbsoluteAppRoot);
|
|
10
10
|
const isServer = api.caller(common_1.getIsServer);
|
|
11
|
+
const isLoaderBundle = api.caller(common_1.getIsLoaderBundle);
|
|
11
12
|
return {
|
|
12
13
|
name: 'expo-server-data-loaders',
|
|
13
14
|
visitor: {
|
|
15
|
+
ExportDefaultDeclaration(path, state) {
|
|
16
|
+
// Early exit if file is not within the `app/` directory
|
|
17
|
+
if (!isInAppDirectory(state.file.opts.filename ?? '', routerAbsoluteRoot)) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
// Only remove default exports in loader-only bundles
|
|
21
|
+
if (!isLoaderBundle) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
debug('Loader bundle: removing default export from', state.file.opts.filename);
|
|
25
|
+
markForConstantFolding(state);
|
|
26
|
+
path.remove();
|
|
27
|
+
},
|
|
14
28
|
ExportNamedDeclaration(path, state) {
|
|
15
|
-
|
|
29
|
+
// NOTE(@hassankhan): Server bundles currently preserve loaders for SSG, a followup is
|
|
30
|
+
// required to remove them.
|
|
31
|
+
if (isServer && !isLoaderBundle) {
|
|
16
32
|
return;
|
|
17
33
|
}
|
|
18
34
|
// Early exit if file is not within the `app/` directory
|
|
@@ -20,38 +36,73 @@ function serverDataLoadersPlugin(api) {
|
|
|
20
36
|
debug('Skipping file outside app directory:', state.file.opts.filename);
|
|
21
37
|
return;
|
|
22
38
|
}
|
|
23
|
-
debug(
|
|
39
|
+
debug(`Processing ${isLoaderBundle ? 'loader' : 'client'} bundle:`, state.file.opts.filename);
|
|
24
40
|
const { declaration, specifiers } = path.node;
|
|
25
41
|
// Is this a type export like `export type Foo`?
|
|
26
42
|
const isTypeExport = path.node.exportKind === 'type';
|
|
27
|
-
// Does this export with `export { loader }`?
|
|
28
43
|
// NOTE(@hassankhan): We should add proper handling for specifiers too
|
|
29
44
|
const hasSpecifiers = specifiers.length > 0;
|
|
30
45
|
if (isTypeExport || hasSpecifiers) {
|
|
31
46
|
return;
|
|
32
47
|
}
|
|
33
|
-
// Handles `export function loader() {}`
|
|
48
|
+
// Handles `export function loader() { ... }`
|
|
34
49
|
if (t.isFunctionDeclaration(declaration)) {
|
|
35
50
|
const name = declaration.id?.name;
|
|
36
51
|
if (name && isLoaderIdentifier(name)) {
|
|
37
|
-
|
|
52
|
+
// Mark the file as having a loader (for all bundle types)
|
|
53
|
+
markWithLoaderReference(state);
|
|
54
|
+
if (!isLoaderBundle) {
|
|
55
|
+
// Client bundles: remove loader
|
|
56
|
+
debug('Found and removed loader function declaration');
|
|
57
|
+
markForConstantFolding(state);
|
|
58
|
+
path.remove();
|
|
59
|
+
}
|
|
60
|
+
// Loader bundle: keep the loader
|
|
61
|
+
}
|
|
62
|
+
else if (name && isLoaderBundle) {
|
|
63
|
+
// Loader bundle: remove non-loader function declarations
|
|
64
|
+
debug('Loader bundle: removing non-loader function declaration:', name);
|
|
38
65
|
markForConstantFolding(state);
|
|
39
66
|
path.remove();
|
|
40
67
|
}
|
|
41
68
|
}
|
|
42
69
|
// Handles `export const loader = ...`
|
|
43
70
|
if (t.isVariableDeclaration(declaration)) {
|
|
44
|
-
let
|
|
45
|
-
|
|
71
|
+
let hasModified = false;
|
|
72
|
+
// Check if any declaration is a loader
|
|
73
|
+
const hasLoaderDeclaration = declaration.declarations.some((declarator) => {
|
|
46
74
|
const name = t.isIdentifier(declarator.id) ? declarator.id.name : null;
|
|
47
|
-
|
|
48
|
-
debug('Found and removed loader variable declaration');
|
|
49
|
-
hasRemovedLoader = true;
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
return true;
|
|
75
|
+
return name && isLoaderIdentifier(name);
|
|
53
76
|
});
|
|
54
|
-
|
|
77
|
+
// Mark the file as having a loader (for all bundle types)
|
|
78
|
+
if (hasLoaderDeclaration) {
|
|
79
|
+
markWithLoaderReference(state);
|
|
80
|
+
}
|
|
81
|
+
if (isLoaderBundle) {
|
|
82
|
+
// Loader bundle: keep only loader declarations, remove others
|
|
83
|
+
declaration.declarations = declaration.declarations.filter((declarator) => {
|
|
84
|
+
const name = t.isIdentifier(declarator.id) ? declarator.id.name : null;
|
|
85
|
+
if (name && !isLoaderIdentifier(name)) {
|
|
86
|
+
debug('Loader bundle: removing non-loader variable declaration:', name);
|
|
87
|
+
hasModified = true;
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
return true;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// Client bundles: remove loader declarations
|
|
95
|
+
declaration.declarations = declaration.declarations.filter((declarator) => {
|
|
96
|
+
const name = t.isIdentifier(declarator.id) ? declarator.id.name : null;
|
|
97
|
+
if (name && isLoaderIdentifier(name)) {
|
|
98
|
+
debug('Found and removed loader variable declaration');
|
|
99
|
+
hasModified = true;
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
return true;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (hasModified) {
|
|
55
106
|
markForConstantFolding(state);
|
|
56
107
|
// If all declarations were removed, remove the export
|
|
57
108
|
if (declaration.declarations.length === 0) {
|
|
@@ -92,3 +143,11 @@ function markForConstantFolding(state) {
|
|
|
92
143
|
assertExpoMetadata(state.file.metadata);
|
|
93
144
|
state.file.metadata.performConstantFolding = true;
|
|
94
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Sets the `loaderReference` metadata to the file path. This is used to collect all modules with
|
|
148
|
+
* loaders in the Metro serializer.
|
|
149
|
+
*/
|
|
150
|
+
function markWithLoaderReference(state) {
|
|
151
|
+
assertExpoMetadata(state.file.metadata);
|
|
152
|
+
state.file.metadata.loaderReference = state.file.opts.filename ?? undefined;
|
|
153
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "babel-preset-expo",
|
|
3
|
-
"version": "54.1.0-canary-
|
|
3
|
+
"version": "54.1.0-canary-20260114-d8e19f5",
|
|
4
4
|
"description": "The Babel preset for Expo projects",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"files": [
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"@babel/runtime": "^7.20.0",
|
|
45
45
|
"react-refresh": ">=0.14.0 <1.0.0",
|
|
46
|
-
"expo": "55.0.0-canary-
|
|
46
|
+
"expo": "55.0.0-canary-20260114-d8e19f5"
|
|
47
47
|
},
|
|
48
48
|
"peerDependenciesMeta": {
|
|
49
49
|
"@babel/runtime": {
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"@babel/core": "^7.26.0",
|
|
82
82
|
"@types/babel__core": "^7.20.5",
|
|
83
83
|
"@expo/metro": "~54.2.0",
|
|
84
|
-
"expo-module-scripts": "5.1.0-canary-
|
|
84
|
+
"expo-module-scripts": "5.1.0-canary-20260114-d8e19f5",
|
|
85
85
|
"jest": "^29.2.1",
|
|
86
86
|
"react-refresh": "^0.14.2"
|
|
87
87
|
}
|