Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/metal-walls-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@tanstack/start-plugin-core': patch
---

Update the workspace Vite dependency from 8.0.14 to 8.2.2 and keep bundled-dev hydration and hot updates working with Vite's separate client runtime and rebuild lifecycle.

Collect bundled-dev SSR styles from completed client bundles without starting a second client plugin lifecycle. Bundled clients compile eagerly while dev SSR styles are enabled so initial CSS and its assets are available; disabling SSR styles preserves lazy compilation. Unbundled development retains its existing style collector.

Reuse SSR module evaluations and transforms across unchanged bundled-dev requests instead of clearing the entire SSR cache for every request.
4 changes: 4 additions & 0 deletions e2e/react-start/dev-ssr-styles/src/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createFileRoute } from '@tanstack/react-router'
import styles from '../styles/ssr.module.css'

export const Route = createFileRoute('/')({
component: Home,
Expand All @@ -11,6 +12,9 @@ function Home() {
<div className="styled-box" data-testid="styled-box">
This box should have a blue background when dev styles are enabled.
</div>
<div className={styles.box} data-testid="css-module-box">
CSS modules should also be styled before hydration.
</div>
</div>
)
}
1 change: 1 addition & 0 deletions e2e/react-start/dev-ssr-styles/src/styles/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ body {

.styled-box {
background-color: #3b82f6;
background-image: url('./ssr-background.svg?no-inline');
color: white;
padding: 24px;
border-radius: 12px;
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions e2e/react-start/dev-ssr-styles/src/styles/ssr.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.box {
border: 7px solid rgb(15, 118, 110);
}
63 changes: 62 additions & 1 deletion e2e/react-start/dev-ssr-styles/tests/app.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test'
import { DEV_STYLES_ATTR } from '@tanstack/router-core'
import { test } from '@tanstack/router-e2e-utils'
import { ssrStylesMode } from '../env'
import { ssrStylesMode, viteBundledDev } from '../env'

// Whitelist errors that can occur in CI:
// - net::ERR_NAME_NOT_RESOLVED: transient network issues
Expand All @@ -14,6 +14,33 @@ const whitelistErrors = [
test.describe(`dev.ssrStyles (mode=${ssrStylesMode})`, () => {
test.use({ whitelistErrors })

if (viteBundledDev && ssrStylesMode !== 'disabled') {
test('cold SSR styles do not start the regular client plugin container', async ({
request,
}) => {
const response = await request.get('/')
expect(response.ok()).toBeTruthy()
const html = await response.text()
const href = html.match(
/href="([^"]*@tanstack-start\/styles\.css[^"]*)"/,
)?.[1]
expect(href).toBeDefined()
const before = await request.get('/__test/client-builds')
const beforeCounts = await before.json()

const cssResponse = await request.get(href!.replaceAll('&amp;', '&'))
expect(cssResponse.ok()).toBeTruthy()
const css = await cssResponse.text()
expect(css).toContain('.styled-box')
expect(css).toContain('7px')

const after = await request.get('/__test/client-builds')
const afterCounts = await after.json()
expect(afterCounts.starts).toBe(beforeCounts.starts)
expect(afterCounts.starts).toBe(afterCounts.bundles)
})
}

test('page renders correctly', async ({ page }) => {
await page.goto('/')
await expect(page.getByTestId('home-heading')).toHaveText(
Expand Down Expand Up @@ -63,6 +90,10 @@ test.describe(`dev.ssrStyles (mode=${ssrStylesMode})`, () => {
(el) => getComputedStyle(el).backgroundColor,
)
expect(backgroundColor).toBe('rgb(59, 130, 246)')
await expect(page.getByTestId('css-module-box')).toHaveCSS(
'border-top-width',
'7px',
)
})
})
}
Expand Down Expand Up @@ -127,8 +158,38 @@ test.describe(`dev.ssrStyles (mode=${ssrStylesMode})`, () => {
(el) => getComputedStyle(el).backgroundColor,
)
expect(backgroundColor).toBe('rgb(59, 130, 246)')
await expect(page.getByTestId('css-module-box')).toHaveCSS(
'border-top-width',
'7px',
)
})
})
})
}

if (viteBundledDev && ssrStylesMode !== 'disabled') {
test('bundled CSS assets remain valid after client modules load', async ({
page,
request,
}) => {
await page.goto('/', { waitUntil: 'networkidle' })
const response = await request.get('/')
const html = await response.text()
const href = html.match(
/href="([^"]*@tanstack-start\/styles\.css[^"]*)"/,
)?.[1]
expect(href).toBeDefined()

const cssResponse = await request.get(href!.replaceAll('&amp;', '&'))
expect(cssResponse.ok()).toBeTruthy()
const css = await cssResponse.text()
expect(css).not.toContain('__VITE_ASSET__')
expect(css).not.toContain('__VITE_PUBLIC_ASSET__')
const assetUrl = css.match(/url\(["']?([^"')]+)["']?\)/)?.[1]
expect(assetUrl).toBeDefined()
const asset = await request.get(assetUrl!)
expect(asset.ok()).toBeTruthy()
expect(asset.headers()['content-type']).toContain('image/svg+xml')
})
}
})
4 changes: 4 additions & 0 deletions e2e/react-start/dev-ssr-styles/tests/setup/global.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export default async function setup() {
const baseURL = `http://localhost:${port}`

await waitForServer(baseURL)
if (viteBundledDev) {
// Keep the bundled client cold so SSR styles are tested before JavaScript runs.
return
}
await preOptimizeDevServer({
baseURL,
readyTestId: 'home-heading',
Expand Down
29 changes: 29 additions & 0 deletions e2e/react-start/dev-ssr-styles/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,34 @@ import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import viteReact from '@vitejs/plugin-react'
import { ssrStylesMode, useNitro, viteBundledDev } from './env'
import type { Plugin } from 'vite'

function clientBuildProbe(): Plugin {
let starts = 0
let bundles = 0
return {
name: 'test:client-build-probe',
apply: 'serve',
applyToEnvironment(environment) {
return environment.name === 'client'
},
buildStart() {
starts++
},
generateBundle() {
bundles++
},
configureServer(server) {
server.middlewares.use((req, res, next) => {
if (req.url !== '/__test/client-builds') {
return next()
}
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ starts, bundles }))
})
},
}
}

function getSsrStylesConfig() {
switch (ssrStylesMode) {
Expand All @@ -25,6 +53,7 @@ export default defineConfig(async () => {
port: 3000,
},
plugins: [
viteBundledDev ? clientBuildProbe() : undefined,
// Nitro is placed BEFORE tanstackStart to test that our CSS middleware
// works regardless of plugin order (nitro has a catch-all middleware)
...nitroPlugin,
Expand Down
3 changes: 0 additions & 3 deletions e2e/react-start/hmr/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,10 @@
"@playwright/test": "^1.61.0",
"@rsbuild/core": "^2.1.0",
"@rsbuild/plugin-react": "^2.0.0",
"@tailwindcss/postcss": "^4.2.2",
"@tailwindcss/vite": "^4.2.2",
"@tanstack/router-e2e-utils": "workspace:^",
"@types/node": "^22.10.2",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"tailwindcss": "^4.2.2",
"@vitejs/plugin-react": "^6.0.1",
"@typescript/native": "npm:typescript@^7.0.2",
"typescript": "npm:@typescript/typescript6@^6.0.2",
Expand Down
5 changes: 0 additions & 5 deletions e2e/react-start/hmr/postcss.config.mjs

This file was deleted.

Loading
Loading