Skip to content

Commit 719a535

Browse files
Fix Nuxt integration (#14319)
We noticed that Nuxt projects were not working with the tailwindcss project. The issue was traced down to the fact that Nuxt starts multiple Vite dev servers and calling the experimental `waitForRequestsIdle()` on one of the test servers would never resolve. This was fixed upstream and is part of the latest Vite/Nuxt release: vitejs/vite#17980. We still need to handle the fact that Vite can spawn multiple dev servers. This is necessary because when we invalidate all roots, we need to find that module inside all of the spawned servers. If we only look at the _last server_ (what we have done before), we would not find the module and thus could not invalidate it.
1 parent e7ca667 commit 719a535

File tree

3 files changed

+96
-32
lines changed

3 files changed

+96
-32
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515

1616
- Ensure content globs defined in `@config` files are relative to that file ([#14314](https://github.com/tailwindlabs/tailwindcss/pull/14314))
1717
- Ensure CSS `theme()` functions are evaluated in media query ranges with collapsed whitespace ((#14321)[https://github.com/tailwindlabs/tailwindcss/pull/14321])
18+
- Fix support for Nuxt projects in the Vite plugin (requires Nuxt 3.13.1+) ([#14319](https://github.com/tailwindlabs/tailwindcss/pull/14319))
1819

1920
## [4.0.0-alpha.21] - 2024-09-02
2021

integrations/vite/nuxt.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { expect } from 'vitest'
2+
import { candidate, css, fetchStyles, html, json, retryAssertion, test, ts } from '../utils'
3+
4+
test(
5+
'dev mode',
6+
{
7+
fs: {
8+
'package.json': json`
9+
{
10+
"type": "module",
11+
"dependencies": {
12+
"@tailwindcss/vite": "workspace:^",
13+
"nuxt": "^3.13.1",
14+
"tailwindcss": "workspace:^",
15+
"vue": "latest"
16+
}
17+
}
18+
`,
19+
'nuxt.config.ts': ts`
20+
import tailwindcss from '@tailwindcss/vite'
21+
22+
// https://nuxt.com/docs/api/configuration/nuxt-config
23+
export default defineNuxtConfig({
24+
vite: {
25+
plugins: [tailwindcss()],
26+
},
27+
28+
css: ['~/assets/css/main.css'],
29+
devtools: { enabled: true },
30+
compatibilityDate: '2024-08-30',
31+
})
32+
`,
33+
'app.vue': html`
34+
<template>
35+
<div class="underline">Hello world!</div>
36+
</template>
37+
`,
38+
'assets/css/main.css': css`@import 'tailwindcss';`,
39+
},
40+
},
41+
async ({ fs, spawn, getFreePort }) => {
42+
let port = await getFreePort()
43+
await spawn(`pnpm nuxt dev --port ${port}`)
44+
45+
await retryAssertion(async () => {
46+
let css = await fetchStyles(port)
47+
expect(css).toContain(candidate`underline`)
48+
})
49+
50+
await fs.write(
51+
'app.vue',
52+
html`
53+
<template>
54+
<div class="underline font-bold">Hello world!</div>
55+
</template>
56+
`,
57+
)
58+
await retryAssertion(async () => {
59+
let css = await fetchStyles(port)
60+
expect(css).toContain(candidate`underline`)
61+
expect(css).toContain(candidate`font-bold`)
62+
})
63+
},
64+
)

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

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import postcssImport from 'postcss-import'
1111
import type { Plugin, ResolvedConfig, Rollup, Update, ViteDevServer } from 'vite'
1212

1313
export default function tailwindcss(): Plugin[] {
14-
let server: ViteDevServer | null = null
14+
let servers: ViteDevServer[] = []
1515
let config: ResolvedConfig | null = null
1616

1717
let isSSR = false
@@ -58,36 +58,35 @@ export default function tailwindcss(): Plugin[] {
5858
}
5959

6060
function invalidateAllRoots(isSSR: boolean) {
61-
// If we're building then we don't need to update anything
62-
if (!server) return
63-
64-
let updates: Update[] = []
65-
for (let id of roots.keys()) {
66-
let module = server.moduleGraph.getModuleById(id)
67-
if (!module) {
68-
// Note: Removing this during SSR is not safe and will produce
69-
// inconsistent results based on the timing of the removal and
70-
// the order / timing of transforms.
71-
if (!isSSR) {
72-
// It is safe to remove the item here since we're iterating on a copy
73-
// of the keys.
74-
roots.delete(id)
61+
for (let server of servers) {
62+
let updates: Update[] = []
63+
for (let id of roots.keys()) {
64+
let module = server.moduleGraph.getModuleById(id)
65+
if (!module) {
66+
// Note: Removing this during SSR is not safe and will produce
67+
// inconsistent results based on the timing of the removal and
68+
// the order / timing of transforms.
69+
if (!isSSR) {
70+
// It is safe to remove the item here since we're iterating on a copy
71+
// of the keys.
72+
roots.delete(id)
73+
}
74+
continue
7575
}
76-
continue
77-
}
7876

79-
roots.get(id).requiresRebuild = false
80-
server.moduleGraph.invalidateModule(module)
81-
updates.push({
82-
type: `${module.type}-update`,
83-
path: module.url,
84-
acceptedPath: module.url,
85-
timestamp: Date.now(),
86-
})
87-
}
77+
roots.get(id).requiresRebuild = false
78+
server.moduleGraph.invalidateModule(module)
79+
updates.push({
80+
type: `${module.type}-update`,
81+
path: module.url,
82+
acceptedPath: module.url,
83+
timestamp: Date.now(),
84+
})
85+
}
8886

89-
if (updates.length > 0) {
90-
server.hot.send({ type: 'update', updates })
87+
if (updates.length > 0) {
88+
server.hot.send({ type: 'update', updates })
89+
}
9190
}
9291
}
9392

@@ -139,8 +138,8 @@ export default function tailwindcss(): Plugin[] {
139138
name: '@tailwindcss/vite:scan',
140139
enforce: 'pre',
141140

142-
configureServer(_server) {
143-
server = _server
141+
configureServer(server) {
142+
servers.push(server)
144143
},
145144

146145
async configResolved(_config) {
@@ -169,7 +168,7 @@ export default function tailwindcss(): Plugin[] {
169168
},
170169
transform(src, id, options) {
171170
let extension = getExtension(id)
172-
if (extension === '' || extension === 'css') return
171+
if (isPotentialCssRootFile(id)) return
173172
scanFile(id, src, extension, options?.ssr ?? false)
174173
},
175174
},
@@ -193,7 +192,7 @@ export default function tailwindcss(): Plugin[] {
193192
// The reason why we can not rely on the invalidation here is that the
194193
// users would otherwise see a flicker in the styles as the CSS might
195194
// be loaded with an invalid set of candidates first.
196-
await server?.waitForRequestsIdle?.(id)
195+
await Promise.all(servers.map((server) => server.waitForRequestsIdle(id)))
197196
}
198197

199198
let generated = await root.generate(src, (file) => this.addWatchFile(file))

0 commit comments

Comments
 (0)