11import type http from 'node:http' ;
22import path from 'node:path' ;
3- import { Server as SocketServer , type Socket } from 'socket.io' ;
43import { watch } from 'chokidar' ;
54import debounce from 'debounce' ;
5+ import { type Socket , Server as SocketServer } from 'socket.io' ;
66import type { HotReloadChange } from '../../../../utils/types/hot-reload-change' ;
77import { createDependencyGraph } from './create-dependency-graph' ;
88import logger from '~/lib/logger' ;
99import chalk from 'chalk' ;
1010
1111export const setupHotreloading = async (
1212 devServer : http . Server ,
13- emailDirRelativePath : string ,
13+ documentsDirRelativePath : string ,
1414) => {
1515 let clients : Socket [ ] = [ ] ;
1616 const io = new SocketServer ( devServer ) ;
@@ -23,33 +23,6 @@ export const setupHotreloading = async (
2323 } ) ;
2424 } ) ;
2525
26- const projectRoot = process . cwd ( ) ;
27- const absolutePathToDocumentsDirectory = path . resolve (
28- projectRoot ,
29- emailDirRelativePath ,
30- ) ;
31-
32- logger . info ( `Watching project root at ${ projectRoot } ` ) ;
33-
34- const watcher = watch ( '**/*' , {
35- ignoreInitial : true ,
36- cwd : projectRoot ,
37- ignored : [
38- '**/node_modules/**' ,
39- '**/.git/**' ,
40- '**/dist/**' ,
41- '**/build/**' ,
42- '**/.next/**' ,
43- '**/coverage/**' ,
44- ] ,
45- } ) ;
46-
47- const exit = ( ) => {
48- void watcher . close ( ) ;
49- } ;
50- process . on ( 'SIGINT' , exit ) ;
51- // process.on('uncaughtException', exit);
52-
5326 // used to keep track of all changes
5427 // and send them at once to the preview app through the web socket
5528 let changes = [ ] as HotReloadChange [ ] ;
@@ -64,20 +37,25 @@ export const setupHotreloading = async (
6437 )
6538 ) ;
6639
67- // Log changes only once, not per client
68- uniqueChanges . forEach ( change => {
69- logger . info ( `${ chalk . yellow ( '!' ) } ${ chalk . gray ( `Changes detected in ${ path . basename ( change . filename ) } , reloading...` ) } ` ) ;
70- } ) ;
71-
72- // Send changes to all clients
73- clients . forEach ( ( client ) => {
74- logger . debug ( `Emitting reload to ${ client . id } ` ) ;
75- client . emit ( 'reload' , uniqueChanges ) ;
76- } ) ;
40+ if ( uniqueChanges . length > 0 ) {
41+ logger . info ( `${ chalk . yellow ( '!' ) } ${ chalk . gray ( `Changes detected, reloading...` ) } ` ) ;
42+
43+ // Send changes to all clients
44+ clients . forEach ( ( client ) => {
45+ logger . debug ( `Emitting reload to ${ client . id } ` ) ;
46+ client . emit ( 'reload' , uniqueChanges ) ;
47+ } ) ;
48+ }
7749
7850 changes = [ ] ;
51+
7952 } , 150 ) ;
8053
54+ const absolutePathToDocumentsDirectory = path . resolve (
55+ process . cwd ( ) ,
56+ documentsDirRelativePath ,
57+ ) ;
58+
8159 const [ dependencyGraph , updateDependencyGraph ] = await createDependencyGraph (
8260 absolutePathToDocumentsDirectory ,
8361 ) ;
@@ -97,42 +75,80 @@ export const setupHotreloading = async (
9775 return dependentPaths ;
9876 } ;
9977
78+ const getFilesOutsideDocumentsDirectory = ( ) =>
79+ Object . keys ( dependencyGraph ) . filter ( ( p ) =>
80+ path . relative ( absolutePathToDocumentsDirectory , p ) . startsWith ( '..' ) ,
81+ ) ;
82+ let filesOutsideDocumentsDirectory = getFilesOutsideDocumentsDirectory ( ) ;
83+
84+ const watcher = watch ( '' , {
85+ ignoreInitial : true ,
86+ cwd : absolutePathToDocumentsDirectory ,
87+ ignored : [
88+ '**/node_modules/**' ,
89+ '**/.git/**' ,
90+ '**/dist/**' ,
91+ '**/build/**' ,
92+ '**/.next/**' ,
93+ '**/coverage/**' ,
94+ ] ,
95+ } ) ;
96+
97+ // adds in to be watched separately all of the files that are outside of
98+ // the user's documents directory
99+ for ( const p of filesOutsideDocumentsDirectory ) {
100+ watcher . add ( p ) ;
101+ }
102+
103+ const exit = async ( ) => {
104+ await watcher . close ( ) ;
105+ } ;
106+ process . on ( 'SIGINT' , exit ) ;
107+ process . on ( 'uncaughtException' , exit ) ;
108+
100109 watcher . on ( 'all' , async ( event , relativePathToChangeTarget ) => {
101110 const file = relativePathToChangeTarget . split ( path . sep ) ;
102111 if ( file . length === 0 ) {
103112 return ;
104113 }
114+ const pathToChangeTarget = path . resolve (
115+ absolutePathToDocumentsDirectory ,
116+ relativePathToChangeTarget ,
117+ ) ;
118+
119+ await updateDependencyGraph ( event , pathToChangeTarget ) ;
120+
121+ const newFilesOutsideDocumentsDirectory = getFilesOutsideDocumentsDirectory ( ) ;
122+ // updates the files outside of the user's documents directory by unwatching
123+ // the inexistant ones and watching the new ones
124+ //
125+ // this is necessary to avoid missing dependencies that are imported from outside
126+ // the documents directory
127+ for ( const p of filesOutsideDocumentsDirectory ) {
128+ if ( ! newFilesOutsideDocumentsDirectory . includes ( p ) ) {
129+ watcher . unwatch ( p ) ;
130+ }
131+ }
132+ for ( const p of newFilesOutsideDocumentsDirectory ) {
133+ if ( ! filesOutsideDocumentsDirectory . includes ( p ) ) {
134+ watcher . add ( p ) ;
135+ }
136+ }
137+ filesOutsideDocumentsDirectory = newFilesOutsideDocumentsDirectory ;
105138
106- logger . debug ( `Detected ${ event } in ${ relativePathToChangeTarget } ` ) ;
107-
108- // Convert the path to be relative to the documents directory if it's within it
109- const absolutePathToChangeTarget = path . resolve ( projectRoot , relativePathToChangeTarget ) ;
110- const isInDocumentsDirectory = absolutePathToChangeTarget . startsWith ( absolutePathToDocumentsDirectory ) ;
111-
112- if ( isInDocumentsDirectory ) {
113- const pathRelativeToDocuments = path . relative ( absolutePathToDocumentsDirectory , absolutePathToChangeTarget ) ;
114-
115- await updateDependencyGraph ( event , absolutePathToChangeTarget ) ;
116-
117- changes . push ( {
118- event,
119- filename : pathRelativeToDocuments ,
120- } ) ;
139+ changes . push ( {
140+ event,
141+ filename : relativePathToChangeTarget ,
142+ } ) ;
121143
122- for ( const dependentPath of resolveDependentsOf ( absolutePathToChangeTarget ) ) {
123- changes . push ( {
124- event : 'change' as const ,
125- filename : path . relative ( absolutePathToDocumentsDirectory , dependentPath ) ,
126- } ) ;
127- }
128- } else {
129- // For files outside documents directory, just notify of the change relative to project root
144+ // These dependents are dependents resolved recursively, so even dependents of dependents
145+ // will be notified of this change so that we ensure that things are updated in the preview.
146+ for ( const dependentPath of resolveDependentsOf ( pathToChangeTarget ) ) {
130147 changes . push ( {
131- event,
132- filename : relativePathToChangeTarget ,
148+ event : 'change' as const ,
149+ filename : path . relative ( absolutePathToDocumentsDirectory , dependentPath ) ,
133150 } ) ;
134151 }
135-
136152 reload ( ) ;
137153 } ) ;
138154
0 commit comments