diff --git a/integrations/vite/build-watch.test.ts b/integrations/vite/build-watch.test.ts
new file mode 100644
index 000000000..a1f6af970
--- /dev/null
+++ b/integrations/vite/build-watch.test.ts
@@ -0,0 +1,88 @@
+import path from 'node:path'
+import { candidate, css, js, json, test } from '../utils'
+
+test(
+ 'build watch ignores a custom output directory',
+ {
+ fs: {
+ 'package.json': json`
+ {
+ "type": "module",
+ "dependencies": {
+ "@tailwindcss/vite": "workspace:^",
+ "tailwindcss": "workspace:^"
+ },
+ "devDependencies": {
+ "vite": "6.2.3"
+ }
+ }
+ `,
+ 'vite.config.js': js`
+ import tailwindcss from '@tailwindcss/vite'
+ import { defineConfig } from 'vite'
+
+ export default defineConfig({
+ plugins: [tailwindcss()],
+ build: {
+ outDir: './assets',
+ emptyOutDir: false,
+ minify: false,
+ rollupOptions: {
+ input: {
+ app: './resources/js/app.js',
+ styles: './resources/css/app.css',
+ },
+ output: {
+ dir: './assets',
+ entryFileNames: '[name].js',
+ chunkFileNames: '[name].js',
+ assetFileNames: '[name].css',
+ },
+ },
+ },
+ })
+ `,
+ 'resources/js/app.js': js`
+ import '../css/app.css'
+
+ document.body.innerHTML = '
Hello
'
+ `,
+ 'resources/css/app.css': css` @import 'tailwindcss'; `,
+ 'assets/app.js': js` console.log('previous build') `,
+ 'assets/styles.css': css`
+ .previous-build {
+ display: block;
+ }
+ `,
+ },
+ },
+ async ({ root, spawn, exec, fs, expect }) => {
+ let version = await exec('pnpm vite --version')
+ expect(version).toContain('vite/6.2.3')
+
+ let process = await spawn('pnpm vite build --watch')
+ await process.onStdout((message) => message.includes('built in'))
+
+ let output = path.join(root, 'assets/styles.css')
+ await fs.expectFileToContain(output, candidate`underline`)
+
+ process.flush()
+ let result = await Promise.race([
+ process.onStdout((message) => message.includes('built in')).then(() => 'rebuilt'),
+ new Promise<'idle'>((resolve) => setTimeout(() => resolve('idle'), 1_500)),
+ ])
+ expect(result).toBe('idle')
+
+ process.flush()
+ await fs.write(
+ 'resources/js/app.js',
+ js`
+ import '../css/app.css'
+
+ document.body.innerHTML = 'Hello
'
+ `,
+ )
+ await process.onStdout((message) => message.includes('built in'))
+ await fs.expectFileToContain(output, candidate`font-bold`)
+ },
+)
diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts
index 8fc660eaa..c9b67d627 100644
--- a/packages/@tailwindcss-vite/src/index.ts
+++ b/packages/@tailwindcss-vite/src/index.ts
@@ -166,6 +166,7 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] {
return new Root(
id,
config!.root,
+ config!.command === 'build' ? path.resolve(config!.root, config!.build.outDir) : null,
// Currently, Vite only supports CSS source maps in development and they
// are off by default. Check to see if we need them or not.
config?.css.devSourcemap ?? false,
@@ -350,6 +351,7 @@ class Root {
constructor(
private id: string,
private base: string,
+ private buildOutputDirectory: string | null,
private enableSourceMaps: boolean,
private customCssResolver: (id: string, base: string) => Promise,
@@ -431,6 +433,16 @@ class Root {
return [{ ...this.compiler.root, negated: false }]
})().concat(this.compiler.sources)
+ if (this.buildOutputDirectory !== null) {
+ // Watching files emitted by Vite would trigger another build every time
+ // the output is written.
+ sources.push({
+ base: this.buildOutputDirectory,
+ pattern: '**/*',
+ negated: true,
+ })
+ }
+
this.scanner = new Scanner({ sources })
DEBUG && I.end('Setup scanner')
} else {