1- import type { Manifest , Plugin , ViteDevServer } from "vite" ;
1+ import type {
2+ EnvironmentModuleNode ,
3+ Manifest ,
4+ Plugin ,
5+ ViteDevServer ,
6+ } from "vite" ;
27import {
38 crawlFsItem ,
49 fsAdapter ,
@@ -26,6 +31,7 @@ export function serverSnapshot(options: ResolvedFreshViteConfig): Plugin[] {
2631 let publicDir = "" ;
2732
2833 const islands = new Map < string , { name : string ; chunk : string | null } > ( ) ;
34+ const islandsByFile = new Set < string > ( ) ;
2935 const islandSpecByName = new Map < string , string > ( ) ;
3036
3137 return [ {
@@ -47,8 +53,55 @@ export function serverSnapshot(options: ResolvedFreshViteConfig): Plugin[] {
4753 } ,
4854 configureServer ( viteServer ) {
4955 server = viteServer ;
50- } ,
51- async buildStart ( ) {
56+
57+ viteServer . watcher . on ( "all" , ( ev , filePath ) => {
58+ const { client, ssr } = viteServer . environments ;
59+
60+ // We can't just check if it's in the client module graph
61+ // because plugins like tailwindcss import _everything_.
62+ // Instead, we walk up the module graph to see if the file
63+ // is imported by an island or the client entry file.
64+ const mods = client . moduleGraph . getModulesByFile ( filePath ) ;
65+ if ( mods !== undefined ) {
66+ const seen = new Set < EnvironmentModuleNode > ( ) ;
67+ const maybe = Array . from ( mods ) ;
68+ for ( let i = 0 ; i < maybe . length ; i ++ ) {
69+ const mod = maybe [ i ] ;
70+
71+ const isIslandFile = walkUp (
72+ mod ,
73+ ( m ) => m . file !== null && islandsByFile . has ( m . file ) ,
74+ seen ,
75+ ) ;
76+
77+ // No need to notify manually, vite takes care of this.
78+ if ( isIslandFile ) return ;
79+ }
80+ }
81+
82+ // Check for route files. We need to invalidate the snapshot if
83+ // they are removed or added.
84+ if (
85+ ( ev === "add" || ev === "unlink" ) &&
86+ ! / [ \\ / ] + \( _ [ ^ ) ] + \) [ \\ / ] + / . test ( filePath )
87+ ) {
88+ const relRoutes = path . relative ( options . routeDir , filePath ) ;
89+ if ( ! relRoutes . startsWith ( ".." ) ) {
90+ const mod = ssr . moduleGraph . getModuleById ( `\0${ modName } ` ) ;
91+ if ( mod !== undefined ) {
92+ // Clear state
93+ islands . clear ( ) ;
94+ islandsByFile . clear ( ) ;
95+ islandSpecByName . clear ( ) ;
96+
97+ ssr . moduleGraph . invalidateModule ( mod ) ;
98+ }
99+ }
100+ }
101+
102+ // Finally, notify the client
103+ viteServer . ws . send ( "fresh:reload" ) ;
104+ } ) ;
52105 } ,
53106 resolveId ( id ) {
54107 if ( id === modName ) {
@@ -71,6 +124,7 @@ export function serverSnapshot(options: ResolvedFreshViteConfig): Plugin[] {
71124
72125 islands . set ( spec , { name, chunk : null } ) ;
73126 islandSpecByName . set ( name , spec ) ;
127+ islandsByFile . add ( spec ) ;
74128 }
75129
76130 const staticFiles : PendingStaticFile [ ] = [ ] ;
@@ -221,3 +275,21 @@ export function serverSnapshot(options: ResolvedFreshViteConfig): Plugin[] {
221275 } ,
222276 } ] ;
223277}
278+
279+ function walkUp (
280+ mod : EnvironmentModuleNode ,
281+ fn : ( mod : EnvironmentModuleNode ) => boolean ,
282+ seen : Set < EnvironmentModuleNode > ,
283+ ) : boolean {
284+ if ( seen . has ( mod ) ) return false ;
285+
286+ if ( fn ( mod ) ) return true ;
287+
288+ const importers = Array . from ( mod . importers ) ;
289+ for ( let i = 0 ; i < importers . length ; i ++ ) {
290+ const imp = importers [ i ] ;
291+ if ( walkUp ( imp , fn , seen ) ) return true ;
292+ }
293+
294+ return false ;
295+ }
0 commit comments