Metro (withLunarCSS)
How LunarCSS wires into Metro for React Native and Expo.
What withLunarCSS does
withLunarCSS(config) wraps your Metro config and adds three things:
- Reads
lunar.config.ts— viajiti(no CSS parsing, pure TS evaluation) - Emits
.lunarcss/__theme__.js— a frozen JS object of flattened token key→value pairs. Content-hash delta checked — only rewritten when tokens change - Routes
@lunar-kit/css/__theme__— adds aresolveRequestinterceptor that redirects the bare specifier to the generated file
Setup
// metro.config.js (Expo)
const { getDefaultConfig } = require('expo/metro-config')
const { withLunarCSS } = require('@lunar-kit/css/metro')
const config = getDefaultConfig(__dirname)
module.exports = withLunarCSS(config)// metro.config.js (RN bare)
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config')
const { withLunarCSS } = require('@lunar-kit/css/metro')
module.exports = withLunarCSS(mergeConfig(getDefaultConfig(__dirname), {}))Options
withLunarCSS(config, {
configFile: '/abs/path/to/lunar.config.ts', // override discovery
})The __theme__ virtual module
src/index.ts imports THEME_TOKENS from '@lunar-kit/css/__theme__':
import { THEME_TOKENS } from '@lunar-kit/css/__theme__'
import { setTokens } from './runtime/tokens.js'
setTokens(THEME_TOKENS)On native:
- Metro's
resolveRequestintercepts'@lunar-kit/css/__theme__' - Routes to
.lunarcss/__theme__.jsin the project root .lunarcss/__theme__.jscontainsexport const THEME_TOKENS = Object.freeze({ '--color-primary': '#6366f1', ... })
On non-Metro environments (tests, web):
- The bare specifier resolves to
src/__theme__.ts— an emptyObject.freeze({})stub
Metro transformer
withLunarCSS sets babelTransformerPath to @lunar-kit/css/metro/transformer. The transformer:
- Filters to
.tsx/.jsx/.ts/.jsfiles only - Parses with
@babel/parser(TypeScript + JSX) - Finds JSX elements that are RN intrinsic components (View, Text, TextInput, etc.)
- Converts
className="..."→__lcssTw("...") - Injects
import { __lcssTw } from '@lunar-kit/css/runtime'if not already present - Passes modified source to the upstream transformer
The upstream transformer is discovered from the previous babelTransformerPath or Metro's default. It is stored in LUNARCSS_UPSTREAM_TRANSFORMER env and lazy-loaded to avoid circular require.
Why Metro, not Babel?
Reanimated's Babel plugin rewrites JSX at the Babel stage. If LunarCSS also rewrites JSX at the Babel stage, the two plugins conflict and Reanimated breaks. By operating at Metro's transformer layer (after Babel), LunarCSS avoids the entire conflict.
.lunarcss/ directory
LunarCSS writes generated files to .lunarcss/ at the project root. Add it to .gitignore:
# LunarCSS
.lunarcss/lunar-css init adds this automatically.
Watch folders
withLunarCSS adds the directory containing lunar.config.ts to watchFolders. This ensures Metro restarts when the config file changes.