Skip to content

Pro 1.7 + public uniwind in a pnpm monorepo: Metro cannot resolve uniwind/index, then uniwind/components recurses #682

Description

@antoinerousseau

What happened?

In a pnpm monorepo, one Expo app depends on Uniwind Pro ("uniwind": "npm:uniwind-pro@^1.7.0") while another app depends on public Uniwind ("uniwind": "^1.12.0"). Both packages publish as "name": "uniwind". With publicHoistPattern: "*", the public copy is hoisted to the workspace root.

That produces two Metro failures:

1. Bundling: missing main / index

Unable to resolve "uniwind" from "src/app/_layout.tsx"
The package `…/node_modules/uniwind/package.json` was successfully found. However, this package itself specifies a
`main` module field that could not be resolved (`…/node_modules/uniwind/index`).

Pro installs as a CLI stub with no exports / main until postinstall. Even after the payload is installed, package.json has an exports map (react-native./src/index.ts) but no top-level main or react-native field. If Metro skips exports (common when the file map cannot see the pnpm store path), it defaults main to index, which does not exist.

Related: #679 (closed as invalid — not filed with the template). Same class of failure as react/metro#1424.

2. Runtime: uniwind/components rewrite hits the public copy

withUniwindConfig rewrites react-nativeuniwind/components using the importing file as originModulePath. Modules outside the Pro app (expo-router, reanimated, …) then resolve uniwind from the hoisted public copy. That copy’s own react-native imports are rewritten again → infinite recursion:

Maximum call stack size exceeded (native stack depth)
"main" has not been registered

This is the same class of bug as #353, fixed for OSS in #570 (pin originModulePath when resolving uniwind / uniwind/*). Pro 1.7 still ships the pre-#570 plugin, so the pin is missing.

require("uniwind/metro") from the Pro app can also walk past the installer stub and load the public plugin.

Steps to Reproduce

  1. pnpm workspace with two Expo apps.
  2. App A: "uniwind": "npm:uniwind-pro@^1.7.0".
  3. App B: "uniwind": "^1.12.0" (public).
  4. publicHoistPattern: "*" (or otherwise hoist public uniwind to the workspace root).
  5. From App A, expo start / expo export --platform ios.
  6. Bundling fails with the uniwind/index error above. If the Pro payload is present but the plugin is the public 1.12 copy (or Pro 1.7 without the origin pin), the device then crashes on launch with a native stack overflow.

Workaround that unblocked us: resolve Pro by filesystem path (<app>/node_modules/uniwind), load withUniwindConfig from that copy’s exports["./metro"], map uniwind / uniwind/* to that copy’s exports targets on disk, and pin originModulePath to App A’s package.json.

Snack or Repository Link (Optional)

Private monorepo — cannot share a public repro. Happy to extract a minimal pnpm workspace if useful.

Uniwind version

uniwind-pro 1.7.0 (app A) + public uniwind 1.12.0 (app B, hoisted)

React Native Version

0.86.3 (Expo SDK 57)

Platforms

iOS

Expo

Yes

Additional information 〰

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions