import { createHighlighterCore, type HighlighterCore } from "shiki/core"; import { createOnigurumaEngine } from "shiki/engine/oniguruma"; import { aardioGrammar } from "./aardioGrammar"; /** 站点 Markdown 代码高亮:显式白名单(非全量) */ export const SHIKI_LANG_IDS = [ "go", "python", "r", "markdown", "javascript", "typescript", "tsx", "jsx", "json", "yaml", "toml", "bash", "sql", "html", "css", "scss", "diff", "plaintext", ] as const; export type SiteShikiLang = (typeof SHIKI_LANG_IDS)[number] | "aardio"; export const SHIKI_LANG_ALIAS: Record = { py: "python", md: "markdown", js: "javascript", ts: "typescript", sh: "bash", shell: "bash", zsh: "bash", yml: "yaml", text: "plaintext", txt: "plaintext", }; export const SHIKI_THEMES = { light: "github-light", dark: "github-dark-dimmed", } as const; const ALLOWED = new Set([...SHIKI_LANG_IDS, "aardio"]); /** 未知语言降级为 plaintext;别名归一到白名单 id */ export function resolveShikiLang(raw?: string | null): SiteShikiLang { const id = (raw || "").trim().toLowerCase(); if (!id) return "plaintext"; const mapped = SHIKI_LANG_ALIAS[id] || id; if (ALLOWED.has(mapped)) return mapped as SiteShikiLang; return "plaintext"; } let highlighterPromise: Promise | null = null; /** 站点单例 highlighter:仅白名单语言 + aardio */ export function getSiteHighlighter(): Promise { if (!highlighterPromise) { highlighterPromise = createHighlighterCore({ themes: [ import("@shikijs/themes/github-light"), import("@shikijs/themes/github-dark-dimmed"), ], langs: [ import("@shikijs/langs/go"), import("@shikijs/langs/python"), import("@shikijs/langs/r"), import("@shikijs/langs/markdown"), import("@shikijs/langs/javascript"), import("@shikijs/langs/typescript"), import("@shikijs/langs/tsx"), import("@shikijs/langs/jsx"), import("@shikijs/langs/json"), import("@shikijs/langs/yaml"), import("@shikijs/langs/toml"), import("@shikijs/langs/bash"), import("@shikijs/langs/sql"), import("@shikijs/langs/html"), import("@shikijs/langs/css"), import("@shikijs/langs/scss"), import("@shikijs/langs/diff"), // plaintext / text / txt 为 Shiki 特殊语言,无需 grammar aardioGrammar, ], langAlias: SHIKI_LANG_ALIAS, engine: createOnigurumaEngine(import("shiki/wasm")), }); } return highlighterPromise; } /** 客户端/工具:按白名单高亮为双主题 HTML */ export async function siteCodeToHtml(code: string, langRaw?: string | null): Promise { const highlighter = await getSiteHighlighter(); const lang = resolveShikiLang(langRaw); return highlighter.codeToHtml(code, { lang, themes: { light: SHIKI_THEMES.light, dark: SHIKI_THEMES.dark, }, defaultColor: false, }); } /** * 供 rehype-pretty-code 的 getHighlighter: * 忽略其默认 langs:['plaintext'],始终返回站点白名单单例。 */ export async function getPrettyCodeHighlighter(): Promise { return getSiteHighlighter(); }