Swap shiki for highlight.js's core build for syntax highlighting

Shiki pulls in a WASM oniguruma engine and bundled theme/grammar JSON
for two full themes; highlight.js's core build plus only the 11
languages content actually uses is much thinner for the same job here.
Token colors now come straight from the design tokens (--accent,
--text-muted, ...) instead of shiki's dual-theme CSS vars, so they
already switch with the site's theme with no extra [data-theme] rules.
This commit is contained in:
2026-09-01 09:20:12 +03:00
parent 74a7f80b29
commit 27e53993d6
5 changed files with 110 additions and 60 deletions
+7 -4
View File
@@ -117,10 +117,13 @@ approved plan for full rationale on each decision referenced below.
- [x] RSS/JSON feeds — `/rss.xml`, `/feed.json`
- [x] Sitemap + robots.txt — `@astrojs/sitemap`, static `public/robots.txt`
- [x] Code block syntax highlighting — `toml-content-loader.ts` renders
fenced code blocks through shiki (`github-light`/`github-dark`,
`defaultColor: false` so only per-token colors are set and our own
`pre` background/border/padding still apply), switched by the same
`[data-theme]`/`prefers-color-scheme` pattern as `tokens.css`
fenced code blocks through `highlight.js`'s core build with only the
11 languages content actually uses registered (no bundled-theme/WASM
grammar weight like shiki, which was tried first and swapped out for
this lighter option); `.hljs-*` token classes in `global.css` are
colored from the existing design tokens (`--accent`, `--text-muted`,
...), so they already switch with the site's theme with no extra
`[data-theme]` rules needed
## Search & comments
+1 -1
View File
@@ -21,9 +21,9 @@
"@fontsource-variable/newsreader": "^5.3.0",
"@fontsource/jetbrains-mono": "^5.3.0",
"astro": "^7.2.9",
"highlight.js": "^11.12.0",
"marked": "^18.0.11",
"pagefind": "^1.5.2",
"shiki": "^4.4.3",
"smol-toml": "^1.8.0",
"three": "^0.185.1"
},
+47 -36
View File
@@ -1,8 +1,19 @@
import { readdirSync, readFileSync, statSync } from "node:fs";
import { extname, join, relative } from "node:path";
import type { Loader } from "astro/loaders";
import hljs from "highlight.js/lib/core";
import hljsBash from "highlight.js/lib/languages/bash";
import hljsC from "highlight.js/lib/languages/c";
import hljsCss from "highlight.js/lib/languages/css";
import hljsDos from "highlight.js/lib/languages/dos";
import hljsIni from "highlight.js/lib/languages/ini";
import hljsJavascript from "highlight.js/lib/languages/javascript";
import hljsPowershell from "highlight.js/lib/languages/powershell";
import hljsPython from "highlight.js/lib/languages/python";
import hljsX86asm from "highlight.js/lib/languages/x86asm";
import hljsXml from "highlight.js/lib/languages/xml";
import hljsYaml from "highlight.js/lib/languages/yaml";
import { Marked } from "marked";
import { createHighlighter } from "shiki";
import { parse as parseToml } from "smol-toml";
const FRONTMATTER_RE = /^\+\+\+\r?\n([\s\S]*?)\r?\n\+\+\+\r?\n?([\s\S]*)$/;
@@ -34,50 +45,50 @@ function stripInlineMarkdown(text: string): string {
// as `headings`, matching the shape a built-in markdown loader would give.
let currentHeadings: Heading[] = [];
// Languages actually used in fenced code blocks across content/ — shiki
// resolves common aliases (js→javascript, sh/zsh→bash, ps1→powershell,
// bat→batch) on its own, so only the langs with no bundled grammar
// (gdb console sessions, svg markup, plain txt) need an explicit remap.
const highlighter = await createHighlighter({
themes: ["github-light", "github-dark"],
langs: [
"asm",
"bash",
"batch",
"c",
"css",
"html",
"javascript",
"powershell",
"python",
"toml",
"xml",
"yaml",
"plaintext",
],
});
// highlight.js's core build (no bundled grammars) plus only the languages
// actually used in content/'s fenced code blocks — keeps the highlighter
// thin instead of shipping its full ~190-language bundle. Aliases below
// map every fence lang seen in content to one of these registered names.
hljs.registerLanguage("bash", hljsBash);
hljs.registerLanguage("c", hljsC);
hljs.registerLanguage("css", hljsCss);
hljs.registerLanguage("dos", hljsDos);
hljs.registerLanguage("ini", hljsIni);
hljs.registerLanguage("javascript", hljsJavascript);
hljs.registerLanguage("powershell", hljsPowershell);
hljs.registerLanguage("python", hljsPython);
hljs.registerLanguage("x86asm", hljsX86asm);
hljs.registerLanguage("xml", hljsXml);
hljs.registerLanguage("yaml", hljsYaml);
const LANG_ALIASES: Record<string, string> = {
asm: "x86asm",
bat: "dos",
gdb: "plaintext",
html: "xml",
js: "javascript",
ps1: "powershell",
sh: "bash",
svg: "xml",
toml: "ini",
txt: "plaintext",
zsh: "bash",
"": "plaintext",
};
function escapeHtml(text: string): string {
return text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
function highlightCode(code: string, lang: string | undefined): string {
const resolved = LANG_ALIASES[lang ?? ""] ?? lang ?? "plaintext";
try {
return highlighter.codeToHtml(code, {
lang: resolved,
themes: { light: "github-light", dark: "github-dark" },
defaultColor: false,
});
} catch {
return highlighter.codeToHtml(code, {
lang: "plaintext",
themes: { light: "github-light", dark: "github-dark" },
defaultColor: false,
});
}
const inner = hljs.getLanguage(resolved)
? hljs.highlight(code, { language: resolved }).value
: escapeHtml(code);
return `<pre class="hljs"><code>${inner}</code></pre>`;
}
const marked = new Marked({
+46 -16
View File
@@ -93,26 +93,56 @@ pre {
border: 1px solid var(--border);
}
/* Shiki syntax highlighting (toml-content-loader.ts renders code fences with
defaultColor: false, so only per-token color vars are set — background,
padding, etc. stay on our own `pre` rule above). Follows the same
light/dark switching pattern as tokens.css: system preference by default,
[data-theme] overrides it either way. */
.shiki,
.shiki span {
color: var(--shiki-light) !important;
/* highlight.js syntax highlighting (toml-content-loader.ts renders code
fences through hljs's core build). Colors are drawn from the existing
design tokens, so they already switch with the site's light/dark theme
without any extra [data-theme] rules here — see tokens.css. */
.hljs-comment,
.hljs-quote {
color: var(--text-muted);
font-style: italic;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) .shiki,
:root:not([data-theme="light"]) .shiki span {
color: var(--shiki-dark) !important;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-built_in,
.hljs-type,
.hljs-literal,
.hljs-link {
color: var(--accent);
}
:root[data-theme="dark"] .shiki,
:root[data-theme="dark"] .shiki span {
color: var(--shiki-dark) !important;
.hljs-string,
.hljs-attr,
.hljs-symbol,
.hljs-addition {
color: #4a9c6d;
}
.hljs-number,
.hljs-meta,
.hljs-doctag {
color: #d1a53d;
}
.hljs-title,
.hljs-title.function_,
.hljs-title.class_,
.hljs-name {
color: var(--text);
font-weight: 600;
}
.hljs-deletion {
color: #d1502f;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: 700;
}
.container {
+9 -3
View File
@@ -99,15 +99,15 @@ importers:
astro:
specifier: ^7.2.9
version: 7.2.9(@emnapi/[email protected])(@emnapi/[email protected])(@types/[email protected])([email protected])([email protected])
highlight.js:
specifier: ^11.12.0
version: 11.12.0
marked:
specifier: ^18.0.11
version: 18.0.11
pagefind:
specifier: ^1.5.2
version: 1.5.2
shiki:
specifier: ^4.4.3
version: 4.4.3
smol-toml:
specifier: ^1.8.0
version: 1.8.0
@@ -4779,6 +4779,10 @@ packages:
[email protected]:
resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
[email protected]:
resolution: {integrity: sha512-nbfWpyRMcMrPMmDwJB+dhX/eiaPKtc2RB+0QZskqJ3WjRA/FDS0e9hZrx8EC/lbEv8gXy98FcDbNa/dspAaJMg==}
engines: {node: '>=12.0.0'}
[email protected]:
resolution: {integrity: sha512-uu0VXUK52JhihhnN/MVVo1lvqNNuhoxkonqgO3IpjvQiGpJBdIXMGkofjQb/j9zvV7a1SW8U9g1FslWx/1HOiQ==}
@@ -13590,6 +13594,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: