55 type Plugin ,
66 type ViteDevServer ,
77} from "vite" ;
8+ import fg from "fast-glob" ;
89import { compile , type CompileOptions } from "./compile.ts" ;
10+ import xxHash32 from "./xxhash32.ts" ;
911
1012export interface ServerFunctionsFilter {
1113 include ?: FilterPattern ;
@@ -25,6 +27,11 @@ const DEFAULT_INCLUDE = "src/**/*.{jsx,tsx,ts,js,mjs,cjs}";
2527const DEFAULT_EXCLUDE = "node_modules/**/*.{jsx,tsx,ts,js,mjs,cjs}" ;
2628const DIRECTIVE = "use server" ;
2729
30+ // Dev-only virtual module used by fns/handler.ts to lazily resolve a server
31+ // function id back to its owning module when the function was never evaluated
32+ // in the server environment (e.g. its route was only client-side navigated to).
33+ const LOOKUP_ID = "solid-start:server-fn-lookup" ;
34+
2835type Manifest = Record < CompileOptions [ "mode" ] , Set < string > > ;
2936
3037function createManifest ( ) : Manifest {
@@ -107,13 +114,15 @@ function invalidateModules(
107114) : void {
108115 if ( server ) {
109116 if ( result . invalidPreload ) {
110- invalidateModule ( server . environments . client . moduleGraph , manifest ) ;
111- invalidateModule ( server . environments . ssr . moduleGraph , manifest ) ;
117+ // Environments are not limited to "client"/"ssr": plugins like nitro
118+ // render in their own environment (e.g. "nitro"), so invalidate everywhere.
119+ for ( const environment of Object . values ( server . environments ) ) {
120+ invalidateModule ( environment . moduleGraph , manifest ) ;
121+ }
112122 }
113123 }
114124}
115125
116-
117126export function serverFunctionsPlugin ( options : ServerFunctionsOptions ) : Plugin [ ] {
118127 const filter = createFilter (
119128 options . filter ?. include || DEFAULT_INCLUDE ,
@@ -179,6 +188,9 @@ export function serverFunctionsPlugin(options: ServerFunctionsOptions): Plugin[]
179188 if ( source === options . manifest ) {
180189 return { id : options . manifest , moduleSideEffects : true } ;
181190 }
191+ if ( source . startsWith ( LOOKUP_ID ) ) {
192+ return { id : source , moduleSideEffects : true } ;
193+ }
182194 return null ;
183195 } ,
184196 async load ( id ) {
@@ -191,12 +203,32 @@ export function serverFunctionsPlugin(options: ServerFunctionsOptions): Plugin[]
191203 const result = await current . promise . reference ;
192204 return result ;
193205 }
206+ if ( id . startsWith ( LOOKUP_ID ) ) {
207+ if ( this . environment . mode !== "dev" ) {
208+ throw new Error ( `${ LOOKUP_ID } is only available in dev` ) ;
209+ }
210+ const functionId = new URLSearchParams ( id . slice ( LOOKUP_ID . length + 1 ) ) . get ( "id" ) ;
211+ // dev function ids are `${xxHash32(file)}-${count}-${name}`
212+ const hash = functionId ?. split ( "-" ) [ 0 ] ;
213+ if ( ! hash ) return "export {};" ;
214+
215+ // Look through the files already discovered by the transform first;
216+ // fall back to scanning the project so a function is found even when
217+ // the browser kept a chunk from before a dev server restart.
218+ let files = [ ...manifest . server ] . filter ( file => xxHash32 ( file ) . toString ( 16 ) === hash ) ;
219+ if ( files . length === 0 ) {
220+ files = fg
221+ . sync ( DEFAULT_INCLUDE , { cwd : this . environment . config . root , absolute : true } )
222+ . filter ( file => filter ( file ) && xxHash32 ( file ) . toString ( 16 ) === hash ) ;
223+ }
224+ return files . map ( file => `import ${ JSON . stringify ( file ) } ;` ) . join ( "\n" ) || "export {};" ;
225+ }
194226 return null ;
195227 } ,
196228 } ,
197229 {
198230 name : "solid-start:server-functions/compiler" ,
199- enforce : ' pre' ,
231+ enforce : " pre" ,
200232 async transform ( code , fileId ) {
201233 const mode = this . environment . config . consumer ;
202234 const [ id ] = fileId . split ( "?" ) ;
0 commit comments