@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.
- package/dist/esm/plugin.js +3 -3
- package/dist/esm/plugin.js.map +1 -1
- package/dist/esm/prerender.js +1 -0
- package/dist/esm/prerender.js.map +1 -1
- package/dist/esm/preview-server-plugin/plugin.js +3 -1
- package/dist/esm/preview-server-plugin/plugin.js.map +1 -1
- package/dist/esm/utils.d.ts +9 -0
- package/dist/esm/utils.js +8 -2
- package/dist/esm/utils.js.map +1 -1
- package/package.json +6 -6
- package/src/plugin.ts +7 -5
- package/src/prerender.ts +1 -0
- package/src/preview-server-plugin/plugin.ts +2 -1
- package/src/utils.ts +18 -0
package/dist/esm/plugin.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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"),
|
package/dist/esm/plugin.js.map
CHANGED
|
@@ -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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"}
|
package/dist/esm/prerender.js
CHANGED
|
@@ -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
|
|
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
|
|
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"}
|
package/dist/esm/utils.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
package/dist/esm/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","names":[],"sources":["../../src/utils.ts"],"sourcesContent":["
|
|
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.
|
|
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.
|
|
64
|
-
"@tanstack/router-generator": "1.166.
|
|
65
|
-
"@tanstack/router-plugin": "1.166.
|
|
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.
|
|
68
|
-
"@tanstack/start-server-core": "1.166.
|
|
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
|
-
|
|
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
|
-
|
|
268
|
+
[bundlerOptionsKey]: {
|
|
268
269
|
input:
|
|
269
|
-
|
|
270
|
-
?.
|
|
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
|
|
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
|
}
|