diff --git a/docs/start/framework/react/guide/server-functions.md b/docs/start/framework/react/guide/server-functions.md
index 3f6180d81b1..a94c0558180 100644
--- a/docs/start/framework/react/guide/server-functions.md
+++ b/docs/start/framework/react/guide/server-functions.md
@@ -228,20 +228,18 @@ Cache server function results at build time for static generation. See [Static S
Handle request cancellation with `AbortSignal` for long-running operations.
-### Function ID generation
+### Function ID generation for production build
Server functions are addressed by a generated, stable function ID under the hood. These IDs are embedded into the client/SSR builds and used by the server to locate and import the correct module at runtime.
-Defaults:
-
-- In development, IDs are URL-safe strings derived from `${filename}--${functionName}` to aid debugging.
-- In production, IDs are SHA256 hashes of the same seed to keep bundles compact and avoid leaking file paths.
-- If two server functions end up with the same ID (including when using a custom generator), the system de-duplicates by appending an incrementing suffix like `_1`, `_2`, etc.
-- IDs are stable for a given file/function tuple for the lifetime of the process (hot updates keep the same mapping).
+By default, IDs are SHA256 hashes of the same seed to keep bundles compact and avoid leaking file paths.
+If two server functions end up with the same ID (including when using a custom generator), the system de-duplicates by appending an incrementing suffix like `_1`, `_2`, etc.
Customization:
-You can customize function ID generation by providing a `generateFunctionId` function when configuring the TanStack Start Vite plugin.
+You can customize function ID generation for the production build by providing a `generateFunctionId` function when configuring the TanStack Start Vite plugin.
+
+Prefer deterministic inputs (filename + functionName) so IDs remain stable between builds.
Please note that this customization is **experimental** und subject to change.
@@ -259,8 +257,10 @@ export default defineConfig({
serverFns: {
generateFunctionId: ({ filename, functionName }) => {
// Return a custom ID string. If you return undefined, the default is used.
- // For example, always hash (even in dev):
- // return createHash('sha256').update(`${filename}--${functionName}`).digest('hex')
+ return crypto
+ .createHash('sha1')
+ .update(`${filename}--${functionName}`)
+ .digest('hex')
return undefined
},
},
@@ -270,12 +270,6 @@ export default defineConfig({
})
```
-Tips:
-
-- Prefer deterministic inputs (filename + functionName) so IDs remain stable between builds.
-- If you don’t want file paths in dev IDs, return a hash in all environments.
-- Ensure the returned ID is **URL-safe**.
-
---
> **Note**: Server functions use a compilation process that extracts server code from client bundles while maintaining seamless calling patterns. On the client, calls become `fetch` requests to the server.
diff --git a/packages/directive-functions-plugin/src/compilers.ts b/packages/directive-functions-plugin/src/compilers.ts
index dec7d4ba8c2..e01c80a5458 100644
--- a/packages/directive-functions-plugin/src/compilers.ts
+++ b/packages/directive-functions-plugin/src/compilers.ts
@@ -26,6 +26,7 @@ export type SupportedFunctionPath =
export type GenerateFunctionIdFn = (opts: {
filename: string
functionName: string
+ extractedFilename: string
}) => string
export type ReplacerFn = (opts: {
@@ -46,7 +47,6 @@ export type CompileDirectivesOpts = ParseAstOptions & {
}) => string
generateFunctionId: GenerateFunctionIdFn
replacer: ReplacerFn
- // devSplitImporter: string
filename: string
root: string
}
@@ -493,7 +493,8 @@ export function findDirectives(
const relativeFilename = path.relative(opts.root, baseFilename)
const functionId = opts.generateFunctionId({
filename: relativeFilename,
- functionName: functionName,
+ functionName,
+ extractedFilename,
})
// If a replacer is provided, replace the function with the replacer
if (opts.replacer) {
diff --git a/packages/directive-functions-plugin/src/index.ts b/packages/directive-functions-plugin/src/index.ts
index ff86b2f6baa..1d540e1c42b 100644
--- a/packages/directive-functions-plugin/src/index.ts
+++ b/packages/directive-functions-plugin/src/index.ts
@@ -39,25 +39,6 @@ export type DirectiveFunctionsViteOptions = Pick<
const createDirectiveRx = (directive: string) =>
new RegExp(`"${directive}"|'${directive}'`, 'gm')
-export function TanStackDirectiveFunctionsPlugin(
- opts: DirectiveFunctionsViteOptions,
-): Plugin {
- let root: string = process.cwd()
-
- const directiveRx = createDirectiveRx(opts.directive)
-
- return {
- name: 'tanstack-start-directive-vite-plugin',
- enforce: 'pre',
- configResolved: (config) => {
- root = config.root
- },
- transform(code, id) {
- return transformCode({ ...opts, code, id, directiveRx, root })
- },
- }
-}
-
export type DirectiveFunctionsVitePluginEnvOptions = Pick<
CompileDirectivesOpts,
'directive' | 'directiveLabel'
@@ -122,7 +103,6 @@ export function TanStackDirectiveFunctionsPluginEnv(
...envOptions,
code,
id,
- directiveRx,
root,
})
},
@@ -133,7 +113,6 @@ export function TanStackDirectiveFunctionsPluginEnv(
function transformCode({
code,
id,
- directiveRx,
envLabel,
directive,
directiveLabel,
@@ -145,17 +124,12 @@ function transformCode({
}: DirectiveFunctionsViteOptions & {
code: string
id: string
- directiveRx: RegExp
root: string
}) {
const url = pathToFileURL(id)
url.searchParams.delete('v')
id = fileURLToPath(url).replace(/\\/g, '/')
- if (!code.match(directiveRx)) {
- return null
- }
-
if (debug) console.info(`${envLabel}: Compiling Directives: `, id)
const { compiledResult, directiveFnsById, isDirectiveSplitParam } =
diff --git a/packages/server-functions-plugin/README.md b/packages/server-functions-plugin/README.md
index 6c0c32bdcc9..deb185341d4 100644
--- a/packages/server-functions-plugin/README.md
+++ b/packages/server-functions-plugin/README.md
@@ -7,40 +7,29 @@
Create a new instance of the plugin with the following options:
```ts
-const TanStackServerFnsPlugin = createTanStackServerFnPlugin({
- // This is the ID (virtual module) that will be made available to look up
- // and import our server function manifest and resolve its modules.
+TanStackServerFnPlugin({
+ // This is the ID that will be available to look up and import
+ // our server function manifest and resolve its module
manifestVirtualImportId: 'tanstack:server-fn-manifest',
+ generateFunctionId: startPluginOpts?.serverFns?.generateFunctionId,
client: {
getRuntimeCode: () =>
- `import { createClientRpc } from '@tanstack/react-start/client-runtime'`,
- replacer: (opts) => `createClientRpc(${JSON.stringify(opts.functionId)})`,
- },
- ssr: {
- getRuntimeCode: () =>
- `import { createSsrRpc } from '@tanstack/react-start/ssr-runtime'`,
- replacer: (opts) => `createSsrRpc(${JSON.stringify(opts.functionId)})`,
+ `import { createClientRpc } from '@tanstack/${corePluginOpts.framework}-start/client-rpc'`,
+ replacer: (d) => `createClientRpc('${d.functionId}')`,
+ envName: 'client',
},
server: {
getRuntimeCode: () =>
- `import { createServerRpc } from '@tanstack/react-start/server-runtime'`,
- replacer: (opts) =>
- `createServerRpc(${JSON.stringify(opts.functionId)}, ${opts.fn})`,
+ `import { createServerRpc } from '@tanstack/${corePluginOpts.framework}-start/server-rpc'`,
+ replacer: (d) => `createServerRpc('${d.functionId}', ${d.fn})`,
+ envName: 'ssr',
},
-})
-```
-
-Then you can inject the plugin into the appropriate vite config plugin arrays:
-
-```ts
-clientVitePlugins: [TanStackServerFnsPlugin.client]
-ssrVitePlugins: [TanStackServerFnsPlugin.ssr]
-serverVitePlugins: [TanStackServerFnsPlugin.server]
+}),
```
## Providing the wrapper implementations
-Each runtime replacement should be implemented by your framework. Generally, on the client and SSR runtimes, you'll end up using a `fetch` call to call the server function your desired endpoint, like this:
+Each runtime replacement should be implemented by your framework. Generally, on the client runtime, you'll end up using a `fetch` call to call the server function your desired endpoint, like this:
```ts
function createClientRpc(functionId: string) {
@@ -71,20 +60,16 @@ function createClientRpc(functionId: string) {
In your server handler, you can import the manifest and use it to look up and dynamically import the server function you want to call.
```ts
-import serverFnManifest from 'tanstack:server-fn-manifest'
+import { getServerFnById } from 'tanstack:server-fn-manifest'
export const handler = async (req: Request) => {
const functionId = req.url.split('/').pop()
invariant(functionId, 'No function ID provided')
- const fnInfo = serverFnManifest[functionId]
- invariant(fn, `Server function ${functionId} not found`)
-
- const fnModule = await fnInfo.importer()
- invariant(fnModule, `Server function ${functionId} could not be imported`)
+ const serverFn = await getServerFnById(functionId)
const args = await req.json()
- return await fnModule(...args)
+ return await serverFn(...args)
}
```
diff --git a/packages/server-functions-plugin/package.json b/packages/server-functions-plugin/package.json
index 0c6665c1991..a6b1211760e 100644
--- a/packages/server-functions-plugin/package.json
+++ b/packages/server-functions-plugin/package.json
@@ -41,17 +41,12 @@
},
"type": "module",
"types": "dist/esm/index.d.ts",
- "main": "dist/cjs/index.cjs",
"module": "dist/esm/index.js",
"exports": {
".": {
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
- },
- "require": {
- "types": "./dist/cjs/index.d.cts",
- "default": "./dist/cjs/index.cjs"
}
},
"./package.json": "./package.json"
diff --git a/packages/server-functions-plugin/src/index.ts b/packages/server-functions-plugin/src/index.ts
index 4edee08bd03..97ba71a0bdd 100644
--- a/packages/server-functions-plugin/src/index.ts
+++ b/packages/server-functions-plugin/src/index.ts
@@ -1,22 +1,18 @@
+///
import crypto from 'node:crypto'
-import {
- TanStackDirectiveFunctionsPlugin,
- TanStackDirectiveFunctionsPluginEnv,
-} from '@tanstack/directive-functions-plugin'
-import type { DevEnvironment, Plugin, ViteDevServer } from 'vite'
+import { TanStackDirectiveFunctionsPluginEnv } from '@tanstack/directive-functions-plugin'
+import type { DevEnvironment, Plugin } from 'vite'
import type {
DirectiveFn,
GenerateFunctionIdFn,
ReplacerFn,
} from '@tanstack/directive-functions-plugin'
-export type CreateRpcFn = (functionId: string, splitImportFn?: string) => any
-
export type GenerateFunctionIdFnOptional = (
- opts: Parameters[0],
+ opts: Omit[0], 'extractedFilename'>,
) => string | undefined
-export type ServerFnPluginOpts = {
+export type TanStackServerFnPluginOpts = {
/**
* The virtual import ID that will be used to import the server function manifest.
* This virtual import ID will be used in the server build to import the manifest
@@ -25,7 +21,6 @@ export type ServerFnPluginOpts = {
manifestVirtualImportId: string
generateFunctionId?: GenerateFunctionIdFnOptional
client: ServerFnPluginEnvOpts
- ssr: ServerFnPluginEnvOpts
server: ServerFnPluginEnvOpts
}
@@ -39,123 +34,8 @@ const debug =
process.env.TSR_VITE_DEBUG &&
['true', 'server-functions-plugin'].includes(process.env.TSR_VITE_DEBUG)
-export function createTanStackServerFnPlugin(opts: ServerFnPluginOpts): {
- client: Array
- ssr: Array
- server: Array
-} {
- const directiveFnsById: Record = {}
- let viteDevServer: ViteDevServer | undefined
-
- const onDirectiveFnsById = buildOnDirectiveFnsByIdCallback({
- directiveFnsById,
- manifestVirtualImportId: opts.manifestVirtualImportId,
- invalidateModule: (id) => {
- if (viteDevServer) {
- const mod = viteDevServer.moduleGraph.getModuleById(id)
- if (mod) {
- if (debug) {
- console.info(`invalidating module ${JSON.stringify(mod.id)}`)
- }
- viteDevServer.moduleGraph.invalidateModule(mod)
- }
- }
- },
- })
- const generateFunctionId = buildGenerateFunctionId(
- (generateFunctionIdOpts, next) =>
- next(
- Boolean(viteDevServer),
- opts.generateFunctionId?.(generateFunctionIdOpts),
- ),
- )
-
- const directive = 'use server'
- const directiveLabel = 'Server Function'
-
- return {
- client: [
- // The client plugin is used to compile the client directives
- // and save them so we can create a manifest
- TanStackDirectiveFunctionsPlugin({
- envLabel: 'Client',
- directive,
- directiveLabel,
- getRuntimeCode: opts.client.getRuntimeCode,
- generateFunctionId,
- replacer: opts.client.replacer,
- onDirectiveFnsById,
- }),
- ],
- ssr: [
- // The SSR plugin is used to compile the server directives
- TanStackDirectiveFunctionsPlugin({
- envLabel: 'SSR',
- directive,
- directiveLabel,
- getRuntimeCode: opts.ssr.getRuntimeCode,
- generateFunctionId,
- replacer: opts.ssr.replacer,
- onDirectiveFnsById,
- }),
- ],
- server: [
- {
- // On the server, we need to be able to read the server-function manifest from the client build.
- // This is likely used in the handler for server functions, so we can find the server function
- // by its ID, import it, and call it.
- name: 'tanstack-start-server-fn-vite-plugin-manifest-server',
- enforce: 'pre',
- configureServer(server) {
- viteDevServer = server
- },
- resolveId(id) {
- if (id === opts.manifestVirtualImportId) {
- return resolveViteId(id)
- }
-
- return undefined
- },
- load(id) {
- if (id !== resolveViteId(opts.manifestVirtualImportId)) {
- return undefined
- }
-
- const manifestWithImports = `
- export default {${Object.entries(directiveFnsById)
- .map(
- ([id, fn]: any) =>
- `'${id}': {
- functionName: '${fn.functionName}',
- importer: () => import(${JSON.stringify(fn.extractedFilename)})
- }`,
- )
- .join(',')}}`
-
- return manifestWithImports
- },
- },
- // On the server, we need to compile the server functions
- // so they can be called by other server functions.
- // This is also where we split the server function into a separate file
- // so we can load them on demand in the worker.
- TanStackDirectiveFunctionsPlugin({
- envLabel: 'Server',
- directive,
- directiveLabel,
- getRuntimeCode: opts.server.getRuntimeCode,
- generateFunctionId,
- replacer: opts.server.replacer,
- onDirectiveFnsById,
- }),
- ],
- }
-}
-
-export type TanStackServerFnPluginEnvOpts = Omit
-
-export function TanStackServerFnPluginEnv(
- _opts: TanStackServerFnPluginEnvOpts,
+export function TanStackServerFnPlugin(
+ _opts: TanStackServerFnPluginOpts,
): Array {
const opts = {
..._opts,
@@ -172,35 +52,75 @@ export function TanStackServerFnPluginEnv(
const directiveFnsById: Record = {}
let serverDevEnv: DevEnvironment | undefined
- const onDirectiveFnsById = buildOnDirectiveFnsByIdCallback({
- directiveFnsById,
- manifestVirtualImportId: opts.manifestVirtualImportId,
- invalidateModule: (id) => {
- if (serverDevEnv) {
- const mod = serverDevEnv.moduleGraph.getModuleById(id)
- if (mod) {
- if (debug) {
- console.info(
- `invalidating module ${JSON.stringify(mod.id)} in server environment`,
- )
- }
- serverDevEnv.moduleGraph.invalidateModule(mod)
- }
- }
- },
- })
+ const onDirectiveFnsById = (d: Record) => {
+ if (serverDevEnv) {
+ return
+ }
+ if (debug) {
+ console.info(`onDirectiveFnsById received: `, d)
+ }
+ Object.assign(directiveFnsById, d)
+ if (debug) {
+ console.info(`directiveFnsById after update: `, directiveFnsById)
+ }
+ }
- const generateFunctionId = buildGenerateFunctionId(
- (generateFunctionIdOpts, next) =>
- next(
- Boolean(serverDevEnv),
- opts.generateFunctionId?.(generateFunctionIdOpts),
- ),
- )
+ const entryIdToFunctionId = new Map()
+ const functionIds = new Set()
+ const generateFunctionId: GenerateFunctionIdFn = ({
+ extractedFilename,
+ functionName,
+ filename,
+ }) => {
+ if (serverDevEnv) {
+ const serverFn: {
+ file: string
+ export: string
+ } = {
+ file: extractedFilename,
+ export: functionName,
+ }
+ const base64 = Buffer.from(JSON.stringify(serverFn), 'utf8').toString(
+ 'base64url',
+ )
+ return base64
+ }
+
+ // production build allows to override the function ID generation
+ const entryId = `${filename}--${functionName}`
+ let functionId = entryIdToFunctionId.get(entryId)
+ if (functionId === undefined) {
+ if (opts.generateFunctionId) {
+ functionId = opts.generateFunctionId({
+ functionName,
+ filename,
+ })
+ }
+ if (!functionId) {
+ functionId = crypto.createHash('sha256').update(entryId).digest('hex')
+ }
+ // Deduplicate in case the generated id conflicts with an existing id
+ if (functionIds.has(functionId)) {
+ let deduplicatedId
+ let iteration = 0
+ do {
+ deduplicatedId = `${functionId}_${++iteration}`
+ } while (functionIds.has(deduplicatedId))
+ functionId = deduplicatedId
+ }
+ entryIdToFunctionId.set(entryId, functionId)
+ functionIds.add(functionId)
+ }
+ return functionId
+ }
const directive = 'use server'
const directiveLabel = 'Server Function'
+ const resolvedManifestVirtualImportId = resolveViteId(
+ opts.manifestVirtualImportId,
+ )
+
return [
// The client plugin is used to compile the client directives
// and save them so we can create a manifest
@@ -240,18 +160,31 @@ export function TanStackServerFnPluginEnv(
},
resolveId: {
filter: { id: new RegExp(opts.manifestVirtualImportId) },
- handler(id) {
- return resolveViteId(id)
+ handler() {
+ return resolvedManifestVirtualImportId
},
},
load: {
- filter: { id: new RegExp(resolveViteId(opts.manifestVirtualImportId)) },
+ filter: { id: new RegExp(resolvedManifestVirtualImportId) },
handler() {
if (this.environment.name !== opts.server.envName) {
return `export default {}`
}
- const manifestWithImports = `
- export default {${Object.entries(directiveFnsById)
+
+ if (this.environment.mode !== 'build') {
+ const mod = `
+ export async function getServerFnById(id) {
+ const decoded = Buffer.from(id, 'base64url').toString('utf8')
+ const devServerFn = JSON.parse(decoded)
+ const mod = await import(/* @vite-ignore */ devServerFn.file)
+ return mod[devServerFn.export]
+ }
+ `
+ return mod
+ }
+
+ const mod = `
+ const manifest = {${Object.entries(directiveFnsById)
.map(
([id, fn]: any) =>
`'${id}': {
@@ -259,9 +192,34 @@ export function TanStackServerFnPluginEnv(
importer: () => import(${JSON.stringify(fn.extractedFilename)})
}`,
)
- .join(',')}}`
-
- return manifestWithImports
+ .join(',')}}
+ export async function getServerFnById(id) {
+ const serverFnInfo = manifest[id]
+ if (!serverFnInfo) {
+ throw new Error('Server function info not found for ' + id)
+ }
+ const fnModule = await serverFnInfo.importer()
+
+ if (!fnModule) {
+ console.info('serverFnInfo', serverFnInfo)
+ throw new Error('Server function module not resolved for ' + id)
+ }
+
+ const action = fnModule[serverFnInfo.functionName]
+
+ if (!action) {
+ console.info('serverFnInfo', serverFnInfo)
+ console.info('fnModule', fnModule)
+
+ throw new Error(
+ \`Server function module export not resolved for serverFn ID: \${id}\`,
+ )
+ }
+ return action
+ }
+ `
+
+ return mod
},
},
},
@@ -271,83 +229,3 @@ export function TanStackServerFnPluginEnv(
function resolveViteId(id: string) {
return `\0${id}`
}
-
-function makeFunctionIdUrlSafe(location: string): string {
- return location
- .replace(/[^a-zA-Z0-9-_]/g, '_') // Replace unsafe chars with underscore
- .replace(/_{2,}/g, '_') // Collapse multiple underscores
- .replace(/^_|_$/g, '') // Trim leading/trailing underscores
- .replace(/_--/g, '--') // Clean up the joiner
-}
-
-function buildGenerateFunctionId(
- delegate: (
- opts: Parameters[0],
- next: (dev: boolean, value?: string) => string,
- ) => string,
-): GenerateFunctionIdFn {
- const entryIdToFunctionId = new Map()
- const functionIds = new Set()
- return (opts) => {
- const entryId = `${opts.filename}--${opts.functionName}`
- let functionId = entryIdToFunctionId.get(entryId)
- if (functionId === undefined) {
- functionId = delegate(opts, (dev, updatedFunctionId) => {
- // If no value provided, then return the url-safe currentId on development
- // and SHA256 using the currentId as seed on production
- if (updatedFunctionId === undefined) {
- if (dev) updatedFunctionId = makeFunctionIdUrlSafe(entryId)
- else
- updatedFunctionId = crypto
- .createHash('sha256')
- .update(entryId)
- .digest('hex')
- }
- return updatedFunctionId
- })
- // Deduplicate in case the generated id conflicts with an existing id
- if (functionIds.has(functionId)) {
- let deduplicatedId
- let iteration = 0
- do {
- deduplicatedId = `${functionId}_${++iteration}`
- } while (functionIds.has(deduplicatedId))
- functionId = deduplicatedId
- }
- entryIdToFunctionId.set(entryId, functionId)
- functionIds.add(functionId)
- }
- return functionId
- }
-}
-
-function buildOnDirectiveFnsByIdCallback(opts: {
- invalidateModule: (resolvedId: string) => void
- directiveFnsById: Record
- manifestVirtualImportId: string
-}) {
- const onDirectiveFnsById = (d: Record) => {
- if (debug) {
- console.info(`onDirectiveFnsById received: `, d)
- }
-
- // do we already know all the server functions? if so, we can exit early
- // this could happen if the same file is compiled first in the client and then in the server environment
- const newKeys = Object.keys(d).filter(
- (key) => !(key in opts.directiveFnsById),
- )
- if (newKeys.length > 0) {
- // When directives are compiled, save them to `directiveFnsById`
- // This state will be used both during development to incrementally
- // look up server functions and during build/production to produce a
- // static manifest that can be read by the server build
- Object.assign(opts.directiveFnsById, d)
- if (debug) {
- console.info(`directiveFnsById after update: `, opts.directiveFnsById)
- }
-
- opts.invalidateModule(resolveViteId(opts.manifestVirtualImportId))
- }
- }
- return onDirectiveFnsById
-}
diff --git a/packages/server-functions-plugin/vite.config.ts b/packages/server-functions-plugin/vite.config.ts
index 5389f0f7391..ac06399a8f3 100644
--- a/packages/server-functions-plugin/vite.config.ts
+++ b/packages/server-functions-plugin/vite.config.ts
@@ -16,5 +16,6 @@ export default mergeConfig(
tanstackViteConfig({
entry: './src/index.ts',
srcDir: './src',
+ cjs: false,
}),
)
diff --git a/packages/start-plugin-core/src/plugin.ts b/packages/start-plugin-core/src/plugin.ts
index 304389349d8..648b812ada0 100644
--- a/packages/start-plugin-core/src/plugin.ts
+++ b/packages/start-plugin-core/src/plugin.ts
@@ -1,6 +1,6 @@
import { joinPaths } from '@tanstack/router-core'
import { VIRTUAL_MODULES } from '@tanstack/start-server-core'
-import { TanStackServerFnPluginEnv } from '@tanstack/server-functions-plugin'
+import { TanStackServerFnPlugin } from '@tanstack/server-functions-plugin'
import * as vite from 'vite'
import { crawlFrameworkPkgs } from 'vitefu'
import { join } from 'pathe'
@@ -326,11 +326,11 @@ export function TanStackStartVitePluginCore(
},
},
tanStackStartRouter(startPluginOpts, getConfig, corePluginOpts),
- // N.B. TanStackStartCompilerPlugin must be before the TanStackServerFnPluginEnv
+ // N.B. TanStackStartCompilerPlugin must be before the TanStackServerFnPlugin
startCompilerPlugin(corePluginOpts.framework),
createServerFnPlugin(corePluginOpts.framework),
- TanStackServerFnPluginEnv({
+ TanStackServerFnPlugin({
// This is the ID that will be available to look up and import
// our server function manifest and resolve its module
manifestVirtualImportId: VIRTUAL_MODULES.serverFnManifest,
diff --git a/packages/start-server-core/package.json b/packages/start-server-core/package.json
index 434f191a540..38441d4be39 100644
--- a/packages/start-server-core/package.json
+++ b/packages/start-server-core/package.json
@@ -55,6 +55,11 @@
},
"./package.json": "./package.json"
},
+ "imports": {
+ "#tanstack-start-server-fn-manifest": {
+ "default": "./dist/esm/fake-start-server-fn-manifest.js"
+ }
+ },
"sideEffects": false,
"files": [
"dist",
diff --git a/packages/start-server-core/src/fake-start-server-fn-manifest.ts b/packages/start-server-core/src/fake-start-server-fn-manifest.ts
new file mode 100644
index 00000000000..9250d5dbf13
--- /dev/null
+++ b/packages/start-server-core/src/fake-start-server-fn-manifest.ts
@@ -0,0 +1 @@
+export function getServerFnById() {}
diff --git a/packages/start-server-core/src/getServerFnById.ts b/packages/start-server-core/src/getServerFnById.ts
index d39033613eb..01a24846a3f 100644
--- a/packages/start-server-core/src/getServerFnById.ts
+++ b/packages/start-server-core/src/getServerFnById.ts
@@ -1,33 +1 @@
-import { loadVirtualModule } from './loadVirtualModule'
-import { VIRTUAL_MODULES } from './virtual-modules'
-
-export async function getServerFnById(serverFnId: string) {
- const { default: serverFnManifest } = await loadVirtualModule(
- VIRTUAL_MODULES.serverFnManifest,
- )
-
- const serverFnInfo = serverFnManifest[serverFnId]
-
- if (!serverFnInfo) {
- console.info('serverFnManifest', serverFnManifest)
- throw new Error('Server function info not found for ' + serverFnId)
- }
-
- const fnModule = await serverFnInfo.importer()
-
- if (!fnModule) {
- console.info('serverFnInfo', serverFnInfo)
- throw new Error('Server function module not resolved for ' + serverFnId)
- }
-
- const action = fnModule[serverFnInfo.functionName]
-
- if (!action) {
- console.info('serverFnInfo', serverFnInfo)
- console.info('fnModule', fnModule)
- throw new Error(
- `Server function module export not resolved for serverFn ID: ${serverFnId}`,
- )
- }
- return action
-}
+export { getServerFnById } from '#tanstack-start-server-fn-manifest'
diff --git a/packages/start-server-core/src/index.tsx b/packages/start-server-core/src/index.tsx
index 3de5b7fa604..5a4d633e5ca 100644
--- a/packages/start-server-core/src/index.tsx
+++ b/packages/start-server-core/src/index.tsx
@@ -9,8 +9,6 @@ export {
} from '@tanstack/router-core/ssr/server'
export type { HandlerCallback } from '@tanstack/router-core/ssr/server'
-export { handleServerAction } from './server-functions-handler'
-
export * from './request-response'
export * from './virtual-modules'
diff --git a/packages/start-server-core/src/loadVirtualModule.ts b/packages/start-server-core/src/loadVirtualModule.ts
index 16399f13c59..a901e13faa1 100644
--- a/packages/start-server-core/src/loadVirtualModule.ts
+++ b/packages/start-server-core/src/loadVirtualModule.ts
@@ -11,8 +11,6 @@ export async function loadVirtualModule(
switch (id) {
case VIRTUAL_MODULES.startManifest:
return (await import('tanstack-start-manifest:v')) as any
- case VIRTUAL_MODULES.serverFnManifest:
- return (await import('tanstack-start-server-fn-manifest:v')) as any
case VIRTUAL_MODULES.injectedHeadScripts:
return (await import('tanstack-start-injected-head-scripts:v')) as any
default:
diff --git a/packages/start-server-core/src/tanstack-start.d.ts b/packages/start-server-core/src/tanstack-start.d.ts
index e6dc8a53ec9..b760a9d2c97 100644
--- a/packages/start-server-core/src/tanstack-start.d.ts
+++ b/packages/start-server-core/src/tanstack-start.d.ts
@@ -10,18 +10,9 @@ declare module 'tanstack-start-route-tree:v' {
export const routeTree: AnyRoute | undefined
}
-declare module 'tanstack-start-server-fn-manifest:v' {
- const serverFnManifest: Record<
- string,
- {
- importer: () => Promise<
- Record) => Promise> | undefined
- >
- functionName: string
- }
- >
-
- export default serverFnManifest
+declare module '#tanstack-start-server-fn-manifest' {
+ type ServerFn = (...args: Array) => Promise
+ export function getServerFnById(id: string): Promise
}
declare module 'tanstack-start-injected-head-scripts:v' {
diff --git a/packages/start-server-core/src/virtual-modules.ts b/packages/start-server-core/src/virtual-modules.ts
index 7869e81ccf1..865af7a43fe 100644
--- a/packages/start-server-core/src/virtual-modules.ts
+++ b/packages/start-server-core/src/virtual-modules.ts
@@ -2,12 +2,11 @@
export const VIRTUAL_MODULES = {
startManifest: 'tanstack-start-manifest:v',
- serverFnManifest: 'tanstack-start-server-fn-manifest:v',
+ serverFnManifest: '#tanstack-start-server-fn-manifest',
injectedHeadScripts: 'tanstack-start-injected-head-scripts:v',
} as const
export type VirtualModules = {
[VIRTUAL_MODULES.startManifest]: typeof import('tanstack-start-manifest:v')
- [VIRTUAL_MODULES.serverFnManifest]: typeof import('tanstack-start-server-fn-manifest:v')
[VIRTUAL_MODULES.injectedHeadScripts]: typeof import('tanstack-start-injected-head-scripts:v')
}
diff --git a/packages/start-server-core/vite.config.ts b/packages/start-server-core/vite.config.ts
index 424345c4ba8..f7975196ac3 100644
--- a/packages/start-server-core/vite.config.ts
+++ b/packages/start-server-core/vite.config.ts
@@ -18,11 +18,16 @@ export default mergeConfig(
config,
tanstackViteConfig({
srcDir: './src',
- entry: ['./src/index.tsx', './src/createServerRpc.ts'],
+ entry: [
+ './src/index.tsx',
+ './src/createServerRpc.ts',
+ './src/fake-start-server-fn-manifest.ts',
+ ],
externalDeps: [
...Object.values(VIRTUAL_MODULES),
'#tanstack-start-entry',
'#tanstack-router-entry',
+ '#tanstack-start-server-fn-manifest',
],
cjs: false,
}),