diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ae4dd48f276..82bb2a70551c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Only generate positive `grid-cols-*` and `grid-rows-*` utilities ([#16020](https://github.com/tailwindlabs/tailwindcss/pull/16020)) - Ensure we process Tailwind CSS features when only using `@reference` or `@variant` ([#16057](https://github.com/tailwindlabs/tailwindcss/pull/16057)) - Refactor gradient implementation to work around [prettier/prettier#17058](https://github.com/prettier/prettier/issues/17058) ([#16072](https://github.com/tailwindlabs/tailwindcss/pull/16072)) +- Vite: Ensure hot-reloading works with SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052)) +- Vite: Fix a crash when starting the development server in SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052)) ## [4.0.1] - 2025-01-29 diff --git a/integrations/vite/solidstart.test.ts b/integrations/vite/solidstart.test.ts new file mode 100644 index 000000000000..bbe9ccc8868d --- /dev/null +++ b/integrations/vite/solidstart.test.ts @@ -0,0 +1,108 @@ +import { candidate, css, fetchStyles, js, json, retryAssertion, test, ts } from '../utils' + +const WORKSPACE = { + 'package.json': json` + { + "type": "module", + "dependencies": { + "@solidjs/start": "^1", + "solid-js": "^1", + "vinxi": "^0", + "@tailwindcss/vite": "workspace:^", + "tailwindcss": "workspace:^" + } + } + `, + 'jsconfig.json': json` + { + "compilerOptions": { + "jsx": "preserve", + "jsxImportSource": "solid-js" + } + } + `, + 'app.config.js': ts` + import { defineConfig } from '@solidjs/start/config' + import tailwindcss from '@tailwindcss/vite' + + export default defineConfig({ + vite: { + plugins: [tailwindcss()], + }, + }) + `, + 'src/entry-server.jsx': js` + // @refresh reload + import { createHandler, StartServer } from '@solidjs/start/server' + + export default createHandler(() => ( + ( + + {assets} + +
{children}
+ {scripts} + + + )} + /> + )) + `, + 'src/entry-client.jsx': js` + // @refresh reload + import { mount, StartClient } from '@solidjs/start/client' + + mount(() => , document.getElementById('app')) + `, + 'src/app.jsx': js` + import './app.css' + export default function App() { + return

Hello world!

+ } + `, + 'src/app.css': css`@import 'tailwindcss';`, +} + +test( + 'dev mode', + { + fs: WORKSPACE, + }, + async ({ fs, spawn, expect }) => { + let process = await spawn('pnpm vinxi dev', { + env: { + TEST: 'false', // VERY IMPORTANT OTHERWISE YOU WON'T GET OUTPUT + NODE_ENV: 'development', + }, + }) + + let url = '' + await process.onStdout((m) => { + let match = /Local:\s*(http.*)\//.exec(m) + if (match) url = match[1] + return Boolean(url) + }) + + await retryAssertion(async () => { + let css = await fetchStyles(url) + expect(css).toContain(candidate`underline`) + }) + + await retryAssertion(async () => { + await fs.write( + 'src/app.jsx', + js` + import './app.css' + export default function App() { + return

Hello world!

+ } + `, + ) + + let css = await fetchStyles(url) + 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 c8976c0276da..86d5d6142d5c 100644 --- a/packages/@tailwindcss-vite/src/index.ts +++ b/packages/@tailwindcss-vite/src/index.ts @@ -63,7 +63,7 @@ export default function tailwindcss(): Plugin[] { ) }) - function scanFile(id: string, content: string, extension: string, isSSR: boolean) { + function scanFile(id: string, content: string, extension: string) { for (let dependency of IGNORED_DEPENDENCIES) { // We validated that Vite IDs always use posix style path separators, even on Windows. // In dev build, Vite precompiles dependencies @@ -83,26 +83,16 @@ export default function tailwindcss(): Plugin[] { } if (updated) { - invalidateAllRoots(isSSR) + invalidateAllRoots() } } - function invalidateAllRoots(isSSR: boolean) { + function invalidateAllRoots() { for (let server of servers) { let updates: Update[] = [] - for (let [id, root] of roots.entries()) { + for (let [id] of roots.entries()) { 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 - } + if (!module) continue roots.get(id).requiresRebuild = false server.moduleGraph.invalidateModule(module) @@ -113,7 +103,6 @@ export default function tailwindcss(): Plugin[] { timestamp: Date.now(), }) } - if (updates.length > 0) { server.hot.send({ type: 'update', updates }) } @@ -210,12 +199,15 @@ export default function tailwindcss(): Plugin[] { // Scan all non-CSS files for candidates transformIndexHtml(html, { path }) { - scanFile(path, html, 'html', isSSR) + // SolidStart emits HTML chunks with an undefined path and the html content of `\`. + if (!path) return + + scanFile(path, html, 'html') }, transform(src, id, options) { let extension = getExtension(id) if (isPotentialCssRootFile(id)) return - scanFile(id, src, extension, options?.ssr ?? false) + scanFile(id, src, extension) }, },