@tanstack/start-plugin-core 1.169.7 → 1.169.8
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/rsbuild/plugin.js +2 -1
- package/dist/esm/rsbuild/plugin.js.map +1 -1
- package/dist/esm/start-compiler/handleCreateServerFn.js +2 -1
- package/dist/esm/start-compiler/handleCreateServerFn.js.map +1 -1
- package/package.json +6 -6
- package/src/rsbuild/plugin.ts +2 -1
- package/src/start-compiler/handleCreateServerFn.ts +2 -5
|
@@ -12,6 +12,7 @@ import { registerRouterPlugins } from "./start-router-plugin.js";
|
|
|
12
12
|
import { postBuildWithRsbuild } from "./post-build.js";
|
|
13
13
|
import { enableSwcReactServerComponents } from "./swc-rsc.js";
|
|
14
14
|
import { dirname, join, resolve } from "node:path";
|
|
15
|
+
import { hasKeys } from "@tanstack/router-core";
|
|
15
16
|
import { fileURLToPath } from "node:url";
|
|
16
17
|
import { joinURL } from "ufo";
|
|
17
18
|
import { existsSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
@@ -215,7 +216,7 @@ function tanStackStartRsbuild(corePluginOpts, startPluginOpts = {}) {
|
|
|
215
216
|
name: "TanStackStartRscServerFnResolverRebuild",
|
|
216
217
|
stage: -10
|
|
217
218
|
}, async (compilation) => {
|
|
218
|
-
if (
|
|
219
|
+
if (!hasKeys(serverFnsById)) return;
|
|
219
220
|
const resolverContent = virtualModuleState.generateCurrentResolverContent(true);
|
|
220
221
|
virtualModuleState.tryUpdateServerFnResolver(resolverContent);
|
|
221
222
|
await rebuildModulesContaining(compilation, virtualModuleState.serverFnResolverPath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","names":[],"sources":["../../../src/rsbuild/plugin.ts"],"sourcesContent":["import { existsSync, readdirSync, realpathSync, statSync } from 'node:fs'\nimport { dirname, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { joinURL } from 'ufo'\nimport {\n applyResolvedBaseAndOutput,\n applyResolvedRouterBasepath,\n createStartConfigContext,\n} from '../config-context'\nimport { normalizePath } from '../utils'\nimport { createServerFnBasePath, normalizePublicBase } from '../planning'\nimport { parseStartConfig } from './schema'\nimport {\n RSBUILD_ENVIRONMENT_NAMES,\n RSBUILD_RSC_LAYERS,\n createRsbuildEnvironmentPlan,\n createRsbuildResolvedEntryAliases,\n resolveRsbuildOutputDirectory,\n} from './planning'\nimport { registerStartCompilerTransforms } from './start-compiler-host'\nimport { registerImportProtection } from './import-protection'\nimport {\n START_MANIFEST_PLACEHOLDER,\n registerVirtualModules,\n} from './virtual-modules'\nimport { createServerSetup } from './dev-server'\nimport { registerClientBuildCapture } from './normalized-client-build'\nimport { registerRouterPlugins } from './start-router-plugin'\nimport { postBuildWithRsbuild } from './post-build'\nimport { enableSwcReactServerComponents } from './swc-rsc'\nimport type { ServerFn } from '../start-compiler/types'\nimport type { TanStackStartRsbuildPluginCoreOptions } from './types'\nimport type {\n ModifyRspackConfigFn,\n RsbuildDevServer,\n RsbuildPlugin,\n RsbuildPluginAPI,\n Rspack,\n rspack as rspackNamespaceType,\n} from '@rsbuild/core'\nimport type { TanStackStartRsbuildInputConfig } from './schema'\n\n// Detect whether this plugin source is running from inside the TanStack\n// Router monorepo (packages/start-plugin-core/src/rsbuild/plugin.ts). When\n// installed from npm in a user app, the path structure is different and\n// this evaluates to false. Used to gate dev-only workarounds that only\n// matter when workspace package dists are symlinked into node_modules.\nconst currentDir = dirname(fileURLToPath(import.meta.url))\nconst isInsideRouterMonoRepo = (() => {\n // src layout: <repo>/packages/start-plugin-core/src/rsbuild → 4 levels up\n // dist layout (CJS/ESM): <repo>/packages/start-plugin-core/dist/<fmt>/rsbuild\n // → also 4 levels up\n const candidate = resolve(currentDir, '../../../../')\n return candidate.endsWith('/packages') || candidate.endsWith('\\\\packages')\n})()\n\ntype RspackNamespace = typeof rspackNamespaceType\ntype RscPluginPair = ReturnType<\n NonNullable<RspackNamespace['experiments']['rsc']>['createPlugins']\n>\ntype RspackConfig = Parameters<ModifyRspackConfigFn>[0]\ntype RspackCompiler = Rspack.Compiler\ntype RspackCompilationExtended = Rspack.Compilation\n\nexport function tanStackStartRsbuild(\n corePluginOpts: TanStackStartRsbuildPluginCoreOptions,\n startPluginOpts: TanStackStartRsbuildInputConfig = {},\n): RsbuildPlugin {\n const rscOpts = corePluginOpts.rsc\n const rscEnabled = Boolean(rscOpts)\n\n const configContext = createStartConfigContext({\n corePluginOpts,\n startPluginOpts,\n parseConfig: parseStartConfig,\n })\n const { getConfig, resolvedStartConfig } = configContext\n const serverFnProviderEnv = corePluginOpts.providerEnvironmentName\n const ssrIsProvider = corePluginOpts.ssrIsProvider\n\n // RSC plugin instances — created lazily when rspack namespace is available\n let rscPlugins: RscPluginPair | undefined\n\n // Reference to the dev server for RSC HMR socket writes\n let devServerRef: Pick<RsbuildDevServer, 'sockWrite'> | null = null\n const serverFnsById: Record<string, ServerFn> = {}\n let updateServerFnResolver: (() => void) | undefined\n\n return {\n name: 'tanstack-start-rsbuild',\n setup(api: RsbuildPluginAPI) {\n // ---------------------------------------------------------------\n // 1. modifyRsbuildConfig — resolve config, set up environments\n // ---------------------------------------------------------------\n api.modifyRsbuildConfig((rsbuildConfig, { mergeRsbuildConfig }) => {\n const root =\n typeof rsbuildConfig.root === 'string'\n ? rsbuildConfig.root\n : process.cwd()\n\n const serverBase = rsbuildConfig.server?.base\n const assetPrefix = rsbuildConfig.output?.assetPrefix\n const publicBase = normalizePublicBase(\n typeof serverBase === 'string'\n ? serverBase\n : typeof assetPrefix === 'string' && assetPrefix !== 'auto'\n ? assetPrefix\n : undefined,\n )\n const rootDistPath = rsbuildConfig.output?.distPath\n const clientDistPath =\n rsbuildConfig.environments?.[RSBUILD_ENVIRONMENT_NAMES.client]?.output\n ?.distPath\n const serverDistPath =\n rsbuildConfig.environments?.[RSBUILD_ENVIRONMENT_NAMES.server]?.output\n ?.distPath\n\n applyResolvedBaseAndOutput({\n resolvedStartConfig,\n root,\n publicBase,\n clientOutputDirectory: resolveRsbuildOutputDirectory({\n distPath: clientDistPath,\n rootDistPath,\n fallback: 'dist/client',\n subdirectory: 'client',\n }),\n serverOutputDirectory: resolveRsbuildOutputDirectory({\n distPath: serverDistPath,\n rootDistPath,\n fallback: 'dist/server',\n subdirectory: 'server',\n }),\n })\n\n const { startConfig } = getConfig()\n const routerBasepath = applyResolvedRouterBasepath({\n resolvedStartConfig,\n startConfig,\n })\n\n const resolvedEntryPlan = configContext.resolveEntries()\n const isDev = api.context.action === 'dev'\n\n const entryAliases = createRsbuildResolvedEntryAliases({\n entryPaths: resolvedEntryPlan.entryPaths,\n })\n\n const environmentPlan = createRsbuildEnvironmentPlan({\n root,\n entryAliases,\n clientOutputDirectory: resolvedStartConfig.outputDirectories.client,\n serverOutputDirectory: resolvedStartConfig.outputDirectories.server,\n publicBase: resolvedStartConfig.basePaths.publicBase,\n serverFnProviderEnv,\n environmentOverrides: corePluginOpts.rsbuild?.environments,\n rsc: rscOpts,\n dev: isDev,\n })\n const serverFnBase = createServerFnBasePath({\n routerBasepath,\n serverFnBase: startConfig.serverFns.base,\n })\n const inlineCssEnabled = !isDev && startConfig.server.build.inlineCss\n\n return mergeRsbuildConfig(rsbuildConfig, {\n source: {\n define: {\n 'process.env.TSS_SERVER_FN_BASE': JSON.stringify(serverFnBase),\n 'import.meta.env.TSS_SERVER_FN_BASE':\n JSON.stringify(serverFnBase),\n 'process.env.TSS_ROUTER_BASEPATH': JSON.stringify(routerBasepath),\n 'import.meta.env.TSS_ROUTER_BASEPATH':\n JSON.stringify(routerBasepath),\n 'process.env.TSS_DEV_SERVER': JSON.stringify(\n isDev ? 'true' : 'false',\n ),\n 'import.meta.env.TSS_DEV_SERVER': JSON.stringify(\n isDev ? 'true' : 'false',\n ),\n // Rsbuild dev already injects emitted CSS asset hrefs, so keep\n // Start's synthetic `/@tanstack-start/styles.css` path disabled.\n 'process.env.TSS_DEV_SSR_STYLES_ENABLED': JSON.stringify('false'),\n 'import.meta.env.TSS_DEV_SSR_STYLES_ENABLED':\n JSON.stringify('false'),\n 'process.env.TSS_DEV_SSR_STYLES_BASEPATH': JSON.stringify(\n resolvedStartConfig.basePaths.publicBase,\n ),\n 'import.meta.env.TSS_DEV_SSR_STYLES_BASEPATH': JSON.stringify(\n resolvedStartConfig.basePaths.publicBase,\n ),\n 'process.env.TSS_INLINE_CSS_ENABLED': JSON.stringify(\n inlineCssEnabled ? 'true' : 'false',\n ),\n 'import.meta.env.TSS_INLINE_CSS_ENABLED': JSON.stringify(\n inlineCssEnabled ? 'true' : 'false',\n ),\n },\n },\n server: {\n // SSR apps render every route on the server — disable HTML\n // fallback so rsbuild doesn't intercept /_serverFn/ URLs.\n htmlFallback: false,\n // server.setup returned callback runs after built-in middleware\n // but BEFORE fallback middleware — the ideal slot for SSR.\n ...(isDev &&\n startPluginOpts.rsbuild?.installDevServerMiddleware !== false\n ? {\n setup: createServerSetup({\n serverFnBasePath: serverFnBase,\n }),\n }\n : {}),\n },\n ...(isDev\n ? {\n dev: {\n lazyCompilation: false,\n ...(rscEnabled ? { liveReload: false } : {}),\n },\n }\n : {}),\n environments: environmentPlan.environments,\n resolve: {\n alias: environmentPlan.alias,\n },\n })\n })\n\n // ---------------------------------------------------------------\n // 2. StartCompiler transforms — server fns, isomorphic fns, etc.\n // ---------------------------------------------------------------\n registerStartCompilerTransforms(api, {\n framework: corePluginOpts.framework,\n // modifyRsbuildConfig copies rsbuildConfig.root into resolvedStartConfig.root,\n // so defer this read until transform time instead of falling back to\n // process.cwd() during plugin setup.\n root: () => resolvedStartConfig.root || process.cwd(),\n providerEnvName: serverFnProviderEnv,\n generateFunctionId: startPluginOpts.serverFns?.generateFunctionId,\n serverFnsById,\n onServerFnsByIdChange: () => {\n updateServerFnResolver?.()\n },\n })\n\n registerImportProtection(api, {\n getConfig,\n framework: corePluginOpts.framework,\n environments: [\n { name: RSBUILD_ENVIRONMENT_NAMES.client, type: 'client' },\n { name: RSBUILD_ENVIRONMENT_NAMES.server, type: 'server' },\n ...(serverFnProviderEnv !== RSBUILD_ENVIRONMENT_NAMES.server &&\n !rscEnabled\n ? [{ name: serverFnProviderEnv, type: 'server' as const }]\n : []),\n ],\n })\n\n // ---------------------------------------------------------------\n // 3. Virtual modules — manifest, server fn resolver, adapters,\n // RSC runtime, RSC HMR\n // ---------------------------------------------------------------\n const virtualModuleState = registerVirtualModules(api, {\n root: resolvedStartConfig.root || process.cwd(),\n getConfig,\n serverFnsById,\n providerEnvName: serverFnProviderEnv,\n ssrIsProvider,\n serializationAdapters: corePluginOpts.serializationAdapters,\n getDevClientEntryUrl: (publicBase: string) =>\n joinURL(publicBase, 'static/js/index.js'),\n rscEnabled,\n })\n updateServerFnResolver = virtualModuleState.updateServerFnResolver\n\n // ---------------------------------------------------------------\n // 4. Client build stats capture via processAssets\n // ---------------------------------------------------------------\n const { getClientBuild } = registerClientBuildCapture(api)\n\n // ---------------------------------------------------------------\n // 4b. Server manifest module generation (build only)\n // For ordinary multi-environment builds, Rsbuild can compile the\n // server environment after the client environment finishes. Generate\n // the final manifest as module source in that phase instead of\n // patching emitted server assets afterwards.\n // ---------------------------------------------------------------\n if (api.context.action !== 'dev') {\n const normalizedManifestPath = normalizePath(\n virtualModuleState.manifestPath,\n )\n const matchesManifestPath = (id: string) =>\n normalizePath(id) === normalizedManifestPath\n\n api.transform(\n {\n test: (id: string) => matchesManifestPath(id),\n environments: [RSBUILD_ENVIRONMENT_NAMES.server],\n },\n ({ code }) => {\n const clientBuild = getClientBuild()\n\n if (clientBuild) {\n return virtualModuleState.generateManifestContent(clientBuild)\n }\n\n if (!rscEnabled) {\n throw new Error(\n 'TanStack Start could not generate the rsbuild server manifest before the client build completed',\n )\n }\n\n // RSC builds cannot express the required client -> server ordering\n // through MultiCompiler dependencies, so keep the placeholder for\n // the RSC-only asset-patching fallback below.\n return code\n },\n )\n }\n\n // ---------------------------------------------------------------\n // 5. Router plugin wiring (generator + code splitter)\n // ---------------------------------------------------------------\n registerRouterPlugins(api, {\n getConfig,\n corePluginOpts,\n startPluginOpts,\n })\n\n // ---------------------------------------------------------------\n // 6. Dev SSR middleware — registered via server.setup in\n // modifyRsbuildConfig above (returned callback runs after\n // built-ins but before fallback middleware)\n // ---------------------------------------------------------------\n\n // ---------------------------------------------------------------\n // 6b. Dev watcher: ignore workspace package `dist/**` directories.\n //\n // In a real user app, `@tanstack/react-router` and friends live\n // inside `node_modules/` and are ignored by Rspack's default\n // watcher. In this monorepo, pnpm symlinks them to\n // `packages/*/dist` (realpath outside node_modules), so the\n // watcher follows them and treats their dist files as live\n // sources. If anything rewrites those files during dev, Rspack\n // sees transient half-written modules and fails to resolve\n // relative imports between them.\n //\n // Only apply this in monorepo development. In user apps this\n // is a no-op — their `node_modules/@tanstack/*/dist/**` is\n // already ignored by Rspack's default watchOptions.\n // ---------------------------------------------------------------\n if (isInsideRouterMonoRepo && api.context.action === 'dev') {\n api.modifyRspackConfig((config) => {\n const workspaceDistRealpaths = resolveWorkspacePackageDistRealpaths()\n if (workspaceDistRealpaths.length === 0) return\n\n const workspaceDistIgnored = new RegExp(\n workspaceDistRealpaths\n .map((path) => `^${escapeRegExp(path)}(?:[\\\\\\\\/]|$)`)\n .join('|'),\n )\n const ignored = config.watchOptions?.ignored\n\n config.watchOptions = {\n ...(config.watchOptions ?? {}),\n ignored:\n ignored == null\n ? new RegExp(\n `${defaultRspackWatchIgnored.source}|${workspaceDistIgnored.source}`,\n )\n : typeof ignored === 'string'\n ? [ignored, ...workspaceDistRealpaths]\n : Array.isArray(ignored)\n ? [...ignored, ...workspaceDistRealpaths]\n : new RegExp(\n `${ignored.source}|${workspaceDistIgnored.source}`,\n ),\n }\n })\n }\n\n // ---------------------------------------------------------------\n // 7. RSC: rspack layer rules + native RSC plugins\n // When RSC is enabled, we add:\n // - issuerLayer rule for react-server condition propagation\n // - SWC reactServerComponents: true\n // - rspack ServerPlugin (server env) / ClientPlugin (client env)\n // The Coordinator inside createPlugins() handles compilation\n // ordering (server→client→server-actions) automatically.\n // ---------------------------------------------------------------\n if (rscEnabled) {\n api.modifyRspackConfig((config, utils) => {\n const envName = utils.environment.name\n const isServerEnv = envName === RSBUILD_ENVIRONMENT_NAMES.server\n const isClientEnv = envName === RSBUILD_ENVIRONMENT_NAMES.client\n\n // Create RSC plugin pair lazily (once per build)\n if (!rscPlugins) {\n rscPlugins = utils.rspack.experiments.rsc.createPlugins()\n }\n\n if (isServerEnv) {\n // --- issuerLayer rule: modules imported from RSC layer\n // get react-server resolve condition ---\n const moduleRules = (config.module.rules ??= [])\n const root = resolvedStartConfig.root || process.cwd()\n\n // Split server-fn provider modules are the actual RSC execution\n // boundary in Start's layered model. They must compile in the\n // RSC layer so React and react-server-dom-rspack resolve their\n // react-server exports without forcing the whole SSR graph into\n // react-server conditions.\n moduleRules.push({\n resourceQuery: /(?:^|[?&])tss-serverfn-split(?:&|$)/,\n layer: RSBUILD_RSC_LAYERS.rsc,\n resolve: {\n conditionNames: ['react-server', '...'],\n },\n })\n\n // All modules imported from the RSC layer inherit\n // the react-server condition (transitive propagation), except\n // route split virtual modules. Those remain ordinary SSR/client\n // route code; only `?tsr-shared=1` modules may be shared with the\n // provider subtree.\n moduleRules.push({\n issuerLayer: RSBUILD_RSC_LAYERS.rsc,\n resourceQuery: {\n not: [/(?:^|[?&])tsr-split(?:=|&|$)/],\n },\n resolve: {\n conditionNames: ['react-server', '...'],\n },\n })\n\n // The RSC ServerPlugin injects imports like\n // `react-server-dom-rspack/server` into transformed modules.\n // Some modules in the server graph resolve from real package paths\n // outside the app root, so relying on the default relative\n // `node_modules` lookup is not enough. Seed resolve.modules with the\n // app root explicitly, without package-manager-specific heuristics.\n seedResolveModules(config, [`${root}/node_modules`, 'node_modules'])\n\n // Add ServerPlugin with HMR callback\n config.plugins.push(\n new rscPlugins.ServerPlugin({\n clientEntryName: 'index',\n runtimeEntryName: 'index',\n injectSsrModulesToEntries: ['index'],\n onServerComponentChanges: () => {\n // Send rsc:update to connected clients for HMR\n devServerRef?.sockWrite('custom', {\n event: 'rsc:update',\n })\n },\n }),\n )\n\n config.plugins.push({\n apply(compiler: RspackCompiler) {\n compiler.hooks.finishMake.tapPromise(\n {\n name: 'TanStackStartRscServerFnResolverRebuild',\n stage: -10,\n },\n async (compilation: RspackCompilationExtended) => {\n if (Object.keys(serverFnsById).length === 0) {\n return\n }\n\n const resolverContent =\n virtualModuleState.generateCurrentResolverContent(true)\n virtualModuleState.tryUpdateServerFnResolver(\n resolverContent,\n )\n\n await rebuildModulesContaining(\n compilation,\n virtualModuleState.serverFnResolverPath,\n )\n },\n )\n },\n })\n\n if (api.context.action !== 'dev') {\n config.plugins.push({\n apply(compiler: RspackCompiler) {\n compiler.hooks.finishMake.tapPromise(\n {\n name: 'TanStackStartRscManifestRebuild',\n // The native RSC ServerPlugin completes the client-entry\n // handoff during its finishMake hook. Rebuild the manifest\n // after that point so the transform hook can emit the final\n // manifest source instead of the placeholder.\n stage: 10,\n },\n async (compilation: RspackCompilationExtended) => {\n const clientBuild = getClientBuild()\n\n if (!clientBuild) {\n return\n }\n\n virtualModuleState.updateManifest(clientBuild)\n\n await rebuildModulesContaining(\n compilation,\n virtualModuleState.manifestPath,\n )\n },\n )\n },\n })\n }\n }\n\n if (isClientEnv) {\n // Add ClientPlugin — the Coordinator links it to the\n // ServerPlugin's compilation state\n config.plugins.push(new rscPlugins.ClientPlugin())\n }\n\n // --- SWC reactServerComponents ---\n // Enable RSC directive detection where the native RSC plugins need it.\n // In the server build, scope it to the actual RSC provider subtree so\n // ordinary route-split modules (e.g. ?tsr-split=component) stay out of\n // RSC validation unless they are really imported by a provider module.\n if (isServerEnv) {\n enableSwcReactServerComponents(config, 'rsc-subtree')\n } else if (isClientEnv) {\n enableSwcReactServerComponents(config, 'all')\n }\n })\n\n // Capture dev server reference for RSC HMR socket writes\n if (api.context.action === 'dev') {\n api.onBeforeStartDevServer(({ server }) => {\n devServerRef = server\n })\n }\n }\n\n // ---------------------------------------------------------------\n // 8. Build ordering — client must complete before server starts\n // so that the manifest virtual module has real client build stats.\n // Uses rspack MultiCompiler.setDependencies() under the hood.\n //\n // IMPORTANT: When RSC is enabled we must NOT set dependencies.\n // The RSC Coordinator already orchestrates server↔client\n // compilation ordering by interleaving phases within compiler\n // hooks. Adding setDependencies(server, [client]) on top of\n // the Coordinator creates a deadlock: MultiCompiler blocks\n // the server compiler until client is `done`, but the\n // Coordinator blocks the client's `make` hook until the\n // server's entries phase completes — neither can start.\n // ---------------------------------------------------------------\n if (!rscEnabled) {\n api.onAfterCreateCompiler(({ compiler }) => {\n // MultiCompiler has a `compilers` array; single compiler does not\n if ('compilers' in compiler) {\n const serverCompiler = compiler.compilers.find(\n (c) => c.name === RSBUILD_ENVIRONMENT_NAMES.server,\n )\n if (serverCompiler) {\n compiler.setDependencies(serverCompiler, [\n RSBUILD_ENVIRONMENT_NAMES.client,\n ])\n }\n }\n })\n }\n\n // ---------------------------------------------------------------\n // 8b. Manifest asset replacement fallback (RSC build only)\n // Rsbuild's native RSC coordinator interleaves server and client\n // compilers, so the server manifest module can compile before Start's\n // normalized client build stats exist. Keep final replacement in the\n // server asset pipeline, after the client processAssets capture has a\n // chance to run.\n // ---------------------------------------------------------------\n if (api.context.action !== 'dev' && rscEnabled) {\n const manifestPlaceholderLiteral = JSON.stringify(\n START_MANIFEST_PLACEHOLDER,\n )\n api.modifyRspackConfig((config, utils) => {\n if (utils.environment.name !== RSBUILD_ENVIRONMENT_NAMES.server)\n return\n\n config.plugins.push({\n apply(compiler: RspackCompiler) {\n compiler.hooks.compilation.tap(\n 'TanStackStartManifestReplace',\n (compilation: RspackCompilationExtended) => {\n compilation.hooks.processAssets.tap(\n {\n name: 'TanStackStartManifestReplace',\n stage:\n utils.rspack.Compilation\n .PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,\n },\n () => {\n const assetsWithPlaceholder = compilation\n .getAssets()\n .flatMap((asset) => {\n if (!asset.name.endsWith('.js')) return []\n\n const sourceStr = String(asset.source.source())\n return sourceStr.includes(manifestPlaceholderLiteral)\n ? [{ asset, sourceStr }]\n : []\n })\n\n if (assetsWithPlaceholder.length === 0) return\n\n const clientBuild = getClientBuild()\n if (!clientBuild) {\n throw new Error(\n 'TanStack Start could not replace the rsbuild RSC server manifest placeholder because the client build was unavailable',\n )\n }\n\n const manifestValueLiteral =\n virtualModuleState.generateManifestValueLiteral(\n clientBuild,\n )\n\n for (const {\n asset,\n sourceStr,\n } of assetsWithPlaceholder) {\n compilation.updateAsset(\n asset.name,\n new utils.rspack.sources.RawSource(\n sourceStr.replace(\n manifestPlaceholderLiteral,\n manifestValueLiteral,\n ),\n ),\n )\n }\n },\n )\n },\n )\n },\n })\n })\n }\n\n // ---------------------------------------------------------------\n // 9. After client env compiles — refresh resolver + manifest\n // ---------------------------------------------------------------\n api.onAfterEnvironmentCompile(({ environment }) => {\n if (environment.name !== RSBUILD_ENVIRONMENT_NAMES.client) return\n\n virtualModuleState.updateServerFnResolver()\n\n const clientBuild = getClientBuild()\n if (clientBuild) {\n virtualModuleState.updateManifest(clientBuild)\n }\n })\n\n if (api.context.action === 'build') {\n api.onAfterBuild(async () => {\n const { startConfig } = getConfig()\n\n await postBuildWithRsbuild({\n startConfig,\n clientOutputDirectory: resolvedStartConfig.outputDirectories.client,\n serverOutputDirectory: resolvedStartConfig.outputDirectories.server,\n })\n })\n }\n },\n }\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nconst defaultRspackWatchIgnored = /[\\\\/](?:\\.git|node_modules)[\\\\/]/\n\nfunction seedResolveModules(\n config: RspackConfig,\n entries: Array<string>,\n): void {\n const resolveModules = (config.resolve.modules ??= [])\n\n for (const entry of entries) {\n if (!resolveModules.includes(entry)) {\n resolveModules.push(entry)\n }\n }\n}\n\nfunction rebuildModulesContaining(\n compilation: RspackCompilationExtended,\n identifierFragment: string,\n): Promise<void> {\n const modulesToRebuild = Array.from(compilation.modules).filter((mod) =>\n mod.identifier().includes(identifierFragment),\n )\n\n if (modulesToRebuild.length === 0) {\n return Promise.resolve()\n }\n\n return Promise.all(\n modulesToRebuild.map(\n (mod) =>\n new Promise<void>((resolve, reject) => {\n compilation.rebuildModule(mod, (err: Error | null) => {\n if (err) reject(err)\n else resolve()\n })\n }),\n ),\n ).then(() => undefined)\n}\n\n/**\n * Return the realpath of every packages/<name>/dist directory in the\n * TanStack Router monorepo. Only meaningful when called from inside the\n * monorepo — in user apps, callers should guard with\n * `isInsideRouterMonoRepo` before invoking this.\n */\nfunction resolveWorkspacePackageDistRealpaths(): Array<string> {\n // currentDir points at either <repo>/packages/start-plugin-core/src/rsbuild\n // or <repo>/packages/start-plugin-core/dist/<fmt>/rsbuild. Four levels up\n // lands on <repo>/packages in both layouts.\n const packagesDir = resolve(currentDir, '../../../../')\n if (!existsSync(packagesDir)) return []\n\n let entries: Array<string>\n try {\n entries = readdirSync(packagesDir)\n } catch {\n return []\n }\n\n const dists: Array<string> = []\n for (const entry of entries) {\n const distPath = join(packagesDir, entry, 'dist')\n try {\n if (!statSync(distPath).isDirectory()) continue\n } catch {\n continue\n }\n try {\n dists.push(realpathSync(distPath))\n } catch {\n dists.push(distPath)\n }\n }\n\n return dists\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.js","names":[],"sources":["../../../src/rsbuild/plugin.ts"],"sourcesContent":["import { existsSync, readdirSync, realpathSync, statSync } from 'node:fs'\nimport { dirname, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { hasKeys } from '@tanstack/router-core'\nimport { joinURL } from 'ufo'\nimport {\n applyResolvedBaseAndOutput,\n applyResolvedRouterBasepath,\n createStartConfigContext,\n} from '../config-context'\nimport { normalizePath } from '../utils'\nimport { createServerFnBasePath, normalizePublicBase } from '../planning'\nimport { parseStartConfig } from './schema'\nimport {\n RSBUILD_ENVIRONMENT_NAMES,\n RSBUILD_RSC_LAYERS,\n createRsbuildEnvironmentPlan,\n createRsbuildResolvedEntryAliases,\n resolveRsbuildOutputDirectory,\n} from './planning'\nimport { registerStartCompilerTransforms } from './start-compiler-host'\nimport { registerImportProtection } from './import-protection'\nimport {\n START_MANIFEST_PLACEHOLDER,\n registerVirtualModules,\n} from './virtual-modules'\nimport { createServerSetup } from './dev-server'\nimport { registerClientBuildCapture } from './normalized-client-build'\nimport { registerRouterPlugins } from './start-router-plugin'\nimport { postBuildWithRsbuild } from './post-build'\nimport { enableSwcReactServerComponents } from './swc-rsc'\nimport type { ServerFn } from '../start-compiler/types'\nimport type { TanStackStartRsbuildPluginCoreOptions } from './types'\nimport type {\n ModifyRspackConfigFn,\n RsbuildDevServer,\n RsbuildPlugin,\n RsbuildPluginAPI,\n Rspack,\n rspack as rspackNamespaceType,\n} from '@rsbuild/core'\nimport type { TanStackStartRsbuildInputConfig } from './schema'\n\n// Detect whether this plugin source is running from inside the TanStack\n// Router monorepo (packages/start-plugin-core/src/rsbuild/plugin.ts). When\n// installed from npm in a user app, the path structure is different and\n// this evaluates to false. Used to gate dev-only workarounds that only\n// matter when workspace package dists are symlinked into node_modules.\nconst currentDir = dirname(fileURLToPath(import.meta.url))\nconst isInsideRouterMonoRepo = (() => {\n // src layout: <repo>/packages/start-plugin-core/src/rsbuild → 4 levels up\n // dist layout (CJS/ESM): <repo>/packages/start-plugin-core/dist/<fmt>/rsbuild\n // → also 4 levels up\n const candidate = resolve(currentDir, '../../../../')\n return candidate.endsWith('/packages') || candidate.endsWith('\\\\packages')\n})()\n\ntype RspackNamespace = typeof rspackNamespaceType\ntype RscPluginPair = ReturnType<\n NonNullable<RspackNamespace['experiments']['rsc']>['createPlugins']\n>\ntype RspackConfig = Parameters<ModifyRspackConfigFn>[0]\ntype RspackCompiler = Rspack.Compiler\ntype RspackCompilationExtended = Rspack.Compilation\n\nexport function tanStackStartRsbuild(\n corePluginOpts: TanStackStartRsbuildPluginCoreOptions,\n startPluginOpts: TanStackStartRsbuildInputConfig = {},\n): RsbuildPlugin {\n const rscOpts = corePluginOpts.rsc\n const rscEnabled = Boolean(rscOpts)\n\n const configContext = createStartConfigContext({\n corePluginOpts,\n startPluginOpts,\n parseConfig: parseStartConfig,\n })\n const { getConfig, resolvedStartConfig } = configContext\n const serverFnProviderEnv = corePluginOpts.providerEnvironmentName\n const ssrIsProvider = corePluginOpts.ssrIsProvider\n\n // RSC plugin instances — created lazily when rspack namespace is available\n let rscPlugins: RscPluginPair | undefined\n\n // Reference to the dev server for RSC HMR socket writes\n let devServerRef: Pick<RsbuildDevServer, 'sockWrite'> | null = null\n const serverFnsById: Record<string, ServerFn> = {}\n let updateServerFnResolver: (() => void) | undefined\n\n return {\n name: 'tanstack-start-rsbuild',\n setup(api: RsbuildPluginAPI) {\n // ---------------------------------------------------------------\n // 1. modifyRsbuildConfig — resolve config, set up environments\n // ---------------------------------------------------------------\n api.modifyRsbuildConfig((rsbuildConfig, { mergeRsbuildConfig }) => {\n const root =\n typeof rsbuildConfig.root === 'string'\n ? rsbuildConfig.root\n : process.cwd()\n\n const serverBase = rsbuildConfig.server?.base\n const assetPrefix = rsbuildConfig.output?.assetPrefix\n const publicBase = normalizePublicBase(\n typeof serverBase === 'string'\n ? serverBase\n : typeof assetPrefix === 'string' && assetPrefix !== 'auto'\n ? assetPrefix\n : undefined,\n )\n const rootDistPath = rsbuildConfig.output?.distPath\n const clientDistPath =\n rsbuildConfig.environments?.[RSBUILD_ENVIRONMENT_NAMES.client]?.output\n ?.distPath\n const serverDistPath =\n rsbuildConfig.environments?.[RSBUILD_ENVIRONMENT_NAMES.server]?.output\n ?.distPath\n\n applyResolvedBaseAndOutput({\n resolvedStartConfig,\n root,\n publicBase,\n clientOutputDirectory: resolveRsbuildOutputDirectory({\n distPath: clientDistPath,\n rootDistPath,\n fallback: 'dist/client',\n subdirectory: 'client',\n }),\n serverOutputDirectory: resolveRsbuildOutputDirectory({\n distPath: serverDistPath,\n rootDistPath,\n fallback: 'dist/server',\n subdirectory: 'server',\n }),\n })\n\n const { startConfig } = getConfig()\n const routerBasepath = applyResolvedRouterBasepath({\n resolvedStartConfig,\n startConfig,\n })\n\n const resolvedEntryPlan = configContext.resolveEntries()\n const isDev = api.context.action === 'dev'\n\n const entryAliases = createRsbuildResolvedEntryAliases({\n entryPaths: resolvedEntryPlan.entryPaths,\n })\n\n const environmentPlan = createRsbuildEnvironmentPlan({\n root,\n entryAliases,\n clientOutputDirectory: resolvedStartConfig.outputDirectories.client,\n serverOutputDirectory: resolvedStartConfig.outputDirectories.server,\n publicBase: resolvedStartConfig.basePaths.publicBase,\n serverFnProviderEnv,\n environmentOverrides: corePluginOpts.rsbuild?.environments,\n rsc: rscOpts,\n dev: isDev,\n })\n const serverFnBase = createServerFnBasePath({\n routerBasepath,\n serverFnBase: startConfig.serverFns.base,\n })\n const inlineCssEnabled = !isDev && startConfig.server.build.inlineCss\n\n return mergeRsbuildConfig(rsbuildConfig, {\n source: {\n define: {\n 'process.env.TSS_SERVER_FN_BASE': JSON.stringify(serverFnBase),\n 'import.meta.env.TSS_SERVER_FN_BASE':\n JSON.stringify(serverFnBase),\n 'process.env.TSS_ROUTER_BASEPATH': JSON.stringify(routerBasepath),\n 'import.meta.env.TSS_ROUTER_BASEPATH':\n JSON.stringify(routerBasepath),\n 'process.env.TSS_DEV_SERVER': JSON.stringify(\n isDev ? 'true' : 'false',\n ),\n 'import.meta.env.TSS_DEV_SERVER': JSON.stringify(\n isDev ? 'true' : 'false',\n ),\n // Rsbuild dev already injects emitted CSS asset hrefs, so keep\n // Start's synthetic `/@tanstack-start/styles.css` path disabled.\n 'process.env.TSS_DEV_SSR_STYLES_ENABLED': JSON.stringify('false'),\n 'import.meta.env.TSS_DEV_SSR_STYLES_ENABLED':\n JSON.stringify('false'),\n 'process.env.TSS_DEV_SSR_STYLES_BASEPATH': JSON.stringify(\n resolvedStartConfig.basePaths.publicBase,\n ),\n 'import.meta.env.TSS_DEV_SSR_STYLES_BASEPATH': JSON.stringify(\n resolvedStartConfig.basePaths.publicBase,\n ),\n 'process.env.TSS_INLINE_CSS_ENABLED': JSON.stringify(\n inlineCssEnabled ? 'true' : 'false',\n ),\n 'import.meta.env.TSS_INLINE_CSS_ENABLED': JSON.stringify(\n inlineCssEnabled ? 'true' : 'false',\n ),\n },\n },\n server: {\n // SSR apps render every route on the server — disable HTML\n // fallback so rsbuild doesn't intercept /_serverFn/ URLs.\n htmlFallback: false,\n // server.setup returned callback runs after built-in middleware\n // but BEFORE fallback middleware — the ideal slot for SSR.\n ...(isDev &&\n startPluginOpts.rsbuild?.installDevServerMiddleware !== false\n ? {\n setup: createServerSetup({\n serverFnBasePath: serverFnBase,\n }),\n }\n : {}),\n },\n ...(isDev\n ? {\n dev: {\n lazyCompilation: false,\n ...(rscEnabled ? { liveReload: false } : {}),\n },\n }\n : {}),\n environments: environmentPlan.environments,\n resolve: {\n alias: environmentPlan.alias,\n },\n })\n })\n\n // ---------------------------------------------------------------\n // 2. StartCompiler transforms — server fns, isomorphic fns, etc.\n // ---------------------------------------------------------------\n registerStartCompilerTransforms(api, {\n framework: corePluginOpts.framework,\n // modifyRsbuildConfig copies rsbuildConfig.root into resolvedStartConfig.root,\n // so defer this read until transform time instead of falling back to\n // process.cwd() during plugin setup.\n root: () => resolvedStartConfig.root || process.cwd(),\n providerEnvName: serverFnProviderEnv,\n generateFunctionId: startPluginOpts.serverFns?.generateFunctionId,\n serverFnsById,\n onServerFnsByIdChange: () => {\n updateServerFnResolver?.()\n },\n })\n\n registerImportProtection(api, {\n getConfig,\n framework: corePluginOpts.framework,\n environments: [\n { name: RSBUILD_ENVIRONMENT_NAMES.client, type: 'client' },\n { name: RSBUILD_ENVIRONMENT_NAMES.server, type: 'server' },\n ...(serverFnProviderEnv !== RSBUILD_ENVIRONMENT_NAMES.server &&\n !rscEnabled\n ? [{ name: serverFnProviderEnv, type: 'server' as const }]\n : []),\n ],\n })\n\n // ---------------------------------------------------------------\n // 3. Virtual modules — manifest, server fn resolver, adapters,\n // RSC runtime, RSC HMR\n // ---------------------------------------------------------------\n const virtualModuleState = registerVirtualModules(api, {\n root: resolvedStartConfig.root || process.cwd(),\n getConfig,\n serverFnsById,\n providerEnvName: serverFnProviderEnv,\n ssrIsProvider,\n serializationAdapters: corePluginOpts.serializationAdapters,\n getDevClientEntryUrl: (publicBase: string) =>\n joinURL(publicBase, 'static/js/index.js'),\n rscEnabled,\n })\n updateServerFnResolver = virtualModuleState.updateServerFnResolver\n\n // ---------------------------------------------------------------\n // 4. Client build stats capture via processAssets\n // ---------------------------------------------------------------\n const { getClientBuild } = registerClientBuildCapture(api)\n\n // ---------------------------------------------------------------\n // 4b. Server manifest module generation (build only)\n // For ordinary multi-environment builds, Rsbuild can compile the\n // server environment after the client environment finishes. Generate\n // the final manifest as module source in that phase instead of\n // patching emitted server assets afterwards.\n // ---------------------------------------------------------------\n if (api.context.action !== 'dev') {\n const normalizedManifestPath = normalizePath(\n virtualModuleState.manifestPath,\n )\n const matchesManifestPath = (id: string) =>\n normalizePath(id) === normalizedManifestPath\n\n api.transform(\n {\n test: (id: string) => matchesManifestPath(id),\n environments: [RSBUILD_ENVIRONMENT_NAMES.server],\n },\n ({ code }) => {\n const clientBuild = getClientBuild()\n\n if (clientBuild) {\n return virtualModuleState.generateManifestContent(clientBuild)\n }\n\n if (!rscEnabled) {\n throw new Error(\n 'TanStack Start could not generate the rsbuild server manifest before the client build completed',\n )\n }\n\n // RSC builds cannot express the required client -> server ordering\n // through MultiCompiler dependencies, so keep the placeholder for\n // the RSC-only asset-patching fallback below.\n return code\n },\n )\n }\n\n // ---------------------------------------------------------------\n // 5. Router plugin wiring (generator + code splitter)\n // ---------------------------------------------------------------\n registerRouterPlugins(api, {\n getConfig,\n corePluginOpts,\n startPluginOpts,\n })\n\n // ---------------------------------------------------------------\n // 6. Dev SSR middleware — registered via server.setup in\n // modifyRsbuildConfig above (returned callback runs after\n // built-ins but before fallback middleware)\n // ---------------------------------------------------------------\n\n // ---------------------------------------------------------------\n // 6b. Dev watcher: ignore workspace package `dist/**` directories.\n //\n // In a real user app, `@tanstack/react-router` and friends live\n // inside `node_modules/` and are ignored by Rspack's default\n // watcher. In this monorepo, pnpm symlinks them to\n // `packages/*/dist` (realpath outside node_modules), so the\n // watcher follows them and treats their dist files as live\n // sources. If anything rewrites those files during dev, Rspack\n // sees transient half-written modules and fails to resolve\n // relative imports between them.\n //\n // Only apply this in monorepo development. In user apps this\n // is a no-op — their `node_modules/@tanstack/*/dist/**` is\n // already ignored by Rspack's default watchOptions.\n // ---------------------------------------------------------------\n if (isInsideRouterMonoRepo && api.context.action === 'dev') {\n api.modifyRspackConfig((config) => {\n const workspaceDistRealpaths = resolveWorkspacePackageDistRealpaths()\n if (workspaceDistRealpaths.length === 0) return\n\n const workspaceDistIgnored = new RegExp(\n workspaceDistRealpaths\n .map((path) => `^${escapeRegExp(path)}(?:[\\\\\\\\/]|$)`)\n .join('|'),\n )\n const ignored = config.watchOptions?.ignored\n\n config.watchOptions = {\n ...(config.watchOptions ?? {}),\n ignored:\n ignored == null\n ? new RegExp(\n `${defaultRspackWatchIgnored.source}|${workspaceDistIgnored.source}`,\n )\n : typeof ignored === 'string'\n ? [ignored, ...workspaceDistRealpaths]\n : Array.isArray(ignored)\n ? [...ignored, ...workspaceDistRealpaths]\n : new RegExp(\n `${ignored.source}|${workspaceDistIgnored.source}`,\n ),\n }\n })\n }\n\n // ---------------------------------------------------------------\n // 7. RSC: rspack layer rules + native RSC plugins\n // When RSC is enabled, we add:\n // - issuerLayer rule for react-server condition propagation\n // - SWC reactServerComponents: true\n // - rspack ServerPlugin (server env) / ClientPlugin (client env)\n // The Coordinator inside createPlugins() handles compilation\n // ordering (server→client→server-actions) automatically.\n // ---------------------------------------------------------------\n if (rscEnabled) {\n api.modifyRspackConfig((config, utils) => {\n const envName = utils.environment.name\n const isServerEnv = envName === RSBUILD_ENVIRONMENT_NAMES.server\n const isClientEnv = envName === RSBUILD_ENVIRONMENT_NAMES.client\n\n // Create RSC plugin pair lazily (once per build)\n if (!rscPlugins) {\n rscPlugins = utils.rspack.experiments.rsc.createPlugins()\n }\n\n if (isServerEnv) {\n // --- issuerLayer rule: modules imported from RSC layer\n // get react-server resolve condition ---\n const moduleRules = (config.module.rules ??= [])\n const root = resolvedStartConfig.root || process.cwd()\n\n // Split server-fn provider modules are the actual RSC execution\n // boundary in Start's layered model. They must compile in the\n // RSC layer so React and react-server-dom-rspack resolve their\n // react-server exports without forcing the whole SSR graph into\n // react-server conditions.\n moduleRules.push({\n resourceQuery: /(?:^|[?&])tss-serverfn-split(?:&|$)/,\n layer: RSBUILD_RSC_LAYERS.rsc,\n resolve: {\n conditionNames: ['react-server', '...'],\n },\n })\n\n // All modules imported from the RSC layer inherit\n // the react-server condition (transitive propagation), except\n // route split virtual modules. Those remain ordinary SSR/client\n // route code; only `?tsr-shared=1` modules may be shared with the\n // provider subtree.\n moduleRules.push({\n issuerLayer: RSBUILD_RSC_LAYERS.rsc,\n resourceQuery: {\n not: [/(?:^|[?&])tsr-split(?:=|&|$)/],\n },\n resolve: {\n conditionNames: ['react-server', '...'],\n },\n })\n\n // The RSC ServerPlugin injects imports like\n // `react-server-dom-rspack/server` into transformed modules.\n // Some modules in the server graph resolve from real package paths\n // outside the app root, so relying on the default relative\n // `node_modules` lookup is not enough. Seed resolve.modules with the\n // app root explicitly, without package-manager-specific heuristics.\n seedResolveModules(config, [`${root}/node_modules`, 'node_modules'])\n\n // Add ServerPlugin with HMR callback\n config.plugins.push(\n new rscPlugins.ServerPlugin({\n clientEntryName: 'index',\n runtimeEntryName: 'index',\n injectSsrModulesToEntries: ['index'],\n onServerComponentChanges: () => {\n // Send rsc:update to connected clients for HMR\n devServerRef?.sockWrite('custom', {\n event: 'rsc:update',\n })\n },\n }),\n )\n\n config.plugins.push({\n apply(compiler: RspackCompiler) {\n compiler.hooks.finishMake.tapPromise(\n {\n name: 'TanStackStartRscServerFnResolverRebuild',\n stage: -10,\n },\n async (compilation: RspackCompilationExtended) => {\n if (!hasKeys(serverFnsById)) {\n return\n }\n\n const resolverContent =\n virtualModuleState.generateCurrentResolverContent(true)\n virtualModuleState.tryUpdateServerFnResolver(\n resolverContent,\n )\n\n await rebuildModulesContaining(\n compilation,\n virtualModuleState.serverFnResolverPath,\n )\n },\n )\n },\n })\n\n if (api.context.action !== 'dev') {\n config.plugins.push({\n apply(compiler: RspackCompiler) {\n compiler.hooks.finishMake.tapPromise(\n {\n name: 'TanStackStartRscManifestRebuild',\n // The native RSC ServerPlugin completes the client-entry\n // handoff during its finishMake hook. Rebuild the manifest\n // after that point so the transform hook can emit the final\n // manifest source instead of the placeholder.\n stage: 10,\n },\n async (compilation: RspackCompilationExtended) => {\n const clientBuild = getClientBuild()\n\n if (!clientBuild) {\n return\n }\n\n virtualModuleState.updateManifest(clientBuild)\n\n await rebuildModulesContaining(\n compilation,\n virtualModuleState.manifestPath,\n )\n },\n )\n },\n })\n }\n }\n\n if (isClientEnv) {\n // Add ClientPlugin — the Coordinator links it to the\n // ServerPlugin's compilation state\n config.plugins.push(new rscPlugins.ClientPlugin())\n }\n\n // --- SWC reactServerComponents ---\n // Enable RSC directive detection where the native RSC plugins need it.\n // In the server build, scope it to the actual RSC provider subtree so\n // ordinary route-split modules (e.g. ?tsr-split=component) stay out of\n // RSC validation unless they are really imported by a provider module.\n if (isServerEnv) {\n enableSwcReactServerComponents(config, 'rsc-subtree')\n } else if (isClientEnv) {\n enableSwcReactServerComponents(config, 'all')\n }\n })\n\n // Capture dev server reference for RSC HMR socket writes\n if (api.context.action === 'dev') {\n api.onBeforeStartDevServer(({ server }) => {\n devServerRef = server\n })\n }\n }\n\n // ---------------------------------------------------------------\n // 8. Build ordering — client must complete before server starts\n // so that the manifest virtual module has real client build stats.\n // Uses rspack MultiCompiler.setDependencies() under the hood.\n //\n // IMPORTANT: When RSC is enabled we must NOT set dependencies.\n // The RSC Coordinator already orchestrates server↔client\n // compilation ordering by interleaving phases within compiler\n // hooks. Adding setDependencies(server, [client]) on top of\n // the Coordinator creates a deadlock: MultiCompiler blocks\n // the server compiler until client is `done`, but the\n // Coordinator blocks the client's `make` hook until the\n // server's entries phase completes — neither can start.\n // ---------------------------------------------------------------\n if (!rscEnabled) {\n api.onAfterCreateCompiler(({ compiler }) => {\n // MultiCompiler has a `compilers` array; single compiler does not\n if ('compilers' in compiler) {\n const serverCompiler = compiler.compilers.find(\n (c) => c.name === RSBUILD_ENVIRONMENT_NAMES.server,\n )\n if (serverCompiler) {\n compiler.setDependencies(serverCompiler, [\n RSBUILD_ENVIRONMENT_NAMES.client,\n ])\n }\n }\n })\n }\n\n // ---------------------------------------------------------------\n // 8b. Manifest asset replacement fallback (RSC build only)\n // Rsbuild's native RSC coordinator interleaves server and client\n // compilers, so the server manifest module can compile before Start's\n // normalized client build stats exist. Keep final replacement in the\n // server asset pipeline, after the client processAssets capture has a\n // chance to run.\n // ---------------------------------------------------------------\n if (api.context.action !== 'dev' && rscEnabled) {\n const manifestPlaceholderLiteral = JSON.stringify(\n START_MANIFEST_PLACEHOLDER,\n )\n api.modifyRspackConfig((config, utils) => {\n if (utils.environment.name !== RSBUILD_ENVIRONMENT_NAMES.server)\n return\n\n config.plugins.push({\n apply(compiler: RspackCompiler) {\n compiler.hooks.compilation.tap(\n 'TanStackStartManifestReplace',\n (compilation: RspackCompilationExtended) => {\n compilation.hooks.processAssets.tap(\n {\n name: 'TanStackStartManifestReplace',\n stage:\n utils.rspack.Compilation\n .PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,\n },\n () => {\n const assetsWithPlaceholder = compilation\n .getAssets()\n .flatMap((asset) => {\n if (!asset.name.endsWith('.js')) return []\n\n const sourceStr = String(asset.source.source())\n return sourceStr.includes(manifestPlaceholderLiteral)\n ? [{ asset, sourceStr }]\n : []\n })\n\n if (assetsWithPlaceholder.length === 0) return\n\n const clientBuild = getClientBuild()\n if (!clientBuild) {\n throw new Error(\n 'TanStack Start could not replace the rsbuild RSC server manifest placeholder because the client build was unavailable',\n )\n }\n\n const manifestValueLiteral =\n virtualModuleState.generateManifestValueLiteral(\n clientBuild,\n )\n\n for (const {\n asset,\n sourceStr,\n } of assetsWithPlaceholder) {\n compilation.updateAsset(\n asset.name,\n new utils.rspack.sources.RawSource(\n sourceStr.replace(\n manifestPlaceholderLiteral,\n manifestValueLiteral,\n ),\n ),\n )\n }\n },\n )\n },\n )\n },\n })\n })\n }\n\n // ---------------------------------------------------------------\n // 9. After client env compiles — refresh resolver + manifest\n // ---------------------------------------------------------------\n api.onAfterEnvironmentCompile(({ environment }) => {\n if (environment.name !== RSBUILD_ENVIRONMENT_NAMES.client) return\n\n virtualModuleState.updateServerFnResolver()\n\n const clientBuild = getClientBuild()\n if (clientBuild) {\n virtualModuleState.updateManifest(clientBuild)\n }\n })\n\n if (api.context.action === 'build') {\n api.onAfterBuild(async () => {\n const { startConfig } = getConfig()\n\n await postBuildWithRsbuild({\n startConfig,\n clientOutputDirectory: resolvedStartConfig.outputDirectories.client,\n serverOutputDirectory: resolvedStartConfig.outputDirectories.server,\n })\n })\n }\n },\n }\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nconst defaultRspackWatchIgnored = /[\\\\/](?:\\.git|node_modules)[\\\\/]/\n\nfunction seedResolveModules(\n config: RspackConfig,\n entries: Array<string>,\n): void {\n const resolveModules = (config.resolve.modules ??= [])\n\n for (const entry of entries) {\n if (!resolveModules.includes(entry)) {\n resolveModules.push(entry)\n }\n }\n}\n\nfunction rebuildModulesContaining(\n compilation: RspackCompilationExtended,\n identifierFragment: string,\n): Promise<void> {\n const modulesToRebuild = Array.from(compilation.modules).filter((mod) =>\n mod.identifier().includes(identifierFragment),\n )\n\n if (modulesToRebuild.length === 0) {\n return Promise.resolve()\n }\n\n return Promise.all(\n modulesToRebuild.map(\n (mod) =>\n new Promise<void>((resolve, reject) => {\n compilation.rebuildModule(mod, (err: Error | null) => {\n if (err) reject(err)\n else resolve()\n })\n }),\n ),\n ).then(() => undefined)\n}\n\n/**\n * Return the realpath of every packages/<name>/dist directory in the\n * TanStack Router monorepo. Only meaningful when called from inside the\n * monorepo — in user apps, callers should guard with\n * `isInsideRouterMonoRepo` before invoking this.\n */\nfunction resolveWorkspacePackageDistRealpaths(): Array<string> {\n // currentDir points at either <repo>/packages/start-plugin-core/src/rsbuild\n // or <repo>/packages/start-plugin-core/dist/<fmt>/rsbuild. Four levels up\n // lands on <repo>/packages in both layouts.\n const packagesDir = resolve(currentDir, '../../../../')\n if (!existsSync(packagesDir)) return []\n\n let entries: Array<string>\n try {\n entries = readdirSync(packagesDir)\n } catch {\n return []\n }\n\n const dists: Array<string> = []\n for (const entry of entries) {\n const distPath = join(packagesDir, entry, 'dist')\n try {\n if (!statSync(distPath).isDirectory()) continue\n } catch {\n continue\n }\n try {\n dists.push(realpathSync(distPath))\n } catch {\n dists.push(distPath)\n }\n }\n\n return dists\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cleanId, codeFrameError, stripMethodCall } from "./utils.js";
|
|
2
|
+
import { hasKeys } from "@tanstack/router-core";
|
|
2
3
|
import path from "pathe";
|
|
3
4
|
import * as t from "@babel/types";
|
|
4
5
|
import babel from "@babel/core";
|
|
@@ -164,7 +165,7 @@ function handleCreateServerFn(candidates, context) {
|
|
|
164
165
|
if (exportNames.size > 0) context.ast.program.body.push(t.exportNamedDeclaration(void 0, Array.from(exportNames).map((name) => t.exportSpecifier(t.identifier(name), t.identifier(name)))));
|
|
165
166
|
if (context.mode === "dev") context.ast.program.body.push(...providerHmrAcceptTemplate());
|
|
166
167
|
}
|
|
167
|
-
if (!isProviderFile &&
|
|
168
|
+
if (!isProviderFile && hasKeys(serverFnsById) && context.onServerFnsById) context.onServerFnsById(serverFnsById);
|
|
168
169
|
const runtimeCode = getCachedRuntimeCode(context.framework, envConfig.runtimeCodeType);
|
|
169
170
|
context.ast.program.body.unshift(t.cloneNode(runtimeCode));
|
|
170
171
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handleCreateServerFn.js","names":[],"sources":["../../../src/start-compiler/handleCreateServerFn.ts"],"sourcesContent":["import * as t from '@babel/types'\nimport babel from '@babel/core'\nimport path from 'pathe'\nimport { cleanId, codeFrameError, stripMethodCall } from './utils'\nimport type { CompilationContext, RewriteCandidate, ServerFn } from './types'\nimport type { CompileStartFrameworkOptions } from '../types'\n\nconst TSS_SERVERFN_SPLIT_PARAM = 'tss-serverfn-split'\n\nconst providerHmrAcceptTemplate = babel.template.statements(\n `\nif (import.meta.hot) {\n import.meta.hot.accept(() => {})\n}\nif (import.meta.webpackHot) {\n import.meta.webpackHot.accept(() => {})\n}\n`,\n {\n placeholderPattern: false,\n },\n)\n\n// ============================================================================\n// Pre-compiled babel templates (compiled once at module load time)\n// ============================================================================\n\n// Template for provider files: createServerRpc(serverFnMeta, fn)\nconst serverRpcTemplate = babel.template.expression(\n `createServerRpc(%%serverFnMeta%%, %%fn%%)`,\n)\n\n// Template for client caller files: createClientRpc(functionId)\nconst clientRpcTemplate = babel.template.expression(\n `createClientRpc(%%functionId%%)`,\n)\n\n// Template for SSR caller files: createSsrRpc(functionId)\nconst ssrRpcManifestTemplate = babel.template.expression(\n `createSsrRpc(%%functionId%%)`,\n)\n\n// ============================================================================\n// Runtime code cache (cached per framework to avoid repeated AST generation)\n// ============================================================================\n\ntype RuntimeCodeType = 'provider' | 'client' | 'ssr'\ntype FrameworkRuntimeCache = Record<RuntimeCodeType, t.Statement>\nconst RuntimeCodeCache = new Map<\n CompileStartFrameworkOptions,\n FrameworkRuntimeCache\n>()\n\nfunction getCachedRuntimeCode(\n framework: CompileStartFrameworkOptions,\n type: RuntimeCodeType,\n): t.Statement {\n let cache = RuntimeCodeCache.get(framework)\n if (!cache) {\n cache = {\n provider: babel.template.ast(\n `import { createServerRpc } from '@tanstack/${framework}-start/server-rpc'`,\n { placeholderPattern: false },\n ) as t.Statement,\n client: babel.template.ast(\n `import { createClientRpc } from '@tanstack/${framework}-start/client-rpc'`,\n { placeholderPattern: false },\n ) as t.Statement,\n ssr: babel.template.ast(\n `import { createSsrRpc } from '@tanstack/${framework}-start/ssr-rpc'`,\n { placeholderPattern: false },\n ) as t.Statement,\n }\n RuntimeCodeCache.set(framework, cache)\n }\n return cache[type]\n}\n\n/**\n * Environment-specific configuration for server function transformation.\n * This is computed internally based on the compilation context.\n */\ninterface EnvConfig {\n /** Whether this environment is a client environment */\n isClientEnvironment: boolean\n /** The runtime code type to use for imports */\n runtimeCodeType: RuntimeCodeType\n}\n\n/**\n * Gets the environment configuration for the current compilation context.\n */\nfunction getEnvConfig(\n context: CompilationContext,\n isProviderFile: boolean,\n): EnvConfig {\n const { env } = context\n\n if (isProviderFile) {\n return {\n isClientEnvironment: false,\n runtimeCodeType: 'provider',\n }\n }\n\n if (env === 'client') {\n return {\n isClientEnvironment: true,\n runtimeCodeType: 'client',\n }\n }\n\n // Server caller (SSR)\n return {\n isClientEnvironment: false,\n runtimeCodeType: 'ssr',\n }\n}\n\n/**\n * Builds the serverFnMeta object literal AST node.\n * The object contains: { id, name, filename }\n */\nfunction buildServerFnMetaObject(\n functionId: string,\n variableName: string,\n filename: string,\n): t.ObjectExpression {\n return t.objectExpression([\n t.objectProperty(t.identifier('id'), t.stringLiteral(functionId)),\n t.objectProperty(t.identifier('name'), t.stringLiteral(variableName)),\n t.objectProperty(t.identifier('filename'), t.stringLiteral(filename)),\n ])\n}\n\n/**\n * Generates the RPC stub expression for provider files.\n * Uses pre-compiled template for performance.\n */\nfunction generateProviderRpcStub(\n serverFnMeta: t.ObjectExpression,\n fn: t.Expression,\n): t.Expression {\n return serverRpcTemplate({\n serverFnMeta,\n fn,\n })\n}\n\n/**\n * Generates the RPC stub expression for caller files.\n * Uses pre-compiled templates for performance.\n * Note: Client and SSR callers only receive the functionId string, not the full metadata.\n */\nfunction generateCallerRpcStub(\n functionId: string,\n envConfig: EnvConfig,\n): t.Expression {\n const functionIdLiteral = t.stringLiteral(functionId)\n\n if (envConfig.runtimeCodeType === 'client') {\n return clientRpcTemplate({\n functionId: functionIdLiteral,\n })\n }\n\n return ssrRpcManifestTemplate({\n functionId: functionIdLiteral,\n })\n}\n\n/**\n * Handles createServerFn transformations for a batch of candidates.\n *\n * This function performs extraction and replacement of server functions\n *\n * For caller files (non-provider):\n * - Replaces the server function with an RPC stub\n * - Does not include the handler function body\n *\n * For provider files:\n * - Creates an extractedFn that calls __executeServer\n * - Modifies .handler() to pass (extractedFn, serverFn) - two arguments\n *\n * @param candidates - All ServerFn candidates to process\n * @param context - The compilation context with helpers and mutable state\n * @returns Result containing runtime code to add, or null if no candidates processed\n */\nexport function handleCreateServerFn(\n candidates: Array<RewriteCandidate>,\n context: CompilationContext,\n) {\n if (candidates.length === 0) {\n return\n }\n\n const isProviderFile = context.id.includes(TSS_SERVERFN_SPLIT_PARAM)\n // Get environment-specific configuration\n const envConfig = getEnvConfig(context, isProviderFile)\n\n // Track function names to ensure uniqueness within this file\n const functionNameSet = new Set<string>()\n\n const exportNames = new Set<string>()\n const serverFnsById: Record<string, ServerFn> = {}\n\n const [baseFilename] = context.id.split('?') as [string]\n const extractedFilename = `${baseFilename}?${TSS_SERVERFN_SPLIT_PARAM}`\n const relativeFilename = path.relative(context.root, baseFilename)\n const knownFns = context.getKnownServerFns()\n const cleanedContextId = cleanId(context.id)\n\n for (const candidate of candidates) {\n const { path: candidatePath, methodChain } = candidate\n const { inputValidator, handler } = methodChain\n\n // Check if the call is assigned to a variable\n if (!candidatePath.parentPath.isVariableDeclarator()) {\n throw new Error('createServerFn must be assigned to a variable!')\n }\n\n // Get the identifier name of the variable\n const variableDeclarator = candidatePath.parentPath.node\n if (!t.isIdentifier(variableDeclarator.id)) {\n throw codeFrameError(\n context.code,\n variableDeclarator.id.loc!,\n 'createServerFn must be assigned to a simple identifier, not a destructuring pattern',\n )\n }\n const existingVariableName = variableDeclarator.id.name\n\n // Generate unique function name with _createServerFn_handler suffix\n // The function name is derived from the variable name\n let functionName = `${existingVariableName}_createServerFn_handler`\n while (functionNameSet.has(functionName)) {\n functionName = incrementFunctionNameVersion(functionName)\n }\n functionNameSet.add(functionName)\n\n // Generate function ID using pre-computed relative filename\n const functionId = context.generateFunctionId({\n filename: relativeFilename,\n functionName,\n extractedFilename,\n })\n\n // Check if this function was already discovered by the client build\n const knownFn = knownFns[functionId]\n // A server function is client-referenced when:\n // 1. We're in the client (browser) environment, OR\n // 2. It was already discovered by another environment (knownFn), OR\n // 3. We're in an SSR caller environment — any server function reachable from\n // SSR module graph is callable via client navigation HTTP requests\n const isClientReferenced =\n envConfig.isClientEnvironment ||\n !!knownFn ||\n envConfig.runtimeCodeType === 'ssr'\n\n // Use canonical extracted filename from known functions if available\n const canonicalExtractedFilename =\n knownFn?.extractedFilename ?? extractedFilename\n\n // Handle input validator - remove on client\n if (inputValidator) {\n const innerInputExpression = inputValidator.callPath.node.arguments[0]\n\n if (!innerInputExpression) {\n throw new Error(\n 'createServerFn().inputValidator() must be called with a validator!',\n )\n }\n\n // If we're on the client, remove the validator call expression\n if (context.env === 'client') {\n stripMethodCall(inputValidator.callPath)\n }\n }\n\n const handlerFnPath = handler?.firstArgPath\n\n if (!handler || !handlerFnPath?.node) {\n throw codeFrameError(\n context.code,\n candidatePath.node.callee.loc!,\n `createServerFn must be called with a \"handler\" property!`,\n )\n }\n\n // Validate the handler argument is an expression (not a SpreadElement, etc.)\n if (!t.isExpression(handlerFnPath.node)) {\n throw codeFrameError(\n context.code,\n handlerFnPath.node.loc!,\n `handler() must be called with an expression, not a ${handlerFnPath.node.type}`,\n )\n }\n\n const handlerFn = handlerFnPath.node\n\n // Register function only from caller files (not provider files)\n // to avoid duplicates - provider files process the same functions\n\n if (!isProviderFile) {\n serverFnsById[functionId] = {\n functionName,\n functionId,\n filename: cleanedContextId,\n extractedFilename: canonicalExtractedFilename,\n isClientReferenced,\n }\n }\n\n if (isProviderFile) {\n // PROVIDER FILE: This is the extracted file that contains the actual implementation\n // We need to:\n // 1. Create an extractedFn that calls __executeServer\n // 2. Modify .handler() to pass (extractedFn, serverFn) - two arguments\n //\n // Expected output format:\n // const extractedFn = createServerRpc({id, name, filename}, (opts) => varName.__executeServer(opts));\n // const varName = createServerFn().handler(extractedFn, originalHandler);\n\n // Build the arrow function: (opts) => varName.__executeServer(opts)\n // The signal parameter is passed through to allow abort signal propagation\n const executeServerArrowFn = t.arrowFunctionExpression(\n [t.identifier('opts')],\n t.callExpression(\n t.memberExpression(\n t.identifier(existingVariableName),\n t.identifier('__executeServer'),\n ),\n [t.identifier('opts')],\n ),\n )\n\n // Build the serverFnMeta object\n const serverFnMeta = buildServerFnMetaObject(\n functionId,\n existingVariableName,\n relativeFilename,\n )\n\n // Generate the replacement using pre-compiled template\n const extractedFnInit = generateProviderRpcStub(\n serverFnMeta,\n executeServerArrowFn,\n )\n\n // Build the extracted function statement\n const extractedFnStatement = t.variableDeclaration('const', [\n t.variableDeclarator(t.identifier(functionName), extractedFnInit),\n ])\n\n // Find the variable declaration statement containing our createServerFn\n const variableDeclaration = candidatePath.parentPath.parentPath\n if (!variableDeclaration.isVariableDeclaration()) {\n throw new Error(\n 'Expected createServerFn to be in a VariableDeclaration',\n )\n }\n\n // Insert the extracted function statement before the variable declaration\n variableDeclaration.insertBefore(extractedFnStatement)\n\n // Modify the .handler() call to pass two arguments: (extractedFn, serverFn)\n // The handlerFnPath.node contains the original serverFn\n const extractedFnIdentifier = t.identifier(functionName)\n const serverFnNode = t.cloneNode(handlerFn, true)\n\n // Replace handler's arguments with [extractedFn, serverFn]\n handler.callPath.node.arguments = [extractedFnIdentifier, serverFnNode]\n\n // Only export the extracted handler (e.g., myFn_createServerFn_handler)\n // The manifest and all import paths only look up this suffixed name.\n // The original variable (e.g., myFn) stays in the file but is not exported\n // since it's only used internally.\n exportNames.add(functionName)\n } else {\n // CALLER FILE: This file calls the server function but doesn't contain the implementation\n // We need to:\n // 1. Remove the handler function body (it will be in the provider file)\n // 2. Replace the handler argument with an RPC stub\n //\n // IMPORTANT: We must keep the createServerFn().handler(extractedFn) structure\n // so that the client middleware chain can unwrap the {result, error, context} response.\n //\n // Expected output format:\n // const myFn = createServerFn().handler(createClientRpc(\"id\"))\n // or\n // const myFn = createServerFn().handler(createSsrRpc(\"id\"))\n\n // If the handler function is an identifier, we need to remove the bound function\n // from the file since it won't be needed\n if (t.isIdentifier(handlerFn)) {\n const binding = handlerFnPath.scope.getBinding(handlerFn.name)\n if (binding) {\n binding.path.remove()\n }\n }\n\n // Generate the RPC stub using pre-compiled templates\n // Note: Caller files only pass functionId, not the full serverFnMeta\n const rpcStub = generateCallerRpcStub(functionId, envConfig)\n\n // Replace ONLY the handler argument with the RPC stub\n // Keep the createServerFn().handler() wrapper intact for client middleware\n handlerFnPath.replaceWith(rpcStub)\n }\n }\n\n // For provider files, add exports for all extracted functions\n if (isProviderFile) {\n // Remove all existing exports first\n safeRemoveExports(context.ast)\n\n // Export all server function related variables from exportNames\n // These were populated by handleCreateServerFn:\n // 1. Extracted handlers: const fn_createServerFn_handler = createServerRpc(...)\n // 2. Original variables: const fn = createServerFn().handler(...)\n if (exportNames.size > 0) {\n context.ast.program.body.push(\n t.exportNamedDeclaration(\n undefined,\n Array.from(exportNames).map((name) =>\n t.exportSpecifier(t.identifier(name), t.identifier(name)),\n ),\n ),\n )\n }\n\n if (context.mode === 'dev') {\n context.ast.program.body.push(...providerHmrAcceptTemplate())\n }\n }\n\n // Notify about discovered functions (only for non-provider files)\n if (\n !isProviderFile &&\n Object.keys(serverFnsById).length > 0 &&\n context.onServerFnsById\n ) {\n context.onServerFnsById(serverFnsById)\n }\n\n // Add runtime import using cached AST node\n const runtimeCode = getCachedRuntimeCode(\n context.framework,\n envConfig.runtimeCodeType,\n )\n context.ast.program.body.unshift(t.cloneNode(runtimeCode))\n}\n\n/**\n * Makes an identifier safe for use as a JavaScript identifier.\n */\nfunction makeIdentifierSafe(identifier: string): string {\n return identifier\n .replace(/[^a-zA-Z0-9_$]/g, '_') // Replace unsafe chars with underscore\n .replace(/^[0-9]/, '_$&') // Prefix leading number with underscore\n .replace(/^\\$/, '_$') // Prefix leading $ with underscore\n .replace(/_{2,}/g, '_') // Collapse multiple underscores\n .replace(/^_|_$/g, '') // Trim leading/trailing underscores\n}\n\n/**\n * Increments the version number suffix on a function name.\n */\nfunction incrementFunctionNameVersion(functionName: string): string {\n const [realReferenceName, count] = functionName.split(/_(\\d+)$/)\n const resolvedCount = Number(count || '0')\n const suffix = `_${resolvedCount + 1}`\n return makeIdentifierSafe(realReferenceName!) + suffix\n}\n\n/**\n * Removes all exports from the AST while preserving the declarations.\n * Used for provider files where we want to re-export only the server functions.\n */\nfunction safeRemoveExports(ast: t.File): void {\n ast.program.body = ast.program.body.flatMap((node) => {\n if (\n t.isExportNamedDeclaration(node) ||\n t.isExportDefaultDeclaration(node)\n ) {\n if (\n t.isFunctionDeclaration(node.declaration) ||\n t.isClassDeclaration(node.declaration) ||\n t.isVariableDeclaration(node.declaration)\n ) {\n // do not remove export if it is an anonymous function / class,\n // otherwise this would produce a syntax error\n if (\n t.isFunctionDeclaration(node.declaration) ||\n t.isClassDeclaration(node.declaration)\n ) {\n if (!node.declaration.id) {\n return node\n }\n }\n return node.declaration\n } else if (node.declaration === null) {\n // remove e.g. `export { RouteComponent as component }`\n return []\n }\n }\n return node\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"handleCreateServerFn.js","names":[],"sources":["../../../src/start-compiler/handleCreateServerFn.ts"],"sourcesContent":["import * as t from '@babel/types'\nimport babel from '@babel/core'\nimport { hasKeys } from '@tanstack/router-core'\nimport path from 'pathe'\nimport { cleanId, codeFrameError, stripMethodCall } from './utils'\nimport type { CompilationContext, RewriteCandidate, ServerFn } from './types'\nimport type { CompileStartFrameworkOptions } from '../types'\n\nconst TSS_SERVERFN_SPLIT_PARAM = 'tss-serverfn-split'\n\nconst providerHmrAcceptTemplate = babel.template.statements(\n `\nif (import.meta.hot) {\n import.meta.hot.accept(() => {})\n}\nif (import.meta.webpackHot) {\n import.meta.webpackHot.accept(() => {})\n}\n`,\n {\n placeholderPattern: false,\n },\n)\n\n// ============================================================================\n// Pre-compiled babel templates (compiled once at module load time)\n// ============================================================================\n\n// Template for provider files: createServerRpc(serverFnMeta, fn)\nconst serverRpcTemplate = babel.template.expression(\n `createServerRpc(%%serverFnMeta%%, %%fn%%)`,\n)\n\n// Template for client caller files: createClientRpc(functionId)\nconst clientRpcTemplate = babel.template.expression(\n `createClientRpc(%%functionId%%)`,\n)\n\n// Template for SSR caller files: createSsrRpc(functionId)\nconst ssrRpcManifestTemplate = babel.template.expression(\n `createSsrRpc(%%functionId%%)`,\n)\n\n// ============================================================================\n// Runtime code cache (cached per framework to avoid repeated AST generation)\n// ============================================================================\n\ntype RuntimeCodeType = 'provider' | 'client' | 'ssr'\ntype FrameworkRuntimeCache = Record<RuntimeCodeType, t.Statement>\nconst RuntimeCodeCache = new Map<\n CompileStartFrameworkOptions,\n FrameworkRuntimeCache\n>()\n\nfunction getCachedRuntimeCode(\n framework: CompileStartFrameworkOptions,\n type: RuntimeCodeType,\n): t.Statement {\n let cache = RuntimeCodeCache.get(framework)\n if (!cache) {\n cache = {\n provider: babel.template.ast(\n `import { createServerRpc } from '@tanstack/${framework}-start/server-rpc'`,\n { placeholderPattern: false },\n ) as t.Statement,\n client: babel.template.ast(\n `import { createClientRpc } from '@tanstack/${framework}-start/client-rpc'`,\n { placeholderPattern: false },\n ) as t.Statement,\n ssr: babel.template.ast(\n `import { createSsrRpc } from '@tanstack/${framework}-start/ssr-rpc'`,\n { placeholderPattern: false },\n ) as t.Statement,\n }\n RuntimeCodeCache.set(framework, cache)\n }\n return cache[type]\n}\n\n/**\n * Environment-specific configuration for server function transformation.\n * This is computed internally based on the compilation context.\n */\ninterface EnvConfig {\n /** Whether this environment is a client environment */\n isClientEnvironment: boolean\n /** The runtime code type to use for imports */\n runtimeCodeType: RuntimeCodeType\n}\n\n/**\n * Gets the environment configuration for the current compilation context.\n */\nfunction getEnvConfig(\n context: CompilationContext,\n isProviderFile: boolean,\n): EnvConfig {\n const { env } = context\n\n if (isProviderFile) {\n return {\n isClientEnvironment: false,\n runtimeCodeType: 'provider',\n }\n }\n\n if (env === 'client') {\n return {\n isClientEnvironment: true,\n runtimeCodeType: 'client',\n }\n }\n\n // Server caller (SSR)\n return {\n isClientEnvironment: false,\n runtimeCodeType: 'ssr',\n }\n}\n\n/**\n * Builds the serverFnMeta object literal AST node.\n * The object contains: { id, name, filename }\n */\nfunction buildServerFnMetaObject(\n functionId: string,\n variableName: string,\n filename: string,\n): t.ObjectExpression {\n return t.objectExpression([\n t.objectProperty(t.identifier('id'), t.stringLiteral(functionId)),\n t.objectProperty(t.identifier('name'), t.stringLiteral(variableName)),\n t.objectProperty(t.identifier('filename'), t.stringLiteral(filename)),\n ])\n}\n\n/**\n * Generates the RPC stub expression for provider files.\n * Uses pre-compiled template for performance.\n */\nfunction generateProviderRpcStub(\n serverFnMeta: t.ObjectExpression,\n fn: t.Expression,\n): t.Expression {\n return serverRpcTemplate({\n serverFnMeta,\n fn,\n })\n}\n\n/**\n * Generates the RPC stub expression for caller files.\n * Uses pre-compiled templates for performance.\n * Note: Client and SSR callers only receive the functionId string, not the full metadata.\n */\nfunction generateCallerRpcStub(\n functionId: string,\n envConfig: EnvConfig,\n): t.Expression {\n const functionIdLiteral = t.stringLiteral(functionId)\n\n if (envConfig.runtimeCodeType === 'client') {\n return clientRpcTemplate({\n functionId: functionIdLiteral,\n })\n }\n\n return ssrRpcManifestTemplate({\n functionId: functionIdLiteral,\n })\n}\n\n/**\n * Handles createServerFn transformations for a batch of candidates.\n *\n * This function performs extraction and replacement of server functions\n *\n * For caller files (non-provider):\n * - Replaces the server function with an RPC stub\n * - Does not include the handler function body\n *\n * For provider files:\n * - Creates an extractedFn that calls __executeServer\n * - Modifies .handler() to pass (extractedFn, serverFn) - two arguments\n *\n * @param candidates - All ServerFn candidates to process\n * @param context - The compilation context with helpers and mutable state\n * @returns Result containing runtime code to add, or null if no candidates processed\n */\nexport function handleCreateServerFn(\n candidates: Array<RewriteCandidate>,\n context: CompilationContext,\n) {\n if (candidates.length === 0) {\n return\n }\n\n const isProviderFile = context.id.includes(TSS_SERVERFN_SPLIT_PARAM)\n // Get environment-specific configuration\n const envConfig = getEnvConfig(context, isProviderFile)\n\n // Track function names to ensure uniqueness within this file\n const functionNameSet = new Set<string>()\n\n const exportNames = new Set<string>()\n const serverFnsById: Record<string, ServerFn> = {}\n\n const [baseFilename] = context.id.split('?') as [string]\n const extractedFilename = `${baseFilename}?${TSS_SERVERFN_SPLIT_PARAM}`\n const relativeFilename = path.relative(context.root, baseFilename)\n const knownFns = context.getKnownServerFns()\n const cleanedContextId = cleanId(context.id)\n\n for (const candidate of candidates) {\n const { path: candidatePath, methodChain } = candidate\n const { inputValidator, handler } = methodChain\n\n // Check if the call is assigned to a variable\n if (!candidatePath.parentPath.isVariableDeclarator()) {\n throw new Error('createServerFn must be assigned to a variable!')\n }\n\n // Get the identifier name of the variable\n const variableDeclarator = candidatePath.parentPath.node\n if (!t.isIdentifier(variableDeclarator.id)) {\n throw codeFrameError(\n context.code,\n variableDeclarator.id.loc!,\n 'createServerFn must be assigned to a simple identifier, not a destructuring pattern',\n )\n }\n const existingVariableName = variableDeclarator.id.name\n\n // Generate unique function name with _createServerFn_handler suffix\n // The function name is derived from the variable name\n let functionName = `${existingVariableName}_createServerFn_handler`\n while (functionNameSet.has(functionName)) {\n functionName = incrementFunctionNameVersion(functionName)\n }\n functionNameSet.add(functionName)\n\n // Generate function ID using pre-computed relative filename\n const functionId = context.generateFunctionId({\n filename: relativeFilename,\n functionName,\n extractedFilename,\n })\n\n // Check if this function was already discovered by the client build\n const knownFn = knownFns[functionId]\n // A server function is client-referenced when:\n // 1. We're in the client (browser) environment, OR\n // 2. It was already discovered by another environment (knownFn), OR\n // 3. We're in an SSR caller environment — any server function reachable from\n // SSR module graph is callable via client navigation HTTP requests\n const isClientReferenced =\n envConfig.isClientEnvironment ||\n !!knownFn ||\n envConfig.runtimeCodeType === 'ssr'\n\n // Use canonical extracted filename from known functions if available\n const canonicalExtractedFilename =\n knownFn?.extractedFilename ?? extractedFilename\n\n // Handle input validator - remove on client\n if (inputValidator) {\n const innerInputExpression = inputValidator.callPath.node.arguments[0]\n\n if (!innerInputExpression) {\n throw new Error(\n 'createServerFn().inputValidator() must be called with a validator!',\n )\n }\n\n // If we're on the client, remove the validator call expression\n if (context.env === 'client') {\n stripMethodCall(inputValidator.callPath)\n }\n }\n\n const handlerFnPath = handler?.firstArgPath\n\n if (!handler || !handlerFnPath?.node) {\n throw codeFrameError(\n context.code,\n candidatePath.node.callee.loc!,\n `createServerFn must be called with a \"handler\" property!`,\n )\n }\n\n // Validate the handler argument is an expression (not a SpreadElement, etc.)\n if (!t.isExpression(handlerFnPath.node)) {\n throw codeFrameError(\n context.code,\n handlerFnPath.node.loc!,\n `handler() must be called with an expression, not a ${handlerFnPath.node.type}`,\n )\n }\n\n const handlerFn = handlerFnPath.node\n\n // Register function only from caller files (not provider files)\n // to avoid duplicates - provider files process the same functions\n\n if (!isProviderFile) {\n serverFnsById[functionId] = {\n functionName,\n functionId,\n filename: cleanedContextId,\n extractedFilename: canonicalExtractedFilename,\n isClientReferenced,\n }\n }\n\n if (isProviderFile) {\n // PROVIDER FILE: This is the extracted file that contains the actual implementation\n // We need to:\n // 1. Create an extractedFn that calls __executeServer\n // 2. Modify .handler() to pass (extractedFn, serverFn) - two arguments\n //\n // Expected output format:\n // const extractedFn = createServerRpc({id, name, filename}, (opts) => varName.__executeServer(opts));\n // const varName = createServerFn().handler(extractedFn, originalHandler);\n\n // Build the arrow function: (opts) => varName.__executeServer(opts)\n // The signal parameter is passed through to allow abort signal propagation\n const executeServerArrowFn = t.arrowFunctionExpression(\n [t.identifier('opts')],\n t.callExpression(\n t.memberExpression(\n t.identifier(existingVariableName),\n t.identifier('__executeServer'),\n ),\n [t.identifier('opts')],\n ),\n )\n\n // Build the serverFnMeta object\n const serverFnMeta = buildServerFnMetaObject(\n functionId,\n existingVariableName,\n relativeFilename,\n )\n\n // Generate the replacement using pre-compiled template\n const extractedFnInit = generateProviderRpcStub(\n serverFnMeta,\n executeServerArrowFn,\n )\n\n // Build the extracted function statement\n const extractedFnStatement = t.variableDeclaration('const', [\n t.variableDeclarator(t.identifier(functionName), extractedFnInit),\n ])\n\n // Find the variable declaration statement containing our createServerFn\n const variableDeclaration = candidatePath.parentPath.parentPath\n if (!variableDeclaration.isVariableDeclaration()) {\n throw new Error(\n 'Expected createServerFn to be in a VariableDeclaration',\n )\n }\n\n // Insert the extracted function statement before the variable declaration\n variableDeclaration.insertBefore(extractedFnStatement)\n\n // Modify the .handler() call to pass two arguments: (extractedFn, serverFn)\n // The handlerFnPath.node contains the original serverFn\n const extractedFnIdentifier = t.identifier(functionName)\n const serverFnNode = t.cloneNode(handlerFn, true)\n\n // Replace handler's arguments with [extractedFn, serverFn]\n handler.callPath.node.arguments = [extractedFnIdentifier, serverFnNode]\n\n // Only export the extracted handler (e.g., myFn_createServerFn_handler)\n // The manifest and all import paths only look up this suffixed name.\n // The original variable (e.g., myFn) stays in the file but is not exported\n // since it's only used internally.\n exportNames.add(functionName)\n } else {\n // CALLER FILE: This file calls the server function but doesn't contain the implementation\n // We need to:\n // 1. Remove the handler function body (it will be in the provider file)\n // 2. Replace the handler argument with an RPC stub\n //\n // IMPORTANT: We must keep the createServerFn().handler(extractedFn) structure\n // so that the client middleware chain can unwrap the {result, error, context} response.\n //\n // Expected output format:\n // const myFn = createServerFn().handler(createClientRpc(\"id\"))\n // or\n // const myFn = createServerFn().handler(createSsrRpc(\"id\"))\n\n // If the handler function is an identifier, we need to remove the bound function\n // from the file since it won't be needed\n if (t.isIdentifier(handlerFn)) {\n const binding = handlerFnPath.scope.getBinding(handlerFn.name)\n if (binding) {\n binding.path.remove()\n }\n }\n\n // Generate the RPC stub using pre-compiled templates\n // Note: Caller files only pass functionId, not the full serverFnMeta\n const rpcStub = generateCallerRpcStub(functionId, envConfig)\n\n // Replace ONLY the handler argument with the RPC stub\n // Keep the createServerFn().handler() wrapper intact for client middleware\n handlerFnPath.replaceWith(rpcStub)\n }\n }\n\n // For provider files, add exports for all extracted functions\n if (isProviderFile) {\n // Remove all existing exports first\n safeRemoveExports(context.ast)\n\n // Export all server function related variables from exportNames\n // These were populated by handleCreateServerFn:\n // 1. Extracted handlers: const fn_createServerFn_handler = createServerRpc(...)\n // 2. Original variables: const fn = createServerFn().handler(...)\n if (exportNames.size > 0) {\n context.ast.program.body.push(\n t.exportNamedDeclaration(\n undefined,\n Array.from(exportNames).map((name) =>\n t.exportSpecifier(t.identifier(name), t.identifier(name)),\n ),\n ),\n )\n }\n\n if (context.mode === 'dev') {\n context.ast.program.body.push(...providerHmrAcceptTemplate())\n }\n }\n\n // Notify about discovered functions (only for non-provider files)\n if (!isProviderFile && hasKeys(serverFnsById) && context.onServerFnsById) {\n context.onServerFnsById(serverFnsById)\n }\n\n // Add runtime import using cached AST node\n const runtimeCode = getCachedRuntimeCode(\n context.framework,\n envConfig.runtimeCodeType,\n )\n context.ast.program.body.unshift(t.cloneNode(runtimeCode))\n}\n\n/**\n * Makes an identifier safe for use as a JavaScript identifier.\n */\nfunction makeIdentifierSafe(identifier: string): string {\n return identifier\n .replace(/[^a-zA-Z0-9_$]/g, '_') // Replace unsafe chars with underscore\n .replace(/^[0-9]/, '_$&') // Prefix leading number with underscore\n .replace(/^\\$/, '_$') // Prefix leading $ with underscore\n .replace(/_{2,}/g, '_') // Collapse multiple underscores\n .replace(/^_|_$/g, '') // Trim leading/trailing underscores\n}\n\n/**\n * Increments the version number suffix on a function name.\n */\nfunction incrementFunctionNameVersion(functionName: string): string {\n const [realReferenceName, count] = functionName.split(/_(\\d+)$/)\n const resolvedCount = Number(count || '0')\n const suffix = `_${resolvedCount + 1}`\n return makeIdentifierSafe(realReferenceName!) + suffix\n}\n\n/**\n * Removes all exports from the AST while preserving the declarations.\n * Used for provider files where we want to re-export only the server functions.\n */\nfunction safeRemoveExports(ast: t.File): void {\n ast.program.body = ast.program.body.flatMap((node) => {\n if (\n t.isExportNamedDeclaration(node) ||\n t.isExportDefaultDeclaration(node)\n ) {\n if (\n t.isFunctionDeclaration(node.declaration) ||\n t.isClassDeclaration(node.declaration) ||\n t.isVariableDeclaration(node.declaration)\n ) {\n // do not remove export if it is an anonymous function / class,\n // otherwise this would produce a syntax error\n if (\n t.isFunctionDeclaration(node.declaration) ||\n t.isClassDeclaration(node.declaration)\n ) {\n if (!node.declaration.id) {\n return node\n }\n }\n return node.declaration\n } else if (node.declaration === null) {\n // remove e.g. `export { RouteComponent as component }`\n return []\n }\n }\n return node\n })\n}\n"],"mappings":";;;;;;AAQA,IAAM,2BAA2B;AAEjC,IAAM,4BAA4B,MAAM,SAAS,WAC/C;;;;;;;GAQA,EACE,oBAAoB,OACrB,CACF;AAOD,IAAM,oBAAoB,MAAM,SAAS,WACvC,4CACD;AAGD,IAAM,oBAAoB,MAAM,SAAS,WACvC,kCACD;AAGD,IAAM,yBAAyB,MAAM,SAAS,WAC5C,+BACD;AAQD,IAAM,mCAAmB,IAAI,KAG1B;AAEH,SAAS,qBACP,WACA,MACa;CACb,IAAI,QAAQ,iBAAiB,IAAI,UAAU;AAC3C,KAAI,CAAC,OAAO;AACV,UAAQ;GACN,UAAU,MAAM,SAAS,IACvB,8CAA8C,UAAU,qBACxD,EAAE,oBAAoB,OAAO,CAC9B;GACD,QAAQ,MAAM,SAAS,IACrB,8CAA8C,UAAU,qBACxD,EAAE,oBAAoB,OAAO,CAC9B;GACD,KAAK,MAAM,SAAS,IAClB,2CAA2C,UAAU,kBACrD,EAAE,oBAAoB,OAAO,CAC9B;GACF;AACD,mBAAiB,IAAI,WAAW,MAAM;;AAExC,QAAO,MAAM;;;;;AAiBf,SAAS,aACP,SACA,gBACW;CACX,MAAM,EAAE,QAAQ;AAEhB,KAAI,eACF,QAAO;EACL,qBAAqB;EACrB,iBAAiB;EAClB;AAGH,KAAI,QAAQ,SACV,QAAO;EACL,qBAAqB;EACrB,iBAAiB;EAClB;AAIH,QAAO;EACL,qBAAqB;EACrB,iBAAiB;EAClB;;;;;;AAOH,SAAS,wBACP,YACA,cACA,UACoB;AACpB,QAAO,EAAE,iBAAiB;EACxB,EAAE,eAAe,EAAE,WAAW,KAAK,EAAE,EAAE,cAAc,WAAW,CAAC;EACjE,EAAE,eAAe,EAAE,WAAW,OAAO,EAAE,EAAE,cAAc,aAAa,CAAC;EACrE,EAAE,eAAe,EAAE,WAAW,WAAW,EAAE,EAAE,cAAc,SAAS,CAAC;EACtE,CAAC;;;;;;AAOJ,SAAS,wBACP,cACA,IACc;AACd,QAAO,kBAAkB;EACvB;EACA;EACD,CAAC;;;;;;;AAQJ,SAAS,sBACP,YACA,WACc;CACd,MAAM,oBAAoB,EAAE,cAAc,WAAW;AAErD,KAAI,UAAU,oBAAoB,SAChC,QAAO,kBAAkB,EACvB,YAAY,mBACb,CAAC;AAGJ,QAAO,uBAAuB,EAC5B,YAAY,mBACb,CAAC;;;;;;;;;;;;;;;;;;;AAoBJ,SAAgB,qBACd,YACA,SACA;AACA,KAAI,WAAW,WAAW,EACxB;CAGF,MAAM,iBAAiB,QAAQ,GAAG,SAAS,yBAAyB;CAEpE,MAAM,YAAY,aAAa,SAAS,eAAe;CAGvD,MAAM,kCAAkB,IAAI,KAAa;CAEzC,MAAM,8BAAc,IAAI,KAAa;CACrC,MAAM,gBAA0C,EAAE;CAElD,MAAM,CAAC,gBAAgB,QAAQ,GAAG,MAAM,IAAI;CAC5C,MAAM,oBAAoB,GAAG,aAAa,GAAG;CAC7C,MAAM,mBAAmB,KAAK,SAAS,QAAQ,MAAM,aAAa;CAClE,MAAM,WAAW,QAAQ,mBAAmB;CAC5C,MAAM,mBAAmB,QAAQ,QAAQ,GAAG;AAE5C,MAAK,MAAM,aAAa,YAAY;EAClC,MAAM,EAAE,MAAM,eAAe,gBAAgB;EAC7C,MAAM,EAAE,gBAAgB,YAAY;AAGpC,MAAI,CAAC,cAAc,WAAW,sBAAsB,CAClD,OAAM,IAAI,MAAM,iDAAiD;EAInE,MAAM,qBAAqB,cAAc,WAAW;AACpD,MAAI,CAAC,EAAE,aAAa,mBAAmB,GAAG,CACxC,OAAM,eACJ,QAAQ,MACR,mBAAmB,GAAG,KACtB,sFACD;EAEH,MAAM,uBAAuB,mBAAmB,GAAG;EAInD,IAAI,eAAe,GAAG,qBAAqB;AAC3C,SAAO,gBAAgB,IAAI,aAAa,CACtC,gBAAe,6BAA6B,aAAa;AAE3D,kBAAgB,IAAI,aAAa;EAGjC,MAAM,aAAa,QAAQ,mBAAmB;GAC5C,UAAU;GACV;GACA;GACD,CAAC;EAGF,MAAM,UAAU,SAAS;EAMzB,MAAM,qBACJ,UAAU,uBACV,CAAC,CAAC,WACF,UAAU,oBAAoB;EAGhC,MAAM,6BACJ,SAAS,qBAAqB;AAGhC,MAAI,gBAAgB;AAGlB,OAAI,CAFyB,eAAe,SAAS,KAAK,UAAU,GAGlE,OAAM,IAAI,MACR,qEACD;AAIH,OAAI,QAAQ,QAAQ,SAClB,iBAAgB,eAAe,SAAS;;EAI5C,MAAM,gBAAgB,SAAS;AAE/B,MAAI,CAAC,WAAW,CAAC,eAAe,KAC9B,OAAM,eACJ,QAAQ,MACR,cAAc,KAAK,OAAO,KAC1B,2DACD;AAIH,MAAI,CAAC,EAAE,aAAa,cAAc,KAAK,CACrC,OAAM,eACJ,QAAQ,MACR,cAAc,KAAK,KACnB,sDAAsD,cAAc,KAAK,OAC1E;EAGH,MAAM,YAAY,cAAc;AAKhC,MAAI,CAAC,eACH,eAAc,cAAc;GAC1B;GACA;GACA,UAAU;GACV,mBAAmB;GACnB;GACD;AAGH,MAAI,gBAAgB;GAYlB,MAAM,uBAAuB,EAAE,wBAC7B,CAAC,EAAE,WAAW,OAAO,CAAC,EACtB,EAAE,eACA,EAAE,iBACA,EAAE,WAAW,qBAAqB,EAClC,EAAE,WAAW,kBAAkB,CAChC,EACD,CAAC,EAAE,WAAW,OAAO,CAAC,CACvB,CACF;GAUD,MAAM,kBAAkB,wBAPH,wBACnB,YACA,sBACA,iBACD,EAKC,qBACD;GAGD,MAAM,uBAAuB,EAAE,oBAAoB,SAAS,CAC1D,EAAE,mBAAmB,EAAE,WAAW,aAAa,EAAE,gBAAgB,CAClE,CAAC;GAGF,MAAM,sBAAsB,cAAc,WAAW;AACrD,OAAI,CAAC,oBAAoB,uBAAuB,CAC9C,OAAM,IAAI,MACR,yDACD;AAIH,uBAAoB,aAAa,qBAAqB;GAItD,MAAM,wBAAwB,EAAE,WAAW,aAAa;GACxD,MAAM,eAAe,EAAE,UAAU,WAAW,KAAK;AAGjD,WAAQ,SAAS,KAAK,YAAY,CAAC,uBAAuB,aAAa;AAMvE,eAAY,IAAI,aAAa;SACxB;AAgBL,OAAI,EAAE,aAAa,UAAU,EAAE;IAC7B,MAAM,UAAU,cAAc,MAAM,WAAW,UAAU,KAAK;AAC9D,QAAI,QACF,SAAQ,KAAK,QAAQ;;GAMzB,MAAM,UAAU,sBAAsB,YAAY,UAAU;AAI5D,iBAAc,YAAY,QAAQ;;;AAKtC,KAAI,gBAAgB;AAElB,oBAAkB,QAAQ,IAAI;AAM9B,MAAI,YAAY,OAAO,EACrB,SAAQ,IAAI,QAAQ,KAAK,KACvB,EAAE,uBACA,KAAA,GACA,MAAM,KAAK,YAAY,CAAC,KAAK,SAC3B,EAAE,gBAAgB,EAAE,WAAW,KAAK,EAAE,EAAE,WAAW,KAAK,CAAC,CAC1D,CACF,CACF;AAGH,MAAI,QAAQ,SAAS,MACnB,SAAQ,IAAI,QAAQ,KAAK,KAAK,GAAG,2BAA2B,CAAC;;AAKjE,KAAI,CAAC,kBAAkB,QAAQ,cAAc,IAAI,QAAQ,gBACvD,SAAQ,gBAAgB,cAAc;CAIxC,MAAM,cAAc,qBAClB,QAAQ,WACR,UAAU,gBACX;AACD,SAAQ,IAAI,QAAQ,KAAK,QAAQ,EAAE,UAAU,YAAY,CAAC;;;;;AAM5D,SAAS,mBAAmB,YAA4B;AACtD,QAAO,WACJ,QAAQ,mBAAmB,IAAI,CAC/B,QAAQ,UAAU,MAAM,CACxB,QAAQ,OAAO,KAAK,CACpB,QAAQ,UAAU,IAAI,CACtB,QAAQ,UAAU,GAAG;;;;;AAM1B,SAAS,6BAA6B,cAA8B;CAClE,MAAM,CAAC,mBAAmB,SAAS,aAAa,MAAM,UAAU;CAEhE,MAAM,SAAS,IADO,OAAO,SAAS,IAAI,GACP;AACnC,QAAO,mBAAmB,kBAAmB,GAAG;;;;;;AAOlD,SAAS,kBAAkB,KAAmB;AAC5C,KAAI,QAAQ,OAAO,IAAI,QAAQ,KAAK,SAAS,SAAS;AACpD,MACE,EAAE,yBAAyB,KAAK,IAChC,EAAE,2BAA2B,KAAK;OAGhC,EAAE,sBAAsB,KAAK,YAAY,IACzC,EAAE,mBAAmB,KAAK,YAAY,IACtC,EAAE,sBAAsB,KAAK,YAAY,EACzC;AAGA,QACE,EAAE,sBAAsB,KAAK,YAAY,IACzC,EAAE,mBAAmB,KAAK,YAAY;SAElC,CAAC,KAAK,YAAY,GACpB,QAAO;;AAGX,WAAO,KAAK;cACH,KAAK,gBAAgB,KAE9B,QAAO,EAAE;;AAGb,SAAO;GACP"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/start-plugin-core",
|
|
3
|
-
"version": "1.169.
|
|
3
|
+
"version": "1.169.8",
|
|
4
4
|
"description": "Modern and scalable routing for React applications",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -86,12 +86,12 @@
|
|
|
86
86
|
"vitefu": "^1.1.1",
|
|
87
87
|
"xmlbuilder2": "^4.0.3",
|
|
88
88
|
"zod": "^3.24.2",
|
|
89
|
-
"@tanstack/router-core": "1.
|
|
90
|
-
"@tanstack/router-generator": "1.166.
|
|
91
|
-
"@tanstack/router-plugin": "1.167.
|
|
89
|
+
"@tanstack/router-core": "1.169.0",
|
|
90
|
+
"@tanstack/router-generator": "1.166.38",
|
|
91
|
+
"@tanstack/router-plugin": "1.167.30",
|
|
92
92
|
"@tanstack/router-utils": "1.161.7",
|
|
93
|
-
"@tanstack/start-client-core": "1.167.
|
|
94
|
-
"@tanstack/start-server-core": "1.167.
|
|
93
|
+
"@tanstack/start-client-core": "1.167.22",
|
|
94
|
+
"@tanstack/start-server-core": "1.167.24"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
97
|
"@rsbuild/core": "^2.0.1",
|
package/src/rsbuild/plugin.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readdirSync, realpathSync, statSync } from 'node:fs'
|
|
2
2
|
import { dirname, join, resolve } from 'node:path'
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { hasKeys } from '@tanstack/router-core'
|
|
4
5
|
import { joinURL } from 'ufo'
|
|
5
6
|
import {
|
|
6
7
|
applyResolvedBaseAndOutput,
|
|
@@ -465,7 +466,7 @@ export function tanStackStartRsbuild(
|
|
|
465
466
|
stage: -10,
|
|
466
467
|
},
|
|
467
468
|
async (compilation: RspackCompilationExtended) => {
|
|
468
|
-
if (
|
|
469
|
+
if (!hasKeys(serverFnsById)) {
|
|
469
470
|
return
|
|
470
471
|
}
|
|
471
472
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as t from '@babel/types'
|
|
2
2
|
import babel from '@babel/core'
|
|
3
|
+
import { hasKeys } from '@tanstack/router-core'
|
|
3
4
|
import path from 'pathe'
|
|
4
5
|
import { cleanId, codeFrameError, stripMethodCall } from './utils'
|
|
5
6
|
import type { CompilationContext, RewriteCandidate, ServerFn } from './types'
|
|
@@ -435,11 +436,7 @@ export function handleCreateServerFn(
|
|
|
435
436
|
}
|
|
436
437
|
|
|
437
438
|
// Notify about discovered functions (only for non-provider files)
|
|
438
|
-
if (
|
|
439
|
-
!isProviderFile &&
|
|
440
|
-
Object.keys(serverFnsById).length > 0 &&
|
|
441
|
-
context.onServerFnsById
|
|
442
|
-
) {
|
|
439
|
+
if (!isProviderFile && hasKeys(serverFnsById) && context.onServerFnsById) {
|
|
443
440
|
context.onServerFnsById(serverFnsById)
|
|
444
441
|
}
|
|
445
442
|
|