diff --git a/packages/directive-functions-plugin/src/compilers.ts b/packages/directive-functions-plugin/src/compilers.ts index e01c80a5458..de71df6eaa5 100644 --- a/packages/directive-functions-plugin/src/compilers.ts +++ b/packages/directive-functions-plugin/src/compilers.ts @@ -41,7 +41,6 @@ export type ReplacerFn = (opts: { export type CompileDirectivesOpts = ParseAstOptions & { directive: string - directiveLabel: string getRuntimeCode?: (opts: { directiveFnsById: Record }) => string @@ -49,26 +48,20 @@ export type CompileDirectivesOpts = ParseAstOptions & { replacer: ReplacerFn filename: string root: string -} - -function buildDirectiveSplitParam(opts: CompileDirectivesOpts) { - return `tsr-directive-${opts.directive.replace(/[^a-zA-Z0-9]/g, '-')}` + isDirectiveSplitParam: boolean + directiveSplitParam: string } export function compileDirectives(opts: CompileDirectivesOpts): { compiledResult: GeneratorResult directiveFnsById: Record - isDirectiveSplitParam: boolean } { - const directiveSplitParam = buildDirectiveSplitParam(opts) - const isDirectiveSplitParam = opts.filename.includes(directiveSplitParam) - const ast = parseAst(opts) const refIdents = findReferencedIdentifiers(ast) const directiveFnsById = findDirectives(ast, { ...opts, - directiveSplitParam, - isDirectiveSplitParam, + directiveSplitParam: opts.directiveSplitParam, + isDirectiveSplitParam: opts.isDirectiveSplitParam, }) // Add runtime code if there are directives @@ -84,7 +77,7 @@ export function compileDirectives(opts: CompileDirectivesOpts): { // If we are in the source file, we need to remove all exports // then make sure that all of our functions are exported under their // directive name - if (isDirectiveSplitParam) { + if (opts.isDirectiveSplitParam) { safeRemoveExports(ast) // Export a single object with all of the functions @@ -113,13 +106,12 @@ export function compileDirectives(opts: CompileDirectivesOpts): { return { compiledResult, directiveFnsById, - isDirectiveSplitParam, } } function findNearestVariableName( path: babel.NodePath, - directiveLabel: string, + directive: string, ): string { let currentPath: babel.NodePath | null = path const nameParts: Array = [] @@ -189,7 +181,7 @@ function findNearestVariableName( babel.types.isObjectMethod(currentPath.node) ) { throw new Error( - `${directiveLabel} in ClassMethod or ObjectMethod not supported`, + `"${directive}" in ClassMethod or ObjectMethod not supported`, ) } @@ -219,7 +211,6 @@ export function findDirectives( ast: babel.types.File, opts: ParseAstOptions & { directive: string - directiveLabel: string replacer?: ReplacerFn generateFunctionId: GenerateFunctionIdFn directiveSplitParam: string @@ -320,7 +311,7 @@ export function findDirectives( throw codeFrameError( opts.code, nearestBlock.node.loc, - `${opts.directiveLabel}s cannot be nested in other blocks or functions`, + `"${opts.directive}" cannot be nested in other blocks or functions`, ) } @@ -335,7 +326,7 @@ export function findDirectives( throw codeFrameError( opts.code, directiveFn.node.loc, - `${opts.directiveLabel}s must be function declarations or function expressions`, + `"${opts.directive}" must be function declarations or function expressions`, ) } @@ -397,7 +388,7 @@ export function findDirectives( } // Find the nearest variable name - let functionName = findNearestVariableName(directiveFn, opts.directiveLabel) + let functionName = findNearestVariableName(directiveFn, opts.directive) const incrementFunctionNameVersion = (functionName: string) => { const [realReferenceName, count] = functionName.split(/_(\d+)$/) diff --git a/packages/directive-functions-plugin/src/index.ts b/packages/directive-functions-plugin/src/index.ts index 1d540e1c42b..d34a7e7196f 100644 --- a/packages/directive-functions-plugin/src/index.ts +++ b/packages/directive-functions-plugin/src/index.ts @@ -23,55 +23,42 @@ export type { export type DirectiveFunctionsViteEnvOptions = Pick< CompileDirectivesOpts, 'getRuntimeCode' | 'replacer' -> & { - envLabel: string +> +export type DirectiveFunctionsViteOptions = DirectiveFunctionsViteEnvOptions & { + directive: string + onDirectiveFnsById?: (directiveFnsById: Record) => void + generateFunctionId: GenerateFunctionIdFn } -export type DirectiveFunctionsViteOptions = Pick< - CompileDirectivesOpts, - 'directive' | 'directiveLabel' -> & - DirectiveFunctionsViteEnvOptions & { - onDirectiveFnsById?: (directiveFnsById: Record) => void - generateFunctionId: GenerateFunctionIdFn - } - const createDirectiveRx = (directive: string) => new RegExp(`"${directive}"|'${directive}'`, 'gm') -export type DirectiveFunctionsVitePluginEnvOptions = Pick< - CompileDirectivesOpts, - 'directive' | 'directiveLabel' -> & { - environments: { - client: DirectiveFunctionsViteEnvOptions & { envName?: string } - server: DirectiveFunctionsViteEnvOptions & { envName?: string } - } +export type DirectiveFunctionsVitePluginEnvOptions = { + directive: string + callers: Array + provider: DirectiveFunctionsViteEnvOptions & { envName: string } onDirectiveFnsById?: (directiveFnsById: Record) => void generateFunctionId: GenerateFunctionIdFn } +function buildDirectiveSplitParam(directive: string) { + return `tsr-directive-${directive.replace(/[^a-zA-Z0-9]/g, '-')}` +} + export function TanStackDirectiveFunctionsPluginEnv( opts: DirectiveFunctionsVitePluginEnvOptions, ): Plugin { - opts = { - ...opts, - environments: { - client: { - envName: 'client', - ...opts.environments.client, - }, - server: { - envName: 'server', - ...opts.environments.server, - }, - }, - } - let root: string = process.cwd() const directiveRx = createDirectiveRx(opts.directive) + const appliedEnvironments = new Set([ + ...opts.callers.map((c) => c.envName), + opts.provider.envName, + ]) + + const directiveSplitParam = buildDirectiveSplitParam(opts.directive) + return { name: 'tanstack-start-directive-vite-plugin', enforce: 'pre', @@ -79,80 +66,61 @@ export function TanStackDirectiveFunctionsPluginEnv( root = this.environment.config.root }, applyToEnvironment(env) { - return [ - opts.environments.client.envName, - opts.environments.server.envName, - ].includes(env.name) + return appliedEnvironments.has(env.name) }, transform: { filter: { code: directiveRx, }, handler(code, id) { - const envOptions = [ - opts.environments.client, - opts.environments.server, - ].find((e) => e.envName === this.environment.name) - - if (!envOptions) { - throw new Error(`Environment ${this.environment.name} not found`) + const url = pathToFileURL(id) + url.searchParams.delete('v') + id = fileURLToPath(url).replace(/\\/g, '/') + + const isDirectiveSplitParam = id.includes(directiveSplitParam) + + let envOptions: DirectiveFunctionsViteEnvOptions & { envName: string } + if (isDirectiveSplitParam) { + envOptions = opts.provider + if (debug) + console.info( + `Compiling Directives for provider in environment ${envOptions.envName}: `, + id, + ) + } else { + envOptions = opts.callers.find( + (e) => e.envName === this.environment.name, + )! + if (debug) + console.info( + `Compiling Directives for caller in environment ${envOptions.envName}: `, + id, + ) } - - return transformCode({ - ...opts, - ...envOptions, + const { compiledResult, directiveFnsById } = compileDirectives({ + directive: opts.directive, + getRuntimeCode: envOptions.getRuntimeCode, + generateFunctionId: opts.generateFunctionId, + replacer: envOptions.replacer, code, - id, root, + filename: id, + directiveSplitParam, + isDirectiveSplitParam, }) - }, - }, - } -} + // when we process a file with a directive split param, we have already encountered the directives in that file + // (otherwise we wouldn't have gotten here) + if (!isDirectiveSplitParam) { + opts.onDirectiveFnsById?.(directiveFnsById) + } -function transformCode({ - code, - id, - envLabel, - directive, - directiveLabel, - getRuntimeCode, - generateFunctionId, - replacer, - onDirectiveFnsById, - root, -}: DirectiveFunctionsViteOptions & { - code: string - id: string - root: string -}) { - const url = pathToFileURL(id) - url.searchParams.delete('v') - id = fileURLToPath(url).replace(/\\/g, '/') - - if (debug) console.info(`${envLabel}: Compiling Directives: `, id) - - const { compiledResult, directiveFnsById, isDirectiveSplitParam } = - compileDirectives({ - directive, - directiveLabel, - getRuntimeCode, - generateFunctionId, - replacer, - code, - root, - filename: id, - }) - // when we process a file with a directive split param, we have already encountered the directives in that file - // (otherwise we wouldn't have gotten here) - if (!isDirectiveSplitParam) { - onDirectiveFnsById?.(directiveFnsById) - } + if (debug) { + logDiff(code, compiledResult.code) + console.log('Output:\n', compiledResult.code + '\n\n') + } - if (debug) { - logDiff(code, compiledResult.code) - console.log('Output:\n', compiledResult.code + '\n\n') + return compiledResult + }, + }, } - - return compiledResult } diff --git a/packages/directive-functions-plugin/tests/compiler.test.ts b/packages/directive-functions-plugin/tests/compiler.test.ts index f3635e140bf..e6d993c9992 100644 --- a/packages/directive-functions-plugin/tests/compiler.test.ts +++ b/packages/directive-functions-plugin/tests/compiler.test.ts @@ -19,27 +19,28 @@ const generateFunctionId: CompileDirectivesOpts['generateFunctionId'] = ( const clientConfig: Omit = { directive: 'use server', - directiveLabel: 'Server function', root: './test-files', filename: './test-files/test.ts', getRuntimeCode: () => 'import { createClientRpc } from "my-rpc-lib-client"', generateFunctionId, replacer: (opts) => `createClientRpc(${JSON.stringify(opts.functionId)})`, + directiveSplitParam: 'tsr-directive-use-server', + isDirectiveSplitParam: false, } const ssrConfig: Omit = { directive: 'use server', - directiveLabel: 'Server function', root: './test-files', filename: './test-files/test.ts', getRuntimeCode: () => 'import { createSsrRpc } from "my-rpc-lib-server"', generateFunctionId, replacer: (opts) => `createSsrRpc(${JSON.stringify(opts.functionId)})`, + directiveSplitParam: 'tsr-directive-use-server', + isDirectiveSplitParam: false, } const serverConfig: Omit = { directive: 'use server', - directiveLabel: 'Server function', root: './test-files', filename: './test-files/test.ts', getRuntimeCode: () => 'import { createServerRpc } from "my-rpc-lib-server"', @@ -52,6 +53,8 @@ const serverConfig: Omit = { // For any other server functions the split function may reference, // we use the splitImportFn which is a dynamic import of the split file. `createServerRpc(${JSON.stringify(opts.functionId)}, ${opts.fn})`, + directiveSplitParam: 'tsr-directive-use-server', + isDirectiveSplitParam: true, } describe('server function compilation', () => { diff --git a/packages/react-start/package.json b/packages/react-start/package.json index 63c72f0f065..ffd74f9445f 100644 --- a/packages/react-start/package.json +++ b/packages/react-start/package.json @@ -62,6 +62,12 @@ "default": "./dist/esm/server-rpc.js" } }, + "./ssr-rpc": { + "import": { + "types": "./dist/esm/ssr-rpc.d.ts", + "default": "./dist/esm/ssr-rpc.js" + } + }, "./plugin/vite": { "import": { "types": "./dist/esm/plugin/vite.d.ts", diff --git a/packages/react-start/src/ssr-rpc.ts b/packages/react-start/src/ssr-rpc.ts new file mode 100644 index 00000000000..568429b567c --- /dev/null +++ b/packages/react-start/src/ssr-rpc.ts @@ -0,0 +1 @@ +export { createSsrRpc } from '@tanstack/start-server-core/createSsrRpc' diff --git a/packages/react-start/vite.config.ts b/packages/react-start/vite.config.ts index f9d4b57ebc7..d7ab699a07b 100644 --- a/packages/react-start/vite.config.ts +++ b/packages/react-start/vite.config.ts @@ -29,6 +29,7 @@ export default mergeConfig( './src/client-rpc.ts', './src/server.tsx', './src/server-rpc.ts', + './src/ssr-rpc.ts', './src/plugin/vite.ts', ], externalDeps: [ diff --git a/packages/server-functions-plugin/src/index.ts b/packages/server-functions-plugin/src/index.ts index fa8bbbf0ca6..fb7e265f52c 100644 --- a/packages/server-functions-plugin/src/index.ts +++ b/packages/server-functions-plugin/src/index.ts @@ -14,20 +14,25 @@ export type GenerateFunctionIdFnOptional = ( 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 * and its modules. */ manifestVirtualImportId: string generateFunctionId?: GenerateFunctionIdFnOptional - client: ServerFnPluginEnvOpts - server: ServerFnPluginEnvOpts + callers: Array< + ServerFnPluginEnvOpts & { + envConsumer: 'client' | 'server' + getServerFnById?: string + } + > + provider: ServerFnPluginEnvOpts + directive: string } export type ServerFnPluginEnvOpts = { getRuntimeCode: () => string replacer: ReplacerFn - envName?: string + envName: string } const debug = @@ -35,20 +40,8 @@ const debug = ['true', 'server-functions-plugin'].includes(process.env.TSR_VITE_DEBUG) export function TanStackServerFnPlugin( - _opts: TanStackServerFnPluginOpts, + opts: TanStackServerFnPluginOpts, ): Array { - const opts = { - ..._opts, - client: { - ..._opts.client, - envName: _opts.client.envName || 'client', - }, - server: { - ..._opts.server, - envName: _opts.server.envName || 'server', - }, - } - const directiveFnsById: Record = {} let serverDevEnv: DevEnvironment | undefined @@ -75,13 +68,15 @@ export function TanStackServerFnPlugin( return path } + let root = process.cwd() + let command: 'build' | 'serve' = 'build' + const generateFunctionId: GenerateFunctionIdFn = ({ extractedFilename, functionName, filename, }) => { - if (serverDevEnv) { - const root = serverDevEnv.config.root + if (command === 'serve') { const rootWithTrailingSlash = withTrailingSlash(root) let file = extractedFilename if (extractedFilename.startsWith(rootWithTrailingSlash)) { @@ -129,35 +124,33 @@ export function TanStackServerFnPlugin( } return functionId } - const directive = 'use server' - const directiveLabel = 'Server Function' const resolvedManifestVirtualImportId = resolveViteId( opts.manifestVirtualImportId, ) + const appliedEnvironments = new Set([ + ...opts.callers + .filter((c) => c.envConsumer === 'server') + .map((c) => c.envName), + opts.provider.envName, + ]) + + const serverCallerEnvironments = new Map( + opts.callers + .filter((c) => c.envConsumer === 'server') + .map((c) => [c.envName, c]), + ) + return [ // The client plugin is used to compile the client directives // and save them so we can create a manifest TanStackDirectiveFunctionsPluginEnv({ - directive, - directiveLabel, + directive: opts.directive, onDirectiveFnsById, generateFunctionId, - environments: { - client: { - envLabel: 'Client', - getRuntimeCode: opts.client.getRuntimeCode, - replacer: opts.client.replacer, - envName: opts.client.envName, - }, - server: { - envLabel: 'Server', - getRuntimeCode: opts.server.getRuntimeCode, - replacer: opts.server.replacer, - envName: opts.server.envName, - }, - }, + provider: opts.provider, + callers: opts.callers, }), { // On the server, we need to be able to read the server-function manifest from the client build. @@ -165,13 +158,12 @@ export function TanStackServerFnPlugin( // by its ID, import it, and call it. name: 'tanstack-start-server-fn-vite-plugin-manifest-server', enforce: 'pre', - configureServer(viteDevServer) { - serverDevEnv = viteDevServer.environments[opts.server.envName] - if (!serverDevEnv) { - throw new Error( - `TanStackServerFnPluginEnv: environment "${opts.server.envName}" not found`, - ) - } + applyToEnvironment: (env) => { + return appliedEnvironments.has(env.name) + }, + configResolved(config) { + root = config.root + command = config.command }, resolveId: { filter: { id: new RegExp(opts.manifestVirtualImportId) }, @@ -182,8 +174,18 @@ export function TanStackServerFnPlugin( load: { filter: { id: new RegExp(resolvedManifestVirtualImportId) }, handler() { - if (this.environment.name !== opts.server.envName) { - return `export default {}` + // a different server side environment is used for e.g. SSR and server functions + if (this.environment.name !== opts.provider.envName) { + const getServerFnById = serverCallerEnvironments.get( + this.environment.name, + )?.getServerFnById + if (!getServerFnById) { + throw new Error( + `No getServerFnById implementation found for environment ${this.environment.name}`, + ) + } + + return getServerFnById } if (this.environment.mode !== 'build') { diff --git a/packages/solid-start/package.json b/packages/solid-start/package.json index 3fd6fd2142b..1f1c252522e 100644 --- a/packages/solid-start/package.json +++ b/packages/solid-start/package.json @@ -56,6 +56,12 @@ "default": "./dist/esm/server-rpc.js" } }, + "./ssr-rpc": { + "import": { + "types": "./dist/esm/ssr-rpc.d.ts", + "default": "./dist/esm/ssr-rpc.js" + } + }, "./server": { "import": { "types": "./dist/esm/server.d.ts", diff --git a/packages/solid-start/src/ssr-rpc.ts b/packages/solid-start/src/ssr-rpc.ts new file mode 100644 index 00000000000..568429b567c --- /dev/null +++ b/packages/solid-start/src/ssr-rpc.ts @@ -0,0 +1 @@ +export { createSsrRpc } from '@tanstack/start-server-core/createSsrRpc' diff --git a/packages/solid-start/vite.config.ts b/packages/solid-start/vite.config.ts index 0a48fb3b744..4004316d919 100644 --- a/packages/solid-start/vite.config.ts +++ b/packages/solid-start/vite.config.ts @@ -27,6 +27,7 @@ export default mergeConfig( './src/index.ts', './src/client.tsx', './src/client-rpc.ts', + './src/ssr-rpc.ts', './src/server-rpc.ts', './src/server.tsx', './src/plugin/vite.ts', diff --git a/packages/start-plugin-core/src/create-server-fn-plugin/compiler.ts b/packages/start-plugin-core/src/create-server-fn-plugin/compiler.ts index f00f89dbb8a..1fb95ee0782 100644 --- a/packages/start-plugin-core/src/create-server-fn-plugin/compiler.ts +++ b/packages/start-plugin-core/src/create-server-fn-plugin/compiler.ts @@ -60,6 +60,7 @@ export class ServerFnCompiler { constructor( private options: { env: 'client' | 'server' + directive: string lookupConfigurations: Array lookupKinds: Set loadModule: (id: string) => Promise @@ -252,7 +253,11 @@ export class ServerFnCompiler { pathsToRewrite.map((p) => { if (p.kind === 'ServerFn') { - handleCreateServerFn(p.nodePath, { env: this.options.env, code }) + handleCreateServerFn(p.nodePath, { + env: this.options.env, + code, + directive: this.options.directive, + }) } else { handleCreateMiddleware(p.nodePath, { env: this.options.env }) } diff --git a/packages/start-plugin-core/src/create-server-fn-plugin/handleCreateServerFn.ts b/packages/start-plugin-core/src/create-server-fn-plugin/handleCreateServerFn.ts index 75ebfecdbff..726735f6971 100644 --- a/packages/start-plugin-core/src/create-server-fn-plugin/handleCreateServerFn.ts +++ b/packages/start-plugin-core/src/create-server-fn-plugin/handleCreateServerFn.ts @@ -10,6 +10,7 @@ export function handleCreateServerFn( opts: { env: 'client' | 'server' code: string + directive: string }, ) { // Traverse the member expression and find the call expressions for diff --git a/packages/start-plugin-core/src/create-server-fn-plugin/plugin.ts b/packages/start-plugin-core/src/create-server-fn-plugin/plugin.ts index 25771328ddc..182f816e128 100644 --- a/packages/start-plugin-core/src/create-server-fn-plugin/plugin.ts +++ b/packages/start-plugin-core/src/create-server-fn-plugin/plugin.ts @@ -39,9 +39,10 @@ const getLookupConfigurationsForEnv = ( return [createServerFnConfig] } } -export function createServerFnPlugin( - framework: CompileStartFrameworkOptions, -): PluginOption { +export function createServerFnPlugin(opts: { + framework: CompileStartFrameworkOptions + directive: string +}): PluginOption { const SERVER_FN_LOOKUP = 'server-fn-module-lookup' const compilers: Partial> = {} @@ -106,10 +107,11 @@ export function createServerFnPlugin( compiler = new ServerFnCompiler({ env, + directive: opts.directive, lookupKinds: LookupKindsPerEnv[env], lookupConfigurations: getLookupConfigurationsForEnv( env, - framework, + opts.framework, ), loadModule: async (id: string) => { if (this.environment.mode === 'build') { diff --git a/packages/start-plugin-core/src/plugin.ts b/packages/start-plugin-core/src/plugin.ts index 648b812ada0..2812d0664f0 100644 --- a/packages/start-plugin-core/src/plugin.ts +++ b/packages/start-plugin-core/src/plugin.ts @@ -34,6 +34,12 @@ export interface TanStackStartVitePluginCoreOptions { server: string start: string } + serverFn?: { + directive?: string + ssr?: { + getServerFnById?: string + } + } } export interface ResolvedStartConfig { @@ -70,6 +76,8 @@ export function TanStackStartVitePluginCore( viteAppBase: '', } + const directive = corePluginOpts.serverFn?.directive ?? 'use server' + let startConfig: TanStackStartOutputConfig | null const getConfig: GetConfigFn = () => { if (!resolvedStartConfig.root) { @@ -328,20 +336,32 @@ export function TanStackStartVitePluginCore( tanStackStartRouter(startPluginOpts, getConfig, corePluginOpts), // N.B. TanStackStartCompilerPlugin must be before the TanStackServerFnPlugin startCompilerPlugin(corePluginOpts.framework), - createServerFnPlugin(corePluginOpts.framework), + createServerFnPlugin({ framework: corePluginOpts.framework, directive }), 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, + directive, generateFunctionId: startPluginOpts?.serverFns?.generateFunctionId, - client: { - getRuntimeCode: () => - `import { createClientRpc } from '@tanstack/${corePluginOpts.framework}-start/client-rpc'`, - replacer: (d) => `createClientRpc('${d.functionId}')`, - envName: VITE_ENVIRONMENT_NAMES.client, - }, - server: { + callers: [ + { + envConsumer: 'client', + getRuntimeCode: () => + `import { createClientRpc } from '@tanstack/${corePluginOpts.framework}-start/client-rpc'`, + replacer: (d) => `createClientRpc('${d.functionId}')`, + envName: VITE_ENVIRONMENT_NAMES.client, + }, + { + envConsumer: 'server', + getRuntimeCode: () => + `import { createSsrRpc } from '@tanstack/${corePluginOpts.framework}-start/ssr-rpc'`, + envName: VITE_ENVIRONMENT_NAMES.server, + replacer: (d) => `createSsrRpc('${d.functionId}')`, + getServerFnById: corePluginOpts.serverFn?.ssr?.getServerFnById, + }, + ], + provider: { getRuntimeCode: () => `import { createServerRpc } from '@tanstack/${corePluginOpts.framework}-start/server-rpc'`, replacer: (d) => `createServerRpc('${d.functionId}', ${d.fn})`, diff --git a/packages/start-plugin-core/tests/createMiddleware-create-server-fn-plugin/createMiddleware.test.ts b/packages/start-plugin-core/tests/createMiddleware-create-server-fn-plugin/createMiddleware.test.ts index 5382d96dba2..088ff312817 100644 --- a/packages/start-plugin-core/tests/createMiddleware-create-server-fn-plugin/createMiddleware.test.ts +++ b/packages/start-plugin-core/tests/createMiddleware-create-server-fn-plugin/createMiddleware.test.ts @@ -31,6 +31,7 @@ async function compile(opts: { resolveId: async (id) => { return id }, + directive: 'use server', }) const result = await compiler.compile({ code: opts.code, id: opts.id }) return result diff --git a/packages/start-plugin-core/tests/createServerFn/createServerFn.test.ts b/packages/start-plugin-core/tests/createServerFn/createServerFn.test.ts index 325459b694f..fca2dd0242b 100644 --- a/packages/start-plugin-core/tests/createServerFn/createServerFn.test.ts +++ b/packages/start-plugin-core/tests/createServerFn/createServerFn.test.ts @@ -27,6 +27,7 @@ async function compile(opts: { resolveId: async (id) => { return id }, + directive: 'use server', }) const result = await compiler.compile({ code: opts.code, id: opts.id }) return result diff --git a/packages/start-server-core/package.json b/packages/start-server-core/package.json index 29e30c487fa..f8a9f2277d1 100644 --- a/packages/start-server-core/package.json +++ b/packages/start-server-core/package.json @@ -53,6 +53,12 @@ "default": "./dist/esm/createServerRpc.js" } }, + "./createSsrRpc": { + "import": { + "types": "./dist/esm/createSsrRpc.d.ts", + "default": "./dist/esm/createSsrRpc.js" + } + }, "./package.json": "./package.json" }, "imports": { diff --git a/packages/start-server-core/src/createServerRpc.ts b/packages/start-server-core/src/createServerRpc.ts index dea1b875542..02fe8d42f45 100644 --- a/packages/start-server-core/src/createServerRpc.ts +++ b/packages/start-server-core/src/createServerRpc.ts @@ -4,10 +4,7 @@ export const createServerRpc = ( functionId: string, splitImportFn: (...args: any) => any, ) => { - const url = process.env.TSS_SERVER_FN_BASE + functionId - return Object.assign(splitImportFn, { - url, functionId, [TSS_SERVER_FUNCTION]: true, }) diff --git a/packages/start-server-core/src/createSsrRpc.ts b/packages/start-server-core/src/createSsrRpc.ts new file mode 100644 index 00000000000..e1695b50b52 --- /dev/null +++ b/packages/start-server-core/src/createSsrRpc.ts @@ -0,0 +1,16 @@ +import { TSS_SERVER_FUNCTION } from '@tanstack/start-client-core' +import { getServerFnById } from './getServerFnById' + +export const createSsrRpc = (functionId: string) => { + const url = process.env.TSS_SERVER_FN_BASE + functionId + const fn = async (...args: Array): Promise => { + const serverFn = await getServerFnById(functionId) + return serverFn(...args) + } + + return Object.assign(fn, { + url, + functionId, + [TSS_SERVER_FUNCTION]: true, + }) +} diff --git a/packages/start-server-core/src/loadVirtualModule.ts b/packages/start-server-core/src/loadVirtualModule.ts deleted file mode 100644 index a901e13faa1..00000000000 --- a/packages/start-server-core/src/loadVirtualModule.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { VIRTUAL_MODULES } from './virtual-modules' -import type { VirtualModules } from './virtual-modules' - -/** - * we need to explicitly enumerate all imports with string literals, - * otherwise vite will not pick them up during build - */ -export async function loadVirtualModule( - id: TId, -): Promise { - switch (id) { - case VIRTUAL_MODULES.startManifest: - return (await import('tanstack-start-manifest:v')) as any - case VIRTUAL_MODULES.injectedHeadScripts: - return (await import('tanstack-start-injected-head-scripts:v')) as any - default: - throw new Error(`Unknown virtual module: ${id}`) - } -} diff --git a/packages/start-server-core/src/router-manifest.ts b/packages/start-server-core/src/router-manifest.ts index 725d06e15c2..a1f41b85644 100644 --- a/packages/start-server-core/src/router-manifest.ts +++ b/packages/start-server-core/src/router-manifest.ts @@ -1,6 +1,4 @@ import { rootRouteId } from '@tanstack/router-core' -import { VIRTUAL_MODULES } from './virtual-modules' -import { loadVirtualModule } from './loadVirtualModule' import type { RouterManagedTag } from '@tanstack/router-core' /** @@ -10,9 +8,7 @@ import type { RouterManagedTag } from '@tanstack/router-core' * between routes or any other data that is not needed for the client. */ export async function getStartManifest() { - const { tsrStartManifest } = await loadVirtualModule( - VIRTUAL_MODULES.startManifest, - ) + const { tsrStartManifest } = await import('tanstack-start-manifest:v') const startManifest = tsrStartManifest() const rootRoute = (startManifest.routes[rootRouteId] = @@ -22,8 +18,8 @@ export async function getStartManifest() { let script = `import('${startManifest.clientEntry}')` if (process.env.TSS_DEV_SERVER === 'true') { - const { injectedHeadScripts } = await loadVirtualModule( - VIRTUAL_MODULES.injectedHeadScripts, + const { injectedHeadScripts } = await import( + 'tanstack-start-injected-head-scripts:v' ) if (injectedHeadScripts) { script = `${injectedHeadScripts + ';'}${script}` diff --git a/packages/start-server-core/src/virtual-modules.ts b/packages/start-server-core/src/virtual-modules.ts index 865af7a43fe..3f0b6b788eb 100644 --- a/packages/start-server-core/src/virtual-modules.ts +++ b/packages/start-server-core/src/virtual-modules.ts @@ -1,12 +1,5 @@ -/* eslint-disable @typescript-eslint/consistent-type-imports */ - export const VIRTUAL_MODULES = { startManifest: 'tanstack-start-manifest:v', - serverFnManifest: '#tanstack-start-server-fn-manifest', injectedHeadScripts: 'tanstack-start-injected-head-scripts:v', + serverFnManifest: '#tanstack-start-server-fn-manifest', } as const - -export type VirtualModules = { - [VIRTUAL_MODULES.startManifest]: typeof import('tanstack-start-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 f7975196ac3..f0ca2cc699f 100644 --- a/packages/start-server-core/vite.config.ts +++ b/packages/start-server-core/vite.config.ts @@ -21,6 +21,7 @@ export default mergeConfig( entry: [ './src/index.tsx', './src/createServerRpc.ts', + './src/createSsrRpc.ts', './src/fake-start-server-fn-manifest.ts', ], externalDeps: [