diff --git a/integrations/vite/index.test.ts b/integrations/vite/index.test.ts
index 91570a6fcc2d..b8b2ad6e39df 100644
--- a/integrations/vite/index.test.ts
+++ b/integrations/vite/index.test.ts
@@ -1103,3 +1103,63 @@ test(
expect(content).toContain('display: flex;')
},
)
+
+test(
+ `the plugin works when using the environment API`,
+ {
+ fs: {
+ 'package.json': txt`
+ {
+ "type": "module",
+ "dependencies": {
+ "@tailwindcss/vite": "workspace:^",
+ "tailwindcss": "workspace:^"
+ },
+ "devDependencies": {
+ "vite": "^7"
+ }
+ }
+ `,
+ 'vite.config.ts': ts`
+ import tailwindcss from '@tailwindcss/vite'
+ import { defineConfig } from 'vite'
+
+ export default defineConfig({
+ plugins: [tailwindcss()],
+ builder: {},
+ environments: {
+ server: {
+ build: {
+ cssMinify: false,
+ emitAssets: true,
+ rollupOptions: { input: './src/server.ts' },
+ },
+ },
+ },
+ })
+ `,
+ // Has to exist or the build fails
+ 'index.html': html`
+
+ `,
+ 'src/server.ts': js`
+ // Import the stylesheet in the server build
+ import a from './index.css?url'
+ console.log(a)
+ `,
+ 'src/index.css': css`
+ @reference 'tailwindcss/theme';
+ @import 'tailwindcss/utilities';
+ `,
+ },
+ },
+ async ({ root, fs, exec, expect }) => {
+ await exec('pnpm vite build', { cwd: root })
+
+ let files = await fs.glob('dist/**/*.css')
+ expect(files).toHaveLength(1)
+ let [filename] = files[0]
+
+ await fs.expectFileToContain(filename, [candidate`content-['index.html']`])
+ },
+)
diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts
index 29b85b2be0ff..709ab9db0a15 100644
--- a/packages/@tailwindcss-vite/src/index.ts
+++ b/packages/@tailwindcss-vite/src/index.ts
@@ -11,7 +11,8 @@ import { clearRequireCache } from '@tailwindcss/node/require-cache'
import { Scanner } from '@tailwindcss/oxide'
import fs from 'node:fs/promises'
import path from 'node:path'
-import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite'
+import type { Environment, Plugin, ResolvedConfig, ViteDevServer } from 'vite'
+import * as vite from 'vite'
const DEBUG = env.DEBUG
const SPECIAL_QUERY_RE = /[?&](?:worker|sharedworker|raw|url)\b/
@@ -28,28 +29,51 @@ export type PluginOptions = {
export default function tailwindcss(opts: PluginOptions = {}): Plugin[] {
let servers: ViteDevServer[] = []
let config: ResolvedConfig | null = null
+ let rootsByEnv = new DefaultMap>((env: string) => new Map())
let isSSR = false
let shouldOptimize = true
let minify = true
- let roots: DefaultMap = new DefaultMap((id) => {
- let cssResolver = config!.createResolver({
- ...config!.resolve,
- extensions: ['.css'],
- mainFields: ['style'],
- conditions: ['style', 'development|production'],
- tryIndex: false,
- preferRelative: true,
- })
- function customCssResolver(id: string, base: string) {
- return cssResolver(id, base, true, isSSR)
- }
+ function createRoot(env: Environment | null, id: string) {
+ type ResolveFn = (id: string, base: string) => Promise
+
+ let customCssResolver: ResolveFn
+ let customJsResolver: ResolveFn
+
+ if (!env) {
+ // Older, pre-environment Vite API
+ // TODO: Can we drop this??
+ let cssResolver = config!.createResolver({
+ ...config!.resolve,
+ extensions: ['.css'],
+ mainFields: ['style'],
+ conditions: ['style', 'development|production'],
+ tryIndex: false,
+ preferRelative: true,
+ })
+
+ let jsResolver = config!.createResolver(config!.resolve)
+
+ customCssResolver = (id: string, base: string) => cssResolver(id, base, true, isSSR)
+ customJsResolver = (id: string, base: string) => jsResolver(id, base, true, isSSR)
+ } else {
+ // Newer Vite versions
+ let cssResolver = vite.createIdResolver(env.config, {
+ ...env.config.resolve,
+ extensions: ['.css'],
+ mainFields: ['style'],
+ conditions: ['style', 'development|production'],
+ tryIndex: false,
+ preferRelative: true,
+ })
- let jsResolver = config!.createResolver(config!.resolve)
- function customJsResolver(id: string, base: string) {
- return jsResolver(id, base, true, isSSR)
+ let jsResolver = vite.createIdResolver(env.config, env.config.resolve)
+
+ customCssResolver = (id: string, base: string) => cssResolver(env, id, base, true)
+ customJsResolver = (id: string, base: string) => jsResolver(env, id, base, true)
}
+
return new Root(
id,
config!.root,
@@ -59,7 +83,7 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] {
customCssResolver,
customJsResolver,
)
- })
+ }
return [
{
@@ -110,7 +134,12 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] {
using I = new Instrumentation()
DEBUG && I.start('[@tailwindcss/vite] Generate CSS (serve)')
+ let roots = rootsByEnv.get(this.environment?.name ?? 'default')
let root = roots.get(id)
+ if (!root) {
+ root ??= createRoot(this.environment ?? null, id)
+ roots.set(id, root)
+ }
let result = await root.generate(src, (file) => this.addWatchFile(file), I)
if (!result) {
@@ -129,7 +158,6 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] {
name: '@tailwindcss/vite:generate:build',
apply: 'build',
enforce: 'pre',
-
transform: {
filter: {
id: {
@@ -143,7 +171,12 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] {
using I = new Instrumentation()
DEBUG && I.start('[@tailwindcss/vite] Generate CSS (build)')
+ let roots = rootsByEnv.get(this.environment?.name ?? 'default')
let root = roots.get(id)
+ if (!root) {
+ root ??= createRoot(this.environment ?? null, id)
+ roots.set(id, root)
+ }
let result = await root.generate(src, (file) => this.addWatchFile(file), I)
if (!result) {
@@ -174,13 +207,15 @@ function getExtension(id: string) {
}
function isPotentialCssRootFile(id: string) {
- if (id.includes('/.vite/')) return
+ if (id.includes('/.vite/')) return false
+
+ // Don't intercept special static asset resources
+ if (SPECIAL_QUERY_RE.test(id)) return false
+ if (COMMON_JS_PROXY_RE.test(id)) return false
+
let extension = getExtension(id)
- let isCssFile =
- (extension === 'css' || id.includes('&lang.css') || id.match(INLINE_STYLE_ID_RE)) &&
- // Don't intercept special static asset resources
- !SPECIAL_QUERY_RE.test(id) &&
- !COMMON_JS_PROXY_RE.test(id)
+ let isCssFile = extension === 'css' || id.includes('&lang.css') || id.match(INLINE_STYLE_ID_RE)
+
return isCssFile
}