@tanstack/start-plugin-core 1.169.1 → 1.169.3

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.
Files changed (39) hide show
  1. package/dist/esm/rsbuild/normalized-client-build.js +9 -1
  2. package/dist/esm/rsbuild/normalized-client-build.js.map +1 -1
  3. package/dist/esm/rsbuild/plugin.js +4 -1
  4. package/dist/esm/rsbuild/plugin.js.map +1 -1
  5. package/dist/esm/rsbuild/schema.d.ts +8 -0
  6. package/dist/esm/rsbuild/virtual-modules.js +14 -13
  7. package/dist/esm/rsbuild/virtual-modules.js.map +1 -1
  8. package/dist/esm/schema.d.ts +25 -0
  9. package/dist/esm/schema.js +4 -1
  10. package/dist/esm/schema.js.map +1 -1
  11. package/dist/esm/start-manifest-plugin/inlineCss.d.ts +6 -0
  12. package/dist/esm/start-manifest-plugin/inlineCss.js +59 -0
  13. package/dist/esm/start-manifest-plugin/inlineCss.js.map +1 -0
  14. package/dist/esm/start-manifest-plugin/manifestBuilder.d.ts +4 -0
  15. package/dist/esm/start-manifest-plugin/manifestBuilder.js +32 -2
  16. package/dist/esm/start-manifest-plugin/manifestBuilder.js.map +1 -1
  17. package/dist/esm/types.d.ts +1 -0
  18. package/dist/esm/vite/planning.d.ts +3 -0
  19. package/dist/esm/vite/planning.js +1 -0
  20. package/dist/esm/vite/planning.js.map +1 -1
  21. package/dist/esm/vite/plugin.js +1 -0
  22. package/dist/esm/vite/plugin.js.map +1 -1
  23. package/dist/esm/vite/schema.d.ts +8 -0
  24. package/dist/esm/vite/start-manifest-plugin/normalized-client-build.js +11 -1
  25. package/dist/esm/vite/start-manifest-plugin/normalized-client-build.js.map +1 -1
  26. package/dist/esm/vite/start-manifest-plugin/plugin.js +2 -1
  27. package/dist/esm/vite/start-manifest-plugin/plugin.js.map +1 -1
  28. package/package.json +8 -7
  29. package/src/rsbuild/normalized-client-build.ts +14 -0
  30. package/src/rsbuild/plugin.ts +7 -0
  31. package/src/rsbuild/virtual-modules.ts +15 -5
  32. package/src/schema.ts +6 -0
  33. package/src/start-manifest-plugin/inlineCss.ts +93 -0
  34. package/src/start-manifest-plugin/manifestBuilder.ts +72 -2
  35. package/src/types.ts +1 -0
  36. package/src/vite/planning.ts +5 -0
  37. package/src/vite/plugin.ts +2 -0
  38. package/src/vite/start-manifest-plugin/normalized-client-build.ts +19 -0
  39. package/src/vite/start-manifest-plugin/plugin.ts +2 -1
@@ -209,20 +209,25 @@ export declare const tanstackStartViteOptionsSchema: z.ZodDefault<z.ZodOptional<
209
209
  entry: z.ZodOptional<z.ZodString>;
210
210
  build: z.ZodDefault<z.ZodOptional<z.ZodObject<{
211
211
  staticNodeEnv: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
212
+ inlineCss: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
212
213
  }, "strip", z.ZodTypeAny, {
213
214
  staticNodeEnv: boolean;
215
+ inlineCss: boolean;
214
216
  }, {
215
217
  staticNodeEnv?: boolean | undefined;
218
+ inlineCss?: boolean | undefined;
216
219
  }>>>;
217
220
  }, "strip", z.ZodTypeAny, {
218
221
  build: {
219
222
  staticNodeEnv: boolean;
223
+ inlineCss: boolean;
220
224
  };
221
225
  entry?: string | undefined;
222
226
  }, {
223
227
  entry?: string | undefined;
224
228
  build?: {
225
229
  staticNodeEnv?: boolean | undefined;
230
+ inlineCss?: boolean | undefined;
226
231
  } | undefined;
227
232
  }>>>;
228
233
  serverFns: z.ZodDefault<z.ZodOptional<z.ZodObject<{
@@ -2391,6 +2396,7 @@ export declare const tanstackStartViteOptionsSchema: z.ZodDefault<z.ZodOptional<
2391
2396
  server: {
2392
2397
  build: {
2393
2398
  staticNodeEnv: boolean;
2399
+ inlineCss: boolean;
2394
2400
  };
2395
2401
  entry?: string | undefined;
2396
2402
  };
@@ -2720,6 +2726,7 @@ export declare const tanstackStartViteOptionsSchema: z.ZodDefault<z.ZodOptional<
2720
2726
  entry?: string | undefined;
2721
2727
  build?: {
2722
2728
  staticNodeEnv?: boolean | undefined;
2729
+ inlineCss?: boolean | undefined;
2723
2730
  } | undefined;
2724
2731
  } | undefined;
2725
2732
  srcDirectory?: string | undefined;
@@ -3099,6 +3106,7 @@ export declare function parseStartConfig(opts: z.input<typeof tanstackStartViteO
3099
3106
  server: {
3100
3107
  build: {
3101
3108
  staticNodeEnv: boolean;
3109
+ inlineCss: boolean;
3102
3110
  };
3103
3111
  entry?: string | undefined;
3104
3112
  };
@@ -1,3 +1,4 @@
1
+ import { getCssAssetSource } from "../../start-manifest-plugin/inlineCss.js";
1
2
  import { tsrSplit } from "@tanstack/router-plugin";
2
3
  //#region src/vite/start-manifest-plugin/normalized-client-build.ts
3
4
  function normalizeViteClientChunk(chunk) {
@@ -25,6 +26,7 @@ function normalizeViteClientBuild(clientBundle) {
25
26
  const chunksByFileName = normalizeViteClientChunks(clientBundle);
26
27
  const chunkFileNamesByRouteFilePath = /* @__PURE__ */ new Map();
27
28
  const cssFilesBySourcePath = /* @__PURE__ */ new Map();
29
+ const cssContentByFileName = /* @__PURE__ */ new Map();
28
30
  for (const chunk of chunksByFileName.values()) {
29
31
  const bundleEntry = clientBundle[chunk.fileName];
30
32
  if (chunk.isEntry) {
@@ -47,12 +49,20 @@ function normalizeViteClientBuild(clientBundle) {
47
49
  cssFilesBySourcePath.set(sourcePath, existing ? Array.from(new Set([...existing, ...chunk.css])) : chunk.css.slice());
48
50
  }
49
51
  }
52
+ for (const fileName in clientBundle) {
53
+ if (!fileName.endsWith(".css")) continue;
54
+ const bundleEntry = clientBundle[fileName];
55
+ if (bundleEntry.type !== "asset") continue;
56
+ const css = getCssAssetSource(bundleEntry.source);
57
+ if (css !== void 0) cssContentByFileName.set(fileName, css);
58
+ }
50
59
  if (!entryChunkFileName) throw new Error("No entry file found");
51
60
  return {
52
61
  entryChunkFileName,
53
62
  chunksByFileName,
54
63
  chunkFileNamesByRouteFilePath,
55
- cssFilesBySourcePath
64
+ cssFilesBySourcePath,
65
+ cssContentByFileName
56
66
  };
57
67
  }
58
68
  function getRouteFilePathsFromModuleIds(moduleIds) {
@@ -1 +1 @@
1
- {"version":3,"file":"normalized-client-build.js","names":[],"sources":["../../../../src/vite/start-manifest-plugin/normalized-client-build.ts"],"sourcesContent":["import { tsrSplit } from '@tanstack/router-plugin'\nimport type { Rollup } from 'vite'\nimport type { NormalizedClientBuild, NormalizedClientChunk } from '../../types'\n\nexport function normalizeViteClientChunk(\n chunk: Rollup.OutputChunk,\n): NormalizedClientChunk {\n return {\n fileName: chunk.fileName,\n isEntry: chunk.isEntry,\n imports: chunk.imports,\n dynamicImports: chunk.dynamicImports,\n css: Array.from(chunk.viteMetadata?.importedCss ?? []),\n routeFilePaths: getRouteFilePathsFromModuleIds(chunk.moduleIds),\n }\n}\n\nexport function normalizeViteClientChunks(\n clientBundle: Rollup.OutputBundle,\n): ReadonlyMap<string, NormalizedClientChunk> {\n const chunksByFileName = new Map<string, NormalizedClientChunk>()\n\n for (const fileName in clientBundle) {\n const bundleEntry = clientBundle[fileName]!\n if (bundleEntry.type !== 'chunk') {\n continue\n }\n\n const normalizedChunk = normalizeViteClientChunk(bundleEntry)\n chunksByFileName.set(normalizedChunk.fileName, normalizedChunk)\n }\n\n return chunksByFileName\n}\n\nexport function normalizeViteClientBuild(\n clientBundle: Rollup.OutputBundle,\n): NormalizedClientBuild {\n let entryChunkFileName: string | undefined\n const chunksByFileName = normalizeViteClientChunks(clientBundle)\n const chunkFileNamesByRouteFilePath = new Map<string, Array<string>>()\n const cssFilesBySourcePath = new Map<string, Array<string>>()\n\n for (const chunk of chunksByFileName.values()) {\n const bundleEntry = clientBundle[chunk.fileName] as Rollup.OutputChunk\n\n if (chunk.isEntry) {\n if (entryChunkFileName) {\n throw new Error(\n `multiple entries detected: ${entryChunkFileName} ${chunk.fileName}`,\n )\n }\n entryChunkFileName = chunk.fileName\n }\n\n for (const routeFilePath of chunk.routeFilePaths) {\n let chunkFileNames = chunkFileNamesByRouteFilePath.get(routeFilePath)\n if (chunkFileNames === undefined) {\n chunkFileNames = []\n chunkFileNamesByRouteFilePath.set(routeFilePath, chunkFileNames)\n }\n chunkFileNames.push(chunk.fileName)\n }\n\n for (const moduleId of bundleEntry.moduleIds) {\n const queryIndex = moduleId.indexOf('?')\n const sourcePath =\n queryIndex >= 0 ? moduleId.slice(0, queryIndex) : moduleId\n if (!sourcePath) continue\n\n const existing = cssFilesBySourcePath.get(sourcePath)\n cssFilesBySourcePath.set(\n sourcePath,\n existing\n ? Array.from(new Set([...existing, ...chunk.css]))\n : chunk.css.slice(),\n )\n }\n }\n\n if (!entryChunkFileName) {\n throw new Error('No entry file found')\n }\n\n return {\n entryChunkFileName,\n chunksByFileName,\n chunkFileNamesByRouteFilePath,\n cssFilesBySourcePath,\n }\n}\n\nexport function getRouteFilePathsFromModuleIds(moduleIds: Array<string>) {\n let routeFilePaths: Array<string> | undefined\n let seenRouteFilePaths: Set<string> | undefined\n\n for (const moduleId of moduleIds) {\n const queryIndex = moduleId.indexOf('?')\n\n if (queryIndex < 0) {\n continue\n }\n\n const query = moduleId.slice(queryIndex + 1)\n\n if (!query.includes(tsrSplit)) {\n continue\n }\n\n if (!new URLSearchParams(query).has(tsrSplit)) {\n continue\n }\n\n const routeFilePath = moduleId.slice(0, queryIndex)\n\n if (seenRouteFilePaths?.has(routeFilePath)) {\n continue\n }\n\n if (routeFilePaths === undefined || seenRouteFilePaths === undefined) {\n routeFilePaths = []\n seenRouteFilePaths = new Set<string>()\n }\n\n routeFilePaths.push(routeFilePath)\n seenRouteFilePaths.add(routeFilePath)\n }\n\n return routeFilePaths ?? []\n}\n"],"mappings":";;AAIA,SAAgB,yBACd,OACuB;AACvB,QAAO;EACL,UAAU,MAAM;EAChB,SAAS,MAAM;EACf,SAAS,MAAM;EACf,gBAAgB,MAAM;EACtB,KAAK,MAAM,KAAK,MAAM,cAAc,eAAe,EAAE,CAAC;EACtD,gBAAgB,+BAA+B,MAAM,UAAU;EAChE;;AAGH,SAAgB,0BACd,cAC4C;CAC5C,MAAM,mCAAmB,IAAI,KAAoC;AAEjE,MAAK,MAAM,YAAY,cAAc;EACnC,MAAM,cAAc,aAAa;AACjC,MAAI,YAAY,SAAS,QACvB;EAGF,MAAM,kBAAkB,yBAAyB,YAAY;AAC7D,mBAAiB,IAAI,gBAAgB,UAAU,gBAAgB;;AAGjE,QAAO;;AAGT,SAAgB,yBACd,cACuB;CACvB,IAAI;CACJ,MAAM,mBAAmB,0BAA0B,aAAa;CAChE,MAAM,gDAAgC,IAAI,KAA4B;CACtE,MAAM,uCAAuB,IAAI,KAA4B;AAE7D,MAAK,MAAM,SAAS,iBAAiB,QAAQ,EAAE;EAC7C,MAAM,cAAc,aAAa,MAAM;AAEvC,MAAI,MAAM,SAAS;AACjB,OAAI,mBACF,OAAM,IAAI,MACR,8BAA8B,mBAAmB,GAAG,MAAM,WAC3D;AAEH,wBAAqB,MAAM;;AAG7B,OAAK,MAAM,iBAAiB,MAAM,gBAAgB;GAChD,IAAI,iBAAiB,8BAA8B,IAAI,cAAc;AACrE,OAAI,mBAAmB,KAAA,GAAW;AAChC,qBAAiB,EAAE;AACnB,kCAA8B,IAAI,eAAe,eAAe;;AAElE,kBAAe,KAAK,MAAM,SAAS;;AAGrC,OAAK,MAAM,YAAY,YAAY,WAAW;GAC5C,MAAM,aAAa,SAAS,QAAQ,IAAI;GACxC,MAAM,aACJ,cAAc,IAAI,SAAS,MAAM,GAAG,WAAW,GAAG;AACpD,OAAI,CAAC,WAAY;GAEjB,MAAM,WAAW,qBAAqB,IAAI,WAAW;AACrD,wBAAqB,IACnB,YACA,WACI,MAAM,KAAK,IAAI,IAAI,CAAC,GAAG,UAAU,GAAG,MAAM,IAAI,CAAC,CAAC,GAChD,MAAM,IAAI,OAAO,CACtB;;;AAIL,KAAI,CAAC,mBACH,OAAM,IAAI,MAAM,sBAAsB;AAGxC,QAAO;EACL;EACA;EACA;EACA;EACD;;AAGH,SAAgB,+BAA+B,WAA0B;CACvE,IAAI;CACJ,IAAI;AAEJ,MAAK,MAAM,YAAY,WAAW;EAChC,MAAM,aAAa,SAAS,QAAQ,IAAI;AAExC,MAAI,aAAa,EACf;EAGF,MAAM,QAAQ,SAAS,MAAM,aAAa,EAAE;AAE5C,MAAI,CAAC,MAAM,SAAS,SAAS,CAC3B;AAGF,MAAI,CAAC,IAAI,gBAAgB,MAAM,CAAC,IAAI,SAAS,CAC3C;EAGF,MAAM,gBAAgB,SAAS,MAAM,GAAG,WAAW;AAEnD,MAAI,oBAAoB,IAAI,cAAc,CACxC;AAGF,MAAI,mBAAmB,KAAA,KAAa,uBAAuB,KAAA,GAAW;AACpE,oBAAiB,EAAE;AACnB,wCAAqB,IAAI,KAAa;;AAGxC,iBAAe,KAAK,cAAc;AAClC,qBAAmB,IAAI,cAAc;;AAGvC,QAAO,kBAAkB,EAAE"}
1
+ {"version":3,"file":"normalized-client-build.js","names":[],"sources":["../../../../src/vite/start-manifest-plugin/normalized-client-build.ts"],"sourcesContent":["import { tsrSplit } from '@tanstack/router-plugin'\nimport { getCssAssetSource } from '../../start-manifest-plugin/inlineCss'\nimport type { Rollup } from 'vite'\nimport type { NormalizedClientBuild, NormalizedClientChunk } from '../../types'\n\nexport function normalizeViteClientChunk(\n chunk: Rollup.OutputChunk,\n): NormalizedClientChunk {\n return {\n fileName: chunk.fileName,\n isEntry: chunk.isEntry,\n imports: chunk.imports,\n dynamicImports: chunk.dynamicImports,\n css: Array.from(chunk.viteMetadata?.importedCss ?? []),\n routeFilePaths: getRouteFilePathsFromModuleIds(chunk.moduleIds),\n }\n}\n\nexport function normalizeViteClientChunks(\n clientBundle: Rollup.OutputBundle,\n): ReadonlyMap<string, NormalizedClientChunk> {\n const chunksByFileName = new Map<string, NormalizedClientChunk>()\n\n for (const fileName in clientBundle) {\n const bundleEntry = clientBundle[fileName]!\n if (bundleEntry.type !== 'chunk') {\n continue\n }\n\n const normalizedChunk = normalizeViteClientChunk(bundleEntry)\n chunksByFileName.set(normalizedChunk.fileName, normalizedChunk)\n }\n\n return chunksByFileName\n}\n\nexport function normalizeViteClientBuild(\n clientBundle: Rollup.OutputBundle,\n): NormalizedClientBuild {\n let entryChunkFileName: string | undefined\n const chunksByFileName = normalizeViteClientChunks(clientBundle)\n const chunkFileNamesByRouteFilePath = new Map<string, Array<string>>()\n const cssFilesBySourcePath = new Map<string, Array<string>>()\n const cssContentByFileName = new Map<string, string>()\n\n for (const chunk of chunksByFileName.values()) {\n const bundleEntry = clientBundle[chunk.fileName] as Rollup.OutputChunk\n\n if (chunk.isEntry) {\n if (entryChunkFileName) {\n throw new Error(\n `multiple entries detected: ${entryChunkFileName} ${chunk.fileName}`,\n )\n }\n entryChunkFileName = chunk.fileName\n }\n\n for (const routeFilePath of chunk.routeFilePaths) {\n let chunkFileNames = chunkFileNamesByRouteFilePath.get(routeFilePath)\n if (chunkFileNames === undefined) {\n chunkFileNames = []\n chunkFileNamesByRouteFilePath.set(routeFilePath, chunkFileNames)\n }\n chunkFileNames.push(chunk.fileName)\n }\n\n for (const moduleId of bundleEntry.moduleIds) {\n const queryIndex = moduleId.indexOf('?')\n const sourcePath =\n queryIndex >= 0 ? moduleId.slice(0, queryIndex) : moduleId\n if (!sourcePath) continue\n\n const existing = cssFilesBySourcePath.get(sourcePath)\n cssFilesBySourcePath.set(\n sourcePath,\n existing\n ? Array.from(new Set([...existing, ...chunk.css]))\n : chunk.css.slice(),\n )\n }\n }\n\n for (const fileName in clientBundle) {\n if (!fileName.endsWith('.css')) {\n continue\n }\n\n const bundleEntry = clientBundle[fileName]!\n if (bundleEntry.type !== 'asset') {\n continue\n }\n\n const css = getCssAssetSource(bundleEntry.source)\n if (css !== undefined) {\n cssContentByFileName.set(fileName, css)\n }\n }\n\n if (!entryChunkFileName) {\n throw new Error('No entry file found')\n }\n\n return {\n entryChunkFileName,\n chunksByFileName,\n chunkFileNamesByRouteFilePath,\n cssFilesBySourcePath,\n cssContentByFileName,\n }\n}\n\nexport function getRouteFilePathsFromModuleIds(moduleIds: Array<string>) {\n let routeFilePaths: Array<string> | undefined\n let seenRouteFilePaths: Set<string> | undefined\n\n for (const moduleId of moduleIds) {\n const queryIndex = moduleId.indexOf('?')\n\n if (queryIndex < 0) {\n continue\n }\n\n const query = moduleId.slice(queryIndex + 1)\n\n if (!query.includes(tsrSplit)) {\n continue\n }\n\n if (!new URLSearchParams(query).has(tsrSplit)) {\n continue\n }\n\n const routeFilePath = moduleId.slice(0, queryIndex)\n\n if (seenRouteFilePaths?.has(routeFilePath)) {\n continue\n }\n\n if (routeFilePaths === undefined || seenRouteFilePaths === undefined) {\n routeFilePaths = []\n seenRouteFilePaths = new Set<string>()\n }\n\n routeFilePaths.push(routeFilePath)\n seenRouteFilePaths.add(routeFilePath)\n }\n\n return routeFilePaths ?? []\n}\n"],"mappings":";;;AAKA,SAAgB,yBACd,OACuB;AACvB,QAAO;EACL,UAAU,MAAM;EAChB,SAAS,MAAM;EACf,SAAS,MAAM;EACf,gBAAgB,MAAM;EACtB,KAAK,MAAM,KAAK,MAAM,cAAc,eAAe,EAAE,CAAC;EACtD,gBAAgB,+BAA+B,MAAM,UAAU;EAChE;;AAGH,SAAgB,0BACd,cAC4C;CAC5C,MAAM,mCAAmB,IAAI,KAAoC;AAEjE,MAAK,MAAM,YAAY,cAAc;EACnC,MAAM,cAAc,aAAa;AACjC,MAAI,YAAY,SAAS,QACvB;EAGF,MAAM,kBAAkB,yBAAyB,YAAY;AAC7D,mBAAiB,IAAI,gBAAgB,UAAU,gBAAgB;;AAGjE,QAAO;;AAGT,SAAgB,yBACd,cACuB;CACvB,IAAI;CACJ,MAAM,mBAAmB,0BAA0B,aAAa;CAChE,MAAM,gDAAgC,IAAI,KAA4B;CACtE,MAAM,uCAAuB,IAAI,KAA4B;CAC7D,MAAM,uCAAuB,IAAI,KAAqB;AAEtD,MAAK,MAAM,SAAS,iBAAiB,QAAQ,EAAE;EAC7C,MAAM,cAAc,aAAa,MAAM;AAEvC,MAAI,MAAM,SAAS;AACjB,OAAI,mBACF,OAAM,IAAI,MACR,8BAA8B,mBAAmB,GAAG,MAAM,WAC3D;AAEH,wBAAqB,MAAM;;AAG7B,OAAK,MAAM,iBAAiB,MAAM,gBAAgB;GAChD,IAAI,iBAAiB,8BAA8B,IAAI,cAAc;AACrE,OAAI,mBAAmB,KAAA,GAAW;AAChC,qBAAiB,EAAE;AACnB,kCAA8B,IAAI,eAAe,eAAe;;AAElE,kBAAe,KAAK,MAAM,SAAS;;AAGrC,OAAK,MAAM,YAAY,YAAY,WAAW;GAC5C,MAAM,aAAa,SAAS,QAAQ,IAAI;GACxC,MAAM,aACJ,cAAc,IAAI,SAAS,MAAM,GAAG,WAAW,GAAG;AACpD,OAAI,CAAC,WAAY;GAEjB,MAAM,WAAW,qBAAqB,IAAI,WAAW;AACrD,wBAAqB,IACnB,YACA,WACI,MAAM,KAAK,IAAI,IAAI,CAAC,GAAG,UAAU,GAAG,MAAM,IAAI,CAAC,CAAC,GAChD,MAAM,IAAI,OAAO,CACtB;;;AAIL,MAAK,MAAM,YAAY,cAAc;AACnC,MAAI,CAAC,SAAS,SAAS,OAAO,CAC5B;EAGF,MAAM,cAAc,aAAa;AACjC,MAAI,YAAY,SAAS,QACvB;EAGF,MAAM,MAAM,kBAAkB,YAAY,OAAO;AACjD,MAAI,QAAQ,KAAA,EACV,sBAAqB,IAAI,UAAU,IAAI;;AAI3C,KAAI,CAAC,mBACH,OAAM,IAAI,MAAM,sBAAsB;AAGxC,QAAO;EACL;EACA;EACA;EACA;EACA;EACD;;AAGH,SAAgB,+BAA+B,WAA0B;CACvE,IAAI;CACJ,IAAI;AAEJ,MAAK,MAAM,YAAY,WAAW;EAChC,MAAM,aAAa,SAAS,QAAQ,IAAI;AAExC,MAAI,aAAa,EACf;EAGF,MAAM,QAAQ,SAAS,MAAM,aAAa,EAAE;AAE5C,MAAI,CAAC,MAAM,SAAS,SAAS,CAC3B;AAGF,MAAI,CAAC,IAAI,gBAAgB,MAAM,CAAC,IAAI,SAAS,CAC3C;EAGF,MAAM,gBAAgB,SAAS,MAAM,GAAG,WAAW;AAEnD,MAAI,oBAAoB,IAAI,cAAc,CACxC;AAGF,MAAI,mBAAmB,KAAA,KAAa,uBAAuB,KAAA,GAAW;AACpE,oBAAiB,EAAE;AACnB,wCAAqB,IAAI,KAAa;;AAGxC,iBAAe,KAAK,cAAc;AAClC,qBAAmB,IAAI,cAAc;;AAGvC,QAAO,kBAAkB,EAAE"}
@@ -25,7 +25,7 @@ function startManifestPlugin(opts) {
25
25
  moduleId: VIRTUAL_MODULES.startManifest,
26
26
  enforce: "pre",
27
27
  load() {
28
- const { resolvedStartConfig } = opts.getConfig();
28
+ const { resolvedStartConfig, startConfig } = opts.getConfig();
29
29
  const clientEntry = joinURL(resolvedStartConfig.basePaths.publicBase, "@id", ENTRY_POINTS.client);
30
30
  if (this.environment.name !== START_ENVIRONMENT_NAMES.server) return getEmptyStartManifestModule(clientEntry);
31
31
  if (this.environment.config.command === "serve") return getEmptyStartManifestModule(clientEntry);
@@ -35,6 +35,7 @@ function startManifestPlugin(opts) {
35
35
  clientBuild,
36
36
  routeTreeRoutes,
37
37
  basePath: resolvedStartConfig.basePaths.publicBase,
38
+ inlineCss: startConfig.server.build.inlineCss,
38
39
  additionalRouteAssets: getViteAdditionalRouteAssets({
39
40
  cssCodeSplitDisabledFileName,
40
41
  basePath: resolvedStartConfig.basePaths.publicBase,
@@ -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 { 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"}
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, startConfig } = 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 inlineCss: startConfig.server.build.inlineCss,\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,qBAAqB,gBAAgB,KAAK,WAAW;GAC7D,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;AAcjD,UAAO,0CAA0C,uBAZ3B,mBAAmB;IACvC;IACA;IACA,UAAU,oBAAoB,UAAU;IACxC,WAAW,YAAY,OAAO,MAAM;IACpC,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.169.1",
3
+ "version": "1.169.3",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -76,6 +76,7 @@
76
76
  "seroval": "^1.5.0",
77
77
  "cheerio": "^1.0.0",
78
78
  "exsolve": "^1.0.7",
79
+ "lightningcss": "^1.32.0",
79
80
  "pathe": "^2.0.3",
80
81
  "picomatch": "^4.0.3",
81
82
  "srvx": "^0.11.9",
@@ -85,15 +86,15 @@
85
86
  "vitefu": "^1.1.1",
86
87
  "xmlbuilder2": "^4.0.3",
87
88
  "zod": "^3.24.2",
88
- "@tanstack/router-core": "1.168.15",
89
- "@tanstack/router-generator": "1.166.33",
90
- "@tanstack/router-plugin": "1.167.23",
89
+ "@tanstack/router-core": "1.168.16",
90
+ "@tanstack/router-plugin": "1.167.25",
91
+ "@tanstack/router-generator": "1.166.34",
91
92
  "@tanstack/router-utils": "1.161.7",
92
- "@tanstack/start-client-core": "1.167.17",
93
- "@tanstack/start-server-core": "1.167.19"
93
+ "@tanstack/start-client-core": "1.167.18",
94
+ "@tanstack/start-server-core": "1.167.20"
94
95
  },
95
96
  "devDependencies": {
96
- "@rsbuild/core": "^2.0.0",
97
+ "@rsbuild/core": "^2.0.1",
97
98
  "@types/babel__code-frame": "^7.0.6",
98
99
  "@types/babel__core": "^7.20.5",
99
100
  "@types/picomatch": "^4.0.2",
@@ -1,4 +1,5 @@
1
1
  import { tsrSplit } from '@tanstack/router-plugin'
2
+ import { getCssAssetSource } from '../start-manifest-plugin/inlineCss'
2
3
  import { RSBUILD_ENVIRONMENT_NAMES } from './planning'
3
4
  import type { RsbuildPluginAPI, Rspack } from '@rsbuild/core'
4
5
  import type { NormalizedClientBuild, NormalizedClientChunk } from '../types'
@@ -162,6 +163,7 @@ export function normalizeRspackClientBuild(
162
163
  const chunksByFileName = new Map<string, NormalizedClientChunk>()
163
164
  const chunkFileNamesByRouteFilePath = new Map<string, Array<string>>()
164
165
  const cssFilesBySourcePath = new Map<string, Array<string>>()
166
+ const cssContentByFileName = new Map<string, string>()
165
167
  let entryChunkFileName: string | undefined
166
168
 
167
169
  // Collect all initial JS file names from the main entry for computing
@@ -272,6 +274,17 @@ export function normalizeRspackClientBuild(
272
274
  throw new Error('No entry file found in rspack client build')
273
275
  }
274
276
 
277
+ for (const asset of compilation.getAssets()) {
278
+ if (!asset.name.endsWith('.css')) {
279
+ continue
280
+ }
281
+
282
+ const css = getCssAssetSource(asset.source.source())
283
+ if (css !== undefined) {
284
+ cssContentByFileName.set(asset.name, css)
285
+ }
286
+ }
287
+
275
288
  // In RSC mode, CSS from server components is associated with the 'rsc'
276
289
  // client entry chunk (not the main 'index' entry). The manifest builder
277
290
  // merges the entry chunk's CSS into __root__, so by appending RSC CSS
@@ -299,6 +312,7 @@ export function normalizeRspackClientBuild(
299
312
  chunksByFileName,
300
313
  chunkFileNamesByRouteFilePath,
301
314
  cssFilesBySourcePath,
315
+ cssContentByFileName,
302
316
  }
303
317
  }
304
318
 
@@ -161,6 +161,7 @@ export function tanStackStartRsbuild(
161
161
  routerBasepath,
162
162
  serverFnBase: startConfig.serverFns.base,
163
163
  })
164
+ const inlineCssEnabled = !isDev && startConfig.server.build.inlineCss
164
165
 
165
166
  return mergeRsbuildConfig(rsbuildConfig, {
166
167
  source: {
@@ -188,6 +189,12 @@ export function tanStackStartRsbuild(
188
189
  'import.meta.env.TSS_DEV_SSR_STYLES_BASEPATH': JSON.stringify(
189
190
  resolvedStartConfig.basePaths.publicBase,
190
191
  ),
192
+ 'process.env.TSS_INLINE_CSS_ENABLED': JSON.stringify(
193
+ inlineCssEnabled ? 'true' : 'false',
194
+ ),
195
+ 'import.meta.env.TSS_INLINE_CSS_ENABLED': JSON.stringify(
196
+ inlineCssEnabled ? 'true' : 'false',
197
+ ),
191
198
  },
192
199
  },
193
200
  server: {
@@ -76,33 +76,39 @@ export const tsrStartManifest = () => globalThis[${JSON.stringify(DEV_START_MANI
76
76
  function buildStartManifestData(
77
77
  clientBuild: NormalizedClientBuild,
78
78
  publicBase: string,
79
+ inlineCss: boolean,
79
80
  ) {
80
81
  const routeTreeRoutes = globalThis.TSS_ROUTES_MANIFEST
81
82
  return buildStartManifest({
82
83
  clientBuild,
83
84
  routeTreeRoutes,
84
85
  basePath: publicBase,
86
+ inlineCss,
85
87
  })
86
88
  }
87
89
 
88
90
  function serializeStartManifestData(
89
91
  clientBuild: NormalizedClientBuild,
90
92
  publicBase: string,
93
+ inlineCss: boolean,
91
94
  ): string {
92
- return JSON.stringify(buildStartManifestData(clientBuild, publicBase))
95
+ return JSON.stringify(
96
+ buildStartManifestData(clientBuild, publicBase, inlineCss),
97
+ )
93
98
  }
94
99
 
95
100
  function generateManifestModuleBuild(
96
101
  clientBuild: NormalizedClientBuild | undefined,
97
102
  publicBase: string,
98
103
  _devClientEntryUrl: string,
104
+ inlineCss: boolean,
99
105
  ): string {
100
106
  if (!clientBuild) {
101
107
  return `const tsrStartManifestData = ${JSON.stringify(START_MANIFEST_PLACEHOLDER)}
102
108
  export const tsrStartManifest = () => tsrStartManifestData`
103
109
  }
104
110
 
105
- return `export const tsrStartManifest = () => (${serializeStartManifestData(clientBuild, publicBase)})`
111
+ return `export const tsrStartManifest = () => (${serializeStartManifestData(clientBuild, publicBase, inlineCss)})`
106
112
  }
107
113
 
108
114
  // ---------------------------------------------------------------------------
@@ -345,7 +351,7 @@ export function registerVirtualModules(
345
351
  // Generate initial content for each virtual module per environment
346
352
  function getInitialContent(environmentName: string): Record<string, string> {
347
353
  // Safe to call getConfig() here — this runs inside modifyRspackConfig
348
- const { resolvedStartConfig } = opts.getConfig()
354
+ const { resolvedStartConfig, startConfig } = opts.getConfig()
349
355
  const isServerEnv = environmentName === RSBUILD_ENVIRONMENT_NAMES.server
350
356
  const isClientEnv = environmentName === RSBUILD_ENVIRONMENT_NAMES.client
351
357
  const content: Record<string, string> = {}
@@ -361,6 +367,7 @@ export function registerVirtualModules(
361
367
  clientBuild,
362
368
  resolvedStartConfig.basePaths.publicBase,
363
369
  devClientEntryUrl,
370
+ startConfig.server.build.inlineCss,
364
371
  )
365
372
  } else {
366
373
  content[paths.manifest] = 'export default {}'
@@ -495,7 +502,7 @@ export function createFromReadableStream() { throw new Error('RSC SSR decode is
495
502
  },
496
503
 
497
504
  generateManifestContent(newClientBuild: NormalizedClientBuild): string {
498
- const { resolvedStartConfig } = opts.getConfig()
505
+ const { resolvedStartConfig, startConfig } = opts.getConfig()
499
506
  const devClientEntryUrl = opts.getDevClientEntryUrl(
500
507
  resolvedStartConfig.basePaths.publicBase,
501
508
  )
@@ -503,16 +510,18 @@ export function createFromReadableStream() { throw new Error('RSC SSR decode is
503
510
  newClientBuild,
504
511
  resolvedStartConfig.basePaths.publicBase,
505
512
  devClientEntryUrl,
513
+ !isDev && startConfig.server.build.inlineCss,
506
514
  )
507
515
  },
508
516
 
509
517
  generateManifestValueLiteral(
510
518
  newClientBuild: NormalizedClientBuild,
511
519
  ): string {
512
- const { resolvedStartConfig } = opts.getConfig()
520
+ const { resolvedStartConfig, startConfig } = opts.getConfig()
513
521
  return serializeStartManifestData(
514
522
  newClientBuild,
515
523
  resolvedStartConfig.basePaths.publicBase,
524
+ !isDev && startConfig.server.build.inlineCss,
516
525
  )
517
526
  },
518
527
 
@@ -528,6 +537,7 @@ export function createFromReadableStream() { throw new Error('RSC SSR decode is
528
537
  )[DEV_START_MANIFEST_GLOBAL] = buildStartManifestData(
529
538
  clientBuild,
530
539
  resolvedStartConfig.basePaths.publicBase,
540
+ false,
531
541
  )
532
542
  }
533
543
  },
package/src/schema.ts CHANGED
@@ -209,6 +209,12 @@ export const tanstackStartOptionsObjectSchema = z.object({
209
209
  build: z
210
210
  .object({
211
211
  staticNodeEnv: z.boolean().optional().default(true),
212
+ /**
213
+ * Inline route CSS into the server-rendered HTML response.
214
+ *
215
+ * @experimental This option is experimental!
216
+ */
217
+ inlineCss: z.boolean().optional().default(false),
212
218
  })
213
219
  .optional()
214
220
  .default({}),
@@ -0,0 +1,93 @@
1
+ import { transform } from 'lightningcss'
2
+
3
+ const cssUrlPattern =
4
+ /url\(\s*(?:"([^"]*)"|'([^']*)'|([^)"']*?))\s*\)|@import\s+(?:url\(\s*(?:"([^"]*)"|'([^']*)'|([^)"']*?))\s*\)|"([^"]*)"|'([^']*)')/gi
5
+
6
+ function isRelativeCssUrl(url: string) {
7
+ if (!url) return false
8
+ if (url.startsWith('#')) return false
9
+ if (url.startsWith('/')) return false
10
+ if (/^[a-z][a-z\d+.-]*:/i.test(url)) return false
11
+ return true
12
+ }
13
+
14
+ export function shouldRebaseInlineCssUrls(css: string) {
15
+ cssUrlPattern.lastIndex = 0
16
+
17
+ for (const match of css.matchAll(cssUrlPattern)) {
18
+ const url = (
19
+ match[1] ??
20
+ match[2] ??
21
+ match[3] ??
22
+ match[4] ??
23
+ match[5] ??
24
+ match[6] ??
25
+ match[7] ??
26
+ match[8] ??
27
+ ''
28
+ ).trim()
29
+
30
+ if (isRelativeCssUrl(url)) {
31
+ return true
32
+ }
33
+ }
34
+
35
+ return false
36
+ }
37
+
38
+ function rebaseCssUrl(url: string, cssHref: string) {
39
+ if (!isRelativeCssUrl(url)) {
40
+ return url
41
+ }
42
+
43
+ const fakeOrigin = 'http://tanstack.local'
44
+ const resolved = new URL(url, new URL(cssHref, fakeOrigin))
45
+
46
+ if (resolved.origin === fakeOrigin) {
47
+ return `${resolved.pathname}${resolved.search}${resolved.hash}`
48
+ }
49
+
50
+ return resolved.href
51
+ }
52
+
53
+ export function rebaseInlineCssUrls(options: { css: string; cssHref: string }) {
54
+ const css = options.css.trim()
55
+
56
+ if (!shouldRebaseInlineCssUrls(css)) {
57
+ return css
58
+ }
59
+
60
+ const result = transform({
61
+ filename: options.cssHref,
62
+ code: Buffer.from(css),
63
+ minify: true,
64
+ visitor: {
65
+ Url(url) {
66
+ return {
67
+ ...url,
68
+ url: rebaseCssUrl(url.url, options.cssHref),
69
+ }
70
+ },
71
+ Rule: {
72
+ import(rule: any) {
73
+ return {
74
+ ...rule,
75
+ value: {
76
+ ...rule.value,
77
+ url: rebaseCssUrl(rule.value.url, options.cssHref),
78
+ },
79
+ }
80
+ },
81
+ },
82
+ },
83
+ })
84
+
85
+ return Buffer.from(result.code).toString('utf8')
86
+ }
87
+
88
+ export function getCssAssetSource(source: unknown) {
89
+ if (typeof source === 'string') return source
90
+ if (source instanceof Uint8Array) return Buffer.from(source).toString('utf8')
91
+ if (source == null) return undefined
92
+ return String(source)
93
+ }
@@ -1,12 +1,17 @@
1
1
  /* eslint-disable @typescript-eslint/prefer-for-of */
2
2
  import { serialize } from 'seroval'
3
3
  import { joinURL } from 'ufo'
4
- import { resolveManifestAssetLink, rootRouteId } from '@tanstack/router-core'
4
+ import {
5
+ getStylesheetHref,
6
+ resolveManifestAssetLink,
7
+ rootRouteId,
8
+ } from '@tanstack/router-core'
5
9
  import {
6
10
  getRouteFilePathsFromModuleIds,
7
11
  normalizeViteClientBuild,
8
12
  normalizeViteClientChunk,
9
13
  } from '../vite/start-manifest-plugin/normalized-client-build'
14
+ import { rebaseInlineCssUrls } from './inlineCss'
10
15
  import type { ManifestAssetLink, RouterManagedTag } from '@tanstack/router-core'
11
16
  import type { NormalizedClientBuild, NormalizedClientChunk } from '../types'
12
17
 
@@ -42,6 +47,9 @@ type DedupeRoute = {
42
47
  export interface StartManifest {
43
48
  routes: Record<string, RouteTreeRoute>
44
49
  clientEntry: string
50
+ inlineCss?: {
51
+ styles: Record<string, string>
52
+ }
45
53
  }
46
54
 
47
55
  export function appendUniqueStrings(
@@ -152,6 +160,7 @@ export function buildStartManifest(options: {
152
160
  clientBuild: NormalizedClientBuild
153
161
  routeTreeRoutes: RouteTreeRoutes
154
162
  basePath: string
163
+ inlineCss?: boolean
155
164
  additionalRouteAssets?: Partial<
156
165
  Record<string, ReadonlyArray<RouterManagedTag>>
157
166
  >
@@ -180,10 +189,20 @@ export function buildStartManifest(options: {
180
189
  }
181
190
  }
182
191
 
183
- return {
192
+ const result: StartManifest = {
184
193
  routes,
185
194
  clientEntry: assetResolvers.getAssetPath(scannedChunks.entryChunk.fileName),
186
195
  }
196
+
197
+ if (options.inlineCss) {
198
+ result.inlineCss = buildInlineCssManifestData({
199
+ routes,
200
+ basePath: options.basePath,
201
+ cssContentByFileName: options.clientBuild.cssContentByFileName,
202
+ })
203
+ }
204
+
205
+ return result
187
206
  }
188
207
 
189
208
  export function serializeStartManifest(startManifest: StartManifest) {
@@ -347,6 +366,57 @@ export function createChunkCssAssetCollector(options: {
347
366
  return { getChunkCssAssets }
348
367
  }
349
368
 
369
+ function buildInlineCssManifestData(options: {
370
+ routes: Record<string, RouteTreeRoute>
371
+ basePath: string
372
+ cssContentByFileName: ReadonlyMap<string, string> | undefined
373
+ }): StartManifest['inlineCss'] {
374
+ const stylesheetHrefs = new Set<string>()
375
+
376
+ for (const route of Object.values(options.routes)) {
377
+ for (const asset of route.assets ?? []) {
378
+ const href = getStylesheetHref(asset)
379
+ if (href) {
380
+ stylesheetHrefs.add(href)
381
+ }
382
+ }
383
+ }
384
+
385
+ if (stylesheetHrefs.size === 0) {
386
+ return { styles: {} }
387
+ }
388
+
389
+ if (!options.cssContentByFileName) {
390
+ throw new Error(
391
+ 'TanStack Start inlineCss is enabled, but the client build did not provide CSS content',
392
+ )
393
+ }
394
+
395
+ const { getAssetPath } = createManifestAssetResolvers(options.basePath)
396
+ const styles: Record<string, string> = {}
397
+ const missingHrefs = new Set(stylesheetHrefs)
398
+
399
+ for (const [cssFile, css] of options.cssContentByFileName) {
400
+ const cssHref = getAssetPath(cssFile)
401
+ if (!stylesheetHrefs.has(cssHref)) {
402
+ continue
403
+ }
404
+
405
+ styles[cssHref] = rebaseInlineCssUrls({ css, cssHref })
406
+ missingHrefs.delete(cssHref)
407
+ }
408
+
409
+ if (missingHrefs.size > 0) {
410
+ throw new Error(
411
+ `TanStack Start inlineCss could not find CSS content for: ${Array.from(
412
+ missingHrefs,
413
+ ).join(', ')}`,
414
+ )
415
+ }
416
+
417
+ return { styles }
418
+ }
419
+
350
420
  export function buildRouteManifestRoutes(options: {
351
421
  routeTreeRoutes: RouteTreeRoutes
352
422
  routeChunksByFilePath: ReadonlyMap<
package/src/types.ts CHANGED
@@ -47,6 +47,7 @@ export interface NormalizedClientBuild {
47
47
  chunksByFileName: ReadonlyMap<string, NormalizedClientChunk>
48
48
  chunkFileNamesByRouteFilePath: ReadonlyMap<string, ReadonlyArray<string>>
49
49
  cssFilesBySourcePath: ReadonlyMap<string, ReadonlyArray<string>>
50
+ cssContentByFileName?: ReadonlyMap<string, string>
50
51
  }
51
52
 
52
53
  export interface TanStackStartCoreOptions {
@@ -136,6 +136,7 @@ export function createViteDefineConfig(opts: {
136
136
  spaEnabled: boolean | undefined
137
137
  devSsrStylesEnabled: boolean
138
138
  devSsrStylesBasepath: string
139
+ inlineCssEnabled: boolean
139
140
  staticNodeEnv: boolean
140
141
  }) {
141
142
  return {
@@ -156,6 +157,10 @@ export function createViteDefineConfig(opts: {
156
157
  'TSS_DEV_SSR_STYLES_BASEPATH',
157
158
  opts.devSsrStylesBasepath,
158
159
  ),
160
+ ...defineReplaceEnv(
161
+ 'TSS_INLINE_CSS_ENABLED',
162
+ opts.inlineCssEnabled ? 'true' : 'false',
163
+ ),
159
164
  ...(opts.command === 'build' && opts.staticNodeEnv
160
165
  ? {
161
166
  'process.env.NODE_ENV': JSON.stringify(
@@ -185,6 +185,8 @@ export function tanStackStartVite(
185
185
  devSsrStylesBasepath:
186
186
  startConfig.dev.ssrStyles.basepath ??
187
187
  resolvedStartConfig.basePaths.publicBase,
188
+ inlineCssEnabled:
189
+ command === 'build' && startConfig.server.build.inlineCss,
188
190
  staticNodeEnv: startConfig.server.build.staticNodeEnv,
189
191
  }),
190
192
  builder: {
@@ -1,4 +1,5 @@
1
1
  import { tsrSplit } from '@tanstack/router-plugin'
2
+ import { getCssAssetSource } from '../../start-manifest-plugin/inlineCss'
2
3
  import type { Rollup } from 'vite'
3
4
  import type { NormalizedClientBuild, NormalizedClientChunk } from '../../types'
4
5
 
@@ -40,6 +41,7 @@ export function normalizeViteClientBuild(
40
41
  const chunksByFileName = normalizeViteClientChunks(clientBundle)
41
42
  const chunkFileNamesByRouteFilePath = new Map<string, Array<string>>()
42
43
  const cssFilesBySourcePath = new Map<string, Array<string>>()
44
+ const cssContentByFileName = new Map<string, string>()
43
45
 
44
46
  for (const chunk of chunksByFileName.values()) {
45
47
  const bundleEntry = clientBundle[chunk.fileName] as Rollup.OutputChunk
@@ -78,6 +80,22 @@ export function normalizeViteClientBuild(
78
80
  }
79
81
  }
80
82
 
83
+ for (const fileName in clientBundle) {
84
+ if (!fileName.endsWith('.css')) {
85
+ continue
86
+ }
87
+
88
+ const bundleEntry = clientBundle[fileName]!
89
+ if (bundleEntry.type !== 'asset') {
90
+ continue
91
+ }
92
+
93
+ const css = getCssAssetSource(bundleEntry.source)
94
+ if (css !== undefined) {
95
+ cssContentByFileName.set(fileName, css)
96
+ }
97
+ }
98
+
81
99
  if (!entryChunkFileName) {
82
100
  throw new Error('No entry file found')
83
101
  }
@@ -87,6 +105,7 @@ export function normalizeViteClientBuild(
87
105
  chunksByFileName,
88
106
  chunkFileNamesByRouteFilePath,
89
107
  cssFilesBySourcePath,
108
+ cssContentByFileName,
90
109
  }
91
110
  }
92
111