Very Popular
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155import { merge } from "../core/utils.ts";import onDemand, { getRouter, MiddlewareOptions,} from "../middlewares/on_demand.ts";import { posix } from "../deps/path.ts";
import type { Logger, Page, Site } from "../core.ts";
export interface Options { /** The file path to save the routes */ routesPath: string;
/** The file path to save the preloaded modules */ preloadPath: string;
/** Extra data to pass to the pages */ extraData?: (request: Request) => Record<string, unknown>;}
// Default optionsexport const defaults: Options = { routesPath: "/_routes.json", preloadPath: "/_preload.ts",};
/** 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 collector = new JsonRouterCollector({ root: site.root(), routesPath: options.routesPath, preloadPath: options.preloadPath, src: site.options.src, });
// Collect and save the routes automatically site.addEventListener("beforeSave", async () => { collector.collectRoutes(site.onDemandPages); const specifiers: string[] = [];
for (const [path, entry] of site.fs.entries) { const ext = posix.extname(path);
switch (ext) { case ".ts": case ".tsx": case ".js": case ".jsx": case ".mjs": specifiers.push(entry.flags.has("remote") ? entry.src : path); break;
default: break; } } await collector.saveRoutes(site.logger, specifiers); });
// Ignore the routes files by the watcher site.options.watcher.ignore.push(options.routesPath); site.options.watcher.ignore.push(options.preloadPath);
// Add the ondemand middleware site.options.server.middlewares ||= [];
site._data.on_demand = { extraData: options.extraData, routesFile: site.root(options.routesPath), } as MiddlewareOptions;
site.options.server.middlewares.push(onDemand({ site, router: getRouter(collector.routes), })); };}
interface JsonRouterCollectorOptions { root: string; routesPath: string; preloadPath: string; src: string;}
/** * Class to load and manage static routes in a JSON file * Used by default if no router is provided */export class JsonRouterCollector { options: JsonRouterCollectorOptions;
/** Pages that must be generated on demand */ routes = new Map<string, string>();
constructor(options: JsonRouterCollectorOptions) { this.options = options; }
/** Collect the routes of all pages with data.ondemand = true */ collectRoutes(pages: Page[]): void { this.routes.clear();
pages.forEach((page) => { this.routes.set( page.data.url as string, page.src.path + (page.src.ext || ""), ); }); }
/** Save the routes into the routesFile */ async saveRoutes(logger: Logger, specifiers: string[]): Promise<void> { if (!this.routes.size) { return; }
const routesFile = posix.join(this.options.root, this.options.routesPath); const preloadFile = posix.join(this.options.root, this.options.preloadPath); const data: Record<string, string> = {};
this.routes.forEach((path, url) => { data[url] = path; });
// Write the routes file await Deno.writeTextFile(routesFile, `${JSON.stringify(data, null, 2)}\n`);
logger.log(`Routes saved at <dim>${routesFile}</dim>`);
// Write the preload file if (specifiers.length && Object.keys(data).length) { const code = [ "/**", " * Don't execute this function", " * It's just statically analyzable so dynamic imports work on Deno Deploy", " * @see https://deno.com/deploy/changelog#statically-analyzable-dynamic-imports", " */", "export function toStaticallyAnalyzableDynamicImports() {", ...specifiers.map((path) => ` import("./${posix.join(".", this.options.src, path)}");` ), "}", "", ].join("\n");
await Deno.writeTextFile(preloadFile, code); logger.log(`Preloader saved at <dim>${preloadFile}</dim>`); } }}