@tanstack/start-plugin-core 1.166.14 → 1.167.0

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,3 +1,4 @@
1
+ import { bundlerOptionsKey, getBundlerOptions } from "./utils.js";
1
2
  import { ENTRY_POINTS, VITE_ENVIRONMENT_NAMES } from "./constants.js";
2
3
  import { startManifestPlugin } from "./start-manifest-plugin/plugin.js";
3
4
  import { tanStackStartRouter } from "./start-router-plugin/plugin.js";
@@ -156,7 +157,7 @@ function TanStackStartVitePluginCore(corePluginOpts, startPluginOpts) {
156
157
  [VITE_ENVIRONMENT_NAMES.client]: {
157
158
  consumer: "client",
158
159
  build: {
159
- rollupOptions: { input: { main: ENTRY_POINTS.client } },
160
+ [bundlerOptionsKey]: { input: { main: ENTRY_POINTS.client } },
160
161
  outDir: getClientOutputDirectory(viteConfig)
161
162
  },
162
163
  optimizeDeps: {
@@ -168,7 +169,7 @@ function TanStackStartVitePluginCore(corePluginOpts, startPluginOpts) {
168
169
  consumer: "server",
169
170
  build: {
170
171
  ssr: true,
171
- rollupOptions: { input: viteConfig.environments?.[VITE_ENVIRONMENT_NAMES.server]?.build?.rollupOptions?.input ?? serverAlias },
172
+ [bundlerOptionsKey]: { input: getBundlerOptions(viteConfig.environments?.[VITE_ENVIRONMENT_NAMES.server]?.build)?.input ?? serverAlias },
172
173
  outDir: getServerOutputDirectory(viteConfig),
173
174
  commonjsOptions: { include: [/node_modules/] },
174
175
  copyPublicDir: viteConfig.environments?.[VITE_ENVIRONMENT_NAMES.server]?.build?.copyPublicDir ?? false
@@ -190,7 +191,6 @@ function TanStackStartVitePluginCore(corePluginOpts, startPluginOpts) {
190
191
  },
191
192
  define: {
192
193
  ...defineReplaceEnv("TSS_SERVER_FN_BASE", TSS_SERVER_FN_BASE),
193
- ...defineReplaceEnv("TSS_CLIENT_OUTPUT_DIR", getClientOutputDirectory(viteConfig)),
194
194
  ...defineReplaceEnv("TSS_ROUTER_BASEPATH", startConfig.router.basepath),
195
195
  ...command === "serve" ? defineReplaceEnv("TSS_SHELL", startConfig.spa?.enabled ? "true" : "false") : {},
196
196
  ...defineReplaceEnv("TSS_DEV_SERVER", command === "serve" ? "true" : "false"),
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","names":[],"sources":["../../src/plugin.ts"],"sourcesContent":["import { joinPaths } from '@tanstack/router-core'\nimport * as vite from 'vite'\nimport { crawlFrameworkPkgs } from 'vitefu'\nimport { join } from 'pathe'\nimport { escapePath } from 'tinyglobby'\nimport { startManifestPlugin } from './start-manifest-plugin/plugin'\nimport { ENTRY_POINTS, VITE_ENVIRONMENT_NAMES } from './constants'\nimport { tanStackStartRouter } from './start-router-plugin/plugin'\nimport { loadEnvPlugin } from './load-env-plugin/plugin'\nimport { devServerPlugin } from './dev-server-plugin/plugin'\nimport { previewServerPlugin } from './preview-server-plugin/plugin'\nimport { parseStartConfig } from './schema'\nimport { resolveEntry } from './resolve-entries'\nimport {\n getClientOutputDirectory,\n getServerOutputDirectory,\n} from './output-directory'\nimport { postServerBuild } from './post-server-build'\nimport { startCompilerPlugin } from './start-compiler-plugin/plugin'\nimport { importProtectionPlugin } from './import-protection-plugin/plugin'\nimport type {\n GetConfigFn,\n ResolvedStartConfig,\n TanStackStartVitePluginCoreOptions,\n} from './types'\nimport type { ViteEnvironmentNames } from './constants'\nimport type {\n TanStackStartInputConfig,\n TanStackStartOutputConfig,\n} from './schema'\nimport type { PluginOption } from 'vite'\n\nfunction isFullUrl(str: string): boolean {\n try {\n new URL(str)\n return true\n } catch {\n return false\n }\n}\n\nexport function TanStackStartVitePluginCore(\n corePluginOpts: TanStackStartVitePluginCoreOptions,\n startPluginOpts: TanStackStartInputConfig,\n): Array<PluginOption> {\n // Determine the provider environment for server functions\n // If providerEnv is set, use that; otherwise default to SSR as the provider\n const serverFnProviderEnv =\n corePluginOpts.serverFn?.providerEnv || VITE_ENVIRONMENT_NAMES.server\n const ssrIsProvider = serverFnProviderEnv === VITE_ENVIRONMENT_NAMES.server\n\n const resolvedStartConfig: ResolvedStartConfig = {\n root: '',\n startFilePath: undefined,\n routerFilePath: '',\n srcDirectory: '',\n viteAppBase: '',\n serverFnProviderEnv,\n }\n\n let startConfig: TanStackStartOutputConfig | null\n const getConfig: GetConfigFn = () => {\n if (!resolvedStartConfig.root) {\n throw new Error(`Cannot get config before root is resolved`)\n }\n if (!startConfig) {\n startConfig = parseStartConfig(\n startPluginOpts,\n corePluginOpts,\n resolvedStartConfig.root,\n )\n }\n return { startConfig, resolvedStartConfig, corePluginOpts }\n }\n\n // When the router basepath and vite base are misaligned during dev,\n // we install a URL rewrite middleware instead of erroring.\n let needsDevBaseRewrite = false\n\n const capturedBundle: Partial<\n Record<ViteEnvironmentNames, vite.Rollup.OutputBundle>\n > = {}\n\n function getBundle(envName: ViteEnvironmentNames): vite.Rollup.OutputBundle {\n const bundle = capturedBundle[envName]\n if (!bundle) {\n throw new Error(`No bundle captured for environment: ${envName}`)\n }\n return bundle\n }\n\n const environments: Array<{ name: string; type: 'client' | 'server' }> = [\n { name: VITE_ENVIRONMENT_NAMES.client, type: 'client' },\n { name: VITE_ENVIRONMENT_NAMES.server, type: 'server' },\n ]\n if (\n corePluginOpts.serverFn?.providerEnv &&\n !environments.find((e) => e.name === corePluginOpts.serverFn?.providerEnv)\n ) {\n environments.push({\n name: corePluginOpts.serverFn.providerEnv,\n type: 'server',\n })\n }\n return [\n {\n name: 'tanstack-start-core:config',\n enforce: 'pre',\n async config(viteConfig, { command }) {\n resolvedStartConfig.viteAppBase = viteConfig.base ?? '/'\n if (!isFullUrl(resolvedStartConfig.viteAppBase)) {\n resolvedStartConfig.viteAppBase = joinPaths([\n '/',\n viteConfig.base,\n '/',\n ])\n }\n const root = viteConfig.root || process.cwd()\n resolvedStartConfig.root = root\n\n const { startConfig } = getConfig()\n if (startConfig.router.basepath === undefined) {\n if (!isFullUrl(resolvedStartConfig.viteAppBase)) {\n startConfig.router.basepath =\n resolvedStartConfig.viteAppBase.replace(/^\\/|\\/$/g, '')\n } else {\n startConfig.router.basepath = '/'\n }\n } else {\n if (command === 'serve' && !viteConfig.server?.middlewareMode) {\n // when serving, we must ensure that router basepath and viteAppBase are aligned\n if (\n !joinPaths(['/', startConfig.router.basepath, '/']).startsWith(\n joinPaths(['/', resolvedStartConfig.viteAppBase, '/']),\n )\n ) {\n // The router basepath and vite base are misaligned.\n // Instead of erroring, we install a dev-server middleware that\n // rewrites incoming request URLs to prepend the vite base prefix.\n // This allows users to have e.g. base: '/_ui/' for asset URLs\n // while keeping router basepath at '/' for page navigation.\n needsDevBaseRewrite = true\n }\n }\n }\n\n const TSS_SERVER_FN_BASE = joinPaths([\n '/',\n startConfig.router.basepath,\n startConfig.serverFns.base,\n '/',\n ])\n const resolvedSrcDirectory = join(root, startConfig.srcDirectory)\n resolvedStartConfig.srcDirectory = resolvedSrcDirectory\n\n const startFilePath = resolveEntry({\n type: 'start entry',\n configuredEntry: startConfig.start.entry,\n defaultEntry: 'start',\n resolvedSrcDirectory,\n required: false,\n })\n resolvedStartConfig.startFilePath = startFilePath\n\n const routerFilePath = resolveEntry({\n type: 'router entry',\n configuredEntry: startConfig.router.entry,\n defaultEntry: 'router',\n resolvedSrcDirectory,\n required: true,\n })\n resolvedStartConfig.routerFilePath = routerFilePath\n\n const clientEntryPath = resolveEntry({\n type: 'client entry',\n configuredEntry: startConfig.client.entry,\n defaultEntry: 'client',\n resolvedSrcDirectory,\n required: false,\n })\n\n const serverEntryPath = resolveEntry({\n type: 'server entry',\n configuredEntry: startConfig.server.entry,\n defaultEntry: 'server',\n resolvedSrcDirectory,\n required: false,\n })\n\n const clientAlias = vite.normalizePath(\n clientEntryPath ?? corePluginOpts.defaultEntryPaths.client,\n )\n const serverAlias = vite.normalizePath(\n serverEntryPath ?? corePluginOpts.defaultEntryPaths.server,\n )\n const startAlias = vite.normalizePath(\n startFilePath ?? corePluginOpts.defaultEntryPaths.start,\n )\n const routerAlias = vite.normalizePath(routerFilePath)\n\n const entryAliasConfiguration: Record<\n (typeof ENTRY_POINTS)[keyof typeof ENTRY_POINTS],\n string\n > = {\n [ENTRY_POINTS.client]: clientAlias,\n [ENTRY_POINTS.server]: serverAlias,\n [ENTRY_POINTS.start]: startAlias,\n [ENTRY_POINTS.router]: routerAlias,\n }\n\n const startPackageName =\n `@tanstack/${corePluginOpts.framework}-start` as const\n\n // crawl packages that have start in \"peerDependencies\"\n // see https://github.com/svitejs/vitefu/blob/d8d82fa121e3b2215ba437107093c77bde51b63b/src/index.js#L95-L101\n\n // this is currently uncached; could be implemented similarly as vite handles lock file changes\n // see https://github.com/vitejs/vite/blob/557f797d29422027e8c451ca50dd84bf8c41b5f0/packages/vite/src/node/optimizer/index.ts#L1282\n\n const crawlFrameworkPkgsResult = await crawlFrameworkPkgs({\n root: process.cwd(),\n isBuild: command === 'build',\n isFrameworkPkgByJson(pkgJson) {\n const peerDependencies = pkgJson['peerDependencies']\n\n if (peerDependencies) {\n if (\n startPackageName in peerDependencies ||\n '@tanstack/start-client-core' in peerDependencies\n ) {\n return true\n }\n }\n\n return false\n },\n })\n\n return {\n // see https://vite.dev/config/shared-options.html#apptype\n // this will prevent vite from injecting middlewares that we don't want\n appType: viteConfig.appType ?? 'custom',\n environments: {\n [VITE_ENVIRONMENT_NAMES.client]: {\n consumer: 'client',\n build: {\n rollupOptions: {\n input: {\n main: ENTRY_POINTS.client,\n },\n },\n outDir: getClientOutputDirectory(viteConfig),\n },\n optimizeDeps: {\n exclude: crawlFrameworkPkgsResult.optimizeDeps.exclude,\n // Ensure user code can be crawled for dependencies\n entries: [clientAlias, routerAlias].map((entry) =>\n // Entries are treated as `tinyglobby` patterns so need to be escaped\n escapePath(entry),\n ),\n },\n },\n [VITE_ENVIRONMENT_NAMES.server]: {\n consumer: 'server',\n build: {\n ssr: true,\n rollupOptions: {\n input:\n viteConfig.environments?.[VITE_ENVIRONMENT_NAMES.server]\n ?.build?.rollupOptions?.input ?? serverAlias,\n },\n outDir: getServerOutputDirectory(viteConfig),\n commonjsOptions: {\n include: [/node_modules/],\n },\n copyPublicDir:\n viteConfig.environments?.[VITE_ENVIRONMENT_NAMES.server]\n ?.build?.copyPublicDir ?? false,\n },\n optimizeDeps: {\n // Ensure user code can be crawled for dependencies\n entries: [serverAlias, startAlias, routerAlias].map((entry) =>\n // Entries are treated as `tinyglobby` patterns so need to be escaped\n escapePath(entry),\n ),\n },\n },\n },\n\n resolve: {\n noExternal: [\n // ENTRY_POINTS.start,\n '@tanstack/start**',\n `@tanstack/${corePluginOpts.framework}-start**`,\n ...crawlFrameworkPkgsResult.ssr.noExternal.sort(),\n ],\n alias: {\n ...entryAliasConfiguration,\n },\n },\n /* prettier-ignore */\n define: {\n // define is an esbuild function that replaces the any instances of given keys with the given values\n // i.e: __FRAMEWORK_NAME__ can be replaced with JSON.stringify(\"TanStack Start\")\n // This is not the same as injecting environment variables.\n\n ...defineReplaceEnv('TSS_SERVER_FN_BASE', TSS_SERVER_FN_BASE),\n ...defineReplaceEnv('TSS_CLIENT_OUTPUT_DIR', getClientOutputDirectory(viteConfig)),\n ...defineReplaceEnv('TSS_ROUTER_BASEPATH', startConfig.router.basepath),\n ...(command === 'serve' ? defineReplaceEnv('TSS_SHELL', startConfig.spa?.enabled ? 'true' : 'false') : {}),\n ...defineReplaceEnv('TSS_DEV_SERVER', command === 'serve' ? 'true' : 'false'),\n // Dev SSR styles: enabled flag and basepath (defaults to vite base for asset URL alignment)\n ...defineReplaceEnv('TSS_DEV_SSR_STYLES_ENABLED', startConfig.dev.ssrStyles.enabled ? 'true' : 'false'),\n ...defineReplaceEnv('TSS_DEV_SSR_STYLES_BASEPATH', startConfig.dev.ssrStyles.basepath ?? resolvedStartConfig.viteAppBase),\n // Replace NODE_ENV during build (unless opted out) for dead code elimination in server bundles\n ...(command === 'build' && startConfig.server.build.staticNodeEnv ? {\n 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || viteConfig.mode || 'production'),\n } : {}),\n },\n builder: {\n sharedPlugins: true,\n async buildApp(builder) {\n const client = builder.environments[VITE_ENVIRONMENT_NAMES.client]\n const server = builder.environments[VITE_ENVIRONMENT_NAMES.server]\n\n if (!client) {\n throw new Error('Client environment not found')\n }\n\n if (!server) {\n throw new Error('SSR environment not found')\n }\n\n if (!client.isBuilt) {\n // Build the client bundle first\n await builder.build(client)\n }\n if (!server.isBuilt) {\n // Build the SSR bundle\n await builder.build(server)\n }\n\n // If a custom provider environment is configured (not SSR),\n // build it last so the manifest includes functions from all environments\n if (!ssrIsProvider) {\n const providerEnv = builder.environments[serverFnProviderEnv]\n if (!providerEnv) {\n throw new Error(\n `Provider environment \"${serverFnProviderEnv}\" not found`,\n )\n }\n if (!providerEnv.isBuilt) {\n // Build the provider environment last\n // This ensures all server functions are discovered from client/ssr builds\n await builder.build(providerEnv)\n }\n }\n },\n },\n }\n },\n },\n // Separate plugin for buildApp hook with enforce: 'post'\n // This ensures proper ordering with other plugins that also have\n // buildApp hooks with order: 'post'. The enforce: 'post' ensures this\n // runs after other plugins (like Nitro) complete their builds.\n {\n name: 'tanstack-start-core:post-build',\n enforce: 'post',\n buildApp: {\n order: 'post',\n async handler(builder) {\n const { startConfig } = getConfig()\n await postServerBuild({ builder, startConfig })\n },\n },\n },\n // Server function plugin handles:\n // 1. Identifying createServerFn().handler() calls\n // 2. Extracting server functions to separate modules\n // 3. Replacing call sites with RPC stubs\n // 4. Generating the server function manifest\n // Also handles createIsomorphicFn, createServerOnlyFn, createClientOnlyFn, createMiddleware\n startCompilerPlugin({\n framework: corePluginOpts.framework,\n environments,\n generateFunctionId: startPluginOpts?.serverFns?.generateFunctionId,\n providerEnvName: serverFnProviderEnv,\n }),\n importProtectionPlugin({\n getConfig,\n framework: corePluginOpts.framework,\n environments,\n providerEnvName: serverFnProviderEnv,\n }),\n tanStackStartRouter(startPluginOpts, getConfig, corePluginOpts),\n loadEnvPlugin(),\n startManifestPlugin({\n getClientBundle: () => getBundle(VITE_ENVIRONMENT_NAMES.client),\n getConfig,\n }),\n // When the vite base and router basepath are misaligned (e.g. base: '/_ui/', basepath: '/'),\n // install a middleware that rewrites incoming request URLs to prepend the vite base prefix.\n // This allows Vite's internal base middleware to accept the requests, then strips the prefix\n // before passing to the SSR handler.\n // Registered BEFORE devServerPlugin so this middleware is added to the Connect stack first,\n // ensuring all subsequent middlewares (CSS, SSR, etc.) see the rewritten URL.\n {\n name: 'tanstack-start-core:dev-base-rewrite',\n configureServer(server) {\n if (!needsDevBaseRewrite) {\n return\n }\n const basePrefix = resolvedStartConfig.viteAppBase.replace(/\\/$/, '')\n server.middlewares.use((req, _res, next) => {\n if (req.url && !req.url.startsWith(basePrefix)) {\n req.url = basePrefix + req.url\n }\n next()\n })\n },\n },\n devServerPlugin({\n getConfig,\n devSsrStylesEnabled: startPluginOpts?.dev?.ssrStyles?.enabled ?? true,\n }),\n previewServerPlugin(),\n {\n name: 'tanstack-start:core:capture-bundle',\n applyToEnvironment(e) {\n return (\n e.name === VITE_ENVIRONMENT_NAMES.client ||\n e.name === VITE_ENVIRONMENT_NAMES.server\n )\n },\n enforce: 'post',\n generateBundle(_options, bundle) {\n const environment = this.environment.name as ViteEnvironmentNames\n if (!Object.values(VITE_ENVIRONMENT_NAMES).includes(environment)) {\n throw new Error(`Unknown environment: ${environment}`)\n }\n capturedBundle[environment] = bundle\n },\n },\n ]\n}\n\nfunction defineReplaceEnv<TKey extends string, TValue extends string>(\n key: TKey,\n value: TValue,\n): { [P in `process.env.${TKey}` | `import.meta.env.${TKey}`]: TValue } {\n return {\n [`process.env.${key}`]: JSON.stringify(value),\n [`import.meta.env.${key}`]: JSON.stringify(value),\n } as { [P in `process.env.${TKey}` | `import.meta.env.${TKey}`]: TValue }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","names":[],"sources":["../../src/plugin.ts"],"sourcesContent":["import { joinPaths } from '@tanstack/router-core'\nimport * as vite from 'vite'\nimport { crawlFrameworkPkgs } from 'vitefu'\nimport { join } from 'pathe'\nimport { escapePath } from 'tinyglobby'\nimport { startManifestPlugin } from './start-manifest-plugin/plugin'\nimport { ENTRY_POINTS, VITE_ENVIRONMENT_NAMES } from './constants'\nimport { bundlerOptionsKey, getBundlerOptions } from './utils'\nimport { tanStackStartRouter } from './start-router-plugin/plugin'\nimport { loadEnvPlugin } from './load-env-plugin/plugin'\nimport { devServerPlugin } from './dev-server-plugin/plugin'\nimport { previewServerPlugin } from './preview-server-plugin/plugin'\nimport { parseStartConfig } from './schema'\nimport { resolveEntry } from './resolve-entries'\nimport {\n getClientOutputDirectory,\n getServerOutputDirectory,\n} from './output-directory'\nimport { postServerBuild } from './post-server-build'\nimport { startCompilerPlugin } from './start-compiler-plugin/plugin'\nimport { importProtectionPlugin } from './import-protection-plugin/plugin'\nimport type {\n GetConfigFn,\n ResolvedStartConfig,\n TanStackStartVitePluginCoreOptions,\n} from './types'\nimport type { ViteEnvironmentNames } from './constants'\nimport type {\n TanStackStartInputConfig,\n TanStackStartOutputConfig,\n} from './schema'\nimport type { PluginOption } from 'vite'\n\nfunction isFullUrl(str: string): boolean {\n try {\n new URL(str)\n return true\n } catch {\n return false\n }\n}\n\nexport function TanStackStartVitePluginCore(\n corePluginOpts: TanStackStartVitePluginCoreOptions,\n startPluginOpts: TanStackStartInputConfig,\n): Array<PluginOption> {\n // Determine the provider environment for server functions\n // If providerEnv is set, use that; otherwise default to SSR as the provider\n const serverFnProviderEnv =\n corePluginOpts.serverFn?.providerEnv || VITE_ENVIRONMENT_NAMES.server\n const ssrIsProvider = serverFnProviderEnv === VITE_ENVIRONMENT_NAMES.server\n\n const resolvedStartConfig: ResolvedStartConfig = {\n root: '',\n startFilePath: undefined,\n routerFilePath: '',\n srcDirectory: '',\n viteAppBase: '',\n serverFnProviderEnv,\n }\n\n let startConfig: TanStackStartOutputConfig | null\n const getConfig: GetConfigFn = () => {\n if (!resolvedStartConfig.root) {\n throw new Error(`Cannot get config before root is resolved`)\n }\n if (!startConfig) {\n startConfig = parseStartConfig(\n startPluginOpts,\n corePluginOpts,\n resolvedStartConfig.root,\n )\n }\n return { startConfig, resolvedStartConfig, corePluginOpts }\n }\n\n // When the router basepath and vite base are misaligned during dev,\n // we install a URL rewrite middleware instead of erroring.\n let needsDevBaseRewrite = false\n\n const capturedBundle: Partial<\n Record<ViteEnvironmentNames, vite.Rollup.OutputBundle>\n > = {}\n\n function getBundle(envName: ViteEnvironmentNames): vite.Rollup.OutputBundle {\n const bundle = capturedBundle[envName]\n if (!bundle) {\n throw new Error(`No bundle captured for environment: ${envName}`)\n }\n return bundle\n }\n\n const environments: Array<{ name: string; type: 'client' | 'server' }> = [\n { name: VITE_ENVIRONMENT_NAMES.client, type: 'client' },\n { name: VITE_ENVIRONMENT_NAMES.server, type: 'server' },\n ]\n if (\n corePluginOpts.serverFn?.providerEnv &&\n !environments.find((e) => e.name === corePluginOpts.serverFn?.providerEnv)\n ) {\n environments.push({\n name: corePluginOpts.serverFn.providerEnv,\n type: 'server',\n })\n }\n return [\n {\n name: 'tanstack-start-core:config',\n enforce: 'pre',\n async config(viteConfig, { command }) {\n resolvedStartConfig.viteAppBase = viteConfig.base ?? '/'\n if (!isFullUrl(resolvedStartConfig.viteAppBase)) {\n resolvedStartConfig.viteAppBase = joinPaths([\n '/',\n viteConfig.base,\n '/',\n ])\n }\n const root = viteConfig.root || process.cwd()\n resolvedStartConfig.root = root\n\n const { startConfig } = getConfig()\n if (startConfig.router.basepath === undefined) {\n if (!isFullUrl(resolvedStartConfig.viteAppBase)) {\n startConfig.router.basepath =\n resolvedStartConfig.viteAppBase.replace(/^\\/|\\/$/g, '')\n } else {\n startConfig.router.basepath = '/'\n }\n } else {\n if (command === 'serve' && !viteConfig.server?.middlewareMode) {\n // when serving, we must ensure that router basepath and viteAppBase are aligned\n if (\n !joinPaths(['/', startConfig.router.basepath, '/']).startsWith(\n joinPaths(['/', resolvedStartConfig.viteAppBase, '/']),\n )\n ) {\n // The router basepath and vite base are misaligned.\n // Instead of erroring, we install a dev-server middleware that\n // rewrites incoming request URLs to prepend the vite base prefix.\n // This allows users to have e.g. base: '/_ui/' for asset URLs\n // while keeping router basepath at '/' for page navigation.\n needsDevBaseRewrite = true\n }\n }\n }\n\n const TSS_SERVER_FN_BASE = joinPaths([\n '/',\n startConfig.router.basepath,\n startConfig.serverFns.base,\n '/',\n ])\n const resolvedSrcDirectory = join(root, startConfig.srcDirectory)\n resolvedStartConfig.srcDirectory = resolvedSrcDirectory\n\n const startFilePath = resolveEntry({\n type: 'start entry',\n configuredEntry: startConfig.start.entry,\n defaultEntry: 'start',\n resolvedSrcDirectory,\n required: false,\n })\n resolvedStartConfig.startFilePath = startFilePath\n\n const routerFilePath = resolveEntry({\n type: 'router entry',\n configuredEntry: startConfig.router.entry,\n defaultEntry: 'router',\n resolvedSrcDirectory,\n required: true,\n })\n resolvedStartConfig.routerFilePath = routerFilePath\n\n const clientEntryPath = resolveEntry({\n type: 'client entry',\n configuredEntry: startConfig.client.entry,\n defaultEntry: 'client',\n resolvedSrcDirectory,\n required: false,\n })\n\n const serverEntryPath = resolveEntry({\n type: 'server entry',\n configuredEntry: startConfig.server.entry,\n defaultEntry: 'server',\n resolvedSrcDirectory,\n required: false,\n })\n\n const clientAlias = vite.normalizePath(\n clientEntryPath ?? corePluginOpts.defaultEntryPaths.client,\n )\n const serverAlias = vite.normalizePath(\n serverEntryPath ?? corePluginOpts.defaultEntryPaths.server,\n )\n const startAlias = vite.normalizePath(\n startFilePath ?? corePluginOpts.defaultEntryPaths.start,\n )\n const routerAlias = vite.normalizePath(routerFilePath)\n\n const entryAliasConfiguration: Record<\n (typeof ENTRY_POINTS)[keyof typeof ENTRY_POINTS],\n string\n > = {\n [ENTRY_POINTS.client]: clientAlias,\n [ENTRY_POINTS.server]: serverAlias,\n [ENTRY_POINTS.start]: startAlias,\n [ENTRY_POINTS.router]: routerAlias,\n }\n\n const startPackageName =\n `@tanstack/${corePluginOpts.framework}-start` as const\n\n // crawl packages that have start in \"peerDependencies\"\n // see https://github.com/svitejs/vitefu/blob/d8d82fa121e3b2215ba437107093c77bde51b63b/src/index.js#L95-L101\n\n // this is currently uncached; could be implemented similarly as vite handles lock file changes\n // see https://github.com/vitejs/vite/blob/557f797d29422027e8c451ca50dd84bf8c41b5f0/packages/vite/src/node/optimizer/index.ts#L1282\n\n const crawlFrameworkPkgsResult = await crawlFrameworkPkgs({\n root: process.cwd(),\n isBuild: command === 'build',\n isFrameworkPkgByJson(pkgJson) {\n const peerDependencies = pkgJson['peerDependencies']\n\n if (peerDependencies) {\n if (\n startPackageName in peerDependencies ||\n '@tanstack/start-client-core' in peerDependencies\n ) {\n return true\n }\n }\n\n return false\n },\n })\n\n return {\n // see https://vite.dev/config/shared-options.html#apptype\n // this will prevent vite from injecting middlewares that we don't want\n appType: viteConfig.appType ?? 'custom',\n environments: {\n [VITE_ENVIRONMENT_NAMES.client]: {\n consumer: 'client',\n build: {\n [bundlerOptionsKey]: {\n input: {\n main: ENTRY_POINTS.client,\n },\n },\n outDir: getClientOutputDirectory(viteConfig),\n },\n optimizeDeps: {\n exclude: crawlFrameworkPkgsResult.optimizeDeps.exclude,\n // Ensure user code can be crawled for dependencies\n entries: [clientAlias, routerAlias].map((entry) =>\n // Entries are treated as `tinyglobby` patterns so need to be escaped\n escapePath(entry),\n ),\n },\n },\n [VITE_ENVIRONMENT_NAMES.server]: {\n consumer: 'server',\n build: {\n ssr: true,\n [bundlerOptionsKey]: {\n input:\n getBundlerOptions(\n viteConfig.environments?.[VITE_ENVIRONMENT_NAMES.server]\n ?.build,\n )?.input ?? serverAlias,\n },\n outDir: getServerOutputDirectory(viteConfig),\n commonjsOptions: {\n include: [/node_modules/],\n },\n copyPublicDir:\n viteConfig.environments?.[VITE_ENVIRONMENT_NAMES.server]\n ?.build?.copyPublicDir ?? false,\n },\n optimizeDeps: {\n // Ensure user code can be crawled for dependencies\n entries: [serverAlias, startAlias, routerAlias].map((entry) =>\n // Entries are treated as `tinyglobby` patterns so need to be escaped\n escapePath(entry),\n ),\n },\n },\n },\n\n resolve: {\n noExternal: [\n // ENTRY_POINTS.start,\n '@tanstack/start**',\n `@tanstack/${corePluginOpts.framework}-start**`,\n ...crawlFrameworkPkgsResult.ssr.noExternal.sort(),\n ],\n alias: {\n ...entryAliasConfiguration,\n },\n },\n /* prettier-ignore */\n define: {\n // define is an esbuild function that replaces the any instances of given keys with the given values\n // i.e: __FRAMEWORK_NAME__ can be replaced with JSON.stringify(\"TanStack Start\")\n // This is not the same as injecting environment variables.\n\n ...defineReplaceEnv('TSS_SERVER_FN_BASE', TSS_SERVER_FN_BASE),\n ...defineReplaceEnv('TSS_ROUTER_BASEPATH', startConfig.router.basepath),\n ...(command === 'serve' ? defineReplaceEnv('TSS_SHELL', startConfig.spa?.enabled ? 'true' : 'false') : {}),\n ...defineReplaceEnv('TSS_DEV_SERVER', command === 'serve' ? 'true' : 'false'),\n // Dev SSR styles: enabled flag and basepath (defaults to vite base for asset URL alignment)\n ...defineReplaceEnv('TSS_DEV_SSR_STYLES_ENABLED', startConfig.dev.ssrStyles.enabled ? 'true' : 'false'),\n ...defineReplaceEnv('TSS_DEV_SSR_STYLES_BASEPATH', startConfig.dev.ssrStyles.basepath ?? resolvedStartConfig.viteAppBase),\n // Replace NODE_ENV during build (unless opted out) for dead code elimination in server bundles\n ...(command === 'build' && startConfig.server.build.staticNodeEnv ? {\n 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || viteConfig.mode || 'production'),\n } : {}),\n },\n builder: {\n sharedPlugins: true,\n async buildApp(builder) {\n const client = builder.environments[VITE_ENVIRONMENT_NAMES.client]\n const server = builder.environments[VITE_ENVIRONMENT_NAMES.server]\n\n if (!client) {\n throw new Error('Client environment not found')\n }\n\n if (!server) {\n throw new Error('SSR environment not found')\n }\n\n if (!client.isBuilt) {\n // Build the client bundle first\n await builder.build(client)\n }\n if (!server.isBuilt) {\n // Build the SSR bundle\n await builder.build(server)\n }\n\n // If a custom provider environment is configured (not SSR),\n // build it last so the manifest includes functions from all environments\n if (!ssrIsProvider) {\n const providerEnv = builder.environments[serverFnProviderEnv]\n if (!providerEnv) {\n throw new Error(\n `Provider environment \"${serverFnProviderEnv}\" not found`,\n )\n }\n if (!providerEnv.isBuilt) {\n // Build the provider environment last\n // This ensures all server functions are discovered from client/ssr builds\n await builder.build(providerEnv)\n }\n }\n },\n },\n }\n },\n },\n // Separate plugin for buildApp hook with enforce: 'post'\n // This ensures proper ordering with other plugins that also have\n // buildApp hooks with order: 'post'. The enforce: 'post' ensures this\n // runs after other plugins (like Nitro) complete their builds.\n {\n name: 'tanstack-start-core:post-build',\n enforce: 'post',\n buildApp: {\n order: 'post',\n async handler(builder) {\n const { startConfig } = getConfig()\n await postServerBuild({ builder, startConfig })\n },\n },\n },\n // Server function plugin handles:\n // 1. Identifying createServerFn().handler() calls\n // 2. Extracting server functions to separate modules\n // 3. Replacing call sites with RPC stubs\n // 4. Generating the server function manifest\n // Also handles createIsomorphicFn, createServerOnlyFn, createClientOnlyFn, createMiddleware\n startCompilerPlugin({\n framework: corePluginOpts.framework,\n environments,\n generateFunctionId: startPluginOpts?.serverFns?.generateFunctionId,\n providerEnvName: serverFnProviderEnv,\n }),\n importProtectionPlugin({\n getConfig,\n framework: corePluginOpts.framework,\n environments,\n providerEnvName: serverFnProviderEnv,\n }),\n tanStackStartRouter(startPluginOpts, getConfig, corePluginOpts),\n loadEnvPlugin(),\n startManifestPlugin({\n getClientBundle: () => getBundle(VITE_ENVIRONMENT_NAMES.client),\n getConfig,\n }),\n // When the vite base and router basepath are misaligned (e.g. base: '/_ui/', basepath: '/'),\n // install a middleware that rewrites incoming request URLs to prepend the vite base prefix.\n // This allows Vite's internal base middleware to accept the requests, then strips the prefix\n // before passing to the SSR handler.\n // Registered BEFORE devServerPlugin so this middleware is added to the Connect stack first,\n // ensuring all subsequent middlewares (CSS, SSR, etc.) see the rewritten URL.\n {\n name: 'tanstack-start-core:dev-base-rewrite',\n configureServer(server) {\n if (!needsDevBaseRewrite) {\n return\n }\n const basePrefix = resolvedStartConfig.viteAppBase.replace(/\\/$/, '')\n server.middlewares.use((req, _res, next) => {\n if (req.url && !req.url.startsWith(basePrefix)) {\n req.url = basePrefix + req.url\n }\n next()\n })\n },\n },\n devServerPlugin({\n getConfig,\n devSsrStylesEnabled: startPluginOpts?.dev?.ssrStyles?.enabled ?? true,\n }),\n previewServerPlugin(),\n {\n name: 'tanstack-start:core:capture-bundle',\n applyToEnvironment(e) {\n return (\n e.name === VITE_ENVIRONMENT_NAMES.client ||\n e.name === VITE_ENVIRONMENT_NAMES.server\n )\n },\n enforce: 'post',\n generateBundle(_options, bundle) {\n const environment = this.environment.name as ViteEnvironmentNames\n if (!Object.values(VITE_ENVIRONMENT_NAMES).includes(environment)) {\n throw new Error(`Unknown environment: ${environment}`)\n }\n capturedBundle[environment] = bundle\n },\n },\n ]\n}\n\nfunction defineReplaceEnv<TKey extends string, TValue extends string>(\n key: TKey,\n value: TValue,\n): { [P in `process.env.${TKey}` | `import.meta.env.${TKey}`]: TValue } {\n return {\n [`process.env.${key}`]: JSON.stringify(value),\n [`import.meta.env.${key}`]: JSON.stringify(value),\n } as { [P in `process.env.${TKey}` | `import.meta.env.${TKey}`]: TValue }\n}\n"],"mappings":"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"}
@@ -28,6 +28,7 @@ async function prerender({ startConfig, builder }) {
28
28
  if (!clientEnv) throw new Error(`Vite's "${VITE_ENVIRONMENT_NAMES.client}" environment not found`);
29
29
  const outputDir = clientEnv.config.build.outDir;
30
30
  process.env.TSS_PRERENDERING = "true";
31
+ process.env.TSS_CLIENT_OUTPUT_DIR = outputDir;
31
32
  const previewServer = await startPreviewServer(serverEnv.config);
32
33
  const baseUrl = getResolvedUrl(previewServer);
33
34
  const isRedirectResponse = (res) => {
@@ -1 +1 @@
1
- {"version":3,"file":"prerender.js","names":[],"sources":["../../src/prerender.ts"],"sourcesContent":["import { promises as fsp } from 'node:fs'\nimport os from 'node:os'\nimport path from 'pathe'\nimport { joinURL, withBase, withTrailingSlash, withoutBase } from 'ufo'\nimport { VITE_ENVIRONMENT_NAMES } from './constants'\nimport { createLogger } from './utils'\nimport { Queue } from './queue'\nimport type { PreviewServer, ResolvedConfig, ViteBuilder } from 'vite'\nimport type { Page, TanStackStartOutputConfig } from './schema'\n\nexport async function prerender({\n startConfig,\n builder,\n}: {\n startConfig: TanStackStartOutputConfig\n builder: ViteBuilder\n}) {\n const logger = createLogger('prerender')\n logger.info('Prerendering pages...')\n\n // If prerender is enabled\n if (startConfig.prerender?.enabled) {\n // default to root page if no pages are defined\n let pages = startConfig.pages.length ? startConfig.pages : [{ path: '/' }]\n\n if (startConfig.prerender.autoStaticPathsDiscovery ?? true) {\n // merge discovered static pages with user-defined pages\n const pagesMap = new Map(pages.map((item) => [item.path, item]))\n const discoveredPages = globalThis.TSS_PRERENDABLE_PATHS || []\n\n for (const page of discoveredPages) {\n if (!pagesMap.has(page.path)) {\n pagesMap.set(page.path, page)\n }\n }\n\n pages = Array.from(pagesMap.values())\n }\n\n startConfig.pages = pages\n }\n\n const routerBasePath = joinURL('/', startConfig.router.basepath ?? '')\n const routerBaseUrl = new URL(routerBasePath, 'http://localhost')\n\n // Enforce that prerender page paths are relative/path-based (no protocol/host)\n startConfig.pages = validateAndNormalizePrerenderPages(\n startConfig.pages,\n routerBaseUrl,\n )\n\n const serverEnv = builder.environments[VITE_ENVIRONMENT_NAMES.server]\n\n if (!serverEnv) {\n throw new Error(\n `Vite's \"${VITE_ENVIRONMENT_NAMES.server}\" environment not found`,\n )\n }\n\n const clientEnv = builder.environments[VITE_ENVIRONMENT_NAMES.client]\n if (!clientEnv) {\n throw new Error(\n `Vite's \"${VITE_ENVIRONMENT_NAMES.client}\" environment not found`,\n )\n }\n\n const outputDir = clientEnv.config.build.outDir\n\n process.env.TSS_PRERENDERING = 'true'\n\n // Start Vite preview server instead of importing module\n const previewServer = await startPreviewServer(serverEnv.config)\n const baseUrl = getResolvedUrl(previewServer)\n\n const isRedirectResponse = (res: Response) => {\n return res.status >= 300 && res.status < 400 && res.headers.get('location')\n }\n async function localFetch(\n path: string,\n options?: RequestInit,\n maxRedirects: number = 5,\n ): Promise<Response> {\n const url = new URL(path, baseUrl)\n const request = new Request(url, options)\n const response = await fetch(request)\n\n if (isRedirectResponse(response) && maxRedirects > 0) {\n const location = response.headers.get('location')!\n if (location.startsWith('http://localhost') || location.startsWith('/')) {\n const newUrl = location.replace('http://localhost', '')\n return localFetch(newUrl, options, maxRedirects - 1)\n } else {\n logger.warn(`Skipping redirect to external location: ${location}`)\n }\n }\n\n return response\n }\n\n try {\n // Crawl all pages\n const pages = await prerenderPages({ outputDir })\n\n logger.info(`Prerendered ${pages.length} pages:`)\n pages.forEach((page) => {\n logger.info(`- ${page}`)\n })\n } catch (error) {\n logger.error(error)\n } finally {\n await previewServer.close()\n }\n\n function extractLinks(html: string): Array<string> {\n const linkRegex = /<a[^>]+href=[\"']([^\"']+)[\"'][^>]*>/g\n const links: Array<string> = []\n let match\n\n while ((match = linkRegex.exec(html)) !== null) {\n const href = match[1]\n if (href && (href.startsWith('/') || href.startsWith('./'))) {\n links.push(href)\n }\n }\n\n return links\n }\n\n async function prerenderPages({ outputDir }: { outputDir: string }) {\n const seen = new Set<string>()\n const prerendered = new Set<string>()\n const retriesByPath = new Map<string, number>()\n const concurrency = startConfig.prerender?.concurrency ?? os.cpus().length\n logger.info(`Concurrency: ${concurrency}`)\n const queue = new Queue({ concurrency })\n const routerBasePath = joinURL('/', startConfig.router.basepath ?? '')\n\n // Normalize discovered pages and enforce path-only entries\n const routerBaseUrl = new URL(routerBasePath, 'http://localhost')\n startConfig.pages = validateAndNormalizePrerenderPages(\n startConfig.pages,\n routerBaseUrl,\n )\n\n startConfig.pages.forEach((page) => addCrawlPageTask(page))\n\n if (queue.isSettled()) {\n logger.info('No pages matched prerender filter; skipping.')\n return Array.from(prerendered)\n }\n\n await queue.start()\n\n return Array.from(prerendered)\n\n function addCrawlPageTask(page: Page) {\n // Was the page already seen?\n if (seen.has(page.path)) return\n\n // Add the page to the seen set\n seen.add(page.path)\n\n if (page.fromCrawl) {\n startConfig.pages.push(page)\n }\n\n // If not enabled, skip\n if (!(page.prerender?.enabled ?? true)) return\n\n // If there is a filter link, check if the page should be prerendered\n if (startConfig.prerender?.filter && !startConfig.prerender.filter(page))\n return\n\n // Resolve the merged default and page-specific prerender options\n const prerenderOptions = {\n ...startConfig.prerender,\n ...page.prerender,\n }\n\n // Add the task\n queue.add(async () => {\n logger.info(`Crawling: ${page.path}`)\n const retries = retriesByPath.get(page.path) || 0\n try {\n // Fetch the route\n\n const res = await localFetch(\n withTrailingSlash(withBase(page.path, routerBasePath)),\n {\n headers: {\n ...(prerenderOptions.headers ?? {}),\n },\n },\n prerenderOptions.maxRedirects,\n )\n\n if (!res.ok) {\n if (isRedirectResponse(res)) {\n logger.warn(`Max redirects reached for ${page.path}`)\n }\n throw new Error(`Failed to fetch ${page.path}: ${res.statusText}`, {\n cause: res,\n })\n }\n\n const cleanPagePath = (\n prerenderOptions.outputPath || page.path\n ).split(/[?#]/)[0]!\n\n // Guess route type and populate fileName\n const contentType = res.headers.get('content-type') || ''\n const isImplicitHTML =\n !cleanPagePath.endsWith('.html') && contentType.includes('html')\n\n const routeWithIndex = cleanPagePath.endsWith('/')\n ? cleanPagePath + 'index'\n : cleanPagePath\n\n const isSpaShell =\n startConfig.spa?.prerender.outputPath === cleanPagePath\n\n let htmlPath: string\n if (isSpaShell) {\n // For SPA shell, ignore autoSubfolderIndex option\n htmlPath = cleanPagePath + '.html'\n } else {\n if (\n cleanPagePath.endsWith('/') ||\n (prerenderOptions.autoSubfolderIndex ?? true)\n ) {\n htmlPath = joinURL(cleanPagePath, 'index.html')\n } else {\n htmlPath = cleanPagePath + '.html'\n }\n }\n\n const filename = withoutBase(\n isImplicitHTML ? htmlPath : routeWithIndex,\n routerBasePath,\n )\n\n const html = await res.text()\n\n const filepath = path.join(outputDir, filename)\n\n await fsp.mkdir(path.dirname(filepath), {\n recursive: true,\n })\n\n await fsp.writeFile(filepath, html)\n\n prerendered.add(page.path)\n\n const newPage = await prerenderOptions.onSuccess?.({ page, html })\n\n if (newPage) {\n Object.assign(page, newPage)\n }\n\n // Find new links\n if (prerenderOptions.crawlLinks ?? true) {\n const links = extractLinks(html)\n for (const link of links) {\n addCrawlPageTask({ path: link, fromCrawl: true })\n }\n }\n } catch (error) {\n if (retries < (prerenderOptions.retryCount ?? 0)) {\n logger.warn(`Encountered error, retrying: ${page.path} in 500ms`)\n await new Promise((resolve) =>\n setTimeout(resolve, prerenderOptions.retryDelay),\n )\n retriesByPath.set(page.path, retries + 1)\n addCrawlPageTask(page)\n } else {\n if (prerenderOptions.failOnError ?? true) {\n throw error\n }\n }\n }\n })\n }\n }\n}\n\nasync function startPreviewServer(\n viteConfig: ResolvedConfig,\n): Promise<PreviewServer> {\n const vite = await import('vite')\n\n try {\n return await vite.preview({\n configFile: viteConfig.configFile,\n preview: {\n port: 0,\n open: false,\n },\n })\n } catch (error) {\n throw new Error(\n 'Failed to start the Vite preview server for prerendering',\n {\n cause: error,\n },\n )\n }\n}\n\nfunction getResolvedUrl(previewServer: PreviewServer): URL {\n const baseUrl = previewServer.resolvedUrls?.local[0]\n\n if (!baseUrl) {\n throw new Error('No resolved URL is available from the Vite preview server')\n }\n\n return new URL(baseUrl)\n}\n\n/**\n * Validates and normalizes prerender page paths to ensure they are relative\n * (no protocol/host) and returns normalized Page objects with cleaned paths.\n * Preserves unicode characters by decoding the pathname after URL validation.\n */\nfunction validateAndNormalizePrerenderPages(\n pages: Array<Page>,\n routerBaseUrl: URL,\n): Array<Page> {\n return pages.map((page) => {\n let url: URL\n try {\n url = new URL(page.path, routerBaseUrl)\n } catch (err) {\n throw new Error(`prerender page path must be relative: ${page.path}`, {\n cause: err,\n })\n }\n\n if (url.origin !== 'http://localhost') {\n throw new Error(`prerender page path must be relative: ${page.path}`)\n }\n\n // Decode the pathname to preserve unicode characters (e.g., /대한민국)\n // The URL constructor encodes non-ASCII characters, but we want to keep\n // the original unicode form for filesystem paths\n const decodedPathname = decodeURIComponent(url.pathname)\n\n return {\n ...page,\n path: decodedPathname + url.search + url.hash,\n }\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"prerender.js","names":[],"sources":["../../src/prerender.ts"],"sourcesContent":["import { promises as fsp } from 'node:fs'\nimport os from 'node:os'\nimport path from 'pathe'\nimport { joinURL, withBase, withTrailingSlash, withoutBase } from 'ufo'\nimport { VITE_ENVIRONMENT_NAMES } from './constants'\nimport { createLogger } from './utils'\nimport { Queue } from './queue'\nimport type { PreviewServer, ResolvedConfig, ViteBuilder } from 'vite'\nimport type { Page, TanStackStartOutputConfig } from './schema'\n\nexport async function prerender({\n startConfig,\n builder,\n}: {\n startConfig: TanStackStartOutputConfig\n builder: ViteBuilder\n}) {\n const logger = createLogger('prerender')\n logger.info('Prerendering pages...')\n\n // If prerender is enabled\n if (startConfig.prerender?.enabled) {\n // default to root page if no pages are defined\n let pages = startConfig.pages.length ? startConfig.pages : [{ path: '/' }]\n\n if (startConfig.prerender.autoStaticPathsDiscovery ?? true) {\n // merge discovered static pages with user-defined pages\n const pagesMap = new Map(pages.map((item) => [item.path, item]))\n const discoveredPages = globalThis.TSS_PRERENDABLE_PATHS || []\n\n for (const page of discoveredPages) {\n if (!pagesMap.has(page.path)) {\n pagesMap.set(page.path, page)\n }\n }\n\n pages = Array.from(pagesMap.values())\n }\n\n startConfig.pages = pages\n }\n\n const routerBasePath = joinURL('/', startConfig.router.basepath ?? '')\n const routerBaseUrl = new URL(routerBasePath, 'http://localhost')\n\n // Enforce that prerender page paths are relative/path-based (no protocol/host)\n startConfig.pages = validateAndNormalizePrerenderPages(\n startConfig.pages,\n routerBaseUrl,\n )\n\n const serverEnv = builder.environments[VITE_ENVIRONMENT_NAMES.server]\n\n if (!serverEnv) {\n throw new Error(\n `Vite's \"${VITE_ENVIRONMENT_NAMES.server}\" environment not found`,\n )\n }\n\n const clientEnv = builder.environments[VITE_ENVIRONMENT_NAMES.client]\n if (!clientEnv) {\n throw new Error(\n `Vite's \"${VITE_ENVIRONMENT_NAMES.client}\" environment not found`,\n )\n }\n\n const outputDir = clientEnv.config.build.outDir\n\n process.env.TSS_PRERENDERING = 'true'\n process.env.TSS_CLIENT_OUTPUT_DIR = outputDir\n\n // Start Vite preview server instead of importing module\n const previewServer = await startPreviewServer(serverEnv.config)\n const baseUrl = getResolvedUrl(previewServer)\n\n const isRedirectResponse = (res: Response) => {\n return res.status >= 300 && res.status < 400 && res.headers.get('location')\n }\n async function localFetch(\n path: string,\n options?: RequestInit,\n maxRedirects: number = 5,\n ): Promise<Response> {\n const url = new URL(path, baseUrl)\n const request = new Request(url, options)\n const response = await fetch(request)\n\n if (isRedirectResponse(response) && maxRedirects > 0) {\n const location = response.headers.get('location')!\n if (location.startsWith('http://localhost') || location.startsWith('/')) {\n const newUrl = location.replace('http://localhost', '')\n return localFetch(newUrl, options, maxRedirects - 1)\n } else {\n logger.warn(`Skipping redirect to external location: ${location}`)\n }\n }\n\n return response\n }\n\n try {\n // Crawl all pages\n const pages = await prerenderPages({ outputDir })\n\n logger.info(`Prerendered ${pages.length} pages:`)\n pages.forEach((page) => {\n logger.info(`- ${page}`)\n })\n } catch (error) {\n logger.error(error)\n } finally {\n await previewServer.close()\n }\n\n function extractLinks(html: string): Array<string> {\n const linkRegex = /<a[^>]+href=[\"']([^\"']+)[\"'][^>]*>/g\n const links: Array<string> = []\n let match\n\n while ((match = linkRegex.exec(html)) !== null) {\n const href = match[1]\n if (href && (href.startsWith('/') || href.startsWith('./'))) {\n links.push(href)\n }\n }\n\n return links\n }\n\n async function prerenderPages({ outputDir }: { outputDir: string }) {\n const seen = new Set<string>()\n const prerendered = new Set<string>()\n const retriesByPath = new Map<string, number>()\n const concurrency = startConfig.prerender?.concurrency ?? os.cpus().length\n logger.info(`Concurrency: ${concurrency}`)\n const queue = new Queue({ concurrency })\n const routerBasePath = joinURL('/', startConfig.router.basepath ?? '')\n\n // Normalize discovered pages and enforce path-only entries\n const routerBaseUrl = new URL(routerBasePath, 'http://localhost')\n startConfig.pages = validateAndNormalizePrerenderPages(\n startConfig.pages,\n routerBaseUrl,\n )\n\n startConfig.pages.forEach((page) => addCrawlPageTask(page))\n\n if (queue.isSettled()) {\n logger.info('No pages matched prerender filter; skipping.')\n return Array.from(prerendered)\n }\n\n await queue.start()\n\n return Array.from(prerendered)\n\n function addCrawlPageTask(page: Page) {\n // Was the page already seen?\n if (seen.has(page.path)) return\n\n // Add the page to the seen set\n seen.add(page.path)\n\n if (page.fromCrawl) {\n startConfig.pages.push(page)\n }\n\n // If not enabled, skip\n if (!(page.prerender?.enabled ?? true)) return\n\n // If there is a filter link, check if the page should be prerendered\n if (startConfig.prerender?.filter && !startConfig.prerender.filter(page))\n return\n\n // Resolve the merged default and page-specific prerender options\n const prerenderOptions = {\n ...startConfig.prerender,\n ...page.prerender,\n }\n\n // Add the task\n queue.add(async () => {\n logger.info(`Crawling: ${page.path}`)\n const retries = retriesByPath.get(page.path) || 0\n try {\n // Fetch the route\n\n const res = await localFetch(\n withTrailingSlash(withBase(page.path, routerBasePath)),\n {\n headers: {\n ...(prerenderOptions.headers ?? {}),\n },\n },\n prerenderOptions.maxRedirects,\n )\n\n if (!res.ok) {\n if (isRedirectResponse(res)) {\n logger.warn(`Max redirects reached for ${page.path}`)\n }\n throw new Error(`Failed to fetch ${page.path}: ${res.statusText}`, {\n cause: res,\n })\n }\n\n const cleanPagePath = (\n prerenderOptions.outputPath || page.path\n ).split(/[?#]/)[0]!\n\n // Guess route type and populate fileName\n const contentType = res.headers.get('content-type') || ''\n const isImplicitHTML =\n !cleanPagePath.endsWith('.html') && contentType.includes('html')\n\n const routeWithIndex = cleanPagePath.endsWith('/')\n ? cleanPagePath + 'index'\n : cleanPagePath\n\n const isSpaShell =\n startConfig.spa?.prerender.outputPath === cleanPagePath\n\n let htmlPath: string\n if (isSpaShell) {\n // For SPA shell, ignore autoSubfolderIndex option\n htmlPath = cleanPagePath + '.html'\n } else {\n if (\n cleanPagePath.endsWith('/') ||\n (prerenderOptions.autoSubfolderIndex ?? true)\n ) {\n htmlPath = joinURL(cleanPagePath, 'index.html')\n } else {\n htmlPath = cleanPagePath + '.html'\n }\n }\n\n const filename = withoutBase(\n isImplicitHTML ? htmlPath : routeWithIndex,\n routerBasePath,\n )\n\n const html = await res.text()\n\n const filepath = path.join(outputDir, filename)\n\n await fsp.mkdir(path.dirname(filepath), {\n recursive: true,\n })\n\n await fsp.writeFile(filepath, html)\n\n prerendered.add(page.path)\n\n const newPage = await prerenderOptions.onSuccess?.({ page, html })\n\n if (newPage) {\n Object.assign(page, newPage)\n }\n\n // Find new links\n if (prerenderOptions.crawlLinks ?? true) {\n const links = extractLinks(html)\n for (const link of links) {\n addCrawlPageTask({ path: link, fromCrawl: true })\n }\n }\n } catch (error) {\n if (retries < (prerenderOptions.retryCount ?? 0)) {\n logger.warn(`Encountered error, retrying: ${page.path} in 500ms`)\n await new Promise((resolve) =>\n setTimeout(resolve, prerenderOptions.retryDelay),\n )\n retriesByPath.set(page.path, retries + 1)\n addCrawlPageTask(page)\n } else {\n if (prerenderOptions.failOnError ?? true) {\n throw error\n }\n }\n }\n })\n }\n }\n}\n\nasync function startPreviewServer(\n viteConfig: ResolvedConfig,\n): Promise<PreviewServer> {\n const vite = await import('vite')\n\n try {\n return await vite.preview({\n configFile: viteConfig.configFile,\n preview: {\n port: 0,\n open: false,\n },\n })\n } catch (error) {\n throw new Error(\n 'Failed to start the Vite preview server for prerendering',\n {\n cause: error,\n },\n )\n }\n}\n\nfunction getResolvedUrl(previewServer: PreviewServer): URL {\n const baseUrl = previewServer.resolvedUrls?.local[0]\n\n if (!baseUrl) {\n throw new Error('No resolved URL is available from the Vite preview server')\n }\n\n return new URL(baseUrl)\n}\n\n/**\n * Validates and normalizes prerender page paths to ensure they are relative\n * (no protocol/host) and returns normalized Page objects with cleaned paths.\n * Preserves unicode characters by decoding the pathname after URL validation.\n */\nfunction validateAndNormalizePrerenderPages(\n pages: Array<Page>,\n routerBaseUrl: URL,\n): Array<Page> {\n return pages.map((page) => {\n let url: URL\n try {\n url = new URL(page.path, routerBaseUrl)\n } catch (err) {\n throw new Error(`prerender page path must be relative: ${page.path}`, {\n cause: err,\n })\n }\n\n if (url.origin !== 'http://localhost') {\n throw new Error(`prerender page path must be relative: ${page.path}`)\n }\n\n // Decode the pathname to preserve unicode characters (e.g., /대한민국)\n // The URL constructor encodes non-ASCII characters, but we want to keep\n // the original unicode form for filesystem paths\n const decodedPathname = decodeURIComponent(url.pathname)\n\n return {\n ...page,\n path: decodedPathname + url.search + url.hash,\n }\n })\n}\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { getBundlerOptions } from "../utils.js";
1
2
  import { VITE_ENVIRONMENT_NAMES } from "../constants.js";
2
3
  import { getServerOutputDirectory } from "../output-directory.js";
3
4
  import { basename, extname, join } from "pathe";
@@ -16,7 +17,8 @@ function previewServerPlugin() {
16
17
  server.middlewares.use(async (req, res, next) => {
17
18
  try {
18
19
  if (!serverBuild) {
19
- const serverInput = server.config.environments[VITE_ENVIRONMENT_NAMES.server]?.build.rollupOptions.input ?? "server";
20
+ const serverEnv = server.config.environments[VITE_ENVIRONMENT_NAMES.server];
21
+ const serverInput = getBundlerOptions(serverEnv?.build)?.input ?? "server";
20
22
  if (typeof serverInput !== "string") throw new Error("Invalid server input. Expected a string.");
21
23
  const outputFilename = `${basename(serverInput, extname(serverInput))}.js`;
22
24
  serverBuild = (await import(pathToFileURL(join(getServerOutputDirectory(server.config), outputFilename)).toString())).default;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","names":[],"sources":["../../../src/preview-server-plugin/plugin.ts"],"sourcesContent":["import { pathToFileURL } from 'node:url'\nimport { basename, extname, join } from 'pathe'\nimport { NodeRequest, sendNodeResponse } from 'srvx/node'\nimport { joinURL } from 'ufo'\nimport { VITE_ENVIRONMENT_NAMES } from '../constants'\nimport { getServerOutputDirectory } from '../output-directory'\nimport type { Plugin } from 'vite'\n\nexport function previewServerPlugin(): Plugin {\n return {\n name: 'tanstack-start-core:preview-server',\n configurePreviewServer: {\n // Run last so platform plugins (Cloudflare, Vercel, etc.) can register their handlers first\n order: 'post',\n handler(server) {\n // Return a function so Vite's internal middlewares (static files, etc.) handle requests first.\n // Our SSR handler only processes requests that nothing else handled.\n return () => {\n // Cache the server build to avoid re-importing on every request\n let serverBuild: any = null\n\n server.middlewares.use(async (req, res, next) => {\n try {\n // Lazy load server build on first request\n if (!serverBuild) {\n // Derive output filename from input\n const serverEnv =\n server.config.environments[VITE_ENVIRONMENT_NAMES.server]\n const serverInput =\n serverEnv?.build.rollupOptions.input ?? 'server'\n\n if (typeof serverInput !== 'string') {\n throw new Error('Invalid server input. Expected a string.')\n }\n\n // Get basename without extension and add .js\n const outputFilename = `${basename(serverInput, extname(serverInput))}.js`\n const serverOutputDir = getServerOutputDirectory(server.config)\n const serverEntryPath = join(serverOutputDir, outputFilename)\n const imported = await import(\n pathToFileURL(serverEntryPath).toString()\n )\n\n serverBuild = imported.default\n }\n\n // Prepend base path to request URL to match routing setup\n req.url = joinURL(server.config.base, req.url ?? '/')\n\n const webReq = new NodeRequest({ req, res })\n const webRes: Response = await serverBuild.fetch(webReq)\n\n // Temporary workaround\n // Vite preview's compression middleware doesn't support flattened array headers that srvx sets\n // Call writeHead() before srvx to avoid corruption\n res.setHeaders(webRes.headers)\n res.writeHead(webRes.status, webRes.statusText)\n\n return sendNodeResponse(res, webRes)\n } catch (error) {\n next(error)\n }\n })\n }\n },\n },\n }\n}\n"],"mappings":";;;;;;;AAQA,SAAgB,sBAA8B;AAC5C,QAAO;EACL,MAAM;EACN,wBAAwB;GAEtB,OAAO;GACP,QAAQ,QAAQ;AAGd,iBAAa;KAEX,IAAI,cAAmB;AAEvB,YAAO,YAAY,IAAI,OAAO,KAAK,KAAK,SAAS;AAC/C,UAAI;AAEF,WAAI,CAAC,aAAa;QAIhB,MAAM,cADJ,OAAO,OAAO,aAAa,uBAAuB,SAEvC,MAAM,cAAc,SAAS;AAE1C,YAAI,OAAO,gBAAgB,SACzB,OAAM,IAAI,MAAM,2CAA2C;QAI7D,MAAM,iBAAiB,GAAG,SAAS,aAAa,QAAQ,YAAY,CAAC,CAAC;AAOtE,uBAJiB,MAAM,OACrB,cAFsB,KADA,yBAAyB,OAAO,OAAO,EACjB,eAAe,CAE7B,CAAC,UAAU,GAGpB;;AAIzB,WAAI,MAAM,QAAQ,OAAO,OAAO,MAAM,IAAI,OAAO,IAAI;OAErD,MAAM,SAAS,IAAI,YAAY;QAAE;QAAK;QAAK,CAAC;OAC5C,MAAM,SAAmB,MAAM,YAAY,MAAM,OAAO;AAKxD,WAAI,WAAW,OAAO,QAAQ;AAC9B,WAAI,UAAU,OAAO,QAAQ,OAAO,WAAW;AAE/C,cAAO,iBAAiB,KAAK,OAAO;eAC7B,OAAO;AACd,YAAK,MAAM;;OAEb;;;GAGP;EACF"}
1
+ {"version":3,"file":"plugin.js","names":[],"sources":["../../../src/preview-server-plugin/plugin.ts"],"sourcesContent":["import { pathToFileURL } from 'node:url'\nimport { basename, extname, join } from 'pathe'\nimport { NodeRequest, sendNodeResponse } from 'srvx/node'\nimport { joinURL } from 'ufo'\nimport { VITE_ENVIRONMENT_NAMES } from '../constants'\nimport { getServerOutputDirectory } from '../output-directory'\nimport { getBundlerOptions } from '../utils'\nimport type { Plugin } from 'vite'\n\nexport function previewServerPlugin(): Plugin {\n return {\n name: 'tanstack-start-core:preview-server',\n configurePreviewServer: {\n // Run last so platform plugins (Cloudflare, Vercel, etc.) can register their handlers first\n order: 'post',\n handler(server) {\n // Return a function so Vite's internal middlewares (static files, etc.) handle requests first.\n // Our SSR handler only processes requests that nothing else handled.\n return () => {\n // Cache the server build to avoid re-importing on every request\n let serverBuild: any = null\n\n server.middlewares.use(async (req, res, next) => {\n try {\n // Lazy load server build on first request\n if (!serverBuild) {\n // Derive output filename from input\n const serverEnv =\n server.config.environments[VITE_ENVIRONMENT_NAMES.server]\n const serverInput =\n getBundlerOptions(serverEnv?.build)?.input ?? 'server'\n\n if (typeof serverInput !== 'string') {\n throw new Error('Invalid server input. Expected a string.')\n }\n\n // Get basename without extension and add .js\n const outputFilename = `${basename(serverInput, extname(serverInput))}.js`\n const serverOutputDir = getServerOutputDirectory(server.config)\n const serverEntryPath = join(serverOutputDir, outputFilename)\n const imported = await import(\n pathToFileURL(serverEntryPath).toString()\n )\n\n serverBuild = imported.default\n }\n\n // Prepend base path to request URL to match routing setup\n req.url = joinURL(server.config.base, req.url ?? '/')\n\n const webReq = new NodeRequest({ req, res })\n const webRes: Response = await serverBuild.fetch(webReq)\n\n // Temporary workaround\n // Vite preview's compression middleware doesn't support flattened array headers that srvx sets\n // Call writeHead() before srvx to avoid corruption\n res.setHeaders(webRes.headers)\n res.writeHead(webRes.status, webRes.statusText)\n\n return sendNodeResponse(res, webRes)\n } catch (error) {\n next(error)\n }\n })\n }\n },\n },\n }\n}\n"],"mappings":";;;;;;;;AASA,SAAgB,sBAA8B;AAC5C,QAAO;EACL,MAAM;EACN,wBAAwB;GAEtB,OAAO;GACP,QAAQ,QAAQ;AAGd,iBAAa;KAEX,IAAI,cAAmB;AAEvB,YAAO,YAAY,IAAI,OAAO,KAAK,KAAK,SAAS;AAC/C,UAAI;AAEF,WAAI,CAAC,aAAa;QAEhB,MAAM,YACJ,OAAO,OAAO,aAAa,uBAAuB;QACpD,MAAM,cACJ,kBAAkB,WAAW,MAAM,EAAE,SAAS;AAEhD,YAAI,OAAO,gBAAgB,SACzB,OAAM,IAAI,MAAM,2CAA2C;QAI7D,MAAM,iBAAiB,GAAG,SAAS,aAAa,QAAQ,YAAY,CAAC,CAAC;AAOtE,uBAJiB,MAAM,OACrB,cAFsB,KADA,yBAAyB,OAAO,OAAO,EACjB,eAAe,CAE7B,CAAC,UAAU,GAGpB;;AAIzB,WAAI,MAAM,QAAQ,OAAO,OAAO,MAAM,IAAI,OAAO,IAAI;OAErD,MAAM,SAAS,IAAI,YAAY;QAAE;QAAK;QAAK,CAAC;OAC5C,MAAM,SAAmB,MAAM,YAAY,MAAM,OAAO;AAKxD,WAAI,WAAW,OAAO,QAAQ;AAC9B,WAAI,UAAU,OAAO,QAAQ,OAAO,WAAW;AAE/C,cAAO,iBAAiB,KAAK,OAAO;eAC7B,OAAO;AACd,YAAK,MAAM;;OAEb;;;GAGP;EACF"}
@@ -1,3 +1,12 @@
1
+ /**
2
+ * Vite 8+ uses Rolldown instead of Rollup, renaming `build.rollupOptions`
3
+ * to `build.rolldownOptions`. Detect which bundler is in use.
4
+ */
5
+ export declare const isRolldown: boolean;
6
+ /** Returns `'rolldownOptions'` when using Rolldown, `'rollupOptions'` otherwise. */
7
+ export declare const bundlerOptionsKey: string;
8
+ /** Read `build.rollupOptions` or `build.rolldownOptions` from a build config. */
9
+ export declare function getBundlerOptions(build: any): any;
1
10
  export declare function resolveViteId(id: string): string;
2
11
  export declare function createLogger(prefix: string): {
3
12
  log: (...args: any) => void;
package/dist/esm/utils.js CHANGED
@@ -1,4 +1,10 @@
1
- //#region src/utils.ts
1
+ import * as vite from "vite";
2
+ /** Returns `'rolldownOptions'` when using Rolldown, `'rollupOptions'` otherwise. */
3
+ var bundlerOptionsKey = "rolldownVersion" in vite ? "rolldownOptions" : "rollupOptions";
4
+ /** Read `build.rollupOptions` or `build.rolldownOptions` from a build config. */
5
+ function getBundlerOptions(build) {
6
+ return build?.rolldownOptions ?? build?.rollupOptions;
7
+ }
2
8
  function resolveViteId(id) {
3
9
  return `\0${id}`;
4
10
  }
@@ -13,6 +19,6 @@ function createLogger(prefix) {
13
19
  };
14
20
  }
15
21
  //#endregion
16
- export { createLogger, resolveViteId };
22
+ export { bundlerOptionsKey, createLogger, getBundlerOptions, resolveViteId };
17
23
 
18
24
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","names":[],"sources":["../../src/utils.ts"],"sourcesContent":["export function resolveViteId(id: string) {\n return `\\0${id}`\n}\n\nexport function createLogger(prefix: string) {\n const label = `[${prefix}]`\n return {\n log: (...args: any) => console.log(label, ...args),\n debug: (...args: any) => console.debug(label, ...args),\n info: (...args: any) => console.info(label, ...args),\n warn: (...args: any) => console.warn(label, ...args),\n error: (...args: any) => console.error(label, ...args),\n }\n}\n"],"mappings":";AAAA,SAAgB,cAAc,IAAY;AACxC,QAAO,KAAK;;AAGd,SAAgB,aAAa,QAAgB;CAC3C,MAAM,QAAQ,IAAI,OAAO;AACzB,QAAO;EACL,MAAM,GAAG,SAAc,QAAQ,IAAI,OAAO,GAAG,KAAK;EAClD,QAAQ,GAAG,SAAc,QAAQ,MAAM,OAAO,GAAG,KAAK;EACtD,OAAO,GAAG,SAAc,QAAQ,KAAK,OAAO,GAAG,KAAK;EACpD,OAAO,GAAG,SAAc,QAAQ,KAAK,OAAO,GAAG,KAAK;EACpD,QAAQ,GAAG,SAAc,QAAQ,MAAM,OAAO,GAAG,KAAK;EACvD"}
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../src/utils.ts"],"sourcesContent":["import * as vite from 'vite'\n\n/**\n * Vite 8+ uses Rolldown instead of Rollup, renaming `build.rollupOptions`\n * to `build.rolldownOptions`. Detect which bundler is in use.\n */\nexport const isRolldown = 'rolldownVersion' in vite\n\n/** Returns `'rolldownOptions'` when using Rolldown, `'rollupOptions'` otherwise. */\nexport const bundlerOptionsKey = isRolldown\n ? 'rolldownOptions'\n : 'rollupOptions'\n\n/** Read `build.rollupOptions` or `build.rolldownOptions` from a build config. */\nexport function getBundlerOptions(build: any): any {\n return build?.rolldownOptions ?? build?.rollupOptions\n}\n\nexport function resolveViteId(id: string) {\n return `\\0${id}`\n}\n\nexport function createLogger(prefix: string) {\n const label = `[${prefix}]`\n return {\n log: (...args: any) => console.log(label, ...args),\n debug: (...args: any) => console.debug(label, ...args),\n info: (...args: any) => console.info(label, ...args),\n warn: (...args: any) => console.warn(label, ...args),\n error: (...args: any) => console.error(label, ...args),\n }\n}\n"],"mappings":";;AASA,IAAa,oBAHa,qBAAqB,OAI3C,oBACA;;AAGJ,SAAgB,kBAAkB,OAAiB;AACjD,QAAO,OAAO,mBAAmB,OAAO;;AAG1C,SAAgB,cAAc,IAAY;AACxC,QAAO,KAAK;;AAGd,SAAgB,aAAa,QAAgB;CAC3C,MAAM,QAAQ,IAAI,OAAO;AACzB,QAAO;EACL,MAAM,GAAG,SAAc,QAAQ,IAAI,OAAO,GAAG,KAAK;EAClD,QAAQ,GAAG,SAAc,QAAQ,MAAM,OAAO,GAAG,KAAK;EACtD,OAAO,GAAG,SAAc,QAAQ,KAAK,OAAO,GAAG,KAAK;EACpD,OAAO,GAAG,SAAc,QAAQ,KAAK,OAAO,GAAG,KAAK;EACpD,QAAQ,GAAG,SAAc,QAAQ,MAAM,OAAO,GAAG,KAAK;EACvD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/start-plugin-core",
3
- "version": "1.166.14",
3
+ "version": "1.167.0",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -60,12 +60,12 @@
60
60
  "vitefu": "^1.1.1",
61
61
  "xmlbuilder2": "^4.0.3",
62
62
  "zod": "^3.24.2",
63
- "@tanstack/router-core": "1.167.3",
64
- "@tanstack/router-generator": "1.166.11",
65
- "@tanstack/router-plugin": "1.166.12",
63
+ "@tanstack/router-core": "1.167.4",
64
+ "@tanstack/router-generator": "1.166.12",
65
+ "@tanstack/router-plugin": "1.166.13",
66
66
  "@tanstack/router-utils": "1.161.6",
67
- "@tanstack/start-client-core": "1.166.11",
68
- "@tanstack/start-server-core": "1.166.11"
67
+ "@tanstack/start-client-core": "1.166.12",
68
+ "@tanstack/start-server-core": "1.166.12"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/babel__code-frame": "^7.0.6",
package/src/plugin.ts CHANGED
@@ -5,6 +5,7 @@ import { join } from 'pathe'
5
5
  import { escapePath } from 'tinyglobby'
6
6
  import { startManifestPlugin } from './start-manifest-plugin/plugin'
7
7
  import { ENTRY_POINTS, VITE_ENVIRONMENT_NAMES } from './constants'
8
+ import { bundlerOptionsKey, getBundlerOptions } from './utils'
8
9
  import { tanStackStartRouter } from './start-router-plugin/plugin'
9
10
  import { loadEnvPlugin } from './load-env-plugin/plugin'
10
11
  import { devServerPlugin } from './dev-server-plugin/plugin'
@@ -244,7 +245,7 @@ export function TanStackStartVitePluginCore(
244
245
  [VITE_ENVIRONMENT_NAMES.client]: {
245
246
  consumer: 'client',
246
247
  build: {
247
- rollupOptions: {
248
+ [bundlerOptionsKey]: {
248
249
  input: {
249
250
  main: ENTRY_POINTS.client,
250
251
  },
@@ -264,10 +265,12 @@ export function TanStackStartVitePluginCore(
264
265
  consumer: 'server',
265
266
  build: {
266
267
  ssr: true,
267
- rollupOptions: {
268
+ [bundlerOptionsKey]: {
268
269
  input:
269
- viteConfig.environments?.[VITE_ENVIRONMENT_NAMES.server]
270
- ?.build?.rollupOptions?.input ?? serverAlias,
270
+ getBundlerOptions(
271
+ viteConfig.environments?.[VITE_ENVIRONMENT_NAMES.server]
272
+ ?.build,
273
+ )?.input ?? serverAlias,
271
274
  },
272
275
  outDir: getServerOutputDirectory(viteConfig),
273
276
  commonjsOptions: {
@@ -305,7 +308,6 @@ export function TanStackStartVitePluginCore(
305
308
  // This is not the same as injecting environment variables.
306
309
 
307
310
  ...defineReplaceEnv('TSS_SERVER_FN_BASE', TSS_SERVER_FN_BASE),
308
- ...defineReplaceEnv('TSS_CLIENT_OUTPUT_DIR', getClientOutputDirectory(viteConfig)),
309
311
  ...defineReplaceEnv('TSS_ROUTER_BASEPATH', startConfig.router.basepath),
310
312
  ...(command === 'serve' ? defineReplaceEnv('TSS_SHELL', startConfig.spa?.enabled ? 'true' : 'false') : {}),
311
313
  ...defineReplaceEnv('TSS_DEV_SERVER', command === 'serve' ? 'true' : 'false'),
package/src/prerender.ts CHANGED
@@ -67,6 +67,7 @@ export async function prerender({
67
67
  const outputDir = clientEnv.config.build.outDir
68
68
 
69
69
  process.env.TSS_PRERENDERING = 'true'
70
+ process.env.TSS_CLIENT_OUTPUT_DIR = outputDir
70
71
 
71
72
  // Start Vite preview server instead of importing module
72
73
  const previewServer = await startPreviewServer(serverEnv.config)
@@ -4,6 +4,7 @@ import { NodeRequest, sendNodeResponse } from 'srvx/node'
4
4
  import { joinURL } from 'ufo'
5
5
  import { VITE_ENVIRONMENT_NAMES } from '../constants'
6
6
  import { getServerOutputDirectory } from '../output-directory'
7
+ import { getBundlerOptions } from '../utils'
7
8
  import type { Plugin } from 'vite'
8
9
 
9
10
  export function previewServerPlugin(): Plugin {
@@ -27,7 +28,7 @@ export function previewServerPlugin(): Plugin {
27
28
  const serverEnv =
28
29
  server.config.environments[VITE_ENVIRONMENT_NAMES.server]
29
30
  const serverInput =
30
- serverEnv?.build.rollupOptions.input ?? 'server'
31
+ getBundlerOptions(serverEnv?.build)?.input ?? 'server'
31
32
 
32
33
  if (typeof serverInput !== 'string') {
33
34
  throw new Error('Invalid server input. Expected a string.')
package/src/utils.ts CHANGED
@@ -1,3 +1,21 @@
1
+ import * as vite from 'vite'
2
+
3
+ /**
4
+ * Vite 8+ uses Rolldown instead of Rollup, renaming `build.rollupOptions`
5
+ * to `build.rolldownOptions`. Detect which bundler is in use.
6
+ */
7
+ export const isRolldown = 'rolldownVersion' in vite
8
+
9
+ /** Returns `'rolldownOptions'` when using Rolldown, `'rollupOptions'` otherwise. */
10
+ export const bundlerOptionsKey = isRolldown
11
+ ? 'rolldownOptions'
12
+ : 'rollupOptions'
13
+
14
+ /** Read `build.rollupOptions` or `build.rolldownOptions` from a build config. */
15
+ export function getBundlerOptions(build: any): any {
16
+ return build?.rolldownOptions ?? build?.rollupOptions
17
+ }
18
+
1
19
  export function resolveViteId(id: string) {
2
20
  return `\0${id}`
3
21
  }