message: make on_demand plugin more flexible
diff: @@ -2,30 +2,26 @@ import { Page, Site } from "../core.ts";
  import { merge, mimes } from "../core/utils.ts";
  export interface Options {
  - routesFile: string;
  + /** A function to return the page file associated with the provided url */
  + router?: Router;
  }
  +export type Router = (url: URL) => Promise<string | undefined>;
  +
  // Default options
  -const defaults: Options = {
  - routesFile: "_routes.json",
  -};
  +const defaults: Options = {};
  /** A plugin to generate pages on demand in the server side */
  export default function (userOptions?: Partial<Options>) {
  const options = merge(defaults, userOptions);
  return (site: Site) => {
  - const routesFile = site.src(options.routesFile);
  - const router = new Router(routesFile);
  -
  - site.addEventListener(
  - "afterRender",
  - () => router.collectRoutes(site.pages),
  - );
  - site.addEventListener("afterBuild", () => router.saveRoutes());
  + if (!options.router) {
  + const router = new JsonRouter(site, site.src("_routes.json"));
  + options.router = router.match.bind(router);
  + }
  - site.options.watcher.ignore.push(routesFile);
  - site.options.server.router ||= (url) => serve(url, site, router);
  + site.options.server.router ||= (url) => serve(url, site, options.router!);
  };
  }
  @@ -35,7 +31,7 @@ async function serve(
  site: Site,
  router: Router,
  ): Promise<[BodyInit, ResponseInit] | undefined> {
  - const file = await router.match(url);
  + const file = await router(url);
  if (!file) {
  return;
  @@ -58,27 +54,39 @@ async function serve(
  return [body, response];
  }
  -/** Class to load and manage static routes */
  -export class Router {
  +/** Class to load and manage static routes in a JSON file
  + * Used by default if no router is provided
  + */
  +export class JsonRouter {
  /** Filename to save the {url: path} of on-demand pages */
  #routesFile: string;
  /** Pages that must be generated on demand */
  #routes?: Map<string, string>;
  - constructor(routesFile: string) {
  + constructor(site: Site, routesFile: string) {
  + // Events to collect and save the routes automatically
  + site.addEventListener(
  + "afterRender",
  + () => this.#collectRoutes(site.pages),
  + );
  + site.addEventListener("afterBuild", () => this.#saveRoutes());
  +
  + // Ignore the routes file by the watcher
  + site.options.watcher.ignore.push(routesFile);
  this.#routesFile = routesFile;
  }
  async match(url: URL): Promise<string | undefined> {
  if (!this.#routes) {
  - await this.loadRoutes();
  + await this.#loadRoutes();
  }
  return this.#routes?.get(url.pathname);
  }
  - collectRoutes(pages: Page[]): void {
  + /** Collect the routes of all pages with data.ondemand = true */
  + #collectRoutes(pages: Page[]): void {
  const routes: Map<string, string> = new Map();
  pages.forEach((page) => {
  @@ -90,7 +98,8 @@ export class Router {
  this.#routes = routes;
  }
  - async loadRoutes(): Promise<void> {
  + /** Load the routes from the routesFile */
  + async #loadRoutes(): Promise<void> {
  try {
  const pages = JSON.parse(
  await Deno.readTextFile(this.#routesFile),
  @@ -101,7 +110,8 @@ export class Router {
  }
  }
  - async saveRoutes(): Promise<void> {
  + /** Save the routes into the routesFile */
  + async #saveRoutes(): Promise<void> {
  if (!this.#routes?.size) {
  return;
  }
  