diff --git a/packages/astro-md/middleware.ts b/packages/astro-md/middleware.ts index 999a4ca..9d47069 100644 --- a/packages/astro-md/middleware.ts +++ b/packages/astro-md/middleware.ts @@ -5,6 +5,7 @@ import { defineMiddleware } from "astro:middleware"; import { myRemark } from "../my-remark"; import { remarkObsidian } from "../remark-obsidian"; import remarkFrontmatter from "remark-frontmatter"; +import { isLangSupported } from "./syntax-highlighting"; // Cache for 30 days const syntaxHighlightCacheTtl = 60 * 60 * 24 * 30; @@ -18,17 +19,27 @@ class SyntaxHighlightRewriter implements HTMLRewriterElementContentHandlers { private lang: string = ""; private code: string = ""; cache: KVNamespace; + cacheVersion: string; + skip: boolean = false; constructor(private runtime: Runtime["runtime"]) { this.cache = runtime.env.KV_HIGHLIGHT; + this.cacheVersion = runtime.env.SYNTAX_VERSION; } element(element: Element) { this.lang = element.getAttribute("data-lang") ?? ""; this.code = ""; + if (!isLangSupported(this.lang)) { + element.removeAttribute("data-lang"); + element.removeAttribute("class"); + this.skip = true; + return; + } element.removeAndKeepContent(); } async text(text: Text) { + if (this.skip) return; this.code += decode(text.text); if (text.lastInTextNode) { const hashBuffer = await crypto.subtle.digest( @@ -39,8 +50,9 @@ class SyntaxHighlightRewriter implements HTMLRewriterElementContentHandlers { const hash = hashArray .map((b) => b.toString(16).padStart(2, "0")) .join(""); + const key = `${this.cacheVersion}:${hash}`; - const cachedResult = await this.cache.get(hash, { + const cachedResult = await this.cache.get(key, { cacheTtl: syntaxHighlightCacheTtl, }); if (cachedResult) { @@ -50,10 +62,10 @@ class SyntaxHighlightRewriter implements HTMLRewriterElementContentHandlers { // When setting cacheTtl, even misses are cached. If we've gotten // to this point the cache is empty, so let's clear the miss so that // the next request has the actual content (after we write it) - this.runtime.ctx.waitUntil(this.cache.get(hash)); + this.runtime.ctx.waitUntil(this.cache.get(key)); const res = await fetch( - `https://highlight.val.just-be.dev?lang=${this.lang}&theme=${theme}`, + `https://just_be-highlight.web.val.run?lang=${this.lang}&theme=${theme}`, { method: "POST", body: this.code, @@ -63,7 +75,7 @@ class SyntaxHighlightRewriter implements HTMLRewriterElementContentHandlers { } ); const data = await res.text(); - this.runtime.ctx.waitUntil(this.cache.put(hash, data)); + this.runtime.ctx.waitUntil(this.cache.put(key, data)); text.replace(data, { html: true }); } else { text.remove(); @@ -98,11 +110,6 @@ export const onRequest = defineMiddleware(async (context, next) => { "code[data-lang]", new SyntaxHighlightRewriter(context.locals.runtime) ) - .on("pre:not(.shiki)", { - element: (element) => { - element.removeAndKeepContent(); - }, - }) .transform(res); } catch (e) { console.error(e); diff --git a/packages/astro-md/package.json b/packages/astro-md/package.json index 9defc54..96bc4a0 100644 --- a/packages/astro-md/package.json +++ b/packages/astro-md/package.json @@ -11,7 +11,8 @@ "license": "MIT", "dependencies": { "@astrojs/markdown-remark": "catalog:", - "remark-frontmatter": "^5.0.0" + "remark-frontmatter": "^5.0.0", + "shiki": "^1.22.0" }, "devDependencies": { "@worker-tools/html-rewriter": "0.1.0-pre.19" diff --git a/packages/astro-md/syntax-highlighting.ts b/packages/astro-md/syntax-highlighting.ts new file mode 100644 index 0000000..6bb5ef2 --- /dev/null +++ b/packages/astro-md/syntax-highlighting.ts @@ -0,0 +1,40 @@ +import { bundledLanguagesInfo } from "shiki/langs"; +import { bundledThemesInfo } from "shiki/themes"; + +interface Theme { + id: string; +} +type ThemeMap = Record; + +const themes: ThemeMap = bundledThemesInfo.reduce( + (themes: ThemeMap, theme: Theme) => { + themes[theme.id] = theme; + return themes; + }, + {} +); + +interface Lang { + id: string; + aliases?: string[]; +} +type LangMap = Record; + +const langs: LangMap = bundledLanguagesInfo.reduce( + (langs: LangMap, lang: Lang) => { + langs[lang.id] = lang; + for (const alias of lang.aliases ?? []) { + langs[alias] = lang; + } + return langs; + }, + {} +); + +export const isThemeSupported = (theme: string) => { + return !!themes[theme]; +}; + +export const isLangSupported = (lang: string) => { + return !!langs[lang]; +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8682ed4..25e32e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,6 +83,9 @@ importers: remark-frontmatter: specifier: ^5.0.0 version: 5.0.0 + shiki: + specifier: ^1.22.0 + version: 1.22.0 devDependencies: '@worker-tools/html-rewriter': specifier: 0.1.0-pre.19 @@ -1060,24 +1063,36 @@ packages: resolution: {integrity: sha512-dFARM360varU+hdU1MCpl0VTL03FkVIC+A9egCE+ureuOryjVNe3cm2mUjv/gnDHHNTOxWC2H2c8BlOkqTGP/w==} engines: {node: '>= 14'} - '@shikijs/core@1.14.1': - resolution: {integrity: sha512-KyHIIpKNaT20FtFPFjCQB5WVSTpLR/n+jQXhWHWVUMm9MaOaG9BGOG0MSyt7yA4+Lm+4c9rTc03tt3nYzeYSfw==} - '@shikijs/core@1.21.0': resolution: {integrity: sha512-zAPMJdiGuqXpZQ+pWNezQAk5xhzRXBNiECFPcJLtUdsFM3f//G95Z15EHTnHchYycU8kIIysqGgxp8OVSj1SPQ==} + '@shikijs/core@1.22.0': + resolution: {integrity: sha512-S8sMe4q71TJAW+qG93s5VaiihujRK6rqDFqBnxqvga/3LvqHEnxqBIOPkt//IdXVtHkQWKu4nOQNk0uBGicU7Q==} + '@shikijs/engine-javascript@1.21.0': resolution: {integrity: sha512-jxQHNtVP17edFW4/0vICqAVLDAxmyV31MQJL4U/Kg+heQALeKYVOWo0sMmEZ18FqBt+9UCdyqGKYE7bLRtk9mg==} + '@shikijs/engine-javascript@1.22.0': + resolution: {integrity: sha512-AeEtF4Gcck2dwBqCFUKYfsCq0s+eEbCEbkUuFou53NZ0sTGnJnJ/05KHQFZxpii5HMXbocV9URYVowOP2wH5kw==} + '@shikijs/engine-oniguruma@1.21.0': resolution: {integrity: sha512-AIZ76XocENCrtYzVU7S4GY/HL+tgHGbVU+qhiDyNw1qgCA5OSi4B4+HY4BtAoJSMGuD/L5hfTzoRVbzEm2WTvg==} + '@shikijs/engine-oniguruma@1.22.0': + resolution: {integrity: sha512-5iBVjhu/DYs1HB0BKsRRFipRrD7rqjxlWTj4F2Pf+nQSPqc3kcyqFFeZXnBMzDf0HdqaFVvhDRAGiYNvyLP+Mw==} + '@shikijs/types@1.21.0': resolution: {integrity: sha512-tzndANDhi5DUndBtpojEq/42+dpUF2wS7wdCDQaFtIXm3Rd1QkrcVgSSRLOvEwexekihOXfbYJINW37g96tJRw==} + '@shikijs/types@1.22.0': + resolution: {integrity: sha512-Fw/Nr7FGFhlQqHfxzZY8Cwtwk5E9nKDUgeLjZgt3UuhcM3yJR9xj3ZGNravZZok8XmEZMiYkSMTPlPkULB8nww==} + '@shikijs/vscode-textmate@9.2.2': resolution: {integrity: sha512-TMp15K+GGYrWlZM8+Lnj9EaHEFmOen0WJBrfa17hF7taDOYthuPPV0GWzfd/9iMij0akS/8Yw2ikquH7uVi/fg==} + '@shikijs/vscode-textmate@9.3.0': + resolution: {integrity: sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==} + '@stardazed/streams-compression@1.0.0': resolution: {integrity: sha512-SQCiwxdIVJ5xxUBYptN+fc+tJpSDbxQuJ0+3u2SmHjIzr6JIRZ28AVFtFnGy6x6j3UBlaLx73w9rC6UAFxnd1g==} @@ -2612,12 +2627,12 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shiki@1.14.1: - resolution: {integrity: sha512-FujAN40NEejeXdzPt+3sZ3F2dx1U24BY2XTY01+MG8mbxCiA2XukXdcbyMyLAHJ/1AUUnQd1tZlvIjefWWEJeA==} - shiki@1.21.0: resolution: {integrity: sha512-apCH5BoWTrmHDPGgg3RF8+HAAbEL/CdbYr8rMw7eIrdhCkZHdVGat5mMNlRtd1erNG01VPMIKHNQ0Pj2HMAiog==} + shiki@1.22.0: + resolution: {integrity: sha512-/t5LlhNs+UOKQCYBtl5ZsH/Vclz73GIqT2yQsCBygr8L/ppTdmpL4w3kPLoZJbMKVWtoG77Ue1feOjZfDxvMkw==} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -3321,7 +3336,7 @@ snapshots: remark-parse: 11.0.0 remark-rehype: 11.1.0 remark-smartypants: 3.0.2 - shiki: 1.14.1 + shiki: 1.22.0 unified: 11.0.5 unist-util-remove-position: 5.0.0 unist-util-visit: 5.0.0 @@ -4033,10 +4048,6 @@ snapshots: - encoding - supports-color - '@shikijs/core@1.14.1': - dependencies: - '@types/hast': 3.0.4 - '@shikijs/core@1.21.0': dependencies: '@shikijs/engine-javascript': 1.21.0 @@ -4046,24 +4057,51 @@ snapshots: '@types/hast': 3.0.4 hast-util-to-html: 9.0.3 + '@shikijs/core@1.22.0': + dependencies: + '@shikijs/engine-javascript': 1.22.0 + '@shikijs/engine-oniguruma': 1.22.0 + '@shikijs/types': 1.22.0 + '@shikijs/vscode-textmate': 9.3.0 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.3 + '@shikijs/engine-javascript@1.21.0': dependencies: '@shikijs/types': 1.21.0 '@shikijs/vscode-textmate': 9.2.2 oniguruma-to-js: 0.4.3 + '@shikijs/engine-javascript@1.22.0': + dependencies: + '@shikijs/types': 1.22.0 + '@shikijs/vscode-textmate': 9.3.0 + oniguruma-to-js: 0.4.3 + '@shikijs/engine-oniguruma@1.21.0': dependencies: '@shikijs/types': 1.21.0 '@shikijs/vscode-textmate': 9.2.2 + '@shikijs/engine-oniguruma@1.22.0': + dependencies: + '@shikijs/types': 1.22.0 + '@shikijs/vscode-textmate': 9.3.0 + '@shikijs/types@1.21.0': dependencies: '@shikijs/vscode-textmate': 9.2.2 '@types/hast': 3.0.4 + '@shikijs/types@1.22.0': + dependencies: + '@shikijs/vscode-textmate': 9.3.0 + '@types/hast': 3.0.4 + '@shikijs/vscode-textmate@9.2.2': {} + '@shikijs/vscode-textmate@9.3.0': {} + '@stardazed/streams-compression@1.0.0': dependencies: '@stardazed/zlib': 1.0.1 @@ -4111,7 +4149,7 @@ snapshots: '@types/hast@3.0.4': dependencies: - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 '@types/mdast@4.0.4': dependencies: @@ -6017,11 +6055,6 @@ snapshots: shebang-regex@3.0.0: {} - shiki@1.14.1: - dependencies: - '@shikijs/core': 1.14.1 - '@types/hast': 3.0.4 - shiki@1.21.0: dependencies: '@shikijs/core': 1.21.0 @@ -6031,6 +6064,15 @@ snapshots: '@shikijs/vscode-textmate': 9.2.2 '@types/hast': 3.0.4 + shiki@1.22.0: + dependencies: + '@shikijs/core': 1.22.0 + '@shikijs/engine-javascript': 1.22.0 + '@shikijs/engine-oniguruma': 1.22.0 + '@shikijs/types': 1.22.0 + '@shikijs/vscode-textmate': 9.3.0 + '@types/hast': 3.0.4 + siginfo@2.0.0: {} signal-exit@4.1.0: {} @@ -6247,7 +6289,7 @@ snapshots: unist-util-position@5.0.0: dependencies: - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 unist-util-remove-position@5.0.0: dependencies: @@ -6290,7 +6332,7 @@ snapshots: vfile-location@5.0.3: dependencies: - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 vfile: 6.0.2 vfile-matter@5.0.0: diff --git a/src/style.css b/src/style.css index 6672ab7..72ab57d 100644 --- a/src/style.css +++ b/src/style.css @@ -52,3 +52,8 @@ blockquote ul { .prose pre:has(code[data-lang]) { @apply bg-inherit my-0; } + +/* Work around for nested pre tags */ +.prose pre:has(pre[data-lang]) { + @apply bg-inherit mx-0 -my-5 p-0; +} diff --git a/worker-configuration.d.ts b/worker-configuration.d.ts index de5dd23..5e388e9 100644 --- a/worker-configuration.d.ts +++ b/worker-configuration.d.ts @@ -1,14 +1,15 @@ // 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; - PUBLISH_KEY: string; - SENTRY_DSN: string; - SENTRY_AUTH_TOKEN: string; - R2_BUCKET: R2Bucket; - R2_ASSETS: R2Bucket; + KV_MAPPINGS: KVNamespace; + KV_HIGHLIGHT: KVNamespace; + NODE_VERSION: "v20.16.0"; + PNPM_VERSION: "v9.7.0"; + SITE: string; + PUBLISH_KEY: string; + SENTRY_DSN: string; + SENTRY_AUTH_TOKEN: string; + SYNTAX_VERSION: string; + R2_BUCKET: R2Bucket; + R2_ASSETS: R2Bucket; } diff --git a/wrangler.toml b/wrangler.toml index 1b8fbe5..98e32f6 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -6,6 +6,7 @@ compatibility_flags = ["nodejs_als"] [vars] NODE_VERSION = "v20.16.0" PNPM_VERSION = "v9.7.0" +SYNTAX_VERSION = "0" [[kv_namespaces]] binding = "KV_MAPPINGS"