Skip to content

Commit e4b6c67

Browse files
Support Vite Environment API (#18970)
Fixes #18002 Very much a work in progress b/c I don't (yet) understand how the newer APIs are intended to function. - [x] Needs env specific tests that verify the environment API is being used
1 parent c532955 commit e4b6c67

File tree

3 files changed

+120
-24
lines changed

3 files changed

+120
-24
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- Include filename and line numbers in CSS parse errors ([#19282](https://github.com/tailwindlabs/tailwindcss/pull/19282))
1414
- Skip comments in Ruby files when checking for class names ([#19243](https://github.com/tailwindlabs/tailwindcss/pull/19243))
1515
- Skip over arbitrary property utilities with a top-level `!` in the value ([#19243](https://github.com/tailwindlabs/tailwindcss/pull/19243))
16+
- Support environment API in `@tailwindcss/vite` ([#18970](https://github.com/tailwindlabs/tailwindcss/pull/18970))
1617

1718
### Added
1819

integrations/vite/index.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,3 +1103,63 @@ test(
11031103
expect(content).toContain('display: flex;')
11041104
},
11051105
)
1106+
1107+
test(
1108+
`the plugin works when using the environment API`,
1109+
{
1110+
fs: {
1111+
'package.json': txt`
1112+
{
1113+
"type": "module",
1114+
"dependencies": {
1115+
"@tailwindcss/vite": "workspace:^",
1116+
"tailwindcss": "workspace:^"
1117+
},
1118+
"devDependencies": {
1119+
"vite": "^7"
1120+
}
1121+
}
1122+
`,
1123+
'vite.config.ts': ts`
1124+
import tailwindcss from '@tailwindcss/vite'
1125+
import { defineConfig } from 'vite'
1126+
1127+
export default defineConfig({
1128+
plugins: [tailwindcss()],
1129+
builder: {},
1130+
environments: {
1131+
server: {
1132+
build: {
1133+
cssMinify: false,
1134+
emitAssets: true,
1135+
rollupOptions: { input: './src/server.ts' },
1136+
},
1137+
},
1138+
},
1139+
})
1140+
`,
1141+
// Has to exist or the build fails
1142+
'index.html': html`
1143+
<div class="content-['index.html']"></div>
1144+
`,
1145+
'src/server.ts': js`
1146+
// Import the stylesheet in the server build
1147+
import a from './index.css?url'
1148+
console.log(a)
1149+
`,
1150+
'src/index.css': css`
1151+
@reference 'tailwindcss/theme';
1152+
@import 'tailwindcss/utilities';
1153+
`,
1154+
},
1155+
},
1156+
async ({ root, fs, exec, expect }) => {
1157+
await exec('pnpm vite build', { cwd: root })
1158+
1159+
let files = await fs.glob('dist/**/*.css')
1160+
expect(files).toHaveLength(1)
1161+
let [filename] = files[0]
1162+
1163+
await fs.expectFileToContain(filename, [candidate`content-['index.html']`])
1164+
},
1165+
)

packages/@tailwindcss-vite/src/index.ts

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import { clearRequireCache } from '@tailwindcss/node/require-cache'
1111
import { Scanner } from '@tailwindcss/oxide'
1212
import fs from 'node:fs/promises'
1313
import path from 'node:path'
14-
import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite'
14+
import type { Environment, Plugin, ResolvedConfig, ViteDevServer } from 'vite'
15+
import * as vite from 'vite'
1516

1617
const DEBUG = env.DEBUG
1718
const SPECIAL_QUERY_RE = /[?&](?:worker|sharedworker|raw|url)\b/
@@ -28,28 +29,51 @@ export type PluginOptions = {
2829
export default function tailwindcss(opts: PluginOptions = {}): Plugin[] {
2930
let servers: ViteDevServer[] = []
3031
let config: ResolvedConfig | null = null
32+
let rootsByEnv = new DefaultMap<string, Map<string, Root>>((env: string) => new Map())
3133

3234
let isSSR = false
3335
let shouldOptimize = true
3436
let minify = true
3537

36-
let roots: DefaultMap<string, Root> = new DefaultMap((id) => {
37-
let cssResolver = config!.createResolver({
38-
...config!.resolve,
39-
extensions: ['.css'],
40-
mainFields: ['style'],
41-
conditions: ['style', 'development|production'],
42-
tryIndex: false,
43-
preferRelative: true,
44-
})
45-
function customCssResolver(id: string, base: string) {
46-
return cssResolver(id, base, true, isSSR)
47-
}
38+
function createRoot(env: Environment | null, id: string) {
39+
type ResolveFn = (id: string, base: string) => Promise<string | false | undefined>
40+
41+
let customCssResolver: ResolveFn
42+
let customJsResolver: ResolveFn
43+
44+
if (!env) {
45+
// Older, pre-environment Vite API
46+
// TODO: Can we drop this??
47+
let cssResolver = config!.createResolver({
48+
...config!.resolve,
49+
extensions: ['.css'],
50+
mainFields: ['style'],
51+
conditions: ['style', 'development|production'],
52+
tryIndex: false,
53+
preferRelative: true,
54+
})
55+
56+
let jsResolver = config!.createResolver(config!.resolve)
57+
58+
customCssResolver = (id: string, base: string) => cssResolver(id, base, true, isSSR)
59+
customJsResolver = (id: string, base: string) => jsResolver(id, base, true, isSSR)
60+
} else {
61+
// Newer Vite versions
62+
let cssResolver = vite.createIdResolver(env.config, {
63+
...env.config.resolve,
64+
extensions: ['.css'],
65+
mainFields: ['style'],
66+
conditions: ['style', 'development|production'],
67+
tryIndex: false,
68+
preferRelative: true,
69+
})
4870

49-
let jsResolver = config!.createResolver(config!.resolve)
50-
function customJsResolver(id: string, base: string) {
51-
return jsResolver(id, base, true, isSSR)
71+
let jsResolver = vite.createIdResolver(env.config, env.config.resolve)
72+
73+
customCssResolver = (id: string, base: string) => cssResolver(env, id, base, true)
74+
customJsResolver = (id: string, base: string) => jsResolver(env, id, base, true)
5275
}
76+
5377
return new Root(
5478
id,
5579
config!.root,
@@ -59,7 +83,7 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] {
5983
customCssResolver,
6084
customJsResolver,
6185
)
62-
})
86+
}
6387

6488
return [
6589
{
@@ -110,7 +134,12 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] {
110134
using I = new Instrumentation()
111135
DEBUG && I.start('[@tailwindcss/vite] Generate CSS (serve)')
112136

137+
let roots = rootsByEnv.get(this.environment?.name ?? 'default')
113138
let root = roots.get(id)
139+
if (!root) {
140+
root ??= createRoot(this.environment ?? null, id)
141+
roots.set(id, root)
142+
}
114143

115144
let result = await root.generate(src, (file) => this.addWatchFile(file), I)
116145
if (!result) {
@@ -129,7 +158,6 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] {
129158
name: '@tailwindcss/vite:generate:build',
130159
apply: 'build',
131160
enforce: 'pre',
132-
133161
transform: {
134162
filter: {
135163
id: {
@@ -143,7 +171,12 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] {
143171
using I = new Instrumentation()
144172
DEBUG && I.start('[@tailwindcss/vite] Generate CSS (build)')
145173

174+
let roots = rootsByEnv.get(this.environment?.name ?? 'default')
146175
let root = roots.get(id)
176+
if (!root) {
177+
root ??= createRoot(this.environment ?? null, id)
178+
roots.set(id, root)
179+
}
147180

148181
let result = await root.generate(src, (file) => this.addWatchFile(file), I)
149182
if (!result) {
@@ -174,13 +207,15 @@ function getExtension(id: string) {
174207
}
175208

176209
function isPotentialCssRootFile(id: string) {
177-
if (id.includes('/.vite/')) return
210+
if (id.includes('/.vite/')) return false
211+
212+
// Don't intercept special static asset resources
213+
if (SPECIAL_QUERY_RE.test(id)) return false
214+
if (COMMON_JS_PROXY_RE.test(id)) return false
215+
178216
let extension = getExtension(id)
179-
let isCssFile =
180-
(extension === 'css' || id.includes('&lang.css') || id.match(INLINE_STYLE_ID_RE)) &&
181-
// Don't intercept special static asset resources
182-
!SPECIAL_QUERY_RE.test(id) &&
183-
!COMMON_JS_PROXY_RE.test(id)
217+
let isCssFile = extension === 'css' || id.includes('&lang.css') || id.match(INLINE_STYLE_ID_RE)
218+
184219
return isCssFile
185220
}
186221

0 commit comments

Comments
 (0)