Skip to content

Commit 3d2e346

Browse files
authored
reload on config change (#1147)
* reload on config change * symmetric style
1 parent def5404 commit 3d2e346

File tree

3 files changed

+22
-12
lines changed

3 files changed

+22
-12
lines changed

src/config.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export interface Config {
5757
search: boolean; // default to false
5858
md: MarkdownIt;
5959
loaders: LoaderResolver;
60+
watchPath?: string;
6061
}
6162

6263
/**
@@ -76,17 +77,16 @@ async function importConfig(path: string): Promise<any> {
7677
}
7778

7879
export async function readConfig(configPath?: string, root?: string): Promise<Config> {
79-
if (configPath === undefined) return readDefaultConfig(root);
80-
return normalizeConfig(await importConfig(resolveConfig(configPath, root)), root);
80+
if (configPath === undefined) configPath = await resolveDefaultConfig(root);
81+
if (configPath === undefined) return normalizeConfig(undefined, root);
82+
return normalizeConfig(await importConfig(configPath), root, configPath);
8183
}
8284

83-
export async function readDefaultConfig(root?: string): Promise<Config> {
85+
async function resolveDefaultConfig(root?: string): Promise<string | undefined> {
8486
const jsPath = resolveConfig("observablehq.config.js", root);
85-
if (existsSync(jsPath)) return normalizeConfig(await importConfig(jsPath), root);
87+
if (existsSync(jsPath)) return jsPath;
8688
const tsPath = resolveConfig("observablehq.config.ts", root);
87-
if (!existsSync(tsPath)) return normalizeConfig(undefined, root);
88-
await import("tsx/esm"); // lazy tsx
89-
return normalizeConfig(await importConfig(tsPath), root);
89+
if (existsSync(tsPath)) return await import("tsx/esm"), tsPath; // lazy tsx
9090
}
9191

9292
let cachedPages: {key: string; pages: Page[]} | null = null;
@@ -127,7 +127,7 @@ export function setCurrentDate(date = new Date()): void {
127127
// module), we want to return the same Config instance.
128128
const configCache = new WeakMap<any, Config>();
129129

130-
export function normalizeConfig(spec: any = {}, defaultRoot = "docs"): Config {
130+
export function normalizeConfig(spec: any = {}, defaultRoot = "docs", watchPath?: string): Config {
131131
const cachedConfig = configCache.get(spec);
132132
if (cachedConfig) return cachedConfig;
133133
let {
@@ -184,7 +184,8 @@ export function normalizeConfig(spec: any = {}, defaultRoot = "docs"): Config {
184184
deploy,
185185
search,
186186
md,
187-
loaders: new LoaderResolver({root, interpreters})
187+
loaders: new LoaderResolver({root, interpreters}),
188+
watchPath
188189
};
189190
if (pages === undefined) Object.defineProperty(config, "pages", {get: () => readPages(root, md)});
190191
if (sidebar === undefined) Object.defineProperty(config, "sidebar", {get: () => config.pages.length > 0});

src/preview.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ function handleWatch(socket: WebSocket, req: IncomingMessage, configPromise: Pro
282282
let files: Map<string, string> | null = null;
283283
let tables: Map<string, string> | null = null;
284284
let stylesheets: string[] | null = null;
285+
let configWatcher: FSWatcher | null = null;
285286
let markdownWatcher: FSWatcher | null = null;
286287
let attachmentWatcher: FileWatchers | null = null;
287288
let emptyTimeout: ReturnType<typeof setTimeout> | null = null;
@@ -352,7 +353,7 @@ function handleWatch(socket: WebSocket, req: IncomingMessage, configPromise: Pro
352353
}
353354

354355
async function hello({path: initialPath, hash: initialHash}: {path: string; hash: string}): Promise<void> {
355-
if (markdownWatcher || attachmentWatcher) throw new Error("already watching");
356+
if (markdownWatcher || configWatcher || attachmentWatcher) throw new Error("already watching");
356357
path = decodeURI(initialPath);
357358
if (!(path = normalize(path)).startsWith("/")) throw new Error("Invalid path: " + initialPath);
358359
if (path.endsWith("/")) path += "index";
@@ -371,6 +372,7 @@ function handleWatch(socket: WebSocket, req: IncomingMessage, configPromise: Pro
371372
stylesheets = Array.from(resolvers.stylesheets, resolvers.resolveStylesheet);
372373
attachmentWatcher = await loaders.watchFiles(path, getWatchFiles(resolvers), () => watcher("change"));
373374
markdownWatcher = watch(join(root, path), (event) => watcher(event));
375+
if (config.watchPath) configWatcher = watch(config.watchPath, () => send({type: "reload"}));
374376
}
375377

376378
socket.on("message", async (data) => {
@@ -402,6 +404,10 @@ function handleWatch(socket: WebSocket, req: IncomingMessage, configPromise: Pro
402404
markdownWatcher.close();
403405
markdownWatcher = null;
404406
}
407+
if (configWatcher) {
408+
configWatcher.close();
409+
configWatcher = null;
410+
}
405411
console.log(faint("socket close"), req.url);
406412
});
407413

test/config-test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import assert from "node:assert";
2+
import {resolve} from "node:path";
23
import MarkdownIt from "markdown-it";
34
import {normalizeConfig as config, mergeToc, readConfig, setCurrentDate} from "../src/config.js";
45
import {LoaderResolver} from "../src/dataloader.js";
@@ -33,7 +34,8 @@ describe("readConfig(undefined, root)", () => {
3334
workspace: "acme",
3435
project: "bi"
3536
},
36-
search: false
37+
search: false,
38+
watchPath: resolve("test/input/build/config/observablehq.config.js")
3739
});
3840
});
3941
it("returns the default config if no config file is found", async () => {
@@ -56,7 +58,8 @@ describe("readConfig(undefined, root)", () => {
5658
footer:
5759
'Built with <a href="https://observablehq.com/" target="_blank">Observable</a> on <a title="2024-01-10T16:00:00">Jan 10, 2024</a>.',
5860
deploy: null,
59-
search: false
61+
search: false,
62+
watchPath: undefined
6063
});
6164
});
6265
});

0 commit comments

Comments
 (0)