diff --git a/integrations/postcss/next.test.ts b/integrations/postcss/next.test.ts
index c99dc0861..f50b34e58 100644
--- a/integrations/postcss/next.test.ts
+++ b/integrations/postcss/next.test.ts
@@ -1,5 +1,5 @@
import { expect } from 'vitest'
-import { candidate, css, fetchStylesFromIndex, js, json, retryAssertion, test } from '../utils'
+import { candidate, css, fetchStyles, js, json, retryAssertion, test } from '../utils'
test(
'production build',
@@ -131,7 +131,7 @@ test(
await spawn(`pnpm next dev ${bundler === 'turbo' ? '--turbo' : ''} --port ${port}`)
await retryAssertion(async () => {
- let css = await fetchStylesFromIndex(port)
+ let css = await fetchStyles(port)
expect(css).toContain(candidate`underline`)
})
@@ -145,7 +145,7 @@ test(
)
await retryAssertion(async () => {
- let css = await fetchStylesFromIndex(port)
+ let css = await fetchStyles(port)
expect(css).toContain(candidate`underline`)
expect(css).toContain(candidate`text-red-500`)
})
diff --git a/integrations/utils.ts b/integrations/utils.ts
index 65b76c960..144b69e1e 100644
--- a/integrations/utils.ts
+++ b/integrations/utils.ts
@@ -262,8 +262,15 @@ export function test(
await fs.mkdir(dir, { recursive: true })
await fs.writeFile(full, content)
},
- read(filePath: string) {
- return fs.readFile(path.resolve(root, filePath), 'utf8')
+ async read(filePath: string) {
+ let content = await fs.readFile(path.resolve(root, filePath), 'utf8')
+
+ // Ensure that files read on Windows have \r\n line endings removed
+ if (IS_WINDOWS) {
+ content = content.replace(/\r\n/g, '\n')
+ }
+
+ return content
},
async glob(pattern: string) {
let files = await fastGlob(pattern, { cwd: root })
@@ -500,8 +507,8 @@ export async function retryAssertion
`, + 'project-a/about.html': html` +
+ + +
+ `,
'project-a/src/index.css': css`
@import 'tailwindcss/theme' theme(reference);
@import 'tailwindcss/utilities';
@@ -119,15 +138,27 @@ test(
},
async ({ root, spawn, getFreePort, fs }) => {
let port = await getFreePort()
- let process = await spawn(`pnpm vite dev --port ${port}`, {
+ await spawn(`pnpm vite dev --port ${port}`, {
cwd: path.join(root, 'project-a'),
})
- await process.onStdout((message) => message.includes('ready in'))
+ // Candidates are resolved lazily, so the first visit of index.html
+ // will only have candidates from this file.
+ await retryAssertion(async () => {
+ let css = await fetchStyles(port, '/index.html')
+ expect(css).toContain(candidate`underline`)
+ expect(css).not.toContain(candidate`font-bold`)
+ })
- let css = await fetchStylesFromIndex(port)
- expect(css).toContain(candidate`underline`)
+ // Going to about.html will extend the candidate list to include
+ // candidates from about.html.
+ await retryAssertion(async () => {
+ let css = await fetchStyles(port, '/about.html')
+ expect(css).toContain(candidate`underline`)
+ expect(css).toContain(candidate`font-bold`)
+ })
+ // Updates are additive and cause new candidates to be added.
await fs.write(
'project-a/index.html',
html`
@@ -139,11 +170,14 @@ test(
`,
)
- await process.onStdout((message) => message.includes('page reload'))
-
- css = await fetchStylesFromIndex(port)
- expect(css).toContain(candidate`m-2`)
+ await retryAssertion(async () => {
+ let css = await fetchStyles(port)
+ expect(css).toContain(candidate`underline`)
+ expect(css).toContain(candidate`font-bold`)
+ expect(css).toContain(candidate`m-2`)
+ })
+ // Manually added `@source`s are watched and trigger a rebuild
await fs.write(
'project-b/src/index.js',
js`
@@ -151,9 +185,33 @@ test(
module.exports = { className }
`,
)
- await process.onStdout((message) => message.includes('page reload'))
+ await retryAssertion(async () => {
+ let css = await fetchStyles(port)
+ expect(css).toContain(candidate`underline`)
+ expect(css).toContain(candidate`font-bold`)
+ expect(css).toContain(candidate`m-2`)
+ expect(css).toContain(candidate`[.changed_&]:content-['project-b/src/index.js']`)
+ })
- css = await fetchStylesFromIndex(port)
- expect(css).toContain(candidate`[.changed_&]:content-['project-b/src/index.js']`)
+ // After updates to the CSS file, all previous candidates should still be in
+ // the generated CSS
+ await fs.write(
+ 'project-a/src/index.css',
+ css`
+ ${await fs.read('project-a/src/index.css')}
+
+ .red {
+ color: red;
+ }
+ `,
+ )
+ await retryAssertion(async () => {
+ let css = await fetchStyles(port)
+ expect(css).toContain(candidate`red`)
+ expect(css).toContain(candidate`m-2`)
+ expect(css).toContain(candidate`underline`)
+ expect(css).toContain(candidate`[.changed_&]:content-['project-b/src/index.js']`)
+ expect(css).toContain(candidate`font-bold`)
+ })
},
)
diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts
index 75dea4b76..25d07dbfd 100644
--- a/packages/@tailwindcss-vite/src/index.ts
+++ b/packages/@tailwindcss-vite/src/index.ts
@@ -11,7 +11,7 @@ export default function tailwindcss(): Plugin[] {
let config: ResolvedConfig | null = null
let scanner: Scanner | null = null
let changedContent: { content: string; extension: string }[] = []
- let candidates: string[] = []
+ let candidates = new Set