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.
This commit is contained in:
@@ -16,8 +16,9 @@
|
|||||||
* hot-reloads the app via `addWatchFile`.
|
* hot-reloads the app via `addWatchFile`.
|
||||||
*/
|
*/
|
||||||
import * as path from 'node:path';
|
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 { collectPackages, loadDefs } from './collect.js';
|
||||||
|
import { readDefFiles } from './parse.js';
|
||||||
import type { Package, SerializedPackage } from './types.js';
|
import type { Package, SerializedPackage } from './types.js';
|
||||||
|
|
||||||
const VIRTUAL_PREFIX = '\0bgm:';
|
const VIRTUAL_PREFIX = '\0bgm:';
|
||||||
@@ -32,7 +33,10 @@ export interface BgmOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function bgm(options: BgmOptions): Plugin {
|
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 collect = (): Package[] => {
|
||||||
const defMap = loadDefs('', root);
|
const defMap = loadDefs('', root);
|
||||||
@@ -42,11 +46,18 @@ export function bgm(options: BgmOptions): Plugin {
|
|||||||
return {
|
return {
|
||||||
name: 'bgm',
|
name: 'bgm',
|
||||||
buildStart() {
|
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);
|
const defMap = loadDefs('', root);
|
||||||
for (const name of defMap.files.keys()) {
|
for (const name of defMap.files.keys()) {
|
||||||
this.addWatchFile(path.join(root, name));
|
this.addWatchFile(path.join(root, name));
|
||||||
}
|
}
|
||||||
|
for (const file of readDefFiles(root, '')) {
|
||||||
|
this.addWatchFile(file.source);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
resolveId(id) {
|
resolveId(id) {
|
||||||
if (id === PACKAGES) return VIRTUAL_PREFIX + PACKAGES;
|
if (id === PACKAGES) return VIRTUAL_PREFIX + PACKAGES;
|
||||||
|
|||||||
@@ -85,10 +85,13 @@ describe('bgm vite plugin', () => {
|
|||||||
(plugin.buildStart as Callable<typeof plugin.buildStart>).call(context, {} as never);
|
(plugin.buildStart as Callable<typeof plugin.buildStart>).call(context, {} as never);
|
||||||
|
|
||||||
// Every def file (real + virtual code blocks) is watched so edits
|
// 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.length).toBeGreaterThan(0);
|
||||||
expect(watched.some((f) => f.endsWith('.yaml'))).toBe(true);
|
expect(watched.some((f) => f.endsWith('.yaml'))).toBe(true);
|
||||||
expect(watched.some((f) => f.endsWith('.csv'))).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);
|
expect(watched.every((f) => path.isAbsolute(f))).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user