mirror of
https://github.com/andwati/andwati.com.git
synced 2026-10-18 01:12:53 +00:00
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:
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
}
|
||||
|
||||
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({
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Generated
+9
-3
@@ -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]:
|
||||
|
||||
Reference in New Issue
Block a user