From 62bdd3f53c1a2e2ccdd1650d0e7695c4381ab578 Mon Sep 17 00:00:00 2001 From: Hubert Bieszczad Date: Thu, 24 Sep 2026 12:58:44 +0200 Subject: [PATCH] fix: resolver skips rewrite when parent directory is react-native --- .../src/bundler/adapters/metro/resolvers.ts | 12 ++- .../tests/native/bundler/resolvers.test.ts | 84 ++++++++++++++++++- 2 files changed, 91 insertions(+), 5 deletions(-) diff --git a/packages/uniwind/src/bundler/adapters/metro/resolvers.ts b/packages/uniwind/src/bundler/adapters/metro/resolvers.ts index eac99b31..f6e68532 100644 --- a/packages/uniwind/src/bundler/adapters/metro/resolvers.ts +++ b/packages/uniwind/src/bundler/adapters/metro/resolvers.ts @@ -75,9 +75,10 @@ export const nativeResolver = ({ const resolution = resolver(context, moduleName, platform) const isInternal = isInternalOrigin(context.originModulePath) - 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}`) + const nodeModulesPath = context.originModulePath.split(`${sep}node_modules${sep}`).at(-1) ?? '' + const isFromNodeModules = nodeModulesPath !== context.originModulePath + const isFromReactNative = nodeModulesPath.startsWith(`react-native${sep}`) + || nodeModulesPath.startsWith(`@react-native${sep}`) const isReactNativeAnimated = context.originModulePath.includes(`${sep}Animated${sep}components${sep}`) if ( @@ -113,11 +114,14 @@ export const webResolver = ({ resolver, }: ResolverConfig) => { const resolution = resolver(context, moduleName, platform) + const resolvedNodeModulesPath = resolution.type === 'sourceFile' + ? resolution.filePath.split(`${sep}node_modules${sep}`).at(-1) ?? '' + : '' if ( isInternalOrigin(context.originModulePath) || resolution.type !== 'sourceFile' - || !resolution.filePath.includes(`${sep}react-native-web${sep}`) + || !resolvedNodeModulesPath.startsWith(`react-native-web${sep}`) ) { return resolution } diff --git a/packages/uniwind/tests/native/bundler/resolvers.test.ts b/packages/uniwind/tests/native/bundler/resolvers.test.ts index 22245bc5..566e87a2 100644 --- a/packages/uniwind/tests/native/bundler/resolvers.test.ts +++ b/packages/uniwind/tests/native/bundler/resolvers.test.ts @@ -2,7 +2,89 @@ import type { CustomResolutionContext, CustomResolver } from 'metro-resolver' import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync } from 'node:fs' import { tmpdir } from 'node:os' import { dirname, join } from 'node:path' -import { nativeResolver } from '../../../src/bundler/adapters/metro/resolvers' +import { nativeResolver, webResolver } from '../../../src/bundler/adapters/metro/resolvers' + +test('rewrites dependency imports when the project path contains a react-native directory', () => { + const root = join(tmpdir(), 'react-native', 'my-app') + const originModulePath = join(root, 'node_modules', 'heroui-native', 'lib', 'surface.js') + const calls: Array = [] + const resolver: CustomResolver = (_context, moduleName) => { + calls.push(moduleName) + + return { + type: 'sourceFile', + filePath: join(root, 'node_modules', moduleName, 'index.js'), + } + } + const context = { + originModulePath, + resolveRequest: resolver, + } as CustomResolutionContext + + const resolution = nativeResolver({ + context, + moduleName: 'react-native', + platform: 'ios', + resolver, + }) + + expect(calls).toEqual(['react-native', 'uniwind/components']) + expect(resolution).toMatchObject({ + type: 'sourceFile', + filePath: join(root, 'node_modules', 'uniwind/components', 'index.js'), + }) +}) + +test('does not rewrite unrelated web modules when the project path contains react-native-web', () => { + const root = join(tmpdir(), 'react-native-web', 'my-app') + const calls: Array = [] + const resolver: CustomResolver = (_context, moduleName) => { + calls.push(moduleName) + + return { + type: 'sourceFile', + filePath: join(root, 'node_modules', 'other-package', 'View', 'index.js'), + } + } + const context = { + originModulePath: join(root, 'src', 'App.tsx'), + resolveRequest: resolver, + } as CustomResolutionContext + + const resolution = webResolver({ + context, + moduleName: 'other-package/View', + platform: 'web', + resolver, + }) + + expect(calls).toEqual(['other-package/View']) + expect(resolution).toMatchObject({ + type: 'sourceFile', + filePath: join(root, 'node_modules', 'other-package', 'View', 'index.js'), + }) +}) + +test('rewrites React Native Web component files', () => { + const root = join(tmpdir(), 'my-app') + const calls: Array = [] + const resolver: CustomResolver = (_context, moduleName) => { + calls.push(moduleName) + + return { + type: 'sourceFile', + filePath: join(root, 'node_modules', 'react-native-web', 'dist', 'exports', 'View', 'index.js'), + } + } + const context = { + originModulePath: join(root, 'src', 'App.tsx'), + resolveRequest: resolver, + } as CustomResolutionContext + + webResolver({ context, moduleName: 'react-native-web', platform: 'web', resolver }) + + expect(calls).toEqual(['react-native-web', 'uniwind/components/View']) +}) test('keeps internal imports internal when Metro reports a symlinked origin', () => { const root = mkdtempSync(join(tmpdir(), 'uniwind-resolver-'))