Skip to content

Metro nativeResolver skips className support for all node_modules when the project path contains a react-native directory #685

Description

@musab-olurode

What happened?

On native platforms, Uniwind stops applying className styles to every component that comes from node_modules (e.g. all of HeroUI Native's Card, Surface, Button, etc. render with no background, padding, radius or fill) whenever the app's absolute path contains a directory named react-native, e.g. ~/Developer/react-native/my-app. The same class strings work on Views in the app's own source.

Root cause: nativeResolver in packages/uniwind/src/bundler/adapters/metro/resolvers.ts decides whether a module is React Native internals with a substring match on the whole absolute path:

const isFromNodeModules = context.originModulePath.includes(`${sep}node_modules${sep}`)
const isFromReactNative = context.originModulePath.includes(`${sep}react-native${sep}`)
    || context.originModulePath.includes(`${sep}@react-native${sep}`)
// ...
|| (isFromReactNative && isFromNodeModules && !isReactNativeAnimated) // Is from react-native but not Animated

For /Users/me/Developer/react-native/my-app/node_modules/heroui-native/lib/module/components/surface/surface.js, both flags are true (because of the parent react-native/ folder), so the react-nativeuniwind/components rewrite is skipped for every dependency. App files don't contain /node_modules/, so they keep working, which makes this look like a component-library bug.

Quick check:

const p = '/Users/me/Developer/react-native/my-app/node_modules/heroui-native/lib/module/components/surface/surface.js'
p.includes('/node_modules/') && p.includes('/react-native/') // true → resolver skips the swap

Suggested fix (only look at the path after the last node_modules segment). We're running this as a local pnpm patch, and it fixes the problem while keeping the React Native internals and the Animated exception behaving as before:

const afterNodeModules = context.originModulePath.split(`${sep}node_modules${sep}`).at(-1) ?? ''
const isFromReactNative = afterNodeModules.startsWith(`react-native${sep}`)
    || afterNodeModules.startsWith(`@react-native${sep}`)

(startsWith('react-native' + sep) also avoids matching sibling packages like react-native-svg.)

Steps to Reproduce

  1. Headless: mkdir -p /tmp/react-native && cd /tmp/react-native && git clone https://github.com/musab-olurode/uniwind-rn-path-repro && cd uniwind-rn-path-repro && npm install && node scripts/check-resolver.js prints BUG. The same steps under /tmp/rn-ok/ print OK.

Or manually:

  1. mkdir -p /tmp/react-native && cd /tmp/react-native. The parent folder name is what matters.
  2. Create an Expo app there and set up Uniwind + HeroUI Native per their quick-starts (withUniwindConfig in metro.config.js, global.css importing tailwindcss, uniwind, heroui-native/styles, app wrapped in GestureHandlerRootView + HeroUINativeProvider).
  3. Render <Card><Card.Body><Card.Title>Hi</Card.Title></Card.Body></Card> and <Button>Press</Button>.
  4. Run on Android. The Card and Button have no background, radius, padding or fill.
  5. Move the same project to a path without a react-native segment (e.g. /tmp/rn/…), clear the Metro cache, and run again. The components are styled correctly.

Snack or Repository Link

https://github.com/musab-olurode/uniwind-rn-path-repro

It includes a headless check, node scripts/check-resolver.js. The script runs expo export --platform android --dev through the real metro.config.js and inspects what heroui-native's surface.js react-native import resolved to. Clone it into a folder named react-native and it prints BUG: heroui-native's react-native import resolved to node_modules/react-native/index.js (not rewritten). Clone it anywhere else and it prints OK: heroui-native's react-native import is rewritten to uniwind/components.

Uniwind version

1.12.0 (the same code is on main)

React Native Version

0.86.3 (Expo SDK 57; heroui-native 1.0.9 and 1.0.10)

Platforms

Android (the code path is the native resolver, so iOS should be affected the same way)

Expo

Yes

Additional information 〰

(Re-filed from #684, which the template bot auto-closed because it was submitted outside the issue form.)

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