Skip to content
Merged
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
201 changes: 201 additions & 0 deletions context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# Uniwind Context

This document captures working context for `packages/uniwind`, the published `uniwind` package in this monorepo. Keep it current when architecture, public APIs, supported platforms, or build/runtime contracts change.

## Product

Uniwind is Tailwind CSS bindings for React Native and React Native Web. It lets users write `className` props on React Native components while doing as much style work as possible at build time.

Primary promise: fast Tailwind styling for React Native with web parity where practical.

Positioning: Uniwind optimizes for build-time Tailwind-to-React-Native artifacts, minimal runtime work, and React Native Web parity. It is not primarily a full design-system/runtime styling framework, a NativeWind compatibility layer, or a web-first CSS bridge.

Core user-facing features:

- Out-of-the-box `className` bindings for React Native components.
- Tailwind v4 CSS compilation into native runtime style artifacts or web CSS.
- Light, dark, and extra named themes.
- `active`, `focus`, `disabled`, RTL, orientation, responsive, data attribute, and platform-aware variants.
- CSS custom property reads and updates from React Native code.
- Scoped themes through `ScopedTheme`.
- Metro and Vite integration.

Supported platforms: iOS, Android, web, Android TV, and Apple TV. Other React Native targets are out of scope until tests and docs explicitly cover them.

## Package Boundaries

Important paths:

- `packages/uniwind/src/index.ts`: public package entrypoint.
- `packages/uniwind/src/components`: React Native component wrappers and web exports.
- `packages/uniwind/src/core`: runtime config, listeners, native style store, and web style extraction.
- `packages/uniwind/src/hooks`: public hooks.
- `packages/uniwind/src/hoc`: `withUniwind` for custom components.
- `packages/uniwind/src/bundler`: Metro/Vite adapters, Tailwind compilation, CSS processing, artifact generation.
- `packages/uniwind/tests`: native, web, type, and e2e tests.
- `packages/uniwind/uniwind.css`: package-level CSS artifact referenced by package `style` export.

Public exports from `src/index.ts`:

- `Uniwind` runtime/config object.
- `ScopedTheme` component.
- `withUniwind` HOC and related types.
- `useCSSVariable`, `useResolveClassNames`, `useUniwind` hooks.
- `ThemeName` and `UniwindConfig` types.

Package subpath exports:

- `uniwind`: main runtime API.
- `uniwind/components`: React Native component replacements.
- `uniwind/components/*`: individual component replacements.
- `uniwind/metro`: Metro adapter.
- `uniwind/vite`: Vite plugin.
- `uniwind/types`: generated/user-facing type support.

Stability policy: public package and subpath exports are semver-stable. Generated artifact internals are implementation details unless explicitly documented, with two notable user-facing surfaces: generated theme typings and the package `style` export (`uniwind.css`).

Dependency policy: peer dependency floors are support contracts. Raising support floors for Tailwind, React, or React Native requires semver-major unless an upstream ecosystem break makes that impossible to honor.

## Runtime Model

Native runtime:

- Build output injects a generated stylesheet callback into `Uniwind.__reinit(...)`.
- `UniwindStore` holds generated style records, theme variables, scoped variables, runtime state, and per-theme caches.
- `UniwindStore.getStyles(className, props, state, context)` resolves classes into React Native style objects.
- Cache keys include class names, component state, and whether theme is scoped.
- Resolved styles subscribe to only dependencies they use, then invalidate cache entries on change.
- Runtime dependencies are represented by `StyleDependency`: theme, dimensions, orientation, insets, font scale, RTL, adaptive themes, and variables.
- Native style resolution filters rules by screen width, orientation, theme, RTL, active/focus/disabled state, and `data-*` props.
- Native post-processing adapts CSS concepts to RN shapes, including line-height multipliers, shadows, transforms, gradients, visibility, borders, outlines, and font variants.

Web runtime:

- Web keeps styles in CSS and passes `{ $$css: true, tailwind: className }` through RNW style arrays.
- `getWebStyles` uses a hidden DOM element to compute style values when a JS value is needed, such as color extraction or `useResolveClassNames`.
- `CSSListener` tracks active CSS rules and media queries, then notifies subscribers when class-dependent media rules change.
- `ScopedTheme` renders a `div` with the theme class and `display: contents` on web.
- Dynamic CSS variable updates are written into a generated `#uniwind-dynamic-styles` style element.

Shared runtime:

- `Uniwind.setTheme(theme | 'system')` switches explicit themes or returns to system-adaptive light/dark.
- `Uniwind.currentTheme` and `Uniwind.hasAdaptiveThemes` back `useUniwind`.
- `Uniwind.updateCSSVariables(theme, variables)` updates theme variables and notifies variable subscribers.
- `Uniwind.updateInsets(insets)` is native-only behavior and updates safe-area-style runtime values.
- `ScopedTheme` sets `UniwindContext.scopedTheme`; scoped subtree ignores global theme changes for style resolution.

## Build And Bundler Model

Configuration shape:

- `cssEntryFile`: required CSS entry path, resolved from `process.cwd()`.
- `extraThemes`: optional named themes added to default `light` and `dark`.
- `dtsFile`: optional generated declaration file path, default `uniwind-types.d.ts`.
- Metro-only `polyfills.rem`: custom rem base, default `16`.
- Metro-only `debug` and `isTV` flags exist in types.

Compilation flow:

- `compileTailwind` reads `cssEntryFile`, runs Tailwind v4 compile, scans files under the CSS entry directory, and builds final CSS.
- `compileCSS` routes to web or native by platform.
- `compileWebCSS` runs Lightning CSS with `UniwindCSSVisitor` and returns CSS.
- `compileNativeCSS` runs `ProcessorBuilder`, serializes variables, scoped variables, and native stylesheet metadata into JS source.
- `UniwindBundlerConfig.generateArtifacts` writes CSS artifacts and generated theme typings.
- Internal package aliases such as `@/*` are only safe inside `packages/uniwind/src/bundler`. Bundler files are built and transformed to JS, but runtime/component/hook/HOC files are published directly as `.ts`/`.tsx` React Native entrypoints, so aliases in those files are not rewritten.

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.
- 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.

Vite integration:

- `uniwind(config)` returns a pre-Vite plugin.
- Vite aliases `react-native` to Uniwind web components, except imports from Uniwind internals resolve back to `react-native-web`.
- Vite replaces RNW `createOrderedCSSStyleSheet` with Uniwind's ordered stylesheet implementation.
- Vite uses Lightning CSS with `UniwindCSSVisitor`.
- Vite generates artifacts on `buildStart` and `generateBundle`.

## CSS Processing

Native processing converts Tailwind-generated CSS into metadata-rich style records.

Important concepts:

- A `Style` record stores entries, breakpoint bounds, orientation, theme, RTL, native flag, dependencies, source index, class name, important properties, selector complexity, pseudo-states, and data attributes.
- CSS variables live in `vars`; theme and platform-scoped variables live in `scopedVars` with internal prefixes.
- The processor treats declarations under `:root` or outside class rules as variables.
- Theme variants are recognized from known theme names.
- Data attribute variants support boolean `data-x` and exact `data-x="value"` matching against component props.
- Media queries drive dimensions, orientation, color scheme, platform, and native/web-specific metadata.
- Important declarations are preserved as `importantProperties`.
- Unsupported CSS features may be silently ignored on native. Prefer documenting support coverage over adding noisy runtime failures for every unsupported CSS construct.

Web visitor behavior:

- Theme root rules in Tailwind theme layer become theme class rules.
- Theme-prefixed class rules are scoped with CSS `@scope` to selected theme classes and excluded from other themes.
- Visitor state is cleaned between transforms.

## Components And HOC

Native components:

- Native wrappers import the underlying `react-native` component.
- `useStyle(className, props, state)` resolves `className` through `UniwindStore` and subscribes to style dependencies.
- Most components combine generated style before user style: `[generatedStyle, props.style]`, preserving user overrides.
- Stateful components such as `Pressable` pass `pressed`, `focused`, and `disabled` state into style resolution.
- Accent-capable components use `accentColor` extraction helpers where needed.

Web components:

- Web wrappers import from `react-native` as resolved by bundler aliases.
- Web wrappers map `className` to RNW CSS style markers through `toRNWClassName`.
- Web wrappers pass generated `dataSet` so data attribute variants can match.

`withUniwind`:

- Auto mode maps `className`-style props to matching RN style props and color class props to color props.
- Manual mode maps custom class props to custom target props and can extract a single style property.
- Native mode resolves to concrete RN style objects.
- Web mode usually emits RNW CSS style markers and uses computed style only for extracted values.

## Testing And Quality Gates

Package scripts:

- `bun run build`: unbuild package outputs.
- `bun run check:typescript`: TypeScript no-emit check.
- `bun run lint`: oxlint on `src`.
- `bun run circular:check`: dpdm circular dependency check.
- `bun run test:native`: Jest native tests.
- `bun run test:web`: Jest web tests.
- `bun run test:types`: type-level tests.
- `bun run test:e2e`: Playwright e2e tests.

Root scripts use Turbo for monorepo-wide build, typecheck, lint, test, format, and circular checks.

Testing layout:

- `tests/native`: component behavior and native style parsing.
- `tests/web`: web config, components, and HOC behavior.
- `tests/type-test`: public type expectations.
- `tests/e2e`: browser checks for web style extraction and generated artifacts.

Source-of-truth policy: repository code and tests win for implementation details. External docs at `docs.uniwind.dev` describe intended public behavior and should be updated when public behavior changes.

## Engineering Constraints

- Runtime performance matters. Prefer build-time CSS processing and narrow runtime invalidation over broad recomputation.
- Preserve user style precedence when adding component wrappers.
- Keep native and web behavior aligned unless platform constraints require divergence.
- Any new runtime dependency should map to `StyleDependency` and invalidate only affected subscribers.
- Theme-aware changes must account for global theme, adaptive system theme, and `ScopedTheme`.
- CSS variables must keep lazy getter semantics on native because values may depend on current runtime state.
- Avoid introducing compatibility paths without known consumers or persisted behavior.
- Add tests for native, web, and types when changing public API or cross-platform behavior.
3 changes: 2 additions & 1 deletion packages/uniwind/.oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"rules": {
"unicorn/no-empty-file": "off",
"typescript/strict-boolean-expressions": "off",
"typescript/no-implied-eval": "off"
"typescript/no-implied-eval": "off",
"typescript/consistent-type-imports": "error"
},
"ignorePatterns": [
"build.config.ts",
Expand Down
40 changes: 39 additions & 1 deletion packages/uniwind/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,28 @@
## What's Changed in 1.6.5
## What's Changed in 1.6.7-beta.0



### 📦 Other
* Merge branch 'main' into major-refactor


**Full Changelog**: https://github.com/uni-stack/uniwind/compare/v1.6.6-beta.0...v1.6.7-beta.0## What's Changed in 1.6.6-beta.0



### 🧪 Testing
* fix meta test after merge


### 🏠 Chores
* lint warning


### 📦 Other
* Merge branch 'main' into major-refactor


**Full Changelog**: https://github.com/uni-stack/uniwind/compare/v1.6.5...v1.6.6-beta.0## What's Changed in 1.6.5



Expand Down Expand Up @@ -26,6 +50,20 @@
* fix: remove deprecated push notification ios export by @Brentlok in [#516](https://github.com/uni-stack/uniwind/pull/516)


### 🔨 Refactoring
* refactored bundler code
* add import aliases and move vite to bundler/adapters


### 🏠 Chores
* minor corrections
* consistent type imports
* fs promise to read file for tailwind compilation
* add new line before reinit in vite adapter
* unused aliases
* remove unused


**Full Changelog**: https://github.com/uni-stack/uniwind/compare/v1.6.3...v1.6.4## What's Changed in 1.6.3


Expand Down
21 changes: 15 additions & 6 deletions packages/uniwind/build.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { fileURLToPath } from 'node:url'
import { BuildConfig, defineBuildConfig } from 'unbuild'

type Config = {
Expand All @@ -7,6 +8,8 @@ type Config = {
declaration?: boolean
}

const srcPath = (path: string) => fileURLToPath(new URL(`./src/${path}`, import.meta.url))

const getConfig = (config: Config) =>
[
{
Expand Down Expand Up @@ -40,30 +43,30 @@ export default defineBuildConfig({
entries: [
{
builder: 'rollup',
input: './src/metro',
input: './src/bundler/adapters/metro',
name: 'metro/index',
},
{
builder: 'rollup',
input: './src/metro/metro-transformer.ts',
name: 'metro/metro-transformer',
input: './src/bundler/adapters/metro/transformer.ts',
name: 'metro/transformer',
},
{
builder: 'mkdist',
input: './src/metro',
input: './src/bundler/adapters/metro',
outDir: 'dist/metro',
pattern: ['index.d.ts'],
declaration: true,
format: 'esm',
},
{
builder: 'rollup',
input: './src/vite',
input: './src/bundler/adapters/vite',
name: 'vite/index',
},
{
builder: 'mkdist',
input: './src/vite',
input: './src/bundler/adapters/vite',
outDir: 'dist/vite',
pattern: ['index.d.ts'],
declaration: true,
Expand All @@ -85,10 +88,16 @@ export default defineBuildConfig({
pattern: [
'**/*',
'!metro/**',
'!bundler/adapters/metro/**',
'!bundler/adapters/vite/**',
],
declaration: true,
}),
],
alias: {
'@/common': srcPath('common'),
'@/bundler': srcPath('bundler'),
},
outDir: 'dist',
clean: true,
externals: [
Expand Down
1 change: 1 addition & 0 deletions packages/uniwind/jest.config.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ export default {
],
moduleNameMapper: {
'^react-native$': '<rootDir>/../../node_modules/react-native',
'^@/(.*)$': '<rootDir>/src/$1',
},
}
1 change: 1 addition & 0 deletions packages/uniwind/jest.config.web.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default {
preset: 'ts-jest',
moduleNameMapper: {
'^react-native$': 'react-native-web',
'^@/(.*)$': '<rootDir>/src/$1',
},
transformIgnorePatterns: [
'node_modules/(?!(react-native-web)/)',
Expand Down
2 changes: 1 addition & 1 deletion packages/uniwind/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": false,
"name": "uniwind",
"version": "1.6.6-beta.0",
"version": "1.6.7-beta.0",
"description": "The fastest Tailwind bindings for React Native",
"homepage": "https://uniwind.dev",
"author": "Unistack",
Expand Down
1 change: 1 addition & 0 deletions packages/uniwind/src/bundler/adapters/metro/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './metro'
Loading