Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ Metro integration:
- `withUniwindConfig(config, uniwindConfig)` patches Metro graph support for uncached modules.
- Metro adds `css` as source extension and removes it from asset extensions.
- Metro transformer handles the configured CSS entry file specially.
- Metro transformer worker selection is lazy, cached per Expo/non-Expo config type, and follows Expo transformer paths or Expo-specific config markers.
- Native platform CSS transforms into a JS module that calls `Uniwind.__reinit(...)`.
- Web platform CSS transforms into CSS plus web runtime setup.
- Resolver swaps React Native component imports to Uniwind-aware implementations where needed.
Expand Down
28 changes: 27 additions & 1 deletion packages/uniwind/src/bundler/adapters/metro/metro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,29 @@ import { nativeResolver, webResolver } from './resolvers'

const isUniwindRequest = (moduleName: string) => moduleName === 'uniwind' || moduleName.startsWith('uniwind/')

type ExpoTransformerConfig = NonNullable<MetroConfig['transformer']> & {
_expoRelativeProjectRoot?: string
_expoRouterPath?: string
expo_customTransformerPath?: string | false
postcssHash?: string | null
}

const isExpoMetroConfig = (config: MetroConfig) => {
const transformerPath = config.transformerPath
const transformer = config.transformer as ExpoTransformerConfig | undefined
const hasExpoTransformerField = transformer
? '_expoRelativeProjectRoot' in transformer
|| '_expoRouterPath' in transformer
|| 'expo_customTransformerPath' in transformer
|| 'postcssHash' in transformer
: false

return Boolean(
transformerPath?.includes('@expo/metro-config')
|| hasExpoTransformerField,
)
}

Comment on lines +12 to +34

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we could just scan all transformer keys to start with expo or _expo? Having those manually defined doesn't seem easy to maintain, especially because it looks like some expo's internal stuff

export const withUniwindConfig = <T extends MetroConfig>(
config: T,
uniwindConfig: UniwindConfig,
Expand All @@ -24,7 +47,10 @@ export const withUniwindConfig = <T extends MetroConfig>(
transformerPath: require.resolve('./transformer.cjs'),
transformer: {
...config.transformer,
uniwind: bundlerConfig.toMetroConfig(),
uniwind: {
...bundlerConfig.toMetroConfig(),
isExpoProject: isExpoMetroConfig(config),
},
Comment on lines -27 to +53

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extend toMetroConfig method to accept isExpoProject param instead of spreading object there to add new property

},
resolver: {
...config.resolver,
Expand Down
32 changes: 23 additions & 9 deletions packages/uniwind/src/bundler/adapters/metro/transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,31 @@ import path from 'path'

const cssArtifactPath = path.resolve(__dirname, '../../uniwind.css')

let worker: typeof MetroTransformWorker
const workerCache = new Map<boolean, typeof MetroTransformWorker>()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

boolean as a Map key seems random at first glance, please add a small comment there explaining it


try {
try {
const { unstable_transformerPath } = require('@expo/metro-config') as typeof ExpoMetroConfig
const getTransformWorker = (isExpoProject?: boolean): typeof MetroTransformWorker => {
const cacheKey = Boolean(isExpoProject)
const cachedWorker = workerCache.get(cacheKey)

worker = require(unstable_transformerPath)
} catch {
worker = require('@expo/metro-config/build/transform-worker/transform-worker.js')
if (cachedWorker) {
return cachedWorker
}
} catch {
worker = require('metro-transform-worker')

const resolvedWorker: typeof MetroTransformWorker = cacheKey
? (() => {
try {
const { unstable_transformerPath } = require('@expo/metro-config') as typeof ExpoMetroConfig

return require(unstable_transformerPath)
} catch {
return require('@expo/metro-config/build/transform-worker/transform-worker.js')
}
})()
: require('metro-transform-worker')

workerCache.set(cacheKey, resolvedWorker)

return resolvedWorker
}

export const transform = async (
Expand All @@ -32,6 +45,7 @@ export const transform = async (
data: Buffer,
options: JsTransformOptions,
) => {
const worker = getTransformWorker(config.uniwind.isExpoProject)
const isCss = options.type !== 'asset' && path.join(process.cwd(), config.uniwind.cssEntryFile) === path.join(projectRoot, filePath)

if (filePath.endsWith('/components/web/metro-injected.js')) {
Expand Down
1 change: 1 addition & 0 deletions packages/uniwind/src/bundler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ export type Polyfills = {
export type UniwindMetroConfig = UniwindConfig & {
polyfills?: Polyfills
debug?: boolean
isExpoProject?: boolean
isTV?: boolean
}