You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Both client-runtime and server-runtime for TanStack Router server functions reference the same fetcher() function implementation in server-fns/fetcher.tsx.
This confuses Vite as fetcher() is mixed into both server-side and client-side (browser) code. Vite therefore assumes that server-only variables referenced inside of server functions are actually utilized client-side as well.
This causes variables referenced outside* of the "use server" pragma that are NOT referenced client-side to not be treeshaken away in the browser bundle.
how i stumbled upon it
I was trying to use TanStack router server functions and wanted to define a few variables outside of the "use server" pragma.
I had a PostgreSQL connection defined in a separate file that was used only inside of server functions, and noticed that the PostgreSQL connection library was included in the browser bundle when it shouldn't have been.
an alternative
I isolated the server function runtime from Solid Start to use for my own project. Solid Start's runtime does not face this same issue. For a second I thought it was an issue with Vinxi, though it did not turn out to be the case.
I included the isolated code at the bottom of this issue.
I found out why server functions included server-only referenced variables into the browser bundle while isolating the server function runtime from Solid Start as I noticed that the difference between Solid Start and TanStack Router's runtime implementation is that fetcher() imported from server-fns/fetcher.tsx is referenced in both server-side and client-side runtimes.
solution found but no pr filed
I was hoping to file a PR to solve this issue as all that needs to be done is to independently isolate fetcher() for both client-side and server-side runtime.
I can't really think of a clear/clean way to do this though and wanted to get some advice on this.
server-fns-runtime.ts (handles server-side server function invocations):
exportfunctioncreateServerReference(fn: Function,id: string,name: string){if(typeoffn!=="function")thrownewError("Export from a 'use server' module must be a function");constbaseURL=import.meta.env.SERVER_BASE_URL;returnnewProxy(fn,{get(target,prop,receiver){if(prop==="url"){return`${baseURL}/_server?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}`;}if(prop==="GET")returnreceiver;if(prop==="withOptions"){return(options: RequestInit)=>receiver;}},apply(target,thisArg,args){returnfn.apply(thisArg,args);},});}
server-runtime.ts (handles client-side server function invocations):
import{deserialize,toJSONAsync}from"seroval";import{CustomEventPlugin,DOMExceptionPlugin,EventPlugin,FormDataPlugin,HeadersPlugin,ReadableStreamPlugin,RequestPlugin,ResponsePlugin,URLPlugin,URLSearchParamsPlugin,}from"seroval-plugins/web";classSerovalChunkReader{reader: ReadableStreamDefaultReader<Uint8Array>;buffer: Uint8Array;done: boolean;constructor(stream: ReadableStream<Uint8Array>){this.reader=stream.getReader();this.buffer=newUint8Array(0);this.done=false;}asyncreadChunk(){// if there's no chunk, read againconstchunk=awaitthis.reader.read();if(!chunk.done){// repopulate the bufferletnewBuffer=newUint8Array(this.buffer.length+chunk.value.length);newBuffer.set(this.buffer);newBuffer.set(chunk.value,this.buffer.length);this.buffer=newBuffer;}else{this.done=true;}}asyncnext(): Promise<any>{// Check if the buffer is emptyif(this.buffer.length===0){// if we are already done...if(this.done){return{done: true,value: undefined,};}// Otherwise, read a new chunkawaitthis.readChunk();returnawaitthis.next();}// Read the "byte header"// The byte header tells us how big the expected data is// so we know how much data we should wait before we// deserialize the dataconsthead=newTextDecoder().decode(this.buffer.subarray(1,11));constbytes=Number.parseInt(head,16);// ;0x00000000;// Check if the buffer has enough bytes to be parsedwhile(bytes>this.buffer.length-12){// If it's not enough, and the reader is done// then the chunk is invalid.if(this.done){thrownewError("Malformed server function stream.");}// Otherwise, we read more chunksawaitthis.readChunk();}// Extract the exact chunk as defined by the byte headerconstpartial=newTextDecoder().decode(this.buffer.subarray(12,12+bytes),);// The rest goes to the bufferthis.buffer=this.buffer.subarray(12+bytes);// Deserialize the chunkreturn{done: false,value: deserialize(partial),};}asyncdrain(){while(true){constresult=awaitthis.next();if(result.done){break;}}}}asyncfunctiondeserializeStream(id: string,response: Response){if(!response.body){thrownewError("missing body");}constreader=newSerovalChunkReader(response.body);constresult=awaitreader.next();if(!result.done){reader.drain().then(()=>{// @ts-ignoredelete$R[id];},()=>{// no-op},);}returnresult.value;}letINSTANCE=0;functioncreateRequest(base: string,id: string,instance: string,options: RequestInit,){returnfetch(base,{method: "POST",
...options,headers: {
...options.headers,"X-Server-Id": id,"X-Server-Instance": instance,},});}constplugins=[CustomEventPlugin,DOMExceptionPlugin,EventPlugin,FormDataPlugin,HeadersPlugin,ReadableStreamPlugin,RequestPlugin,ResponsePlugin,URLSearchParamsPlugin,URLPlugin,];asyncfunctionfetchServerFunction(base: string,id: string,options: Omit<RequestInit,"body">,args: any[],){constinstance=`server-fn:${INSTANCE++}`;constresponse=await(args.length===0
? createRequest(base,id,instance,options)
: args.length===1&&args[0]instanceofFormData
? createRequest(base,id,instance,{ ...options,body: args[0]})
: createRequest(base,id,instance,{
...options,body: JSON.stringify(awaitPromise.resolve(toJSONAsync(args,{ plugins })),),headers: { ...options.headers,"Content-Type": "application/json"},}));if(response.headers.get("Location")||response.headers.get("X-Revalidate")){if(response.body){(responseasany).customBody=()=>{returndeserializeStream(instance,response);};}returnresponse;}constcontentType=response.headers.get("Content-Type");letresult;if(contentType&&contentType.startsWith("text/plain")){result=awaitresponse.text();}elseif(contentType&&contentType.startsWith("application/json")){result=awaitresponse.json();}else{result=awaitdeserializeStream(instance,response);}if(response.headers.has("X-Error")){throwresult;}returnresult;}exportfunctioncreateServerReference(fn: Function,id: string,name: string){constbaseURL=import.meta.env.SERVER_BASE_URL;returnnewProxy(fn,{get(target,prop,receiver){if(prop==="url"){return`${baseURL}/_server?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}`;}if(prop==="GET"){returnreceiver.withOptions({method: "GET"});}if(prop==="withOptions"){consturl=`${baseURL}/_server/?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}`;return(options: RequestInit)=>{constfn=async(...args: any[])=>{constencodeArgs=options.method&&options.method.toUpperCase()==="GET";returnfetchServerFunction(encodeArgs
? url+(args.length
? `&args=${encodeURIComponent(JSON.stringify(awaitPromise.resolve(toJSONAsync(args,{ plugins }),),),)}`
: "")
: `${baseURL}/_server`,`${id}#${name}`,options,encodeArgs ? [] : args,);};fn.url=url;returnfn;};}},apply(target,thisArg,args){returnfetchServerFunction(`${baseURL}/_server`,`${id}#${name}`,{},args,);},});}
server-handler.ts (the server function HTTP handler):
Define variables outside of the server function that are used inside of server functions that do not get referenced client-side. They will appear in the client-side bundle.
Expected behavior
Server-only referenced variables should be treeshaken away in the browser bundle.
Screenshots or Videos
No response
Platform
OS: [e.g. macOS, Windows, Linux]
Browser: [e.g. Chrome, Safari, Firefox]
Version: [e.g. 91.1]
Additional context
No response
The text was updated successfully, but these errors were encountered:
Describe the bug
problem
Both
client-runtime
andserver-runtime
for TanStack Router server functions reference the samefetcher()
function implementation inserver-fns/fetcher.tsx
.This confuses Vite as
fetcher()
is mixed into both server-side and client-side (browser) code. Vite therefore assumes that server-only variables referenced inside of server functions are actually utilized client-side as well.This causes variables referenced outside* of the "use server" pragma that are NOT referenced client-side to not be treeshaken away in the browser bundle.
how i stumbled upon it
I was trying to use TanStack router server functions and wanted to define a few variables outside of the "use server" pragma.
I had a PostgreSQL connection defined in a separate file that was used only inside of server functions, and noticed that the PostgreSQL connection library was included in the browser bundle when it shouldn't have been.
an alternative
I isolated the server function runtime from Solid Start to use for my own project. Solid Start's runtime does not face this same issue. For a second I thought it was an issue with Vinxi, though it did not turn out to be the case.
I included the isolated code at the bottom of this issue.
I found out why server functions included server-only referenced variables into the browser bundle while isolating the server function runtime from Solid Start as I noticed that the difference between Solid Start and TanStack Router's runtime implementation is that
fetcher()
imported fromserver-fns/fetcher.tsx
is referenced in both server-side and client-side runtimes.solution found but no pr filed
I was hoping to file a PR to solve this issue as all that needs to be done is to independently isolate
fetcher()
for both client-side and server-side runtime.I can't really think of a clear/clean way to do this though and wanted to get some advice on this.
solid start isolated server function runtime
types.ts
:fetchEvent.ts
:server-fns-runtime.ts
(handles server-side server function invocations):server-runtime.ts
(handles client-side server function invocations):server-handler.ts
(the server function HTTP handler):Your Example Website or App
N/A
Steps to Reproduce the Bug or Issue
Define variables outside of the server function that are used inside of server functions that do not get referenced client-side. They will appear in the client-side bundle.
Expected behavior
Server-only referenced variables should be treeshaken away in the browser bundle.
Screenshots or Videos
No response
Platform
Additional context
No response
The text was updated successfully, but these errors were encountered: