From c360697a6a8ba99adb28901bd6c0f0a23f577916 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Fri, 4 Oct 2024 19:24:43 -0400 Subject: [PATCH] Add syntax highlighting html rewriter --- packages/astro-md/middleware.ts | 67 ++++++++++++++++++++++++++++++++- packages/astro-md/package.json | 3 ++ pnpm-lock.yaml | 29 ++++++++++++++ worker-configuration.d.ts | 4 +- wrangler.toml | 4 ++ 5 files changed, 104 insertions(+), 3 deletions(-) diff --git a/packages/astro-md/middleware.ts b/packages/astro-md/middleware.ts index 052c6ef..8d0a628 100644 --- a/packages/astro-md/middleware.ts +++ b/packages/astro-md/middleware.ts @@ -6,6 +6,61 @@ import { myRemark } from "../my-remark"; import { remarkObsidian } from "../remark-obsidian"; import remarkFrontmatter from "remark-frontmatter"; +const decode = (str: string) => + str.replace(/&#x(\d+);/g, (_, hex) => String.fromCharCode(parseInt(hex, 16))); + +const theme = "nord"; + +class SyntaxHighlightRewriter implements HTMLRewriterElementContentHandlers { + private lang: string = ""; + private code: string = ""; + cache: KVNamespace; + + constructor(private runtime: Runtime["runtime"]) { + this.cache = runtime.env.KV_HIGHLIGHT; + } + + element(element: Element) { + this.lang = element.getAttribute("data-lang") ?? ""; + this.code = ""; + } + async text(text: Text) { + this.code += decode(text.text); + if (text.lastInTextNode) { + const hashBuffer = await crypto.subtle.digest( + "SHA-1", + new TextEncoder().encode(`${theme}-${this.lang}-${this.code}`) + ); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hash = hashArray + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + + const cachedResult = await this.cache.get(hash); + if (cachedResult) { + text.replace(cachedResult, { html: true }); + return; + } + + const res = await fetch( + `https://highlight.val.just-be.dev?lang=${this.lang}&theme=${theme}`, + { + method: "POST", + body: this.code, + headers: { + Accept: "text/html", + }, + } + ); + const data = await res.text(); + this.runtime.ctx.waitUntil(this.cache.put(hash, data)); + text.replace(data, { html: true }); + } else { + text.remove(); + } + } +} + export const onRequest = defineMiddleware(async (context, next) => { const fileResolver = async (path: string) => { return (await context.locals.runtime.env.KV_MAPPINGS.get(path)) ?? path; @@ -18,5 +73,15 @@ export const onRequest = defineMiddleware(async (context, next) => { ], }); context.locals.render = processor.render; - return next(); + const res = await next(); + if (import.meta.env.DEV && typeof HTMLRewriter === "undefined") { + // @ts-expect-error Isn't defined on globalThis, but that's fine + globalThis.HTMLRewriter = ( + await import("@worker-tools/html-rewriter/base64") + ).HTMLRewriter; + } + + return new HTMLRewriter() + .on("code[data-lang]", new SyntaxHighlightRewriter(context.locals.runtime)) + .transform(res); }); diff --git a/packages/astro-md/package.json b/packages/astro-md/package.json index a27852e..9defc54 100644 --- a/packages/astro-md/package.json +++ b/packages/astro-md/package.json @@ -12,5 +12,8 @@ "dependencies": { "@astrojs/markdown-remark": "catalog:", "remark-frontmatter": "^5.0.0" + }, + "devDependencies": { + "@worker-tools/html-rewriter": "0.1.0-pre.19" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 356afa1..90353d3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,6 +83,10 @@ importers: remark-frontmatter: specifier: ^5.0.0 version: 5.0.0 + devDependencies: + '@worker-tools/html-rewriter': + specifier: 0.1.0-pre.19 + version: 0.1.0-pre.19 packages/my-remark: dependencies: @@ -1313,6 +1317,12 @@ packages: '@shikijs/vscode-textmate@9.2.2': resolution: {integrity: sha512-TMp15K+GGYrWlZM8+Lnj9EaHEFmOen0WJBrfa17hF7taDOYthuPPV0GWzfd/9iMij0akS/8Yw2ikquH7uVi/fg==} + '@stardazed/streams-compression@1.0.0': + resolution: {integrity: sha512-SQCiwxdIVJ5xxUBYptN+fc+tJpSDbxQuJ0+3u2SmHjIzr6JIRZ28AVFtFnGy6x6j3UBlaLx73w9rC6UAFxnd1g==} + + '@stardazed/zlib@1.0.1': + resolution: {integrity: sha512-MS6PCYiRNJ32c69+3NPyL+bu7LpiFDvMcBXnlhQnF8CKMG0R/xRVCCo422NPkQ0+Cox3xKpk5Lc1LfWFS4b3cw==} + '@tailwindcss/typography@0.5.15': resolution: {integrity: sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==} peerDependencies: @@ -1419,6 +1429,12 @@ packages: '@vscode/l10n@0.0.18': resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==} + '@worker-tools/html-rewriter@0.1.0-pre.19': + resolution: {integrity: sha512-IUbEZwvSdp8vtaB1LAZ/sBRJGNycd9a8cbkqO05rMEPm5MC59Sb+wiGCWaaRXrwQLDAyWeod4QC/q0TuSUI5EA==} + + '@worker-tools/resolvable-promise@0.2.0-pre.6': + resolution: {integrity: sha512-+5RcuruCLB/P3FYYb376RgyImXaq9BBQoOskUbgPyNK3O0AXPakrXUj1EELv+lcnUSdFws/ufdrrWODLJyoTmg==} + acorn-walk@8.3.3: resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} engines: {node: '>=0.4.0'} @@ -4435,6 +4451,12 @@ snapshots: '@shikijs/vscode-textmate@9.2.2': {} + '@stardazed/streams-compression@1.0.0': + dependencies: + '@stardazed/zlib': 1.0.1 + + '@stardazed/zlib@1.0.1': {} + '@tailwindcss/typography@0.5.15(tailwindcss@3.4.13)': dependencies: lodash.castarray: 4.4.0 @@ -4593,6 +4615,13 @@ snapshots: '@vscode/l10n@0.0.18': {} + '@worker-tools/html-rewriter@0.1.0-pre.19': + dependencies: + '@stardazed/streams-compression': 1.0.0 + '@worker-tools/resolvable-promise': 0.2.0-pre.6 + + '@worker-tools/resolvable-promise@0.2.0-pre.6': {} + acorn-walk@8.3.3: dependencies: acorn: 8.12.1 diff --git a/worker-configuration.d.ts b/worker-configuration.d.ts index 1424cef..de5dd23 100644 --- a/worker-configuration.d.ts +++ b/worker-configuration.d.ts @@ -1,8 +1,8 @@ -// Generated by Wrangler on Wed Aug 21 2024 20:59:22 GMT-0400 (Eastern Daylight Time) -// by running `wrangler types` +// Generated by Wrangler by running `wrangler types` interface Env { KV_MAPPINGS: KVNamespace; + KV_HIGHLIGHT: KVNamespace; NODE_VERSION: "v20.16.0"; PNPM_VERSION: "v9.7.0"; SITE: string; diff --git a/wrangler.toml b/wrangler.toml index 3096158..1b8fbe5 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -11,6 +11,10 @@ PNPM_VERSION = "v9.7.0" binding = "KV_MAPPINGS" id = "36e4319084c4498fa08e5bf00f36331a" +[[kv_namespaces]] +binding = "KV_HIGHLIGHT" +id = "9503a7c1e6f948cb8f1754c08e046179" + [[r2_buckets]] binding = "R2_BUCKET" bucket_name = "just-be-dev"