Skip to content

Commit ed9c43d

Browse files
fix: dedupe repeated css assets in the start manifest (#7030)
1 parent 7640f6e commit ed9c43d

File tree

3 files changed

+554
-83
lines changed

3 files changed

+554
-83
lines changed

.changeset/fair-rivers-drum.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+
Deduplicate CSS assets in the Start manifest so shared stylesheets are not repeated within a route entry or across an active parent-child route chain.

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

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ interface ManifestAssetResolvers {
3131
getStylesheetAsset: (cssFile: string) => RouterManagedTag
3232
}
3333

34-
type DedupePreloadRoute = {
34+
type DedupeRoute = {
3535
preloads?: Array<ManifestAssetLink>
36+
assets?: Array<RouterManagedTag>
3637
children?: Array<string>
3738
}
3839

@@ -158,7 +159,7 @@ export function buildStartManifest(options: {
158159
assetResolvers,
159160
})
160161

161-
dedupeNestedRoutePreloads(routes[rootRouteId]!, routes)
162+
dedupeNestedRouteManifestEntries(rootRouteId, routes[rootRouteId]!, routes)
162163

163164
// Prune routes with no assets or preloads from the manifest
164165
for (const routeId of Object.keys(routes)) {
@@ -418,6 +419,20 @@ export function createChunkCssAssetCollector(options: {
418419
const assetsByChunk = new Map<Rollup.OutputChunk, Array<RouterManagedTag>>()
419420
const stateByChunk = new Map<Rollup.OutputChunk, number>()
420421

422+
const appendAsset = (
423+
assets: Array<RouterManagedTag>,
424+
seenAssets: Set<string>,
425+
asset: RouterManagedTag,
426+
) => {
427+
const identity = getAssetIdentity(asset)
428+
if (seenAssets.has(identity)) {
429+
return
430+
}
431+
432+
seenAssets.add(identity)
433+
assets.push(asset)
434+
}
435+
421436
const getChunkCssAssets = (
422437
chunk: Rollup.OutputChunk,
423438
): Array<RouterManagedTag> => {
@@ -432,9 +447,10 @@ export function createChunkCssAssetCollector(options: {
432447
stateByChunk.set(chunk, VISITING_CHUNK)
433448

434449
const assets: Array<RouterManagedTag> = []
450+
const seenAssets = new Set<string>()
435451

436452
for (const cssFile of chunk.viteMetadata?.importedCss ?? []) {
437-
assets.push(options.getStylesheetAsset(cssFile))
453+
appendAsset(assets, seenAssets, options.getStylesheetAsset(cssFile))
438454
}
439455

440456
for (let i = 0; i < chunk.imports.length; i++) {
@@ -445,7 +461,7 @@ export function createChunkCssAssetCollector(options: {
445461

446462
const importedAssets = getChunkCssAssets(importedChunk)
447463
for (let j = 0; j < importedAssets.length; j++) {
448-
assets.push(importedAssets[j]!)
464+
appendAsset(assets, seenAssets, importedAssets[j]!)
449465
}
450466
}
451467

@@ -510,12 +526,15 @@ export function buildRouteManifestRoutes(options: {
510526
return routes
511527
}
512528

513-
export function dedupeNestedRoutePreloads(
514-
route: DedupePreloadRoute,
515-
routesById: Record<string, DedupePreloadRoute>,
529+
function dedupeNestedRouteManifestEntries(
530+
routeId: string,
531+
route: DedupeRoute,
532+
routesById: Record<string, DedupeRoute>,
516533
seenPreloads = new Set<string>(),
534+
seenAssets = new Set<string>(),
517535
) {
518536
let routePreloads = route.preloads
537+
let routeAssets = route.assets
519538

520539
if (routePreloads && routePreloads.length > 0) {
521540
let dedupedPreloads: Array<ManifestAssetLink> | undefined
@@ -544,12 +563,49 @@ export function dedupeNestedRoutePreloads(
544563
}
545564
}
546565

566+
if (routeAssets && routeAssets.length > 0) {
567+
let dedupedAssets: Array<RouterManagedTag> | undefined
568+
569+
for (let i = 0; i < routeAssets.length; i++) {
570+
const asset = routeAssets[i]!
571+
const identity = getAssetIdentity(asset)
572+
573+
if (seenAssets.has(identity)) {
574+
if (dedupedAssets === undefined) {
575+
dedupedAssets = routeAssets.slice(0, i)
576+
}
577+
continue
578+
}
579+
580+
seenAssets.add(identity)
581+
582+
if (dedupedAssets) {
583+
dedupedAssets.push(asset)
584+
}
585+
}
586+
587+
if (dedupedAssets) {
588+
routeAssets = dedupedAssets
589+
route.assets = dedupedAssets
590+
}
591+
}
592+
547593
if (route.children) {
548594
for (const childRouteId of route.children) {
549-
dedupeNestedRoutePreloads(
550-
routesById[childRouteId]!,
595+
const childRoute = routesById[childRouteId]
596+
597+
if (!childRoute) {
598+
throw new Error(
599+
`Route tree references child route ${childRouteId} from ${routeId}, but no route entry was found`,
600+
)
601+
}
602+
603+
dedupeNestedRouteManifestEntries(
604+
childRouteId,
605+
childRoute,
551606
routesById,
552607
seenPreloads,
608+
seenAssets,
553609
)
554610
}
555611
}
@@ -559,4 +615,10 @@ export function dedupeNestedRoutePreloads(
559615
seenPreloads.delete(resolveManifestAssetLink(routePreloads[i]!).href)
560616
}
561617
}
618+
619+
if (routeAssets) {
620+
for (let i = routeAssets.length - 1; i >= 0; i--) {
621+
seenAssets.delete(getAssetIdentity(routeAssets[i]!))
622+
}
623+
}
562624
}

0 commit comments

Comments
 (0)