Skip to content

Commit c5daa3b

Browse files
authored
feat(jsx-email): reimplement template dependency watching in preview (#286)
1 parent 567b4fb commit c5daa3b

File tree

1 file changed

+74
-51
lines changed

1 file changed

+74
-51
lines changed

packages/jsx-email/src/cli/watcher.mts

Lines changed: 74 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -56,49 +56,52 @@ const getEntrypoints = async (files: BuildTempatesResult[]) => {
5656
return Array.from(entrypoints).filter(Boolean);
5757
};
5858

59-
const getWatchPaths = async (files: BuildTempatesResult[]) => {
59+
const getWatchDirectories = async (files: BuildTempatesResult[], depPaths: string[]) => {
6060
const entrypoints = await getEntrypoints(files);
61-
const paths = entrypoints.map((path) => dirname(path));
61+
const paths = [
62+
...entrypoints.map((path) => dirname(path)),
63+
...depPaths.map((path) => dirname(path))
64+
];
6265
const uniquePaths = Array.from(new Set(paths));
6366
const watchPaths = removeChildPaths(uniquePaths);
6467

6568
log.debug({ watchPaths });
6669

67-
return watchPaths;
70+
return { entrypoints, watchPaths };
6871
};
6972

70-
// const mapDeps = async (files: BuildTempatesResult[]) => {
71-
// const depPaths: string[] = [];
72-
// const metaReads = files.map(async ({ metaPath }) => {
73-
// log.debug({ exists: await exists(metaPath ?? ''), metaPath });
74-
75-
// if (!metaPath || !(await exists(metaPath))) return null;
76-
// const contents = await readFile(metaPath, 'utf-8');
77-
// const metafile = JSON.parse(contents) as Metafile;
78-
// const { outputs } = metafile;
79-
// const result = new Map<string, Set<string>>();
80-
81-
// Object.entries(outputs).forEach(([_, meat]) => {
82-
// const { entryPoint, inputs } = meat;
83-
// const resolvedEntry = resolve(originalCwd, entryPoint!);
84-
// depPaths.push(resolvedEntry);
85-
86-
// for (const dep of Object.keys(inputs)) {
87-
// const resolvedDepPath = resolve(originalCwd, dep);
88-
// const set = result.get(resolvedDepPath) ?? new Set();
73+
const mapDeps = async (files: BuildTempatesResult[]) => {
74+
const depPaths: string[] = [];
75+
const metaReads = files.map(async ({ metaPath }) => {
76+
log.debug({ exists: await exists(metaPath ?? ''), metaPath });
8977

90-
// depPaths.push(resolvedDepPath);
91-
// set.add(resolvedEntry);
92-
// result.set(resolvedDepPath, set);
93-
// }
94-
// });
78+
if (!metaPath || !(await exists(metaPath))) return null;
79+
const contents = await readFile(metaPath, 'utf-8');
80+
const metafile = JSON.parse(contents) as Metafile;
81+
const { outputs } = metafile;
82+
const result = new Map<string, Set<string>>();
83+
84+
Object.entries(outputs).forEach(([_, meat]) => {
85+
const { entryPoint, inputs } = meat;
86+
const resolvedEntry = resolve(originalCwd, entryPoint!);
87+
depPaths.push(resolvedEntry);
88+
89+
for (const dep of Object.keys(inputs)) {
90+
const resolvedDepPath = resolve(originalCwd, dep);
91+
const set = result.get(resolvedDepPath) ?? new Set();
92+
93+
depPaths.push(resolvedDepPath);
94+
set.add(resolvedEntry);
95+
result.set(resolvedDepPath, set);
96+
}
97+
});
9598

96-
// return result;
97-
// });
98-
// const deps = (await Promise.all(metaReads)).filter(Boolean);
99+
return result;
100+
});
101+
const deps = (await Promise.all(metaReads)).filter(Boolean);
99102

100-
// return { depPaths, deps };
101-
// };
103+
return { depPaths, deps };
104+
};
102105

103106
export const watch = async (args: WatchArgs) => {
104107
newline();
@@ -107,22 +110,40 @@ export const watch = async (args: WatchArgs) => {
107110
const { common, files, server } = args;
108111
const { argv } = common;
109112
const extensions = ['.css', '.js', '.jsx', '.ts', '.tsx'];
110-
const watchPaths = await getWatchPaths(files);
111-
112-
// const { depPaths, deps: metaDeps } = await mapDeps(files);
113-
// const templateDeps = new Map<string, Set<string>>();
114-
115-
// for (const map of metaDeps) {
116-
// map!.forEach((value, key) => templateDeps.set(key, value));
117-
// }
113+
const { depPaths, deps: metaDeps } = await mapDeps(files);
114+
const dependencyPaths = depPaths.filter((path) => !path.includes('/node_modules/'));
115+
const { entrypoints, watchPaths: watchDirectories } = await getWatchDirectories(
116+
files,
117+
dependencyPaths
118+
);
119+
const templateDeps = new Map<string, Set<string>>();
120+
const validFiles = Array.from(new Set([...entrypoints, ...dependencyPaths]));
121+
122+
for (const map of metaDeps) {
123+
map!.forEach((value, key) => templateDeps.set(key, value));
124+
}
125+
126+
log.info({ validFiles });
127+
128+
const handler: watcher.SubscribeCallback = async (_, incoming) => {
129+
// Note: We perform this filter in case someone has a dependency of a template,
130+
// or has templates, at a path that includes node_modules. We also don't any
131+
// non-template files having builds attempted on them, so check to make sure
132+
// the event path is in the set of files we want to watch, unless it's a create
133+
// event
134+
const events = incoming.filter((event) => {
135+
if (event.path.includes('/node_modules/')) return false;
136+
if (event.type !== 'create') return validFiles.includes(event.path);
137+
return true;
138+
});
118139

119-
const handler: watcher.SubscribeCallback = async (_, events) => {
120140
const changedFiles = events
121141
.filter((event) => event.type !== 'create' && event.type !== 'delete')
122142
.map((e) => e.path)
123143
.filter((path) => extensions.includes(extname(path)));
124-
const templateFileNames = files.map((file) => file.fileName);
125-
const changedTemplates = changedFiles.filter((file) => templateFileNames.includes(file));
144+
const changedTemplates = changedFiles
145+
.flatMap((file) => [...(templateDeps.get(file) || [])])
146+
.filter(Boolean);
126147
const createdFiles = events
127148
.filter((event) => event.type === 'create')
128149
.map((e) => e.path)
@@ -149,13 +170,16 @@ export const watch = async (args: WatchArgs) => {
149170
);
150171

151172
deletedFiles.forEach((path) => {
152-
const index = files.findIndex(({ fileName }) => path === fileName);
173+
let index: any = files.findIndex(({ fileName }) => path === fileName);
153174
if (index === -1) return;
154175
const file = files[index];
155176
files.splice(index, 1);
156177
// Note: Don't await either, we don't need to
157178
unlink(file.compiledPath);
158179
unlink(`${file.writePathBase}.js`);
180+
181+
index = validFiles.find((fileName) => path === fileName);
182+
if (index > -1) validFiles.splice(index, 1);
159183
});
160184
}
161185

@@ -177,7 +201,11 @@ export const watch = async (args: WatchArgs) => {
177201
targetPath: path
178202
});
179203

204+
const mappedDeps = await mapDeps(results);
180205
files.push(...results);
206+
validFiles.push(
207+
...[path, ...mappedDeps.depPaths.filter((p) => !p.includes('/node_modules/'))]
208+
);
181209

182210
await writePreviewDataFiles(results);
183211
})
@@ -200,14 +228,9 @@ export const watch = async (args: WatchArgs) => {
200228
});
201229
};
202230

203-
// const watchPathSet = new Set([
204-
// ...depPaths.filter((path) => !path.includes('/node_modules/')).map((path) => dirname(path))
205-
// ]);
206-
// const watchPaths = removeChildPaths([...watchPathSet]);
207-
208-
log.debug('Watching Paths:', watchPaths.sort());
231+
log.debug('Watching Paths:', watchDirectories.sort());
209232

210-
const subPromises = watchPaths.map((path) => watcher.subscribe(path, handler));
233+
const subPromises = watchDirectories.map((path) => watcher.subscribe(path, handler));
211234
const subscriptions = await Promise.all(subPromises);
212235

213236
server.httpServer!.on('close', () => {

0 commit comments

Comments
 (0)