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
10 changes: 5 additions & 5 deletions apps/vite-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
"@tailwindcss/vite": "4.1.17",
"@tailwindcss/vite": "4.3.0",
"react": "catalog:",
"react-dom": "catalog:",
"react-native": "catalog:",
Expand All @@ -18,13 +18,13 @@
"uniwind": "workspace:*"
},
"devDependencies": {
"@types/node": "24.10.1",
"@types/node": "25.9.1",
"@types/react": "catalog:",
"@types/react-dom": "19.2.3",
"@vitejs/plugin-react": "5.1.1",
"globals": "16.5.0",
"@vitejs/plugin-react": "6.0.2",
"globals": "17.6.0",
"typescript": "catalog:",
"vite": "catalog:",
"vite-plugin-rnw": "0.0.10"
"vite-plugin-rnw": "0.0.11"
}
}
588 changes: 249 additions & 339 deletions bun.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,16 @@
"react-native": "0.85.3",
"react-native-web": "0.21.2",
"tailwindcss": "4.3.0",
"vite": "7.2.6"
"vite": "8.0.14"
}
},
"devDependencies": {
"typescript": "catalog:",
"dprint": "0.54.0",
"oxlint": "1.66.0",
"oxlint": "1.67.0",
"oxlint-tsgolint": "0.23.0",
"husky": "9.1.7",
"turbo": "2.9.14"
"turbo": "2.9.16"
},
"packageManager": "bun@1.3.14",
"trustedDependencies": [
Expand Down
9 changes: 4 additions & 5 deletions packages/uniwind/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"test:native": "jest --config jest.config.native.js",
"test:web": "jest --config jest.config.web.js",
"test:types": "tsc --project tests/type-test/tsconfig.json",
"test:e2e": "playwright test",
"test:e2e": "bun --bun playwright test",
"release": "release-it"
},
"keywords": [
Expand Down Expand Up @@ -83,8 +83,8 @@
"LICENSE"
],
"dependencies": {
"@tailwindcss/node": "4.2.1",
"@tailwindcss/oxide": "4.2.1",
"@tailwindcss/node": "4.3.0",
"@tailwindcss/oxide": "4.3.0",
"culori": "4.0.2",
"lightningcss": "1.30.1"
},
Expand Down Expand Up @@ -118,7 +118,6 @@
"release-it": "20.0.1",
"typescript": "catalog:",
"unbuild": "3.6.1",
"vite": "catalog:",
"esbuild": "0.28.0"
"vite": "catalog:"
}
}
155 changes: 104 additions & 51 deletions packages/uniwind/src/bundler/adapters/vite/vite.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { normalizePath } from '@tailwindcss/node'
import { createRequire } from 'node:module'
import path from 'path'
import type { Plugin } from 'vite'
import type { PluginContext } from 'rollup'
import type { Plugin, UserConfig } from 'vite'

import { UniwindBundlerConfig } from '@/bundler/config'
import type { UniwindConfig } from '@/bundler/types'
Expand All @@ -15,6 +17,96 @@ const styleSheetPath = path.resolve(
'../module/components/web/createOrderedCSSStyleSheet.js',
)
const cssArtifactPath = path.resolve(dirname, '../../uniwind.css')
const require = createRequire(import.meta.url)
const viteVersion = require('vite/package.json').version as string

const isVite8 = Number(viteVersion.split('.')[0]) >= 8

type EsbuildResolveArgs = {
path: string
importer: string
}

type EsbuildBuild = {
onResolve: (
options: { filter: RegExp },
callback: (args: EsbuildResolveArgs) => { path: string } | undefined,
) => void
}

const resolveOrderedCSSStyleSheet = (source: string, importer: string | undefined) => {
const normalizedSource = normalizePath(source)
const isTarget = source === './createOrderedCSSStyleSheet'
|| normalizedSource.endsWith('react-native-web/dist/exports/StyleSheet/dom/createOrderedCSSStyleSheet.js')

if (isTarget && importer !== undefined && normalizePath(importer).includes('react-native-web/dist/exports/StyleSheet')) {
return styleSheetPath
}
}

const vite8OptimizeDeps = {
exclude: ['uniwind', 'react-native'],
rolldownOptions: {
plugins: [{
name: 'uniwind-rolldown-plugin',
resolveId: resolveOrderedCSSStyleSheet,
}],
},
}

const vite7OptimizeDeps = {
exclude: ['uniwind', 'react-native'],
esbuildOptions: {
plugins: [{
name: 'uniwind-esbuild-plugin',
setup: (build: EsbuildBuild) => {
build.onResolve(
{ filter: /^\.\/createOrderedCSSStyleSheet$/ },
args => {
const resolved = resolveOrderedCSSStyleSheet(args.path, args.importer)

if (resolved !== undefined) {
return { path: resolved }
}
},
)
},
}],
},
}

const vite8Resolve = {
alias: [{
find: /^react-native$/,
replacement: componentPath,
customResolver: {
resolveId(this: PluginContext, _: string, importer: string | undefined) {
if (importer !== undefined && normalizePath(importer).includes('uniwind/dist')) {
return this.resolve('react-native-web', importer, { skipSelf: true })
}

return componentPath
},
},
}],
}

const vite7Resolve = {
alias: [{
find: /^react-native$/,
replacement: componentPath,
customResolver: {
resolveId(this: PluginContext, _: string, importer: string | undefined) {
// Check if import comes from uniwind
if (importer !== undefined && normalizePath(importer).includes('uniwind/dist')) {
return this.resolve('react-native-web')
}

return componentPath
},
},
}],
}

export const uniwind = (config: UniwindConfig): Plugin => {
const bundlerConfig = UniwindBundlerConfig.fromViteConfig(config)
Expand All @@ -23,58 +115,19 @@ export const uniwind = (config: UniwindConfig): Plugin => {
name: 'uniwind',
enforce: 'pre',
resolveId: (source, importer) => {
const normalizedSource = normalizePath(source)
const isTarget = source === './createOrderedCSSStyleSheet'
|| normalizedSource.endsWith('react-native-web/dist/exports/StyleSheet/dom/createOrderedCSSStyleSheet.js')

if (isTarget && importer !== undefined && normalizePath(importer).includes('react-native-web/dist/exports/StyleSheet')) {
return styleSheetPath
}
return resolveOrderedCSSStyleSheet(source, importer)
},
config: () => ({
css: {
transformer: 'lightningcss',
lightningcss: {
visitor: bundlerConfig.cssVisitor,
},
},
optimizeDeps: {
exclude: ['uniwind', 'react-native'],
esbuildOptions: {
plugins: [{
name: 'uniwind-esbuild-plugin',
setup: build => {
build.onResolve(
{ filter: /^\.\/createOrderedCSSStyleSheet$/ },
args => {
if (normalizePath(args.importer).includes('react-native-web/dist/exports/StyleSheet')) {
return { path: styleSheetPath }
}
},
)
},
}],
},
},
resolve: {
alias: [
{
find: /^react-native$/,
replacement: componentPath,
customResolver: {
resolveId(_, importer) {
// Check if import comes from uniwind
if (importer !== undefined && normalizePath(importer).includes('uniwind/dist')) {
return this.resolve('react-native-web')
}

return componentPath
},
},
config: () =>
({
css: {
transformer: 'lightningcss',
lightningcss: {
visitor: bundlerConfig.cssVisitor,
},
],
},
}),
},
optimizeDeps: isVite8 ? vite8OptimizeDeps : vite7OptimizeDeps,
resolve: isVite8 ? vite8Resolve : vite7Resolve,
}) as unknown as UserConfig,
transform: (code, id) => {
const normalizedId = normalizePath(id)

Expand Down
4 changes: 2 additions & 2 deletions packages/uniwind/src/bundler/css-visitor/rule-visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export class RuleVisitor implements LightningRuleVisitors {

this.processedClassNames.add(firstSelector.name)

return {
return this.removeNulls({
type: 'scope',
value: {
loc: styleRule.value.loc,
Expand All @@ -128,7 +128,7 @@ export class RuleVisitor implements LightningRuleVisitors {
.filter(theme => theme !== selectedVariant)
.map(theme => [{ type: 'class', name: theme }]),
},
}
}) as ReturnedRule
}

// Fixes lightningcss serialization bug
Expand Down
27 changes: 13 additions & 14 deletions packages/uniwind/tests/e2e/global-setup.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { build } from 'esbuild'
import Bun from 'bun'
import { mkdirSync, writeFileSync } from 'fs'
import { resolve } from 'path'
import { UniwindBundlerConfig } from '../../src/bundler/config'
Expand All @@ -25,7 +25,7 @@ export default async function globalSetup() {
writeFileSync(CSS_PATH, compiledCSS, 'utf-8')
console.log(`[e2e setup] Compiled CSS written to ${CSS_PATH}`)

// 2. Bundle getWebStyles.ts into a browser IIFE via esbuild
// 2. Bundle getWebStyles.ts into a browser IIFE via Bun
// The bundle exports getWebStyles and getWebVariable as globals on window.__uniwind
const getWebStylesPath = resolve(ROOT, 'src/core/web/getWebStyles')
const entryContent = [
Expand All @@ -35,21 +35,20 @@ export default async function globalSetup() {
const entryPath = resolve(GENERATED_DIR, '_entry.ts')
writeFileSync(entryPath, entryContent, 'utf-8')

await build({
entryPoints: [entryPath],
bundle: true,
const bundle = await Bun.build({
entrypoints: [entryPath],
target: 'browser',
format: 'iife',
platform: 'browser',
outfile: BUNDLE_PATH,
// getWebStyles uses document/window at module load time,
// so we must NOT tree-shake the side-effectful top-level code
treeShaking: false,
// culori is an ESM-only package; esbuild handles it fine with bundle:true
mainFields: ['module', 'browser', 'main'],
outdir: GENERATED_DIR,
naming: {
entry: 'getWebStyles.iife.js',
},
conditions: ['browser', 'import', 'default'],
tsconfig: resolve(ROOT, 'tsconfig.json'),
logLevel: 'warning',
})

if (!bundle.success) {
throw new Error(bundle.logs.map(log => log.message).join('\n'))
}
Comment thread
Brentlok marked this conversation as resolved.

console.log(`[e2e setup] Browser bundle written to ${BUNDLE_PATH}`)
}