Skip to content

Bun monorepo + Expo Router: Unexpectedly escaped traversal with uniwind@1.6.1 #513

Description

@AZagatti

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions