Skip to content

Commit c09bb5e

Browse files
Fix Vite issues with SolidStart (#16052)
Fixes #16045 This PR fixes two Vite issues found with SolidStart: - SolidStart seems to emit an empty HTML chunk (where the content is literally just `/`) with _no pathname_. Since we use the path to generate an `id` for HTML chunks, this would currently cause a crash. This was reported in #16045 - While testing the fix for the above, we also found that hot reloading was not working in SolidStart since `4.0.0-alpha.22`. After doing some bisecting we found that this is happening as SolidStart has the same module ID in different servers and we were invalidating the root when we shouldn't. After trying to restructure this code so that it only cleans up the root when it is _no longer part of any server_, we noticed some other compatibility issues with Nuxt and SvelteKit. It seems that the safest bet is to no longer update a root at all during rebuilds in the SSR step. This makes `invalidateAllRoots` a function that only notifiers the servers about a change which is conceptually also less confusing. ## Test plan - Added an integration test for SolidStart dev mode - Manually tested the dev mode across all Vite based templates in https://github.com/philipp-spiess/tailwindcss-playgrounds: Astro, Nuxt, Remix, Solid, SvelteKit, and Vue. --------- Co-authored-by: Robin Malfait <[email protected]>
1 parent 2242941 commit c09bb5e

File tree

3 files changed

+120
-18
lines changed

3 files changed

+120
-18
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- Only generate positive `grid-cols-*` and `grid-rows-*` utilities ([#16020](https://github.com/tailwindlabs/tailwindcss/pull/16020))
1313
- Ensure we process Tailwind CSS features when only using `@reference` or `@variant` ([#16057](https://github.com/tailwindlabs/tailwindcss/pull/16057))
1414
- Refactor gradient implementation to work around [prettier/prettier#17058](https://github.com/prettier/prettier/issues/17058) ([#16072](https://github.com/tailwindlabs/tailwindcss/pull/16072))
15+
- Vite: Ensure hot-reloading works with SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052))
16+
- Vite: Fix a crash when starting the development server in SolidStart setups ([#16052](https://github.com/tailwindlabs/tailwindcss/pull/16052))
1517

1618
## [4.0.1] - 2025-01-29
1719

integrations/vite/solidstart.test.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { candidate, css, fetchStyles, js, json, retryAssertion, test, ts } from '../utils'
2+
3+
const WORKSPACE = {
4+
'package.json': json`
5+
{
6+
"type": "module",
7+
"dependencies": {
8+
"@solidjs/start": "^1",
9+
"solid-js": "^1",
10+
"vinxi": "^0",
11+
"@tailwindcss/vite": "workspace:^",
12+
"tailwindcss": "workspace:^"
13+
}
14+
}
15+
`,
16+
'jsconfig.json': json`
17+
{
18+
"compilerOptions": {
19+
"jsx": "preserve",
20+
"jsxImportSource": "solid-js"
21+
}
22+
}
23+
`,
24+
'app.config.js': ts`
25+
import { defineConfig } from '@solidjs/start/config'
26+
import tailwindcss from '@tailwindcss/vite'
27+
28+
export default defineConfig({
29+
vite: {
30+
plugins: [tailwindcss()],
31+
},
32+
})
33+
`,
34+
'src/entry-server.jsx': js`
35+
// @refresh reload
36+
import { createHandler, StartServer } from '@solidjs/start/server'
37+
38+
export default createHandler(() => (
39+
<StartServer
40+
document={({ assets, children, scripts }) => (
41+
<html lang="en">
42+
<head>{assets}</head>
43+
<body>
44+
<div id="app">{children}</div>
45+
{scripts}
46+
</body>
47+
</html>
48+
)}
49+
/>
50+
))
51+
`,
52+
'src/entry-client.jsx': js`
53+
// @refresh reload
54+
import { mount, StartClient } from '@solidjs/start/client'
55+
56+
mount(() => <StartClient />, document.getElementById('app'))
57+
`,
58+
'src/app.jsx': js`
59+
import './app.css'
60+
export default function App() {
61+
return <h1 class="underline">Hello world!</h1>
62+
}
63+
`,
64+
'src/app.css': css`@import 'tailwindcss';`,
65+
}
66+
67+
test(
68+
'dev mode',
69+
{
70+
fs: WORKSPACE,
71+
},
72+
async ({ fs, spawn, expect }) => {
73+
let process = await spawn('pnpm vinxi dev', {
74+
env: {
75+
TEST: 'false', // VERY IMPORTANT OTHERWISE YOU WON'T GET OUTPUT
76+
NODE_ENV: 'development',
77+
},
78+
})
79+
80+
let url = ''
81+
await process.onStdout((m) => {
82+
let match = /Local:\s*(http.*)\//.exec(m)
83+
if (match) url = match[1]
84+
return Boolean(url)
85+
})
86+
87+
await retryAssertion(async () => {
88+
let css = await fetchStyles(url)
89+
expect(css).toContain(candidate`underline`)
90+
})
91+
92+
await retryAssertion(async () => {
93+
await fs.write(
94+
'src/app.jsx',
95+
js`
96+
import './app.css'
97+
export default function App() {
98+
return <h1 class="underline font-bold">Hello world!</h1>
99+
}
100+
`,
101+
)
102+
103+
let css = await fetchStyles(url)
104+
expect(css).toContain(candidate`underline`)
105+
expect(css).toContain(candidate`font-bold`)
106+
})
107+
},
108+
)

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

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export default function tailwindcss(): Plugin[] {
6363
)
6464
})
6565

66-
function scanFile(id: string, content: string, extension: string, isSSR: boolean) {
66+
function scanFile(id: string, content: string, extension: string) {
6767
for (let dependency of IGNORED_DEPENDENCIES) {
6868
// We validated that Vite IDs always use posix style path separators, even on Windows.
6969
// In dev build, Vite precompiles dependencies
@@ -83,26 +83,16 @@ export default function tailwindcss(): Plugin[] {
8383
}
8484

8585
if (updated) {
86-
invalidateAllRoots(isSSR)
86+
invalidateAllRoots()
8787
}
8888
}
8989

90-
function invalidateAllRoots(isSSR: boolean) {
90+
function invalidateAllRoots() {
9191
for (let server of servers) {
9292
let updates: Update[] = []
93-
for (let [id, root] of roots.entries()) {
93+
for (let [id] of roots.entries()) {
9494
let module = server.moduleGraph.getModuleById(id)
95-
if (!module) {
96-
// Note: Removing this during SSR is not safe and will produce
97-
// inconsistent results based on the timing of the removal and
98-
// the order / timing of transforms.
99-
if (!isSSR) {
100-
// It is safe to remove the item here since we're iterating on a copy
101-
// of the keys.
102-
roots.delete(id)
103-
}
104-
continue
105-
}
95+
if (!module) continue
10696

10797
roots.get(id).requiresRebuild = false
10898
server.moduleGraph.invalidateModule(module)
@@ -113,7 +103,6 @@ export default function tailwindcss(): Plugin[] {
113103
timestamp: Date.now(),
114104
})
115105
}
116-
117106
if (updates.length > 0) {
118107
server.hot.send({ type: 'update', updates })
119108
}
@@ -210,12 +199,15 @@ export default function tailwindcss(): Plugin[] {
210199

211200
// Scan all non-CSS files for candidates
212201
transformIndexHtml(html, { path }) {
213-
scanFile(path, html, 'html', isSSR)
202+
// SolidStart emits HTML chunks with an undefined path and the html content of `\`.
203+
if (!path) return
204+
205+
scanFile(path, html, 'html')
214206
},
215207
transform(src, id, options) {
216208
let extension = getExtension(id)
217209
if (isPotentialCssRootFile(id)) return
218-
scanFile(id, src, extension, options?.ssr ?? false)
210+
scanFile(id, src, extension)
219211
},
220212
},
221213

0 commit comments

Comments
 (0)