Skip to content

Commit

Permalink
Cache expensive Typst operations for quick iterations during development
Browse files Browse the repository at this point in the history
  • Loading branch information
pklaschka committed Jun 5, 2024
1 parent 176a896 commit a35a3ae
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 21 deletions.
32 changes: 32 additions & 0 deletions lib/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {hash} from "./hash.ts";

const cacheKeys = new Map<string, string>();

/**
* 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};
}
12 changes: 12 additions & 0 deletions lib/hash.ts
Original file line number Diff line number Diff line change
@@ -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);
}
26 changes: 19 additions & 7 deletions lib/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,25 @@ export const MetadataSchema = z.object({

export type Metadata = z.infer<typeof MetadataSchema>;

export async function readMetadata(filePath: string): Promise<Metadata> {
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<string, Metadata>();

/**
* 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<Metadata> {
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(
Expand Down
35 changes: 21 additions & 14 deletions lib/typst-delegis.ts
Original file line number Diff line number Diff line change
@@ -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<string, Metadata> = {};
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({
Expand All @@ -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"]);
};

0 comments on commit a35a3ae

Please sign in to comment.