Docs: minimal Web Worker example for offloading highlighting?
Author: blacklizardCreated Jun 26, 2026Updated Jun 26, 2026
The best-performance guide mentions offloading highlighting to a Web Worker, but the section is still a `` placeholder.
I wired this up recently and it turned out to be pretty small with createHighlighterCore + the
JavaScript engine, so I'm sharing a minimal version in case it's a useful starting point (or saves
writing one from scratch).
// shiki-worker.ts
import { createHighlighterCore, type HighlighterCore } from 'shiki/core'
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript'
let highlighter: Promise<HighlighterCore> | undefined
function getHighlighter() {
return (highlighter ??= createHighlighterCore({
themes: [import('@shikijs/themes/github-dark')],
langs: [import('@shikijs/langs/typescript')],
engine: createJavaScriptRegexEngine(), // no WASM -> smaller worker, faster start
}))
}
self.onmessage = async (e: MessageEvent<{ id: number, code: string, lang: string }>) => {
const { id, code, lang } = e.data
const hl = await getHighlighter()
self.postMessage({ id, html: hl.codeToHtml(code, { lang, theme: 'github-dark' }) })
}// main.ts — request/response wrapper so concurrent calls don't cross wires
const worker = new Worker(new URL('./shiki-worker.ts', import.meta.url), { type: 'module' })
const pending = new Map<number, (html: string) => void>()
let nextId = 0
worker.onmessage = (e: MessageEvent<{ id: number, html: string }>) =>
pending.get(e.data.id)?.(e.data.html)
export function highlight(code: string, lang: string): Promise<string> {
const id = nextId++
return new Promise((resolve) => {
pending.set(id, resolve)
worker.postMessage({ id, code, lang })
})
}A few things that a guide would probably want to cover (the parts that weren't obvious):
- the bundler worker syntax —
new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' })(Vite / webpack 5 / modern bundlers) - the JS engine vs Oniguruma WASM tradeoff specifically inside a worker
- matching responses to requests by id, so multiple in-flight highlights don't get swapped
tsconfiglib: ["webworker"]forselftyping- a Node
worker_threadsvariant alongside the browser one
If you'd want a docs page out of this, I'm happy to open a PR — just let me know the shape you'd
prefer (which bundlers/frameworks to show, raw postMessage vs Comlink).
Source: shikijs/shiki