Skip to content

Commit 7da877a

Browse files
committed
fix: also filter route-owned CSS from root route's entry assets
When Rolldown hoists route chunks into the entry's static imports, route-owned CSS also leaks into __root__.assets via getChunkCssAssets recursive traversal, causing every page to load all route CSS. - Collect route-owned CSS paths into routeChunkFileNames alongside JS - Filter getChunkCssAssets for root route the same way as getChunkPreloads - Add test: route-owned CSS does not leak into root assets
1 parent 2c61610 commit 7da877a

File tree

2 files changed

+60
-7
lines changed

2 files changed

+60
-7
lines changed

packages/start-plugin-core/src/start-manifest-plugin/manifestBuilder.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -395,25 +395,33 @@ export function buildRouteManifestRoutes(options: {
395395
}
396396
}
397397

398-
// Collect filenames of chunks that belong to specific routes so we can
399-
// exclude them from the root route's entry-chunk preloads. Without this,
400-
// when Rolldown moves route chunks into the entry's static `imports`
401-
// (common in large projects), every route chunk leaks into root preloads
402-
// and gets modulepreloaded on every page.
398+
// Collect filenames of chunks and CSS that belong to specific routes so we
399+
// can exclude them from the root route's entry-chunk preloads and assets.
400+
// Without this, when Rolldown moves route chunks into the entry's static
401+
// `imports` (common in large projects), every route chunk leaks into root
402+
// preloads and every route-owned CSS leaks into root assets.
403403
const routeChunkFileNames = new Set<string>()
404404
for (const chunks of options.routeChunksByFilePath.values()) {
405405
for (const chunk of chunks) {
406406
routeChunkFileNames.add(
407407
options.assetResolvers.getAssetPath(chunk.fileName),
408408
)
409+
for (const cssFile of chunk.css) {
410+
routeChunkFileNames.add(
411+
options.assetResolvers.getAssetPath(cssFile),
412+
)
413+
}
409414
}
410415
}
411416

412417
const rootRoute = (routes[rootRouteId] = routes[rootRouteId] || {})
413418
mergeRouteChunkData({
414419
route: rootRoute,
415420
chunk: options.entryChunk,
416-
getChunkCssAssets,
421+
getChunkCssAssets: (chunk) => {
422+
const assets = getChunkCssAssets(chunk)
423+
return assets.filter((a) => !routeChunkFileNames.has(a.attrs.href))
424+
},
417425
getChunkPreloads: (chunk) => {
418426
const preloads = options.assetResolvers.getChunkPreloads(chunk)
419427
return preloads.filter((p) => !routeChunkFileNames.has(p))

packages/start-plugin-core/tests/start-manifest-plugin/manifestBuilder.test.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1156,7 +1156,7 @@ describe('multi-chunk routes must merge assets and preloads', () => {
11561156
})
11571157
})
11581158

1159-
describe('root route should not include route-owned preloads', () => {
1159+
describe('root route should not include route-owned preloads or assets', () => {
11601160
test('entry chunk that statically imports route chunks does not leak them into root preloads', () => {
11611161
const routeChunk = makeChunk({
11621162
fileName: 'about-chunk.js',
@@ -1231,6 +1231,51 @@ describe('root route should not include route-owned preloads', () => {
12311231
// Route chunk should be filtered out from root preloads
12321232
expect(rootPreloads).not.toContain('/assets/about-chunk.js')
12331233
})
1234+
1235+
test('route-owned CSS does not leak into root assets', () => {
1236+
const routeChunk = makeChunk({
1237+
fileName: 'about-chunk.js',
1238+
moduleIds: ['/routes/about.tsx?tsr-split=component'],
1239+
importedCss: ['about.css'],
1240+
})
1241+
// Entry chunk statically imports the route chunk (happens in large projects with Rolldown)
1242+
const entryChunk = makeChunk({
1243+
fileName: 'entry.js',
1244+
isEntry: true,
1245+
imports: ['about-chunk.js'],
1246+
importedCss: ['entry.css'],
1247+
})
1248+
1249+
const manifest = buildStartManifest({
1250+
clientBuild: normalizeViteClientBuild({
1251+
'entry.js': entryChunk,
1252+
'about-chunk.js': routeChunk,
1253+
}),
1254+
routeTreeRoutes: {
1255+
__root__: { children: ['/about'] } as any,
1256+
'/about': { filePath: '/routes/about.tsx' },
1257+
},
1258+
basePath: '/assets',
1259+
})
1260+
1261+
const rootAssets = manifest.routes['__root__']?.assets ?? []
1262+
const aboutAssets = manifest.routes['/about']?.assets ?? []
1263+
1264+
// Route CSS should be in its own route's assets
1265+
expect(aboutAssets).toContainEqual(
1266+
expect.objectContaining({ attrs: expect.objectContaining({ href: '/assets/about.css' }) }),
1267+
)
1268+
1269+
// Route CSS should NOT leak into root assets
1270+
expect(rootAssets).not.toContainEqual(
1271+
expect.objectContaining({ attrs: expect.objectContaining({ href: '/assets/about.css' }) }),
1272+
)
1273+
1274+
// Entry CSS should still be in root assets
1275+
expect(rootAssets).toContainEqual(
1276+
expect.objectContaining({ attrs: expect.objectContaining({ href: '/assets/entry.css' }) }),
1277+
)
1278+
})
12341279
})
12351280

12361281
describe('buildStartManifest route pruning', () => {

0 commit comments

Comments
 (0)