@tanstack/start-plugin-core 1.167.33 → 1.167.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,34 +1,68 @@
1
1
  import { ENTRY_POINTS, START_ENVIRONMENT_NAMES } from "../../constants.js";
2
2
  import { createVirtualModule } from "../createVirtualModule.js";
3
- import { buildStartManifest, serializeStartManifest } from "../../start-manifest-plugin/manifestBuilder.js";
3
+ import { normalizeViteClientBuild } from "./normalized-client-build.js";
4
+ import { buildStartManifest, createManifestAssetResolvers, serializeStartManifest } from "../../start-manifest-plugin/manifestBuilder.js";
5
+ import { rootRouteId } from "@tanstack/router-core";
4
6
  import { VIRTUAL_MODULES } from "@tanstack/start-server-core";
5
7
  import { joinURL } from "ufo";
6
8
  //#region src/vite/start-manifest-plugin/plugin.ts
7
9
  function startManifestPlugin(opts) {
8
- return createVirtualModule({
10
+ let clientBuild;
11
+ let cssCodeSplitDisabledFileName;
12
+ return [{
13
+ name: "tanstack-start:start-manifest-capture-client-build",
14
+ applyToEnvironment(environment) {
15
+ return environment.name === START_ENVIRONMENT_NAMES.client;
16
+ },
17
+ enforce: "post",
18
+ generateBundle(_options, bundle) {
19
+ if (this.environment.name !== START_ENVIRONMENT_NAMES.client) throw new Error(`Unexpected environment for client build capture: ${this.environment.name}`);
20
+ clientBuild = normalizeViteClientBuild(bundle);
21
+ cssCodeSplitDisabledFileName = getAssetFileNameByName(bundle, "style.css");
22
+ }
23
+ }, createVirtualModule({
9
24
  name: "tanstack-start:start-manifest-plugin",
10
25
  moduleId: VIRTUAL_MODULES.startManifest,
11
26
  enforce: "pre",
12
27
  load() {
13
28
  const { resolvedStartConfig } = opts.getConfig();
14
- if (this.environment.name !== START_ENVIRONMENT_NAMES.server) return "export default {}";
15
- if (this.environment.config.command === "serve") return `export const tsrStartManifest = () => ({
16
- routes: {},
17
- clientEntry: '${joinURL(resolvedStartConfig.basePaths.publicBase, "@id", ENTRY_POINTS.client)}',
18
- })`;
29
+ const clientEntry = joinURL(resolvedStartConfig.basePaths.publicBase, "@id", ENTRY_POINTS.client);
30
+ if (this.environment.name !== START_ENVIRONMENT_NAMES.server) return getEmptyStartManifestModule(clientEntry);
31
+ if (this.environment.config.command === "serve") return getEmptyStartManifestModule(clientEntry);
19
32
  const routeTreeRoutes = globalThis.TSS_ROUTES_MANIFEST;
20
- const clientBuild = opts.getClientBuild();
21
- if (!clientBuild) return `export const tsrStartManifest = () => ({
22
- routes: {},
23
- clientEntry: '${joinURL(resolvedStartConfig.basePaths.publicBase, "@id", ENTRY_POINTS.client)}',
24
- })`;
33
+ if (!clientBuild) return getEmptyStartManifestModule(clientEntry);
25
34
  return `export const tsrStartManifest = () => (${serializeStartManifest(buildStartManifest({
26
35
  clientBuild,
27
36
  routeTreeRoutes,
28
- basePath: resolvedStartConfig.basePaths.publicBase
37
+ basePath: resolvedStartConfig.basePaths.publicBase,
38
+ additionalRouteAssets: getViteAdditionalRouteAssets({
39
+ cssCodeSplitDisabledFileName,
40
+ basePath: resolvedStartConfig.basePaths.publicBase,
41
+ cssCodeSplit: this.environment.config.build.cssCodeSplit
42
+ })
29
43
  }))})`;
30
44
  }
31
- });
45
+ })];
46
+ }
47
+ function getViteAdditionalRouteAssets(options) {
48
+ if (options.cssCodeSplit !== false) return;
49
+ if (!options.cssCodeSplitDisabledFileName) throw new Error("TanStack Start could not find Vite's generated `style.css` manifest entry while `build.cssCodeSplit` is disabled");
50
+ const { getStylesheetAsset } = createManifestAssetResolvers(options.basePath);
51
+ return { [rootRouteId]: [getStylesheetAsset(options.cssCodeSplitDisabledFileName)] };
52
+ }
53
+ function getAssetFileNameByName(bundle, assetName) {
54
+ for (const fileName in bundle) {
55
+ const bundleEntry = bundle[fileName];
56
+ if (bundleEntry.type !== "asset") continue;
57
+ if (bundleEntry.name === assetName) return fileName;
58
+ if ("names" in bundleEntry && bundleEntry.names.includes(assetName)) return fileName;
59
+ }
60
+ }
61
+ function getEmptyStartManifestModule(clientEntry) {
62
+ return `export const tsrStartManifest = () => ({
63
+ routes: {},
64
+ clientEntry: '${clientEntry}',
65
+ })`;
32
66
  }
33
67
  //#endregion
34
68
  export { startManifestPlugin };
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","names":[],"sources":["../../../../src/vite/start-manifest-plugin/plugin.ts"],"sourcesContent":["import { joinURL } from 'ufo'\nimport { VIRTUAL_MODULES } from '@tanstack/start-server-core'\nimport { ENTRY_POINTS, START_ENVIRONMENT_NAMES } from '../../constants'\nimport {\n buildStartManifest,\n serializeStartManifest,\n} from '../../start-manifest-plugin/manifestBuilder'\nimport { createVirtualModule } from '../createVirtualModule'\nimport type { GetConfigFn, NormalizedClientBuild } from '../../types'\nimport type { PluginOption } from 'vite'\n\nexport function startManifestPlugin(opts: {\n getClientBuild: () => NormalizedClientBuild | undefined\n getConfig: GetConfigFn\n}): PluginOption {\n return createVirtualModule({\n name: 'tanstack-start:start-manifest-plugin',\n moduleId: VIRTUAL_MODULES.startManifest,\n enforce: 'pre',\n load() {\n const { resolvedStartConfig } = opts.getConfig()\n if (this.environment.name !== START_ENVIRONMENT_NAMES.server) {\n return 'export default {}'\n }\n\n if (this.environment.config.command === 'serve') {\n return `export const tsrStartManifest = () => ({\n routes: {},\n clientEntry: '${joinURL(resolvedStartConfig.basePaths.publicBase, '@id', ENTRY_POINTS.client)}',\n })`\n }\n\n const routeTreeRoutes = globalThis.TSS_ROUTES_MANIFEST\n const clientBuild = opts.getClientBuild()\n // TODO this needs further discussion with vite-rsc, this is a temporary workaround\n // If the client bundle isn't available yet (e.g., during RSC scan builds),\n // return a dummy manifest. The real manifest will be generated in the actual build.\n if (!clientBuild) {\n return `export const tsrStartManifest = () => ({\n routes: {},\n clientEntry: '${joinURL(resolvedStartConfig.basePaths.publicBase, '@id', ENTRY_POINTS.client)}',\n })`\n }\n const startManifest = buildStartManifest({\n clientBuild,\n routeTreeRoutes,\n basePath: resolvedStartConfig.basePaths.publicBase,\n })\n\n return `export const tsrStartManifest = () => (${serializeStartManifest(startManifest)})`\n },\n })\n}\n"],"mappings":";;;;;;AAWA,SAAgB,oBAAoB,MAGnB;AACf,QAAO,oBAAoB;EACzB,MAAM;EACN,UAAU,gBAAgB;EAC1B,SAAS;EACT,OAAO;GACL,MAAM,EAAE,wBAAwB,KAAK,WAAW;AAChD,OAAI,KAAK,YAAY,SAAS,wBAAwB,OACpD,QAAO;AAGT,OAAI,KAAK,YAAY,OAAO,YAAY,QACtC,QAAO;;4BAEa,QAAQ,oBAAoB,UAAU,YAAY,OAAO,aAAa,OAAO,CAAC;;GAIpG,MAAM,kBAAkB,WAAW;GACnC,MAAM,cAAc,KAAK,gBAAgB;AAIzC,OAAI,CAAC,YACH,QAAO;;8BAEe,QAAQ,oBAAoB,UAAU,YAAY,OAAO,aAAa,OAAO,CAAC;;AAStG,UAAO,0CAA0C,uBAN3B,mBAAmB;IACvC;IACA;IACA,UAAU,oBAAoB,UAAU;IACzC,CAAC,CAEoF,CAAC;;EAE1F,CAAC"}
1
+ {"version":3,"file":"plugin.js","names":[],"sources":["../../../../src/vite/start-manifest-plugin/plugin.ts"],"sourcesContent":["import { joinURL } from 'ufo'\nimport { VIRTUAL_MODULES } from '@tanstack/start-server-core'\nimport { rootRouteId } from '@tanstack/router-core'\nimport { ENTRY_POINTS, START_ENVIRONMENT_NAMES } from '../../constants'\nimport {\n buildStartManifest,\n createManifestAssetResolvers,\n normalizeViteClientBuild,\n serializeStartManifest,\n} from '../../start-manifest-plugin/manifestBuilder'\nimport { createVirtualModule } from '../createVirtualModule'\nimport type { GetConfigFn, NormalizedClientBuild } from '../../types'\nimport type { PluginOption, Rollup } from 'vite'\n\nexport function startManifestPlugin(opts: {\n getConfig: GetConfigFn\n}): PluginOption {\n let clientBuild: NormalizedClientBuild | undefined\n let cssCodeSplitDisabledFileName: string | undefined\n\n return [\n {\n name: 'tanstack-start:start-manifest-capture-client-build',\n applyToEnvironment(environment) {\n return environment.name === START_ENVIRONMENT_NAMES.client\n },\n enforce: 'post',\n generateBundle(_options, bundle) {\n if (this.environment.name !== START_ENVIRONMENT_NAMES.client) {\n throw new Error(\n `Unexpected environment for client build capture: ${this.environment.name}`,\n )\n }\n\n clientBuild = normalizeViteClientBuild(bundle)\n cssCodeSplitDisabledFileName = getAssetFileNameByName(\n bundle,\n 'style.css',\n )\n },\n },\n createVirtualModule({\n name: 'tanstack-start:start-manifest-plugin',\n moduleId: VIRTUAL_MODULES.startManifest,\n enforce: 'pre',\n load() {\n const { resolvedStartConfig } = opts.getConfig()\n const clientEntry = joinURL(\n resolvedStartConfig.basePaths.publicBase,\n '@id',\n ENTRY_POINTS.client,\n )\n\n if (this.environment.name !== START_ENVIRONMENT_NAMES.server) {\n return getEmptyStartManifestModule(clientEntry)\n }\n\n if (this.environment.config.command === 'serve') {\n return getEmptyStartManifestModule(clientEntry)\n }\n\n const routeTreeRoutes = globalThis.TSS_ROUTES_MANIFEST\n // TODO this needs further discussion with vite-rsc, this is a temporary workaround\n // If the client bundle isn't available yet (e.g., during RSC scan builds),\n // return a dummy manifest. The real manifest will be generated in the actual build.\n if (!clientBuild) {\n return getEmptyStartManifestModule(clientEntry)\n }\n const startManifest = buildStartManifest({\n clientBuild,\n routeTreeRoutes,\n basePath: resolvedStartConfig.basePaths.publicBase,\n additionalRouteAssets: getViteAdditionalRouteAssets({\n cssCodeSplitDisabledFileName,\n basePath: resolvedStartConfig.basePaths.publicBase,\n cssCodeSplit: this.environment.config.build.cssCodeSplit,\n }),\n })\n\n return `export const tsrStartManifest = () => (${serializeStartManifest(startManifest)})`\n },\n }),\n ]\n}\n\nfunction getViteAdditionalRouteAssets(options: {\n cssCodeSplitDisabledFileName: string | undefined\n basePath: string\n cssCodeSplit: boolean | undefined\n}) {\n if (options.cssCodeSplit !== false) {\n return undefined\n }\n\n if (!options.cssCodeSplitDisabledFileName) {\n throw new Error(\n \"TanStack Start could not find Vite's generated `style.css` manifest entry while `build.cssCodeSplit` is disabled\",\n )\n }\n\n const { getStylesheetAsset } = createManifestAssetResolvers(options.basePath)\n\n return {\n [rootRouteId]: [getStylesheetAsset(options.cssCodeSplitDisabledFileName)],\n }\n}\n\nfunction getAssetFileNameByName(\n bundle: Rollup.OutputBundle,\n assetName: string,\n) {\n for (const fileName in bundle) {\n const bundleEntry = bundle[fileName]!\n\n if (bundleEntry.type !== 'asset') {\n continue\n }\n\n if (bundleEntry.name === assetName) {\n return fileName\n }\n\n if ('names' in bundleEntry && bundleEntry.names.includes(assetName)) {\n return fileName\n }\n }\n\n return undefined\n}\n\nfunction getEmptyStartManifestModule(clientEntry: string) {\n return `export const tsrStartManifest = () => ({\n routes: {},\n clientEntry: '${clientEntry}',\n })`\n}\n"],"mappings":";;;;;;;;AAcA,SAAgB,oBAAoB,MAEnB;CACf,IAAI;CACJ,IAAI;AAEJ,QAAO,CACL;EACE,MAAM;EACN,mBAAmB,aAAa;AAC9B,UAAO,YAAY,SAAS,wBAAwB;;EAEtD,SAAS;EACT,eAAe,UAAU,QAAQ;AAC/B,OAAI,KAAK,YAAY,SAAS,wBAAwB,OACpD,OAAM,IAAI,MACR,oDAAoD,KAAK,YAAY,OACtE;AAGH,iBAAc,yBAAyB,OAAO;AAC9C,kCAA+B,uBAC7B,QACA,YACD;;EAEJ,EACD,oBAAoB;EAClB,MAAM;EACN,UAAU,gBAAgB;EAC1B,SAAS;EACT,OAAO;GACL,MAAM,EAAE,wBAAwB,KAAK,WAAW;GAChD,MAAM,cAAc,QAClB,oBAAoB,UAAU,YAC9B,OACA,aAAa,OACd;AAED,OAAI,KAAK,YAAY,SAAS,wBAAwB,OACpD,QAAO,4BAA4B,YAAY;AAGjD,OAAI,KAAK,YAAY,OAAO,YAAY,QACtC,QAAO,4BAA4B,YAAY;GAGjD,MAAM,kBAAkB,WAAW;AAInC,OAAI,CAAC,YACH,QAAO,4BAA4B,YAAY;AAajD,UAAO,0CAA0C,uBAX3B,mBAAmB;IACvC;IACA;IACA,UAAU,oBAAoB,UAAU;IACxC,uBAAuB,6BAA6B;KAClD;KACA,UAAU,oBAAoB,UAAU;KACxC,cAAc,KAAK,YAAY,OAAO,MAAM;KAC7C,CAAC;IACH,CAAC,CAEoF,CAAC;;EAE1F,CAAC,CACH;;AAGH,SAAS,6BAA6B,SAInC;AACD,KAAI,QAAQ,iBAAiB,MAC3B;AAGF,KAAI,CAAC,QAAQ,6BACX,OAAM,IAAI,MACR,mHACD;CAGH,MAAM,EAAE,uBAAuB,6BAA6B,QAAQ,SAAS;AAE7E,QAAO,GACJ,cAAc,CAAC,mBAAmB,QAAQ,6BAA6B,CAAC,EAC1E;;AAGH,SAAS,uBACP,QACA,WACA;AACA,MAAK,MAAM,YAAY,QAAQ;EAC7B,MAAM,cAAc,OAAO;AAE3B,MAAI,YAAY,SAAS,QACvB;AAGF,MAAI,YAAY,SAAS,UACvB,QAAO;AAGT,MAAI,WAAW,eAAe,YAAY,MAAM,SAAS,UAAU,CACjE,QAAO;;;AAOb,SAAS,4BAA4B,aAAqB;AACxD,QAAO;;sBAEa,YAAY"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/start-plugin-core",
3
- "version": "1.167.33",
3
+ "version": "1.167.35",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -67,12 +67,12 @@
67
67
  "vitefu": "^1.1.1",
68
68
  "xmlbuilder2": "^4.0.3",
69
69
  "zod": "^3.24.2",
70
- "@tanstack/router-core": "1.168.14",
71
- "@tanstack/router-generator": "1.166.31",
72
- "@tanstack/router-plugin": "1.167.21",
70
+ "@tanstack/router-core": "1.168.15",
71
+ "@tanstack/router-generator": "1.166.32",
72
+ "@tanstack/router-plugin": "1.167.22",
73
73
  "@tanstack/router-utils": "1.161.6",
74
- "@tanstack/start-client-core": "1.167.16",
75
- "@tanstack/start-server-core": "1.167.18"
74
+ "@tanstack/start-client-core": "1.167.17",
75
+ "@tanstack/start-server-core": "1.167.19"
76
76
  },
77
77
  "devDependencies": {
78
78
  "@types/babel__code-frame": "^7.0.6",
@@ -10,8 +10,6 @@ import {
10
10
  import type { ManifestAssetLink, RouterManagedTag } from '@tanstack/router-core'
11
11
  import type { NormalizedClientBuild, NormalizedClientChunk } from '../types'
12
12
 
13
- const ROUTER_MANAGED_MODE = 1
14
- const NON_ROUTE_DYNAMIC_MODE = 2
15
13
  const VISITING_CHUNK = 1
16
14
 
17
15
  type RouteTreeRoute = {
@@ -27,7 +25,6 @@ interface ScannedClientChunks {
27
25
  entryChunk: NormalizedClientChunk
28
26
  chunksByFileName: ReadonlyMap<string, NormalizedClientChunk>
29
27
  routeChunksByFilePath: ReadonlyMap<string, Array<NormalizedClientChunk>>
30
- routeEntryChunks: ReadonlySet<NormalizedClientChunk>
31
28
  }
32
29
 
33
30
  interface ManifestAssetResolvers {
@@ -114,19 +111,25 @@ export function appendUniqueAssets(
114
111
  }
115
112
 
116
113
  function getAssetIdentity(asset: RouterManagedTag) {
117
- if (asset.tag === 'link' || asset.tag === 'script') {
118
- const attrs = asset.attrs ?? {}
119
- return [
120
- asset.tag,
121
- 'href' in attrs ? String(attrs.href) : '',
122
- 'src' in attrs ? String(attrs.src) : '',
123
- 'rel' in attrs ? String(attrs.rel) : '',
124
- 'type' in attrs ? String(attrs.type) : '',
125
- asset.children ?? '',
126
- ].join('|')
114
+ return JSON.stringify({
115
+ tag: asset.tag,
116
+ attrs: normalizeAssetAttrs(asset.attrs),
117
+ children: 'children' in asset ? (asset.children ?? null) : null,
118
+ })
119
+ }
120
+
121
+ function normalizeAssetAttrs(attrs: Record<string, any> | undefined) {
122
+ if (!attrs) {
123
+ return null
127
124
  }
128
125
 
129
- return JSON.stringify(asset)
126
+ const entries = Object.entries(attrs)
127
+ if (entries.length === 0) {
128
+ return null
129
+ }
130
+
131
+ entries.sort(([left], [right]) => left.localeCompare(right))
132
+ return Object.fromEntries(entries)
130
133
  }
131
134
 
132
135
  function mergeRouteChunkData(options: {
@@ -149,17 +152,12 @@ export function buildStartManifest(options: {
149
152
  clientBuild: NormalizedClientBuild
150
153
  routeTreeRoutes: RouteTreeRoutes
151
154
  basePath: string
155
+ additionalRouteAssets?: Partial<
156
+ Record<string, ReadonlyArray<RouterManagedTag>>
157
+ >
152
158
  }): StartManifest {
153
159
  const scannedChunks = scanClientChunks(options.clientBuild)
154
- const hashedCssFiles = collectDynamicImportCss(
155
- scannedChunks.routeEntryChunks,
156
- scannedChunks.chunksByFileName,
157
- scannedChunks.entryChunk,
158
- )
159
- const assetResolvers = createManifestAssetResolvers({
160
- basePath: options.basePath,
161
- hashedCssFiles,
162
- })
160
+ const assetResolvers = createManifestAssetResolvers(options.basePath)
163
161
 
164
162
  const routes = buildRouteManifestRoutes({
165
163
  routeTreeRoutes: options.routeTreeRoutes,
@@ -167,6 +165,7 @@ export function buildStartManifest(options: {
167
165
  chunksByFileName: scannedChunks.chunksByFileName,
168
166
  entryChunk: scannedChunks.entryChunk,
169
167
  assetResolvers,
168
+ additionalRouteAssets: options.additionalRouteAssets,
170
169
  })
171
170
 
172
171
  dedupeNestedRouteManifestEntries(rootRouteId, routes[rootRouteId]!, routes)
@@ -202,13 +201,10 @@ export function scanClientChunks(
202
201
  throw new Error(`Missing entry chunk: ${clientBuild.entryChunkFileName}`)
203
202
  }
204
203
 
205
- const routeEntryChunks = new Set<NormalizedClientChunk>()
206
204
  const routeChunksByFilePath = new Map<string, Array<NormalizedClientChunk>>()
207
205
 
208
206
  for (const chunk of clientBuild.chunksByFileName.values()) {
209
207
  if (chunk.routeFilePaths.length > 0) {
210
- routeEntryChunks.add(chunk)
211
-
212
208
  for (const routeFilePath of chunk.routeFilePaths) {
213
209
  let chunks = routeChunksByFilePath.get(routeFilePath)
214
210
  if (chunks === undefined) {
@@ -224,92 +220,12 @@ export function scanClientChunks(
224
220
  entryChunk,
225
221
  chunksByFileName: clientBuild.chunksByFileName,
226
222
  routeChunksByFilePath,
227
- routeEntryChunks,
228
223
  }
229
224
  }
230
225
 
231
- export function collectDynamicImportCss(
232
- routeEntryChunks: ReadonlySet<NormalizedClientChunk>,
233
- chunksByFileName: ReadonlyMap<string, NormalizedClientChunk>,
234
- entryChunk?: NormalizedClientChunk,
235
- ) {
236
- const routerManagedCssFiles = new Set<string>()
237
- const nonRouteDynamicCssFiles = new Set<string>()
238
- const hashedCssFiles = new Set<string>()
239
- const visitedByChunk = new Map<NormalizedClientChunk, number>()
240
- const chunkStack: Array<NormalizedClientChunk> = []
241
- const modeStack: Array<number> = []
242
-
243
- for (const routeEntryChunk of routeEntryChunks) {
244
- chunkStack.push(routeEntryChunk)
245
- modeStack.push(ROUTER_MANAGED_MODE)
246
- }
247
-
248
- if (entryChunk) {
249
- chunkStack.push(entryChunk)
250
- modeStack.push(ROUTER_MANAGED_MODE)
251
- }
252
-
253
- while (chunkStack.length > 0) {
254
- const chunk = chunkStack.pop()!
255
- const mode = modeStack.pop()!
256
- const previousMode = visitedByChunk.get(chunk) ?? 0
257
-
258
- if ((previousMode & mode) === mode) {
259
- continue
260
- }
261
-
262
- visitedByChunk.set(chunk, previousMode | mode)
263
-
264
- if ((mode & ROUTER_MANAGED_MODE) !== 0) {
265
- for (const cssFile of chunk.css) {
266
- routerManagedCssFiles.add(cssFile)
267
- }
268
- }
269
-
270
- if ((mode & NON_ROUTE_DYNAMIC_MODE) !== 0) {
271
- for (const cssFile of chunk.css) {
272
- nonRouteDynamicCssFiles.add(cssFile)
273
- }
274
- }
275
-
276
- for (let i = 0; i < chunk.imports.length; i++) {
277
- const importedChunk = chunksByFileName.get(chunk.imports[i]!)
278
- if (importedChunk) {
279
- chunkStack.push(importedChunk)
280
- modeStack.push(mode)
281
- }
282
- }
283
-
284
- for (let i = 0; i < chunk.dynamicImports.length; i++) {
285
- const dynamicImportedChunk = chunksByFileName.get(
286
- chunk.dynamicImports[i]!,
287
- )
288
- if (dynamicImportedChunk) {
289
- chunkStack.push(dynamicImportedChunk)
290
- modeStack.push(
291
- (mode & NON_ROUTE_DYNAMIC_MODE) !== 0 ||
292
- !routeEntryChunks.has(dynamicImportedChunk)
293
- ? NON_ROUTE_DYNAMIC_MODE
294
- : ROUTER_MANAGED_MODE,
295
- )
296
- }
297
- }
298
- }
299
-
300
- for (const cssFile of routerManagedCssFiles) {
301
- if (nonRouteDynamicCssFiles.has(cssFile)) {
302
- hashedCssFiles.add(cssFile)
303
- }
304
- }
305
-
306
- return hashedCssFiles
307
- }
308
-
309
- export function createManifestAssetResolvers(options: {
310
- basePath: string
311
- hashedCssFiles?: Set<string>
312
- }): ManifestAssetResolvers {
226
+ export function createManifestAssetResolvers(
227
+ basePath: string,
228
+ ): ManifestAssetResolvers {
313
229
  const assetPathByFileName = new Map<string, string>()
314
230
  const stylesheetAssetByFileName = new Map<string, RouterManagedTag>()
315
231
  const preloadsByChunk = new Map<NormalizedClientChunk, Array<string>>()
@@ -320,7 +236,7 @@ export function createManifestAssetResolvers(options: {
320
236
  return cachedPath
321
237
  }
322
238
 
323
- const assetPath = joinURL(options.basePath, fileName)
239
+ const assetPath = joinURL(basePath, fileName)
324
240
  assetPathByFileName.set(fileName, assetPath)
325
241
  return assetPath
326
242
  }
@@ -336,7 +252,7 @@ export function createManifestAssetResolvers(options: {
336
252
  tag: 'link',
337
253
  attrs: {
338
254
  rel: 'stylesheet',
339
- href: options.hashedCssFiles?.has(cssFile) ? `${href}#` : href,
255
+ href,
340
256
  type: 'text/css',
341
257
  },
342
258
  } satisfies RouterManagedTag
@@ -380,15 +296,14 @@ export function createChunkCssAssetCollector(options: {
380
296
 
381
297
  const appendAsset = (
382
298
  assets: Array<RouterManagedTag>,
383
- seenAssets: Set<string>,
299
+ seenAssets: Set<RouterManagedTag>,
384
300
  asset: RouterManagedTag,
385
301
  ) => {
386
- const identity = getAssetIdentity(asset)
387
- if (seenAssets.has(identity)) {
302
+ if (seenAssets.has(asset)) {
388
303
  return
389
304
  }
390
305
 
391
- seenAssets.add(identity)
306
+ seenAssets.add(asset)
392
307
  assets.push(asset)
393
308
  }
394
309
 
@@ -406,7 +321,7 @@ export function createChunkCssAssetCollector(options: {
406
321
  stateByChunk.set(chunk, VISITING_CHUNK)
407
322
 
408
323
  const assets: Array<RouterManagedTag> = []
409
- const seenAssets = new Set<string>()
324
+ const seenAssets = new Set<RouterManagedTag>()
410
325
 
411
326
  for (const cssFile of chunk.css) {
412
327
  appendAsset(assets, seenAssets, options.getStylesheetAsset(cssFile))
@@ -441,6 +356,9 @@ export function buildRouteManifestRoutes(options: {
441
356
  chunksByFileName: ReadonlyMap<string, NormalizedClientChunk>
442
357
  entryChunk: NormalizedClientChunk
443
358
  assetResolvers: ManifestAssetResolvers
359
+ additionalRouteAssets?: Partial<
360
+ Record<string, ReadonlyArray<RouterManagedTag>>
361
+ >
444
362
  }) {
445
363
  const routes: Record<string, RouteTreeRoute> = {}
446
364
  const getChunkCssAssets = createChunkCssAssetCollector({
@@ -485,6 +403,25 @@ export function buildRouteManifestRoutes(options: {
485
403
  getChunkPreloads: options.assetResolvers.getChunkPreloads,
486
404
  })
487
405
 
406
+ if (options.additionalRouteAssets) {
407
+ for (const [routeId, assets] of Object.entries(
408
+ options.additionalRouteAssets,
409
+ )) {
410
+ if (!assets || assets.length === 0) {
411
+ continue
412
+ }
413
+
414
+ if (!(routeId in options.routeTreeRoutes)) {
415
+ throw new Error(
416
+ `expected additionalRouteAssets routeId to exist in routeTreeRoutes: ${routeId}`,
417
+ )
418
+ }
419
+
420
+ const route = (routes[routeId] = routes[routeId] || {})
421
+ route.assets = appendUniqueAssets(route.assets, [...assets])
422
+ }
423
+ }
424
+
488
425
  return routes
489
426
  }
490
427
 
@@ -22,7 +22,6 @@ import {
22
22
  import { devServerPlugin } from './dev-server-plugin/plugin'
23
23
  import { previewServerPlugin } from './preview-server-plugin/plugin'
24
24
  import {
25
- createCaptureClientBuildPlugin,
26
25
  createDevBaseRewritePlugin,
27
26
  createPostBuildPlugin,
28
27
  createVirtualClientEntryPlugin,
@@ -36,12 +35,10 @@ import {
36
35
  } from './output-directory'
37
36
  import { postServerBuild } from './post-server-build'
38
37
  import { serializationAdaptersPlugin } from './serialization-adapters-plugin'
39
- import type { NormalizedClientBuild } from '../types'
40
38
  import type {
41
39
  TanStackStartVitePluginCoreOptions,
42
40
  ViteRscForwardSsrResolverStrategy,
43
41
  } from './types'
44
- import type { StartEnvironmentName } from '../constants'
45
42
  import type { TanStackStartViteInputConfig } from './schema'
46
43
  import type { PluginOption } from 'vite'
47
44
 
@@ -64,16 +61,6 @@ export function tanStackStartVite(
64
61
  // we install a URL rewrite middleware instead of erroring.
65
62
  let needsDevBaseRewrite = false
66
63
 
67
- const capturedClientBuild: Partial<
68
- Record<StartEnvironmentName, NormalizedClientBuild>
69
- > = {}
70
-
71
- function getClientBuild(
72
- envName: StartEnvironmentName,
73
- ): NormalizedClientBuild | undefined {
74
- return capturedClientBuild[envName]
75
- }
76
-
77
64
  const environments: Array<{
78
65
  name: string
79
66
  type: 'client' | 'server'
@@ -242,7 +229,6 @@ export function tanStackStartVite(
242
229
  getClientEntry: () => configContext.resolveEntries().entryPaths.client,
243
230
  }),
244
231
  startManifestPlugin({
245
- getClientBuild: () => getClientBuild(START_ENVIRONMENT_NAMES.client),
246
232
  getConfig,
247
233
  }),
248
234
  // When the vite base and router basepath are misaligned (e.g. base: '/_ui/', basepath: '/'),
@@ -266,9 +252,6 @@ export function tanStackStartVite(
266
252
  serializationAdaptersPlugin({
267
253
  adapters: corePluginOpts.serializationAdapters,
268
254
  }),
269
- createCaptureClientBuildPlugin({
270
- capturedClientBuild,
271
- }),
272
255
  ]
273
256
  }
274
257
 
@@ -1,13 +1,7 @@
1
1
  import { normalizePath } from 'vite'
2
- import { ENTRY_POINTS, START_ENVIRONMENT_NAMES } from '../constants'
2
+ import { ENTRY_POINTS } from '../constants'
3
3
  import { createVirtualModule } from './createVirtualModule'
4
- import { normalizeViteClientBuild } from './start-manifest-plugin/normalized-client-build'
5
- import type {
6
- GetConfigFn,
7
- NormalizedClientBuild,
8
- ResolvedStartConfig,
9
- } from '../types'
10
- import type { StartEnvironmentName } from '../constants'
4
+ import type { GetConfigFn, ResolvedStartConfig } from '../types'
11
5
  import type { PluginOption, ViteBuilder } from 'vite'
12
6
 
13
7
  export function createVirtualClientEntryPlugin(opts: {
@@ -69,28 +63,3 @@ export function createDevBaseRewritePlugin(opts: {
69
63
  },
70
64
  }
71
65
  }
72
-
73
- export function createCaptureClientBuildPlugin(opts: {
74
- capturedClientBuild: Partial<
75
- Record<StartEnvironmentName, NormalizedClientBuild>
76
- >
77
- }): PluginOption {
78
- return {
79
- name: 'tanstack-start:core:capture-bundle',
80
- applyToEnvironment(environment) {
81
- return environment.name === START_ENVIRONMENT_NAMES.client
82
- },
83
- enforce: 'post',
84
- generateBundle(_options, bundle) {
85
- const environment = this.environment.name as StartEnvironmentName
86
-
87
- if (environment !== START_ENVIRONMENT_NAMES.client) {
88
- throw new Error(
89
- `Unexpected environment for client build capture: ${environment}`,
90
- )
91
- }
92
-
93
- opts.capturedClientBuild[environment] = normalizeViteClientBuild(bundle)
94
- },
95
- }
96
- }
@@ -42,6 +42,8 @@ export function normalizeViteClientBuild(
42
42
  const cssFilesBySourcePath = new Map<string, Array<string>>()
43
43
 
44
44
  for (const chunk of chunksByFileName.values()) {
45
+ const bundleEntry = clientBundle[chunk.fileName] as Rollup.OutputChunk
46
+
45
47
  if (chunk.isEntry) {
46
48
  if (entryChunkFileName) {
47
49
  throw new Error(
@@ -60,22 +62,19 @@ export function normalizeViteClientBuild(
60
62
  chunkFileNames.push(chunk.fileName)
61
63
  }
62
64
 
63
- const bundleEntry = clientBundle[chunk.fileName]
64
- if (bundleEntry?.type === 'chunk') {
65
- for (const moduleId of bundleEntry.moduleIds) {
66
- const queryIndex = moduleId.indexOf('?')
67
- const sourcePath =
68
- queryIndex >= 0 ? moduleId.slice(0, queryIndex) : moduleId
69
- if (!sourcePath) continue
70
-
71
- const existing = cssFilesBySourcePath.get(sourcePath)
72
- cssFilesBySourcePath.set(
73
- sourcePath,
74
- existing
75
- ? Array.from(new Set([...existing, ...chunk.css]))
76
- : chunk.css.slice(),
77
- )
78
- }
65
+ for (const moduleId of bundleEntry.moduleIds) {
66
+ const queryIndex = moduleId.indexOf('?')
67
+ const sourcePath =
68
+ queryIndex >= 0 ? moduleId.slice(0, queryIndex) : moduleId
69
+ if (!sourcePath) continue
70
+
71
+ const existing = cssFilesBySourcePath.get(sourcePath)
72
+ cssFilesBySourcePath.set(
73
+ sourcePath,
74
+ existing
75
+ ? Array.from(new Set([...existing, ...chunk.css]))
76
+ : chunk.css.slice(),
77
+ )
79
78
  }
80
79
  }
81
80