From 719a5351c1ffe7a6fef0da00f3af2929ce415fb6 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Wed, 4 Sep 2024 16:53:15 +0200 Subject: [PATCH] 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: https://github.com/vitejs/vite/issues/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. --- CHANGELOG.md | 1 + integrations/vite/nuxt.test.ts | 64 +++++++++++++++++++++++++ packages/@tailwindcss-vite/src/index.ts | 63 ++++++++++++------------ 3 files changed, 96 insertions(+), 32 deletions(-) create mode 100644 integrations/vite/nuxt.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a1d007eba..9035dc387 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ensure content globs defined in `@config` files are relative to that file ([#14314](https://github.com/tailwindlabs/tailwindcss/pull/14314)) - Ensure CSS `theme()` functions are evaluated in media query ranges with collapsed whitespace ((#14321)[https://github.com/tailwindlabs/tailwindcss/pull/14321]) +- Fix support for Nuxt projects in the Vite plugin (requires Nuxt 3.13.1+) ([#14319](https://github.com/tailwindlabs/tailwindcss/pull/14319)) ## [4.0.0-alpha.21] - 2024-09-02 diff --git a/integrations/vite/nuxt.test.ts b/integrations/vite/nuxt.test.ts new file mode 100644 index 000000000..7004ccada --- /dev/null +++ b/integrations/vite/nuxt.test.ts @@ -0,0 +1,64 @@ +import { expect } from 'vitest' +import { candidate, css, fetchStyles, html, json, retryAssertion, test, ts } from '../utils' + +test( + 'dev mode', + { + fs: { + 'package.json': json` + { + "type": "module", + "dependencies": { + "@tailwindcss/vite": "workspace:^", + "nuxt": "^3.13.1", + "tailwindcss": "workspace:^", + "vue": "latest" + } + } + `, + 'nuxt.config.ts': ts` + import tailwindcss from '@tailwindcss/vite' + + // https://nuxt.com/docs/api/configuration/nuxt-config + export default defineNuxtConfig({ + vite: { + plugins: [tailwindcss()], + }, + + css: ['~/assets/css/main.css'], + devtools: { enabled: true }, + compatibilityDate: '2024-08-30', + }) + `, + 'app.vue': html` + + `, + 'assets/css/main.css': css`@import 'tailwindcss';`, + }, + }, + async ({ fs, spawn, getFreePort }) => { + let port = await getFreePort() + await spawn(`pnpm nuxt dev --port ${port}`) + + await retryAssertion(async () => { + let css = await fetchStyles(port) + expect(css).toContain(candidate`underline`) + }) + + await fs.write( + 'app.vue', + html` + + `, + ) + await retryAssertion(async () => { + let css = await fetchStyles(port) + expect(css).toContain(candidate`underline`) + expect(css).toContain(candidate`font-bold`) + }) + }, +) diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts index ad43ec061..65d863548 100644 --- a/packages/@tailwindcss-vite/src/index.ts +++ b/packages/@tailwindcss-vite/src/index.ts @@ -11,7 +11,7 @@ import postcssImport from 'postcss-import' import type { Plugin, ResolvedConfig, Rollup, Update, ViteDevServer } from 'vite' export default function tailwindcss(): Plugin[] { - let server: ViteDevServer | null = null + let servers: ViteDevServer[] = [] let config: ResolvedConfig | null = null let isSSR = false @@ -58,36 +58,35 @@ export default function tailwindcss(): Plugin[] { } function invalidateAllRoots(isSSR: boolean) { - // If we're building then we don't need to update anything - if (!server) return - - let updates: Update[] = [] - for (let id of roots.keys()) { - let module = server.moduleGraph.getModuleById(id) - if (!module) { - // Note: Removing this during SSR is not safe and will produce - // inconsistent results based on the timing of the removal and - // the order / timing of transforms. - if (!isSSR) { - // It is safe to remove the item here since we're iterating on a copy - // of the keys. - roots.delete(id) + for (let server of servers) { + let updates: Update[] = [] + for (let id of roots.keys()) { + let module = server.moduleGraph.getModuleById(id) + if (!module) { + // Note: Removing this during SSR is not safe and will produce + // inconsistent results based on the timing of the removal and + // the order / timing of transforms. + if (!isSSR) { + // It is safe to remove the item here since we're iterating on a copy + // of the keys. + roots.delete(id) + } + continue } - continue + + roots.get(id).requiresRebuild = false + server.moduleGraph.invalidateModule(module) + updates.push({ + type: `${module.type}-update`, + path: module.url, + acceptedPath: module.url, + timestamp: Date.now(), + }) } - roots.get(id).requiresRebuild = false - server.moduleGraph.invalidateModule(module) - updates.push({ - type: `${module.type}-update`, - path: module.url, - acceptedPath: module.url, - timestamp: Date.now(), - }) - } - - if (updates.length > 0) { - server.hot.send({ type: 'update', updates }) + if (updates.length > 0) { + server.hot.send({ type: 'update', updates }) + } } } @@ -139,8 +138,8 @@ export default function tailwindcss(): Plugin[] { name: '@tailwindcss/vite:scan', enforce: 'pre', - configureServer(_server) { - server = _server + configureServer(server) { + servers.push(server) }, async configResolved(_config) { @@ -169,7 +168,7 @@ export default function tailwindcss(): Plugin[] { }, transform(src, id, options) { let extension = getExtension(id) - if (extension === '' || extension === 'css') return + if (isPotentialCssRootFile(id)) return scanFile(id, src, extension, options?.ssr ?? false) }, }, @@ -193,7 +192,7 @@ export default function tailwindcss(): Plugin[] { // The reason why we can not rely on the invalidation here is that the // users would otherwise see a flicker in the styles as the CSS might // be loaded with an invalid set of candidates first. - await server?.waitForRequestsIdle?.(id) + await Promise.all(servers.map((server) => server.waitForRequestsIdle(id))) } let generated = await root.generate(src, (file) => this.addWatchFile(file))