Skip to content

Commit

Permalink
Fix Vite issues with SolidStart (#16052)
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
philipp-spiess and RobinMalfait authored Jan 30, 2025
1 parent 2242941 commit c09bb5e
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 18 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
108 changes: 108 additions & 0 deletions integrations/vite/solidstart.test.ts
Original file line number Diff line number Diff line change
@@ -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(() => (
<StartServer
document={({ assets, children, scripts }) => (
<html lang="en">
<head>{assets}</head>
<body>
<div id="app">{children}</div>
{scripts}
</body>
</html>
)}
/>
))
`,
'src/entry-client.jsx': js`
// @refresh reload
import { mount, StartClient } from '@solidjs/start/client'
mount(() => <StartClient />, document.getElementById('app'))
`,
'src/app.jsx': js`
import './app.css'
export default function App() {
return <h1 class="underline">Hello world!</h1>
}
`,
'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 <h1 class="underline font-bold">Hello world!</h1>
}
`,
)

let css = await fetchStyles(url)
expect(css).toContain(candidate`underline`)
expect(css).toContain(candidate`font-bold`)
})
},
)
28 changes: 10 additions & 18 deletions packages/@tailwindcss-vite/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -113,7 +103,6 @@ export default function tailwindcss(): Plugin[] {
timestamp: Date.now(),
})
}

if (updates.length > 0) {
server.hot.send({ type: 'update', updates })
}
Expand Down Expand Up @@ -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)
},
},

Expand Down

0 comments on commit c09bb5e

Please sign in to comment.