1
1
import type http from 'node:http' ;
2
2
import path from 'node:path' ;
3
- import { Server as SocketServer , type Socket } from 'socket.io' ;
4
3
import { watch } from 'chokidar' ;
5
4
import debounce from 'debounce' ;
5
+ import { type Socket , Server as SocketServer } from 'socket.io' ;
6
6
import type { HotReloadChange } from '../../../../utils/types/hot-reload-change' ;
7
7
import { createDependencyGraph } from './create-dependency-graph' ;
8
8
import logger from '~/lib/logger' ;
9
9
import chalk from 'chalk' ;
10
10
11
11
export const setupHotreloading = async (
12
12
devServer : http . Server ,
13
- emailDirRelativePath : string ,
13
+ documentsDirRelativePath : string ,
14
14
) => {
15
15
let clients : Socket [ ] = [ ] ;
16
16
const io = new SocketServer ( devServer ) ;
@@ -23,33 +23,6 @@ export const setupHotreloading = async (
23
23
} ) ;
24
24
} ) ;
25
25
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
-
53
26
// used to keep track of all changes
54
27
// and send them at once to the preview app through the web socket
55
28
let changes = [ ] as HotReloadChange [ ] ;
@@ -64,20 +37,25 @@ export const setupHotreloading = async (
64
37
)
65
38
) ;
66
39
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
+ }
77
49
78
50
changes = [ ] ;
51
+
79
52
} , 150 ) ;
80
53
54
+ const absolutePathToDocumentsDirectory = path . resolve (
55
+ process . cwd ( ) ,
56
+ documentsDirRelativePath ,
57
+ ) ;
58
+
81
59
const [ dependencyGraph , updateDependencyGraph ] = await createDependencyGraph (
82
60
absolutePathToDocumentsDirectory ,
83
61
) ;
@@ -97,42 +75,80 @@ export const setupHotreloading = async (
97
75
return dependentPaths ;
98
76
} ;
99
77
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
+
100
109
watcher . on ( 'all' , async ( event , relativePathToChangeTarget ) => {
101
110
const file = relativePathToChangeTarget . split ( path . sep ) ;
102
111
if ( file . length === 0 ) {
103
112
return ;
104
113
}
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 ;
105
138
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
+ } ) ;
121
143
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 ) ) {
130
147
changes . push ( {
131
- event,
132
- filename : relativePathToChangeTarget ,
148
+ event : 'change' as const ,
149
+ filename : path . relative ( absolutePathToDocumentsDirectory , dependentPath ) ,
133
150
} ) ;
134
151
}
135
-
136
152
reload ( ) ;
137
153
} ) ;
138
154
0 commit comments