Summary
When metro.config.js sets babelTransformerPath to require.resolve('react-native-svg-transformer/expo'), babel-preset-expo's expo-inline-or-reference-env-vars plugin no longer inlines EXPO_PUBLIC_* env vars during npx expo export (the command used by OTA tooling like eas update, eoas publish, etc.). The same transformer config works correctly during npx expo export:embed (the native-build path used by eas build).
Result: OTA bundles ship with process.env.EXPO_PUBLIC_* resolving to undefined at runtime, even though .ipa/.aab built from the same source tree have the values correctly inlined.
Environment
react-native-svg-transformer: 1.5.3
expo: ^55.0.0 (babel-preset-expo from this SDK)
react-native: 0.83.6
- Workflow: bare (
ios/ + android/ checked in)
metro.config.js (relevant lines)
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.transformer = {
...config.transformer,
babelTransformerPath: require.resolve('react-native-svg-transformer/expo'),
};
config.resolver = {
...config.resolver,
assetExts: config.resolver.assetExts.filter((ext) => ext !== 'svg'),
sourceExts: [...config.resolver.sourceExts, 'svg'],
};
module.exports = config;
Reproduction
EXPO_PUBLIC_API_BASE_URL=https://api.example.com \
NODE_ENV=production \
npx expo export --platform ios --output-dir dist-test
grep -ao "api\.example\.com" dist-test/_expo/static/js/ios/entry-*.hbc | wc -l
Empirical matrix
Comparing babelTransformerPath values, all other things equal:
| metro.config.js |
expo export URL count |
expo export:embed URL count |
react-native-svg-transformer/expo |
0 ❌ |
1 ✅ |
react-native-svg-transformer (root) |
1 ✅ |
1 ✅ |
| (default — no override) |
1 ✅ |
1 ✅ |
Switching from the /expo subpath to the root entry point of the same package fully fixes env var inlining for expo export, while still bundling SVGs correctly.
Hypothesis
babel-preset-expo's expo-inline-or-reference-env-vars plugin reads api.caller(getIsProd) to decide whether to inline process.env.EXPO_PUBLIC_* as literals (production) or replace them with imports from expo/virtual/env (development). I suspect the /expo subpath's createTransformer wrapper doesn't forward the Metro babel caller context (specifically caller.isDev === false) when expo export invokes it, so the plugin falls through to the development branch — which only resolves env values when a dev server is running. expo export:embed apparently invokes Metro with caller context that survives the wrapper, which is why native builds (and store-shipped binaries) are unaffected.
Impact
Anyone using `react-native-svg-transformer/expo` together with self-hosted OTA setups (`expo-open-ota`, custom Updates protocol implementations) — or EAS Update without realizing — will ship JS bundles where `EXPO_PUBLIC_*` is silently `undefined`. The bundle still launches; downstream `fetch`/network/auth code fails at runtime. Hard to diagnose because native builds work fine.
Workaround
Use `require.resolve('react-native-svg-transformer')` (root) instead of `/expo`.
Summary
When
metro.config.jssetsbabelTransformerPathtorequire.resolve('react-native-svg-transformer/expo'),babel-preset-expo'sexpo-inline-or-reference-env-varsplugin no longer inlinesEXPO_PUBLIC_*env vars duringnpx expo export(the command used by OTA tooling likeeas update,eoas publish, etc.). The same transformer config works correctly duringnpx expo export:embed(the native-build path used byeas build).Result: OTA bundles ship with
process.env.EXPO_PUBLIC_*resolving toundefinedat runtime, even though.ipa/.aabbuilt from the same source tree have the values correctly inlined.Environment
react-native-svg-transformer: 1.5.3expo: ^55.0.0 (babel-preset-expofrom this SDK)react-native: 0.83.6ios/+android/checked in)metro.config.js (relevant lines)
Reproduction
Empirical matrix
Comparing
babelTransformerPathvalues, all other things equal:expo exportURL countexpo export:embedURL countreact-native-svg-transformer/exporeact-native-svg-transformer(root)Switching from the
/exposubpath to the root entry point of the same package fully fixes env var inlining forexpo export, while still bundling SVGs correctly.Hypothesis
babel-preset-expo'sexpo-inline-or-reference-env-varsplugin readsapi.caller(getIsProd)to decide whether to inlineprocess.env.EXPO_PUBLIC_*as literals (production) or replace them with imports fromexpo/virtual/env(development). I suspect the/exposubpath'screateTransformerwrapper doesn't forward the Metro babel caller context (specificallycaller.isDev === false) whenexpo exportinvokes it, so the plugin falls through to the development branch — which only resolves env values when a dev server is running.expo export:embedapparently invokes Metro with caller context that survives the wrapper, which is why native builds (and store-shipped binaries) are unaffected.Impact
Anyone using `react-native-svg-transformer/expo` together with self-hosted OTA setups (`expo-open-ota`, custom Updates protocol implementations) — or EAS Update without realizing — will ship JS bundles where `EXPO_PUBLIC_*` is silently `undefined`. The bundle still launches; downstream `fetch`/network/auth code fails at runtime. Hard to diagnose because native builds work fine.
Workaround
Use `require.resolve('react-native-svg-transformer')` (root) instead of `/expo`.