Skip to content

Commit 9a4d924

Browse files
fix: keep CSS when referenced both statically and dynamically (#6891)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 986776c commit 9a4d924

14 files changed

Lines changed: 1690 additions & 232 deletions

File tree

.changeset/open-pants-vanish.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/start-plugin-core': patch
3+
---
4+
5+
fix: keep CSS when referenced both statically and dynamically

e2e/react-start/css-modules/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
"test:e2e:dev:nitro": "MODE=dev VITE_CONFIG=nitro playwright test --project=chromium",
2020
"test:e2e:dev:basepath": "MODE=dev VITE_CONFIG=basepath playwright test --project=chromium",
2121
"_test:e2e:dev:cloudflare": "MODE=dev VITE_CONFIG=cloudflare playwright test --project=chromium",
22-
"test:e2e:prod": "playwright test --project=chromium",
23-
"test:e2e": "rm -rf port*.txt; pnpm run test:e2e:dev",
22+
"test:e2e:prod": "MODE=prod playwright test --project=chromium",
23+
"test:e2e": "rm -rf port*.txt; pnpm run test:e2e:dev && pnpm run test:e2e:prod",
2424
"test:e2e:nitro": "rm -rf port*.txt; pnpm run test:e2e:dev:nitro",
2525
"test:e2e:basepath": "rm -rf port*.txt; pnpm run test:e2e:dev:basepath",
2626
"_test:e2e:cloudflare": "echo 'Cloudflare dev mode disabled - React duplication issues' && exit 0"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { SharedWidget } from './shared-widget'
2+
3+
export default function SharedWidgetLazy() {
4+
return <SharedWidget />
5+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/// <reference types="vite/client" />
2+
import styles from '~/styles/shared-widget.module.css'
3+
4+
export function SharedWidget() {
5+
return (
6+
<div className={styles.widget} data-testid="shared-widget">
7+
<div className={styles.title} data-testid="shared-widget-title">
8+
Shared widget styles
9+
</div>
10+
<div className={styles.content} data-testid="shared-widget-content">
11+
This widget uses a CSS module shared by a static route and a lazy route.
12+
</div>
13+
</div>
14+
)
15+
}

e2e/react-start/css-modules/src/routeTree.gen.ts

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { Route as rootRouteImport } from './routes/__root'
1212
import { Route as SassMixinRouteImport } from './routes/sass-mixin'
1313
import { Route as QuotesRouteImport } from './routes/quotes'
1414
import { Route as ModulesRouteImport } from './routes/modules'
15+
import { Route as LazyCssStaticRouteImport } from './routes/lazy-css-static'
16+
import { Route as LazyCssLazyRouteImport } from './routes/lazy-css-lazy'
1517
import { Route as IndexRouteImport } from './routes/index'
1618

1719
const SassMixinRoute = SassMixinRouteImport.update({
@@ -29,6 +31,16 @@ const ModulesRoute = ModulesRouteImport.update({
2931
path: '/modules',
3032
getParentRoute: () => rootRouteImport,
3133
} as any)
34+
const LazyCssStaticRoute = LazyCssStaticRouteImport.update({
35+
id: '/lazy-css-static',
36+
path: '/lazy-css-static',
37+
getParentRoute: () => rootRouteImport,
38+
} as any)
39+
const LazyCssLazyRoute = LazyCssLazyRouteImport.update({
40+
id: '/lazy-css-lazy',
41+
path: '/lazy-css-lazy',
42+
getParentRoute: () => rootRouteImport,
43+
} as any)
3244
const IndexRoute = IndexRouteImport.update({
3345
id: '/',
3446
path: '/',
@@ -37,33 +49,60 @@ const IndexRoute = IndexRouteImport.update({
3749

3850
export interface FileRoutesByFullPath {
3951
'/': typeof IndexRoute
52+
'/lazy-css-lazy': typeof LazyCssLazyRoute
53+
'/lazy-css-static': typeof LazyCssStaticRoute
4054
'/modules': typeof ModulesRoute
4155
'/quotes': typeof QuotesRoute
4256
'/sass-mixin': typeof SassMixinRoute
4357
}
4458
export interface FileRoutesByTo {
4559
'/': typeof IndexRoute
60+
'/lazy-css-lazy': typeof LazyCssLazyRoute
61+
'/lazy-css-static': typeof LazyCssStaticRoute
4662
'/modules': typeof ModulesRoute
4763
'/quotes': typeof QuotesRoute
4864
'/sass-mixin': typeof SassMixinRoute
4965
}
5066
export interface FileRoutesById {
5167
__root__: typeof rootRouteImport
5268
'/': typeof IndexRoute
69+
'/lazy-css-lazy': typeof LazyCssLazyRoute
70+
'/lazy-css-static': typeof LazyCssStaticRoute
5371
'/modules': typeof ModulesRoute
5472
'/quotes': typeof QuotesRoute
5573
'/sass-mixin': typeof SassMixinRoute
5674
}
5775
export interface FileRouteTypes {
5876
fileRoutesByFullPath: FileRoutesByFullPath
59-
fullPaths: '/' | '/modules' | '/quotes' | '/sass-mixin'
77+
fullPaths:
78+
| '/'
79+
| '/lazy-css-lazy'
80+
| '/lazy-css-static'
81+
| '/modules'
82+
| '/quotes'
83+
| '/sass-mixin'
6084
fileRoutesByTo: FileRoutesByTo
61-
to: '/' | '/modules' | '/quotes' | '/sass-mixin'
62-
id: '__root__' | '/' | '/modules' | '/quotes' | '/sass-mixin'
85+
to:
86+
| '/'
87+
| '/lazy-css-lazy'
88+
| '/lazy-css-static'
89+
| '/modules'
90+
| '/quotes'
91+
| '/sass-mixin'
92+
id:
93+
| '__root__'
94+
| '/'
95+
| '/lazy-css-lazy'
96+
| '/lazy-css-static'
97+
| '/modules'
98+
| '/quotes'
99+
| '/sass-mixin'
63100
fileRoutesById: FileRoutesById
64101
}
65102
export interface RootRouteChildren {
66103
IndexRoute: typeof IndexRoute
104+
LazyCssLazyRoute: typeof LazyCssLazyRoute
105+
LazyCssStaticRoute: typeof LazyCssStaticRoute
67106
ModulesRoute: typeof ModulesRoute
68107
QuotesRoute: typeof QuotesRoute
69108
SassMixinRoute: typeof SassMixinRoute
@@ -92,6 +131,20 @@ declare module '@tanstack/react-router' {
92131
preLoaderRoute: typeof ModulesRouteImport
93132
parentRoute: typeof rootRouteImport
94133
}
134+
'/lazy-css-static': {
135+
id: '/lazy-css-static'
136+
path: '/lazy-css-static'
137+
fullPath: '/lazy-css-static'
138+
preLoaderRoute: typeof LazyCssStaticRouteImport
139+
parentRoute: typeof rootRouteImport
140+
}
141+
'/lazy-css-lazy': {
142+
id: '/lazy-css-lazy'
143+
path: '/lazy-css-lazy'
144+
fullPath: '/lazy-css-lazy'
145+
preLoaderRoute: typeof LazyCssLazyRouteImport
146+
parentRoute: typeof rootRouteImport
147+
}
95148
'/': {
96149
id: '/'
97150
path: '/'
@@ -104,6 +157,8 @@ declare module '@tanstack/react-router' {
104157

105158
const rootRouteChildren: RootRouteChildren = {
106159
IndexRoute: IndexRoute,
160+
LazyCssLazyRoute: LazyCssLazyRoute,
161+
LazyCssStaticRoute: LazyCssStaticRoute,
107162
ModulesRoute: ModulesRoute,
108163
QuotesRoute: QuotesRoute,
109164
SassMixinRoute: SassMixinRoute,

e2e/react-start/css-modules/src/routes/__root.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,20 @@ function RootComponent() {
6262
>
6363
Quoted CSS
6464
</Link>
65+
<Link
66+
to="/lazy-css-static"
67+
style={{ color: '#0284c7', textDecoration: 'none' }}
68+
data-testid="nav-lazy-css-static"
69+
>
70+
Lazy CSS Static
71+
</Link>
72+
<Link
73+
to="/lazy-css-lazy"
74+
style={{ color: '#0284c7', textDecoration: 'none' }}
75+
data-testid="nav-lazy-css-lazy"
76+
>
77+
Lazy CSS Lazy
78+
</Link>
6579
</nav>
6680

6781
<main style={{ padding: '20px' }}>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as React from 'react'
2+
import { createFileRoute } from '@tanstack/react-router'
3+
4+
const LazySharedWidget = React.lazy(
5+
() => import('~/components/shared-widget-lazy'),
6+
)
7+
8+
export const Route = createFileRoute('/lazy-css-lazy')({
9+
component: LazyCssLazyRoute,
10+
})
11+
12+
function LazyCssLazyRoute() {
13+
return (
14+
<div>
15+
<h1 data-testid="lazy-css-lazy-heading">Lazy CSS Repro - Lazy Route</h1>
16+
<p>
17+
This route renders the same widget through React.lazy so the CSS only
18+
exists behind a dynamic import boundary.
19+
</p>
20+
21+
<React.Suspense
22+
fallback={<div data-testid="shared-widget-loading">Loading...</div>}
23+
>
24+
<LazySharedWidget />
25+
</React.Suspense>
26+
</div>
27+
)
28+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { ClientOnly, createFileRoute } from '@tanstack/react-router'
2+
import { SharedWidget } from '~/components/shared-widget'
3+
4+
export const Route = createFileRoute('/lazy-css-static')({
5+
component: LazyCssStaticRoute,
6+
})
7+
8+
function LazyCssStaticRoute() {
9+
return (
10+
<div>
11+
<h1>Lazy CSS Repro - Static Route</h1>
12+
<p>
13+
This route statically imports the shared widget so its CSS is present in
14+
the SSR head.
15+
</p>
16+
17+
<ClientOnly>
18+
<div data-testid="lazy-css-static-hydrated">hydrated</div>
19+
</ClientOnly>
20+
21+
<SharedWidget />
22+
</div>
23+
)
24+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
.widget {
2+
background: rgb(255, 247, 237);
3+
border: 2px solid rgb(249, 115, 22);
4+
border-radius: 14px;
5+
padding: 20px;
6+
}
7+
8+
.title {
9+
color: rgb(154, 52, 18);
10+
font-size: 20px;
11+
font-weight: 700;
12+
margin-bottom: 8px;
13+
}
14+
15+
.content {
16+
color: rgb(194, 65, 12);
17+
font-size: 14px;
18+
line-height: 1.5;
19+
}

e2e/react-start/css-modules/tests/css.spec.ts renamed to e2e/react-start/css-modules/tests/css.dev.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const whitelistErrors = [
88
'Failed to load resource: net::ERR_NAME_NOT_RESOLVED',
99
'Failed to load resource: the server responded with a status of 504',
1010
]
11+
test.skip(process.env.MODE === 'prod', 'Dev-only repro')
1112

1213
test.describe('CSS styles in SSR (dev mode)', () => {
1314
test.use({ whitelistErrors })

0 commit comments

Comments
 (0)