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, }),