| 1 | // supervise wraps the entire process in order to allow @clo/lib/incremental to |
| 2 | // restart the entire process if this file or node_modules changes, all without |
| 3 | // losing incremental state where it matters. |
| 4 | await incremental.supervise(async () => { |
| 5 | const graph = new incremental.Graph({ |
| 6 | db: ".clover/incremental-markdown.db", |
| 7 | root: import.meta.dirname, |
| 8 | }); |
| 9 | |
| 10 | for await (const result of graph.incrementalWatch({ buildAndWriteOutput, progress })) { |
| 11 | // errors stay listed until they are fixed, even when cached |
| 12 | if (!result.ok) console.error(result.error); |
| 13 | log.info(result.statusLine); |
| 14 | } |
| 15 | }); |
| 16 | |
| 17 | async function buildAndWriteOutput(ref: incremental.Ref) { |
| 18 | using incr = ref.start("build markdown blog"); // see @clo/lib/progress |
| 19 | |
| 20 | // First, place a dependency on the contents of `src/posts`. |
| 21 | const files = await incr.step(async (b) => (await b.readDir("posts")).filter(x => x.endsWith(".md"))).read(); |
| 22 | |
| 23 | // Then, build every page as it's own step. |
| 24 | const pages = await Promise.all(files.map(async (file) => { |
| 25 | const { text, title } = await incr.step(renderPage, `posts/${file}`).read(); |
| 26 | const base = file.replace(/\.md$/, ""); |
| 27 | await incr.writeFile(`out/${base}.html`, text); |
| 28 | return { title, url: `${base}.html` }; |
| 29 | })); |
| 30 | |
| 31 | // Then, build the homepage and write it out. |
| 32 | const index = incr.step(renderHomepage, pages); |
| 33 | await incr.writeFile("out/index.html", index); |
| 34 | } |
| 35 | |
| 36 | async function renderPage( |
| 37 | b: incremental.Build, |
| 38 | file: string, |
| 39 | ) { |
| 40 | using _ = b.start(`Render ${file}`); |
| 41 | log.info(`renderPage(${file})`); |
| 42 | |
| 43 | await async.delay(500 + Math.random() * 500); |
| 44 | |
| 45 | // Place a dependency on the markdown library. Similarly, this could be |
| 46 | // importing the page itself if it was code. |
| 47 | const lib = await b.import<typeof import("./formatter.ts")>("formatter.ts"); |
| 48 | // Place a dependency on the page's file contents. |
| 49 | const source = await b.readFile(file).text(); |
| 50 | |
| 51 | return lib.formatPage(source); |
| 52 | } |
| 53 | |
| 54 | async function renderHomepage( |
| 55 | b: incremental.Build, |
| 56 | pages: Array<Page>, |
| 57 | ) { |
| 58 | using _ = b.start(`Build homepage`); |
| 59 | |
| 60 | const lib = await b.import<typeof import("./formatter.ts")>("formatter.ts"); |
| 61 | const { text } = await renderPage(b, "index.md"); |
| 62 | return text.replace("%index%", lib.formatList(pages)); |
| 63 | } |
| 64 | |
| 65 | import * as async from "@clo/lib/async"; |
| 66 | import * as incremental from "@clo/lib/incremental"; |
| 67 | import * as log from "@clo/lib/log"; |
| 68 | import * as progress from "@clo/lib/progress"; |
| 69 | import type { Page } from "./formatter.ts"; |