Files
RawMark/web/src/highlight/worker.ts
T
eezd a1597697e5 feat(web): add M0 live preview editor prototype
- CodeMirror 6 editor with Obsidian/Typora-style live preview: block
  markup revealed per line, inline markup revealed per element
- Fenced code highlighting via Shiki (github-dark, JS regex engine) in a
  Web Worker, languages loaded on demand
- Lezer syntax tree to HTML renderer for share pages, checked against
  the CommonMark 0.31.2 spec (640/652; 12 known parser deviations)
- Use markdownLanguage directly to avoid bundling lang-html/css/js
2026-10-16 17:23:58 +08:00

47 lines
1.6 KiB
TypeScript

import { createHighlighterCore, type HighlighterCore } from 'shiki/core'
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript'
import { bundledLanguages } from 'shiki/langs'
import githubDark from 'shiki/themes/github-dark.mjs'
import type { HighlightRequest, HighlightResponse, TokenRun } from './protocol'
const THEME = 'github-dark'
const highlighterReady: Promise<HighlighterCore> = createHighlighterCore({
themes: [githubDark],
langs: [],
engine: createJavaScriptRegexEngine({ forgiving: true }),
})
// 同一语言的并发请求共用一次加载
const langLoads = new Map<string, Promise<boolean>>()
function ensureLanguage(hl: HighlighterCore, lang: string): Promise<boolean> {
let load = langLoads.get(lang)
if (!load) {
const importer = bundledLanguages[lang as keyof typeof bundledLanguages]
load = importer
? importer().then((mod) => hl.loadLanguage(mod.default)).then(() => true)
: Promise.resolve(false)
langLoads.set(lang, load)
}
return load
}
async function highlight(req: HighlightRequest): Promise<TokenRun | null> {
const hl = await highlighterReady
if (!(await ensureLanguage(hl, req.lang))) return null
const run: TokenRun = []
for (const line of hl.codeToTokensBase(req.code, { lang: req.lang, theme: THEME })) {
for (const t of line) {
if (!t.content.trim()) continue
run.push(t.offset, t.content.length, t.color ?? '', t.fontStyle ?? 0)
}
}
return run
}
self.addEventListener('message', async (e: MessageEvent<HighlightRequest>) => {
const res: HighlightResponse = { id: e.data.id, tokens: await highlight(e.data) }
;(self as unknown as Worker).postMessage(res)
})