Skip to content

Commit 816d0d1

Browse files
feat: allow to configure dev ssr style injection (#6797)
1 parent 5f7e9e8 commit 816d0d1

25 files changed

Lines changed: 823 additions & 96 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
dist
3+
test-results
4+
playwright-report
5+
port*.txt
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
src/routeTree.gen.ts
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { z } from 'zod'
2+
3+
const ssrStylesModeSchema = z
4+
.enum(['default', 'disabled', 'custom-basepath'])
5+
.default('default')
6+
7+
export type SsrStylesMode = z.infer<typeof ssrStylesModeSchema>
8+
9+
export const ssrStylesMode: SsrStylesMode = ssrStylesModeSchema.parse(
10+
process.env.SSR_STYLES,
11+
)
12+
13+
export const useNitro = process.env.VITE_USE_NITRO === 'true'
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"name": "tanstack-react-start-e2e-dev-ssr-styles",
3+
"private": true,
4+
"sideEffects": false,
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite dev --port 3000",
8+
"dev:e2e": "vite dev --port $PORT",
9+
"dev:e2e:disabled": "SSR_STYLES=disabled vite dev --port $PORT",
10+
"dev:e2e:custom-basepath": "SSR_STYLES=custom-basepath vite dev --port $PORT",
11+
"dev:e2e:nitro": "VITE_USE_NITRO=true vite dev --port $PORT",
12+
"dev:e2e:disabled:nitro": "SSR_STYLES=disabled VITE_USE_NITRO=true vite dev --port $PORT",
13+
"dev:e2e:custom-basepath:nitro": "SSR_STYLES=custom-basepath VITE_USE_NITRO=true vite dev --port $PORT",
14+
"build": "vite build && tsc --noEmit",
15+
"start": "pnpx srvx --prod -s ../client dist/server/server.js",
16+
"test:e2e:dev": "MODE=dev playwright test --project=chromium",
17+
"test:e2e:dev:disabled": "MODE=dev SSR_STYLES=disabled playwright test --project=chromium",
18+
"test:e2e:dev:custom-basepath": "MODE=dev SSR_STYLES=custom-basepath playwright test --project=chromium",
19+
"test:e2e:dev:nitro": "MODE=dev VITE_USE_NITRO=true playwright test --project=chromium",
20+
"test:e2e:dev:disabled:nitro": "MODE=dev SSR_STYLES=disabled VITE_USE_NITRO=true playwright test --project=chromium",
21+
"test:e2e:dev:custom-basepath:nitro": "MODE=dev SSR_STYLES=custom-basepath VITE_USE_NITRO=true playwright test --project=chromium",
22+
"test:e2e": "rm -rf port*.txt; pnpm run test:e2e:dev && pnpm run test:e2e:dev:disabled && pnpm run test:e2e:dev:custom-basepath",
23+
"test:e2e:nitro": "rm -rf port*.txt; pnpm run test:e2e:dev:nitro && pnpm run test:e2e:dev:disabled:nitro && pnpm run test:e2e:dev:custom-basepath:nitro"
24+
},
25+
"dependencies": {
26+
"@tanstack/react-router": "workspace:*",
27+
"@tanstack/react-start": "workspace:*",
28+
"react": "^19.0.0",
29+
"react-dom": "^19.0.0"
30+
},
31+
"devDependencies": {
32+
"@playwright/test": "^1.50.1",
33+
"@tanstack/router-e2e-utils": "workspace:*",
34+
"@types/node": "^22.10.2",
35+
"@types/react": "^19.0.8",
36+
"@types/react-dom": "^19.0.3",
37+
"@vitejs/plugin-react": "^4.3.4",
38+
"nitro": "3.0.1-alpha.2",
39+
"srvx": "^0.11.7",
40+
"typescript": "^5.7.2",
41+
"vite": "^7.3.1",
42+
"vite-tsconfig-paths": "^5.1.4",
43+
"zod": "^3.24.2"
44+
}
45+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { defineConfig, devices } from '@playwright/test'
2+
import { getTestServerPort } from '@tanstack/router-e2e-utils'
3+
import packageJson from './package.json' with { type: 'json' }
4+
import { ssrStylesMode, useNitro } from './env'
5+
6+
const mode = process.env.MODE ?? 'prod'
7+
const isDev = mode === 'dev'
8+
9+
// Build a unique port key per dimension combination (ssrStyles mode + nitro)
10+
// e.g. "...dev-ssr-styles", "...dev-ssr-styles-disabled", "...dev-ssr-styles-nitro",
11+
// "...dev-ssr-styles-disabled-nitro"
12+
function getPortKey() {
13+
let key = packageJson.name
14+
if (ssrStylesMode !== 'default') {
15+
key += `-${ssrStylesMode}`
16+
}
17+
if (useNitro) {
18+
key += '-nitro'
19+
}
20+
return key
21+
}
22+
23+
const PORT = await getTestServerPort(getPortKey())
24+
const baseURL = `http://localhost:${PORT}`
25+
26+
// Select the appropriate dev command based on SSR_STYLES + VITE_USE_NITRO
27+
function getDevCommand() {
28+
const scriptParts = ['dev:e2e']
29+
if (ssrStylesMode !== 'default') {
30+
scriptParts.push(ssrStylesMode)
31+
}
32+
if (useNitro) {
33+
scriptParts.push('nitro')
34+
}
35+
return `pnpm ${scriptParts.join(':')}`
36+
}
37+
38+
export default defineConfig({
39+
testDir: './tests',
40+
workers: 1,
41+
reporter: [['line']],
42+
43+
globalSetup: './tests/setup/global.setup.ts',
44+
globalTeardown: './tests/setup/global.teardown.ts',
45+
46+
use: {
47+
baseURL,
48+
},
49+
50+
webServer: {
51+
command: isDev ? getDevCommand() : `pnpm build && PORT=${PORT} pnpm start`,
52+
url: baseURL,
53+
reuseExistingServer: !process.env.CI,
54+
stdout: 'pipe',
55+
env: {
56+
VITE_NODE_ENV: 'test',
57+
PORT: String(PORT),
58+
},
59+
},
60+
61+
projects: [
62+
{
63+
name: 'chromium',
64+
use: {
65+
...devices['Desktop Chrome'],
66+
},
67+
},
68+
],
69+
})
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/* eslint-disable */
2+
3+
// @ts-nocheck
4+
5+
// noinspection JSUnusedGlobalSymbols
6+
7+
// This file was automatically generated by TanStack Router.
8+
// You should NOT make any changes in this file as it will be overwritten.
9+
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
10+
11+
import { Route as rootRouteImport } from './routes/__root'
12+
import { Route as IndexRouteImport } from './routes/index'
13+
14+
const IndexRoute = IndexRouteImport.update({
15+
id: '/',
16+
path: '/',
17+
getParentRoute: () => rootRouteImport,
18+
} as any)
19+
20+
export interface FileRoutesByFullPath {
21+
'/': typeof IndexRoute
22+
}
23+
export interface FileRoutesByTo {
24+
'/': typeof IndexRoute
25+
}
26+
export interface FileRoutesById {
27+
__root__: typeof rootRouteImport
28+
'/': typeof IndexRoute
29+
}
30+
export interface FileRouteTypes {
31+
fileRoutesByFullPath: FileRoutesByFullPath
32+
fullPaths: '/'
33+
fileRoutesByTo: FileRoutesByTo
34+
to: '/'
35+
id: '__root__' | '/'
36+
fileRoutesById: FileRoutesById
37+
}
38+
export interface RootRouteChildren {
39+
IndexRoute: typeof IndexRoute
40+
}
41+
42+
declare module '@tanstack/react-router' {
43+
interface FileRoutesByPath {
44+
'/': {
45+
id: '/'
46+
path: '/'
47+
fullPath: '/'
48+
preLoaderRoute: typeof IndexRouteImport
49+
parentRoute: typeof rootRouteImport
50+
}
51+
}
52+
}
53+
54+
const rootRouteChildren: RootRouteChildren = {
55+
IndexRoute: IndexRoute,
56+
}
57+
export const routeTree = rootRouteImport
58+
._addFileChildren(rootRouteChildren)
59+
._addFileTypes<FileRouteTypes>()
60+
61+
import type { getRouter } from './router.tsx'
62+
import type { createStart } from '@tanstack/react-start'
63+
declare module '@tanstack/react-start' {
64+
interface Register {
65+
ssr: true
66+
router: Awaited<ReturnType<typeof getRouter>>
67+
}
68+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { createRouter } from '@tanstack/react-router'
2+
import { routeTree } from './routeTree.gen'
3+
4+
export function getRouter() {
5+
const router = createRouter({
6+
routeTree,
7+
scrollRestoration: true,
8+
defaultPreload: false,
9+
})
10+
11+
return router
12+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {
2+
HeadContent,
3+
Outlet,
4+
Scripts,
5+
createRootRoute,
6+
} from '@tanstack/react-router'
7+
import '~/styles/app.css'
8+
9+
export const Route = createRootRoute({
10+
head: () => ({
11+
meta: [
12+
{ charSet: 'utf-8' },
13+
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
14+
],
15+
}),
16+
component: RootComponent,
17+
})
18+
19+
function RootComponent() {
20+
return (
21+
<html>
22+
<head>
23+
<HeadContent />
24+
</head>
25+
<body>
26+
<Outlet />
27+
<Scripts />
28+
</body>
29+
</html>
30+
)
31+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { createFileRoute } from '@tanstack/react-router'
2+
3+
export const Route = createFileRoute('/')({
4+
component: Home,
5+
})
6+
7+
function Home() {
8+
return (
9+
<div>
10+
<h1 data-testid="home-heading">Dev SSR Styles Test</h1>
11+
<div className="styled-box" data-testid="styled-box">
12+
This box should have a blue background when dev styles are enabled.
13+
</div>
14+
</div>
15+
)
16+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
body {
2+
font-family: sans-serif;
3+
margin: 0;
4+
padding: 20px;
5+
background-color: #f0f4f8;
6+
}
7+
8+
.styled-box {
9+
background-color: #3b82f6;
10+
color: white;
11+
padding: 24px;
12+
border-radius: 12px;
13+
}

0 commit comments

Comments
 (0)