website
git clone https://git.pyrossh.dev/website
木 Personal website of pyrossh. Built with astrojs, shiki, vite.
src/_helpers/highlightCode.js
import Prism from "prismjs";
import loadPrismLanguage from "prismjs/components/index.js";
// Avoid "Language does not exist" console noise for extensions we don't
// have a mapping for — those just fall back to plain, unhighlighted text.
loadPrismLanguage.silent = true;
// File-extension -> Prism language name, for the cases where they don't
// already match (or where Prism has no component at all, mapped to null
// so we skip straight to plain text instead of a failed load attempt).
const EXT_TO_LANG = {
h: "c",
html: "markup",
jsonc: "json",
kt: "kotlin",
lock: "toml",
md: "markdown",
mdx: "markdown",
mjs: "javascript",
plist: "markup",
rs: "rust",
scm: "scheme",
sh: "bash",
ts: "typescript",
mi: null,
nu: null,
plum: null,
txt: null,
typ: null,
};
// Fenced-code-block language identifiers (the "rust" in ```rust) mostly
// already match Prism's own component ids, unlike file extensions —
// except for a handful of common short forms people actually type in
// READMEs, which need the same kind of aliasing EXT_TO_LANG does above.
const FENCE_LANG_ALIASES = {
js: "javascript",
sh: "bash",
rs: "rust",
ts: "typescript",
yml: "yaml",
html: "markup",
htm: "markup",
};
const grammarCache = new Map();
/** Load (and cache) a Prism grammar by its canonical component id. */
const loadGrammar = (lang) => {
if (grammarCache.has(lang)) return grammarCache.get(lang);
if (!Prism.languages[lang]) loadPrismLanguage(lang);
const grammar = Prism.languages[lang] ?? null;
const result = grammar ? { lang, grammar } : null;
grammarCache.set(lang, result);
return result;
};
const getLanguage = (ext) => {
const lang = Object.prototype.hasOwnProperty.call(EXT_TO_LANG, ext) ? EXT_TO_LANG[ext] : ext;
return lang ? loadGrammar(lang) : null;
};
/**
* Highlight source text for a given file extension using Prism, at build
* time — no client-side highlighter ships to the browser. Falls back to
* (still-escaped) plain text when there's no Prism grammar for the
* extension.
*
* @returns {{ html: string, lang: string }}
*/
export const highlightCode = (text, ext) => {
const found = getLanguage(ext);
if (!found) {
return { html: escapeHtml(text), lang: ext || "text" };
}
return { html: Prism.highlight(text, found.grammar, found.lang), lang: found.lang };
};
/**
* Highlight a fenced markdown code block by its declared language (the
* "rust" in ```rust), for markdown-it's `highlight` option — used for
* repo READMEs, which render through a separate MarkdownIt instance
* rather than highlightCode()'s extension-keyed lookup. markdown-it
* wraps whatever this returns in <pre><code class="language-{lang}">,
* matching the shared pre/code styling and .token.* colors in
* shared.css. Falls back to plain escaped text, same as highlightCode.
*
* @returns {string} inner HTML for markdown-it's fence renderer
*/
export const highlightFenceCode = (text, lang) => {
const normalized = lang ? (FENCE_LANG_ALIASES[lang.toLowerCase()] ?? lang.toLowerCase()) : null;
const found = normalized ? loadGrammar(normalized) : null;
return found ? Prism.highlight(text, found.grammar, found.lang) : escapeHtml(text);
};
const escapeHtml = (str) =>
String(str).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);