Skip to content

Commit 7a5492d

Browse files
committed
fix dependent hot reloading
1 parent e7dae3a commit 7a5492d

File tree

2 files changed

+86
-65
lines changed

2 files changed

+86
-65
lines changed

.changeset/afraid-grapes-hide.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"htmldocs": patch
3+
---
4+
5+
fix dependent hot reloading

packages/htmldocs/src/cli/utils/preview/hot-reloading/setup-hot-reloading.ts

+81-65
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import type http from 'node:http';
22
import path from 'node:path';
3-
import { Server as SocketServer, type Socket } from 'socket.io';
43
import { watch } from 'chokidar';
54
import debounce from 'debounce';
5+
import { type Socket, Server as SocketServer } from 'socket.io';
66
import type { HotReloadChange } from '../../../../utils/types/hot-reload-change';
77
import { createDependencyGraph } from './create-dependency-graph';
88
import logger from '~/lib/logger';
99
import chalk from 'chalk';
1010

1111
export 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

Comments
 (0)