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))