Current behavior
A Bun workspace Expo Router app crashes during Metro bundling with:
Invariant Violation: Unexpectedly escaped traversal
This reproduces from a public minimal repo generated from create-better-t-stack's uniwind template and then reduced.
Repro repo:
Expected behavior
Metro should bundle successfully with the default withUniwindConfig(...) setup.
Reproduction
git clone https://github.com/AZagatti/uniwind-better-t-stack-repro.git
cd uniwind-better-t-stack-repro
bun install
cd apps/native
bunx expo export --platform android --clear
Result:
- Metro fails with
Invariant Violation: Unexpectedly escaped traversal
The repo is intentionally left in the broken default state.
Exact broken config
Default Metro config in the repro:
const { getDefaultConfig } = require("expo/metro-config");
const { withUniwindConfig } = require("uniwind/metro");
const { wrapWithReanimatedMetroConfig } = require("react-native-reanimated/metro-config");
const config = getDefaultConfig(__dirname);
module.exports = withUniwindConfig(wrapWithReanimatedMetroConfig(config), {
cssEntryFile: "./global.css",
dtsFile: "./uniwind-types.d.ts",
});
Relevant repo files:
apps/native/metro.config.js
apps/native/global.css
apps/native/app/(drawer)/index.tsx
packages/ui/src/index.tsx
Important details
- package manager:
bun@1.3.13
- Expo SDK:
55.0.17
- React Native:
0.83.6
- Expo Router:
55.0.13
- Reanimated:
4.2.1
- Uniwind:
1.6.1
- workspace package imported from
packages/ui
global.css includes @source '../../packages/ui/src'
Isolated trigger
I tried another reduced monorepo first and it did not reproduce. This generated starter was the first setup that reproduced consistently.
I also tested the same reduced repo on a newer Uniwind version:
uniwind@1.6.1 => reproduces Unexpectedly escaped traversal
uniwind@1.6.3 => same repo bundles successfully
So the concrete trigger isolated here is:
uniwind@1.6.1 + Bun workspace + Expo Router + external workspace component source
Workaround
The repro repo includes a separate workaround file:
apps/native/metro.config.workaround.js
Testing it:
cd apps/native
cp metro.config.workaround.js metro.config.js
bunx expo export --platform android --clear
That workaround makes the bundle pass by intercepting uniwind/components and resolving directly to:
node_modules/uniwind/src/components/index.ts
node_modules/uniwind/src/components/native/*.tsx
The exact workaround logic is:
const { getDefaultConfig } = require("expo/metro-config");
const path = require("node:path");
const { withUniwindConfig } = require("uniwind/metro");
const { wrapWithReanimatedMetroConfig } = require("react-native-reanimated/metro-config");
const config = getDefaultConfig(__dirname);
const uniwindRoot = path.resolve(__dirname, "node_modules/uniwind");
config.resolver = {
...config.resolver,
resolveRequest: (context, moduleName, platform) => {
if (platform !== "web" && moduleName === "uniwind/components") {
return {
type: "sourceFile",
filePath: path.resolve(uniwindRoot, "src/components/index.ts"),
};
}
if (platform !== "web" && moduleName.startsWith("uniwind/components/")) {
const componentName = moduleName.slice("uniwind/components/".length);
return {
type: "sourceFile",
filePath: path.resolve(uniwindRoot, `src/components/native/${componentName}.tsx`),
};
}
return context.resolveRequest(context, moduleName, platform);
},
};
module.exports = withUniwindConfig(wrapWithReanimatedMetroConfig(config), {
cssEntryFile: "./global.css",
dtsFile: "./uniwind-types.d.ts",
});
Notes
- The default broken config is left in
apps/native/metro.config.js
- The verified passing workaround is left in
apps/native/metro.config.workaround.js
- This issue replaces the earlier report that was auto-closed because it lacked a minimal reproduction
Current behavior
A Bun workspace Expo Router app crashes during Metro bundling with:
Invariant Violation: Unexpectedly escaped traversalThis reproduces from a public minimal repo generated from
create-better-t-stack'suniwindtemplate and then reduced.Repro repo:
Expected behavior
Metro should bundle successfully with the default
withUniwindConfig(...)setup.Reproduction
Result:
Invariant Violation: Unexpectedly escaped traversalThe repo is intentionally left in the broken default state.
Exact broken config
Default Metro config in the repro:
Relevant repo files:
apps/native/metro.config.jsapps/native/global.cssapps/native/app/(drawer)/index.tsxpackages/ui/src/index.tsxImportant details
bun@1.3.1355.0.170.83.655.0.134.2.11.6.1packages/uiglobal.cssincludes@source '../../packages/ui/src'Isolated trigger
I tried another reduced monorepo first and it did not reproduce. This generated starter was the first setup that reproduced consistently.
I also tested the same reduced repo on a newer Uniwind version:
uniwind@1.6.1=> reproducesUnexpectedly escaped traversaluniwind@1.6.3=> same repo bundles successfullySo the concrete trigger isolated here is:
uniwind@1.6.1+ Bun workspace + Expo Router + external workspace component sourceWorkaround
The repro repo includes a separate workaround file:
apps/native/metro.config.workaround.jsTesting it:
That workaround makes the bundle pass by intercepting
uniwind/componentsand resolving directly to:node_modules/uniwind/src/components/index.tsnode_modules/uniwind/src/components/native/*.tsxThe exact workaround logic is:
Notes
apps/native/metro.config.jsapps/native/metro.config.workaround.js