diff --git a/src/esbuild_plugins/build_routes.ts b/src/esbuild_plugins/build_routes.ts index 901f9fb..854f24e 100644 --- a/src/esbuild_plugins/build_routes.ts +++ b/src/esbuild_plugins/build_routes.ts @@ -1,4 +1,4 @@ -import type { Plugin } from "esbuild"; +import type { OutputFile, Plugin } from "esbuild"; import { dirname } from "@std/path/dirname"; import { ensureDir } from "@std/fs/ensure-dir"; import { get_route_html } from "./get_route_html.ts"; @@ -9,6 +9,48 @@ interface SSROutput { css?: { code: string }; } +const FAILURE_FLAG = ""; + +/** safely render Svelte components */ +async function render( + { text }: OutputFile, +): Promise<{ html: string; css: string; head: string }> { + try { + const module = await import( + "data:application/javascript," + encodeURIComponent(text) + ) as { + default: { + render(): SSROutput; + }; + }; + + const { html, css: raw_css, head: raw_head } = module.default.render(); + + const css = raw_css?.code ?? ""; + + // remove any duplicate module imports (in cases where a page uses an island more than once) + const modules = new Set(); + const head = raw_head.replace( + //g, + (module) => { + if (modules.has(module)) { + return ""; + } + modules.add(module); + return module; + }, + ); + + return { html, css, head }; + } catch (error) { + return { + html: `${FAILURE_FLAG}

ERROR

${String(error)}
`, + head: "", + css: "", + }; + } +} + export const build_routes: Plugin = { name: "mononykus/build-routes", setup(build) { @@ -17,51 +59,32 @@ export const build_routes: Plugin = { const routes = result.outputFiles ?? []; - await Promise.allSettled(routes.map(async (route) => { - const module = await import( - "data:application/javascript," + encodeURIComponent(route.text) - ) as { - default: { - render(): SSROutput; - }; - }; - - const { html, css: _css, head } = module.default.render(); - - // remove any duplicate module imports (in cases where a page uses an island more than once) - const modules = new Set(); - const deduped_head = head.replace( - //g, - (module) => { - if (modules.has(module)) { - return ""; - } - modules.add(module); - return module; - }, - ); - - const css = _css?.code ?? ""; - + const results = await Promise.all(routes.map(async (route) => { const dist_path = route.path.replace(".js", ".html"); await ensureDir(dirname(dist_path)); + const template = await render(route); + await Deno.writeTextFile( dist_path, - await get_route_html({ html, css, head: deduped_head }), + await get_route_html(template), ); - })).then((results) => { - const { length } = results.filter(({ status }) => - status === "rejected" - ); - if (length > 0) console.log(`Failed to build ${length} routes.`); - }); + + return template.html.startsWith(FAILURE_FLAG) ? dist_path : undefined; + })); console.log( `Built ${routes.length} routes in ${ Math.ceil(performance.now() - start) }ms`, ); + + const failures = results.filter((result) => !!result); + if (failures.length > 0) { + console.warn( + ["–––", "Failed to build some routes:", ...failures].join("\n"), + ); + } }); }, };