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 }