From 90baa35c7cdc4e4961ce26823f3e3bea346340f5 Mon Sep 17 00:00:00 2001 From: hypercross Date: Mon, 10 Aug 2026 00:06:43 +0800 Subject: [PATCH] fix(bgm): hot-reload markdown definition edits Normalize the games root to POSIX separators so the HMR path guard matches on Windows, and watch the real on-disk markdown files (which defMap.files omits in favor of their virtual code blocks) so edits trigger a re-collect instead of a manual restart. --- packages/bgm/src/vite.ts | 17 ++++++++++++++--- packages/bgm/src/vite.unit.test.ts | 5 ++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/bgm/src/vite.ts b/packages/bgm/src/vite.ts index 3314da1..7181f49 100644 --- a/packages/bgm/src/vite.ts +++ b/packages/bgm/src/vite.ts @@ -16,8 +16,9 @@ * hot-reloads the app via `addWatchFile`. */ import * as path from 'node:path'; -import type { ModuleNode, Plugin } from 'vite'; +import { normalizePath, type ModuleNode, type Plugin } from 'vite'; import { collectPackages, loadDefs } from './collect.js'; +import { readDefFiles } from './parse.js'; import type { Package, SerializedPackage } from './types.js'; const VIRTUAL_PREFIX = '\0bgm:'; @@ -32,7 +33,10 @@ export interface BgmOptions { } export function bgm(options: BgmOptions): Plugin { - const root = options.root; + // Vite normalizes `ctx.file` to POSIX separators before HMR hooks run, but + // `fileURLToPath` retains backslashes on Windows. Normalize `root` to the + // same form so `ctx.file.startsWith(root)` matches regardless of platform. + const root = normalizePath(options.root); const collect = (): Package[] => { const defMap = loadDefs('', root); @@ -42,11 +46,18 @@ export function bgm(options: BgmOptions): Plugin { return { name: 'bgm', buildStart() { - // Watch every source file so edits trigger a reload/re-collect. + // Watch every real definition source under the games root so edits + // trigger a re-collect. `defMap.files` only holds the virtual code-block + // files plus real non-markdown files; the markdown files themselves are + // consumed for their code blocks and never appear there, so watch the + // real files on disk too (markdown and anything else the loader reads). const defMap = loadDefs('', root); for (const name of defMap.files.keys()) { this.addWatchFile(path.join(root, name)); } + for (const file of readDefFiles(root, '')) { + this.addWatchFile(file.source); + } }, resolveId(id) { if (id === PACKAGES) return VIRTUAL_PREFIX + PACKAGES; diff --git a/packages/bgm/src/vite.unit.test.ts b/packages/bgm/src/vite.unit.test.ts index fca3923..2e8b38e 100644 --- a/packages/bgm/src/vite.unit.test.ts +++ b/packages/bgm/src/vite.unit.test.ts @@ -85,10 +85,13 @@ describe('bgm vite plugin', () => { (plugin.buildStart as Callable).call(context, {} as never); // Every def file (real + virtual code blocks) is watched so edits - // trigger a re-collect. + // trigger a re-collect. The real markdown source must be watched too: + // `defMap.files` only lists virtual code-block files, so without watching + // the on-disk `.md` file the dev server would never notice an edit. expect(watched.length).toBeGreaterThan(0); expect(watched.some((f) => f.endsWith('.yaml'))).toBe(true); expect(watched.some((f) => f.endsWith('.csv'))).toBe(true); + expect(watched.some((f) => f.endsWith('.md'))).toBe(true); expect(watched.every((f) => path.isAbsolute(f))).toBe(true); }); });