diff --git a/lib/cache.ts b/lib/cache.ts new file mode 100644 index 0000000..4989a7f --- /dev/null +++ b/lib/cache.ts @@ -0,0 +1,32 @@ +import {hash} from "./hash.ts"; + +const cacheKeys = new Map(); + +/** + * Check if the content of a file has changed since the last time it was checked. + * Stores the hash of the content in a cache to compare it later. + * + * @param path the path to the file + * @param content the content of the file + * @returns an object with the cache key and a boolean indicating if the content has changed since the last time it was checked + * + * @example + * ```ts + * const {cacheKey, shouldRecompile} = checkForChanges( + * "file.txt", + * "Hello, world!" + * ); + * + * if (shouldRecompile) { + * // Recompile the file + * // ... +* } + * console.log("The cache key is", cacheKey); + * ``` + */ +export function checkForChanges(path: string, content: string) { + const cacheKey = hash(content) + const shouldRecompile = cacheKeys.get(path) !== cacheKey; + cacheKeys.set(path, cacheKey); + return {cacheKey, shouldRecompile}; +} \ No newline at end of file diff --git a/lib/hash.ts b/lib/hash.ts new file mode 100644 index 0000000..c588f78 --- /dev/null +++ b/lib/hash.ts @@ -0,0 +1,12 @@ +/** + * Hash a string + * @param s + */ +export function hash(s: string) { + let hash = 0; + for (let i = 0; i < s.length; i++) { + hash = ((hash << 5) - hash) + s.charCodeAt(i); + hash |= 0; + } + return hash.toString(16); +} \ No newline at end of file diff --git a/lib/metadata.ts b/lib/metadata.ts index d370aec..3c44409 100644 --- a/lib/metadata.ts +++ b/lib/metadata.ts @@ -15,13 +15,25 @@ export const MetadataSchema = z.object({ export type Metadata = z.infer; -export async function readMetadata(filePath: string): Promise { - return { - title: await readMetadataField(filePath, "title"), - abbreviation: await readMetadataField(filePath, "abbreviation"), - resolution: await readMetadataField(filePath, "resolution"), - inEffect: await readMetadataField(filePath, "in-effect"), - }; +const metadataCache = new Map(); + +/** + * Reads metadata from a file using the typst CLI. + * The metadata is cached to avoid reading the same file multiple times, which is slow. + * @param filePath The path to the file to read metadata from + * @param cacheKey A key to use for caching the metadata. + * This should be a hash of the file content. + */ +export async function readMetadata(filePath: string, cacheKey: string): Promise { + if (!metadataCache.has(cacheKey)) { + metadataCache.set(cacheKey, { + title: await readMetadataField(filePath, "title"), + abbreviation: await readMetadataField(filePath, "abbreviation"), + resolution: await readMetadataField(filePath, "resolution"), + inEffect: await readMetadataField(filePath, "in-effect"), + }); + } + return metadataCache.get(cacheKey)!; } export async function readMetadataField( diff --git a/lib/typst-delegis.ts b/lib/typst-delegis.ts index d276e7b..864620e 100644 --- a/lib/typst-delegis.ts +++ b/lib/typst-delegis.ts @@ -1,13 +1,17 @@ -import { Page } from "lume/core/file.ts"; +import {Page} from "lume/core/file.ts"; import Site from "lume/core/site.ts"; -import { Metadata, readMetadata } from "./metadata.ts"; -import { compileTypst } from "./compile.ts"; +import {Metadata, readMetadata} from "./metadata.ts"; +import {compileTypst} from "./compile.ts"; +import {checkForChanges} from "./cache.ts"; export const typstDelegis = () => (site: Site) => { site.preprocess([".typ"], async (filteredPages, allPages) => { const urls: Record = {}; for (const page of filteredPages) { - const metadata = await readMetadata(site.src(page.sourcePath)); + const {cacheKey, shouldRecompile} = checkForChanges(page.sourcePath, page.data.content?.toString() ?? ""); + + const metadata = await readMetadata(site.src(page.sourcePath), cacheKey); + urls[site.url(page.data.url)] = metadata; allPages.push(Page.create({ @@ -22,22 +26,25 @@ export const typstDelegis = () => (site: Site) => { page.data = { ...page.data, layout: "vo.tsx", - content: site.url(page.data.url + "rendered.pdf"), + // cache key included to make the SSG detect changes in the resulting content and reload the page + content: site.url(page.data.url + "rendered.pdf#" + cacheKey), title: metadata.abbreviation + " - " + metadata.title + ` (WüSpace VOS)`, }; - await Deno.mkdir( - site.dest(page.outputPath, ".."), - { recursive: true }, - ); - - await compileTypst( - site.src(page.sourcePath), - site.dest(page.outputPath.replace("index.html", "rendered.pdf")), - ); + if (shouldRecompile) { + await Deno.mkdir( + site.dest(page.outputPath, ".."), + { recursive: true }, + ); + await compileTypst( + site.src(page.sourcePath), + site.dest(page.outputPath.replace("index.html", "rendered.pdf")), + ); + } } allPages.map((page) => page.data = { ...page.data, vos: urls }); }); site.loadPages([".typ"]); }; +