shiki-stream Deep Dive: Let LLM-Generated Code Highlight in Real Time, Like a Typewriter
shiki-stream Deep Dive: Let LLM-Generated Code Highlight in Real Time, Like a Typewriter
Core idea: "Code arrives progressively, so syntax highlighting must work progressively too." When an LLM spits out code one character at a time — just like a typist — the traditional "wait for the full code before highlighting" approach breaks down. shiki-stream answers elegantly with Web Streams API: it turns a text stream into a token stream on the fly, coloring code the moment it's produced, and introduces a recall-token mechanism to solve the trickiest problem of streaming syntax highlighting — the fact that syntax depends on context.
1. Project Overview
1.1 What Is It?
shiki-stream is a streaming syntax-highlighting library open-sourced by Anthony Fu (antfu) — a streaming/incremental-highlighting wrapper built around the Shiki syntax-highlighting engine.
The official one-line description:
- "Streaming highlighting with Shiki. Useful for highlighting text streams like LLM outputs."
Key facts:
- Repository:
https://github.com/antfu/shiki-stream - Stars: ~594
- Author: Anthony Fu (antfu) — Vue core team member, Vite core contributor, author of UnoCSS, Slidev, Vitesse
- License: MIT
- Language: TypeScript / JavaScript
- Created: February 2025
- Status: Archived — because the implementation moved to Shiki's official
@shikijs/stream - Live demo: https://shiki-stream.netlify.app/
1.2 What Problem Does It Solve?
Imagine using ChatGPT or Claude to generate code: the AI does not output a whole blob at once — it goes character by character, token by token, like a typewriter. Traditional syntax highlighters — including native Shiki — assume "you have the complete code," which breaks in streaming scenarios:
- Code arrives progressively — at each moment you only have a fragment;
- Parsing depends on context — earlier characters can change the meaning of later ones (e.g. a line starting
//swallows everything after it; an unclosed string/bracket affects what follows); - Rendering must be instant — users don't want to wait for the whole stream before seeing highlights.
shiki-stream's answer: use the standard Web Streams API to convert a string stream into a highlighting token stream in real time, so highlighting runs in parallel with generation.
2. Core Philosophy
2.1 From "Bulk Highlighting" to "Streaming Highlighting"
The traditional pipeline was:
Full code → tokenize once → color once
shiki-stream's pipeline is the opposite:
String stream → tokenize on the fly → publish tokens → render incrementally
It replaces the "static input" assumption with "dynamic input," so highlighting can genuinely apply to streaming output.
2.2 The Core API: CodeToTokenTransformStream
At the heart of shiki-stream is a standard Web TransformStream that converts ReadableStream<string> into ReadableStream<ThemedToken | RecallToken>:
import { CodeToTokenScrollStream, ... } from '@shikijs/stream'
import { createHighlighter } from 'shiki'
// 1. Create the Shiki highlighter
const highlighter = await createHighlighter({
langs: ['javascript'],
themes: ['nord'],
})
// 2. The text stream you want to highlight (e.g. an LLM output)
const textStream = getTextStreamFromSomewhere()
// 3. Pipe the text stream into a token stream
const tokensStream = textStream
.pipeThrough(new CodeToTokenTransformStream({
highlighter,
lang: 'javascript',
theme: 'nord',
allowRecalls: true, // see explanation below
}))
2.3 Recall Tokens
Because highlighting depends on code context, previously emitted tokens can change as the stream moves forward. But streams are one-directional — you can't rewrite history. So shiki-stream introduces a special recall token that tells the receiver to discard the last N already-emitted tokens that have changed:
const receivedTokens: ThemedToken[] = []
tokensStream.pipeTo(new WritableStream({
async write(token) {
if ('recall' in token) {
// discard the last token.recall tokens
receivedTokens.length -= token.recall
}
else {
receivedTokens.push(token)
}
},
}))
The key knob: allowRecalls:
- By default
allowRecalls: false— only stable tokens are emitted, at a coarser granularity (usually line-by-line), for simple consumers; allowRecalls: true— finer-grained tokens are emitted, and the framework component handles "recall-redraw," for Vue / React / Solid components.
In one sentence: recall is an elegant engineering answer to "the stream can't go back — so tell the consumer to redraw."
3. Technical Architecture
3.1 Built on the Standard Web Streams API
The whole core is built on the browser/Node-native Web Streams API, no extra stream library required:
ReadableStreamWritableStreamTransformStream
This is the basis of composability — any text stream can be .pipeThrough() a CodeToTokenTransformStream to become a token stream, and then consumed by .pipeTo().
3.2 Framework Renderers (Out of the Box)
Besides consuming the token stream manually, shiki-stream ships a ready ShikiStreamRenderer component for four major frontend frameworks — just pass in the stream:
- Vue —
@shikijs/stream/vue - React —
@shikijs/stream/react - Solid —
@shikijs/stream/solid - Svelte —
@shikijs/stream/svelte
<script setup lang="ts">
import { ShikiStreamRenderer } from '@shikijs/stream/vue'
// get the token stream
</script>
<template>
<ShikiStreamRenderer :stream="tokensStream" />
</template>
3.3 CachedRenderer (Experimental)
shiki-stream also provides a simpler renderer API, ShikiCachedRenderer, for rendering incrementally-updated code strings — you keep a ref('') code and append to it; no manual token handling needed:
<script setup lang="ts">
import { ShikiCachedRenderer } from '@shikijs/stream/vue'
import { createHighlighter } from 'shiki'
const highlighter = await createHighlighter({
langs: ['javascript'],
themes: ['vitesse-light'],
})
const code = ref('') // code should only be updated incrementally
// For demo purposes
onMounted(() => {
setInterval(() => {
code.value += '\nconsole.log("Hello");'
}, 1000)
})
</script>
<template>
<ShikiCachedRenderer
:highlighter="highlighter"
:code="code"
lang="js"
theme="vitesse-light"
/>
</template>
3.4 Core Tech Stack
- Shiki: the underlying syntax-highlighting engine (~14K main repo)
- Vue ≥ 3.2 (optional)
- React ≥ 19 (optional)
- Solid-js ≥ 1.9 (optional)
- Web Streams API — zero extra stream dependencies
4. Design Philosophy
4.1 "Built for a real need, not over-engineering"
In the PR that ported shiki-stream and related community packages into the Shiki official monorepo, Anthony Fu wrote:
"Ports two community packages — antfu/shiki-stream and shikijs/shiki-magic-move — into this monorepo as official @shikijs/* packages."
This confirms streaming highlights is a real pain point — lexical in the LLM era, code streams are everywhere, and the classic full-code highlighting fails — so it's not over-engineering but spot-on.
4.2 Offer a Choice, Don't Decide for You
The allowRecalls design reflects a clear trade-off philosophy:
- Stable mode (default): coarser granularity (line-by-line), simple and fast — fits most consumers;
- Recall mode: finer granularity (per-token), needs downstream "recall-redraw" — more elegant.
Don't force one solution on all scenarios; leave the trade-off to the user.
4.3 Framework-Agnostic Abstraction + Framework-Specific Adapters
The core CodeToTokenTransformStream is totally framework-agnostic (pure Web Streams API); the framework-specific parts (Vue/React/Solid/Svelte renderers) live in separate optional adapters. Core logic has zero framework dependency; adapters live independently — matching Shiki's "separate core from integrations" philosophy.
5. Full Tutorial: Getting Started with shiki-stream
Note: per the latest official guidance, import from
@shikijs/stream(since v4.2.0 / v0.1.5,shiki-streamis a thin re-export — swap the package name to keep existing imports working).
5.1 Install
# npm
npm i -D @shikijs/stream
# yarn
yarn add -D @shikijs/stream
# pnpm
pnpm add -D @shikijs/stream
# bun
bun add -D @shikijs/stream
5.2 Migrate from the old package
- import { CodeToTokenTransformStream } from 'shiki-stream'
- import { ShikiStreamRenderer } from 'shiki-stream/vue'
- import { ShikiStreamRenderer } from 'shiki-stream/react'
- import { ShikiStreamRenderer } from 'shiki-stream/solid'
+ import { CodeToTokenTransformStream } from '@shikijs/stream'
+ import { ShikiStreamRenderer } from '@shikijs/stream/vue'
+ import { ShikiStreamRenderer } from '@shikijs/stream/react'
+ import { ShikiStreamRenderer } from '@shikijs/stream/solid'
5.3 Basic Streaming Highlight (Complete Example)
import { CodeToTokenTransformStream } from '@shikijs/stream'
import { createHighlighter, createJavaScriptRegexEngine } from 'shiki'
// 1. Create the highlighter
const highlighter = await createHighlighter({
langs: ['javascript', 'typescript', 'python'],
themes: ['nord', 'vitesse-dark'],
engine: createJavaScriptRegexEngine(),
})
// 2. Make a mock LLM output stream
function createLLMStream(): ReadableStream<string> {
return new ReadableStream({
async start(controller) {
const code = `function hello() {\n console.log("world")\n}`
for (const char of code) {
controller.enqueue(char)
await new Promise(r => setTimeout(r, 50)) // simulate typing
}
controller.close()
},
})
}
// 3. Pipe it: text stream → token stream
const tokensStream = createLLMStream()
.pipeThrough(new CodeToTokenTransformStream({
highlighter,
lang: 'javascript',
theme: 'nord',
allowRecalls: true,
}))
// 4. Consume the token stream
for await (const token of tokensStream) {
console.log(token)
}
5.4 Consume the Token Stream Manually
tokensStream.pipeTo(new WritableStream({
async write(token) {
console.log(token)
},
}))
5.5 Integrate into Frontend Frameworks with Renderers
React:
import { ShikiStreamRenderer } from '@shikijs/stream/react'
export function MyComponent() {
return <ShikiStreamRenderer stream={tokensStream} />
}
Vue:
<template>
<ShikiStreamRenderer :stream="tokensStream" />
</template>
Solid:
import { ShikiStreamRenderer } from '@shikijs/stream/solid'
export function MyComponent() {
return <ShikiStreamRenderer stream={tokensStream} />
}
5.6 Render Incremental Code with ShikiCachedRenderer
For "don't manage tokens, just want to render a code string that keeps growing":
<script setup lang="ts">
import { ShikiCachedRenderer } from '@shikijs/stream/vue'
import { createHighlighter } from 'shiki'
const highlighter = await createHighlighter({
langs: ['javascript'],
themes: ['vitesse-light'],
})
const code = ref('')
</script>
<template>
<ShikiCachedRenderer
:highlighter="highlighter"
:code="code"
lang="js"
theme="vitesse-light"
/>
</template>
6. Feature Checklist
- Streaming highlight: converts a text stream into a token stream in real time (Web Streams API)
- CodeToTokenTransformStream: a standard TransformStream, pipes against any text stream
- Recall-token mechanism: controlled by
allowRecalls, solves context-dependent token changes - Stable-token mode: by default coarser granularity (line-by-line), stable tokens only
- Four framework renderers:
ShikiStreamRendererfor Vue / React / Solid / Svelte - CachedRenderer: simpler rendering API for incrementally-updated code strings (experimental, fully implemented for Vue)
- Framework-agnostic core: pure stream-processing logic, zero framework dependency
- Manual consumption:
.pipeTo()+WritableStream, orfor awaititeration - Official ecosystem: adopted into the Shiki official repo as
@shikijs/stream
7. Summary: Viewpoints and Conclusions
7.1 Core Viewpoints
- "Waiting for the full code to highlight" is a dead paradigm in the LLM era. When code is generated as a stream, classic bulk highlighting stalls the render and delays the experience; shiki-stream cascades a progressive token stream so highlighting comes alive along with the code — a mental upgrade from batch processing to stream processing.
- The recall token is the key abstraction of streaming highlighting. Since one-directional streams can't rewrite history, shiki-stream lets the consumer accept a "redraw" recall signal — an elegant protocol that works around the fundamental limit of one-directional streams.
- The standard Web Streams API is the right foundation. It doesn't invent a new streaming protocol — it reuses the browser's native
TransformStream, making shiki-stream composable, pipeable, and reusable with any spec-compliant stream — first-class interoperability. - A framework-agnostic core plus framework-specific adapters is a textbook pattern for multi-framework libraries. Pure logic with zero dependencies; Vue/React/Solid/Svelte each adapt independently without interfering.
- From community package to official package is the best proof that a need is real. Being adopted into Shiki's official monorepo (PR #1283) validates its value to the ecosystem — and the archive of the original repo is the strongest proof of its success.
7.2 Where It Stands in the Shiki Ecosystem
- Shiki main repo (~14K stars) — the core engine;
@shikijs/stream(streaming highlight, formerly shiki-stream) — subject of this article;- shiki-magic-move (code-block animation) — a similar community package;
- Used across the Shiki ecosystem / antfu ecosystem products such as Slidev and Vitesse.
7.3 Takeaways for Developers
- Building an LLM product? Want AI-generated code highlighted in real time? Streaming token rendering is the answer — don't wait for the whole code;
- Learning the "pipeline mindset" of Web Streams lets you chain "text stream → token stream → render stream" into one capable line;
- When "object state keeps changing," the recall "redraw" model is a broadly usable model worth borrowing.
7.4 Conclusion
In 2026, when "LLM + code generation" has become mainstream, shiki-stream looks like a small tool — yet it precisely solves one of the frontend's biggest everyday pains of the AI era: real-time code display. With an elegant "one-directional stream + recall" design, it stitches together the two otherwise-conflicting needs of "streaming" and "highlighting."
As AI starts writing code one character at a time, the frontend must learn to highlight one character at a time. shiki-stream is the answer to "catching up asynchronously."
References
- shiki-stream official repo: https://github.com/antfu/shiki-stream
- Official docs (@shikijs/stream): https://shiki.style/packages/stream
- Live demo: https://shiki-stream.netlify.app/
- npm package: https://www.npmjs.com/package/shiki-stream
- Shiki main repo: https://github.com/shikijs/shiki
- Shiki's streaming implementation: https://github.com/shikijs/shiki/tree/main/packages/stream
Frequently Asked Questions
Who is behind TopDigg?
TopDigg is created by Eric, a researcher focused on AI trends and SEO/GEO strategies.
How often is content updated?
Blog posts are published regularly. AI Daily is updated daily with the latest AI news.
Can I republish or share content from TopDigg?
Please contact us for content licensing and collaboration inquiries.
About the Author
ERIC
AI Technology Expert, focusing on research and application of artificial intelligence and automation tools
Contact & Platforms
