diff --git a/packages/uniwind/src/bundler/adapters/metro/resolvers.ts b/packages/uniwind/src/bundler/adapters/metro/resolvers.ts index 1b18d044..eac99b31 100644 --- a/packages/uniwind/src/bundler/adapters/metro/resolvers.ts +++ b/packages/uniwind/src/bundler/adapters/metro/resolvers.ts @@ -1,4 +1,5 @@ import type { CustomResolutionContext, CustomResolver } from 'metro-resolver' +import { realpathSync } from 'node:fs' import { basename, dirname, sep } from 'node:path' type ResolverConfig = { @@ -10,6 +11,35 @@ type ResolverConfig = { let cachedInternalBasePath: string | null = null +const isInternalOrigin = (originModulePath: string) => { + if (cachedInternalBasePath === null) { + try { + cachedInternalBasePath = dirname(realpathSync(require.resolve('uniwind/package.json'))) + } catch { + cachedInternalBasePath = '' + } + } + + if (cachedInternalBasePath === '') { + return false + } + + const internalPathPrefix = `${cachedInternalBasePath}${sep}` + if (originModulePath.startsWith(internalPathPrefix)) { + return true + } + + if (!originModulePath.includes(`${sep}node_modules${sep}uniwind${sep}`)) { + return false + } + + try { + return realpathSync(originModulePath).startsWith(internalPathPrefix) + } catch { + return false + } +} + const SUPPORTED_COMPONENTS = [ 'ActivityIndicator', 'Button', @@ -44,15 +74,7 @@ export const nativeResolver = ({ }: ResolverConfig) => { const resolution = resolver(context, moduleName, platform) - if (cachedInternalBasePath === null) { - try { - cachedInternalBasePath = dirname(require.resolve('uniwind/package.json')) - } catch { - cachedInternalBasePath = '' - } - } - - const isInternal = cachedInternalBasePath !== '' && context.originModulePath.startsWith(cachedInternalBasePath) + 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}`) @@ -92,16 +114,8 @@ export const webResolver = ({ }: ResolverConfig) => { const resolution = resolver(context, moduleName, platform) - if (cachedInternalBasePath === null) { - try { - cachedInternalBasePath = dirname(require.resolve('uniwind/package.json')) - } catch { - cachedInternalBasePath = '' - } - } - if ( - (cachedInternalBasePath !== '' && context.originModulePath.startsWith(cachedInternalBasePath)) + isInternalOrigin(context.originModulePath) || resolution.type !== 'sourceFile' || !resolution.filePath.includes(`${sep}react-native-web${sep}`) ) { diff --git a/packages/uniwind/tests/native/bundler/resolvers.test.ts b/packages/uniwind/tests/native/bundler/resolvers.test.ts new file mode 100644 index 00000000..4a89c4cb --- /dev/null +++ b/packages/uniwind/tests/native/bundler/resolvers.test.ts @@ -0,0 +1,45 @@ +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' + +test('keeps internal imports internal when Metro reports a symlinked origin', () => { + const root = mkdtempSync(join(tmpdir(), 'uniwind-resolver-')) + + try { + const internalRoot = dirname(realpathSync(require.resolve('uniwind/package.json'))) + const linkedRoot = join(root, 'node_modules', 'uniwind') + mkdirSync(dirname(linkedRoot), { recursive: true }) + symlinkSync(internalRoot, linkedRoot, 'dir') + + const originModulePath = join(linkedRoot, 'src', 'components', 'index.ts') + expect(realpathSync(originModulePath)).toBe(join(internalRoot, 'src', 'components', 'index.ts')) + + 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']) + expect(resolution.filePath).toBe(join(root, 'node_modules', 'react-native', 'index.js')) + } finally { + rmSync(root, { force: true, recursive: true }) + } +})