Code Block Rendering
Overview
Code blocks can be rendered in three ways depending on which optional dependencies you install and how you configure the library:
- Enhanced surface (recommended for large or interactive code blocks): install
stream-diffsfor File and FileDiff rendering, syntax highlighting, and diff interactions.CodeBlockNodeloads the core runtime on demand after the code block has completed streaming and entered the viewport. - Shiki (MarkdownCodeBlockNode): install
stream-markdownand override thecode_blocknode viasetCustomComponentsto use a lightweight Markdown-driven renderer. - Fallback (no extra deps): if neither optional package is installed, code blocks render as plain
<pre><code>blocks with basic styling.
stream-diffs surface (recommended)
- Install:
bash
pnpm add stream-diffs
# or
npm i stream-diffs- Boundary: the
stream-diffsroot entry is framework-agnostic. Its controllers receive anHTMLElementand plain code/diff data; it has no Vue lifecycle.stream-diffs/vueis a separate optional convenience entry and is not used bymarkstream-vue. - Behavior: this Vue adapter keeps the stable
PreCodeNoderepresentation while content is streaming. Once the block is complete and visible,CodeBlockNodemounts onestream-diffsFile or FileDiff surface and applies language highlighting. CodeBlockShellowns the title and action bar. The innerdata-diffs-headeris disabled so File surfaces do not render a second header.- No worker plugin or extra CSS import is required for this integration. See also: /guide/monaco for runtime and preload details.
Shiki mode (MarkdownCodeBlockNode)
- Install:
bash
pnpm add stream-markdown
# or
npm i stream-markdown- Override the
code_blocknode viasetCustomComponentsto register the Shiki-based renderer:
ts
import { MarkdownCodeBlockNode, setCustomComponents } from 'markstream-vue'
setCustomComponents({ code_block: MarkdownCodeBlockNode })Once set, MarkdownCodeBlockNode (powered by Shiki via stream-markdown) will be used for code_block nodes. You can also supply your own component that uses stream-markdown directly.
Language icon lazy loading
To keep the main bundle smaller, infrequent language icons are split into an async chunk:
- Common languages (JS/TS/HTML/CSS/JSON/Python/etc.) stay in the main bundle.
- Rare languages load on demand and will update icon output automatically after the async chunk resolves.
- If you prefer to avoid first-hit fallback icons, preload once during app idle:
ts
import { preloadExtendedLanguageIcons } from 'markstream-vue'
if (typeof window !== 'undefined')
void preloadExtendedLanguageIcons()Vue CLI 4 (Webpack 4) notes
If you use Vue CLI 4 (Webpack 4), it’s recommended to use the Shiki mode for code blocks and override code_block to avoid Monaco + legacy-bundler edge cases.
Key pitfalls and fixes (see playground-vue2-cli):
- Webpack 4 doesn’t support
package.json#exports→ preferdist/*paths viaresolve.alias. - ESM-only packages (like
stream-markdown) may not be discoverable viarequire.resolve()insidevue.config.js(CJS) → use a filesystem fallback to findnode_modules/stream-markdown, and alias it todist/index.js. - If you use
IgnorePluginto skip optional deps, don’t accidentally ignorestream-markdown(otherwise you’ll getwebpackMissingModuleat runtime).
Fallback
If you don't install either optional package the renderer falls back to a simple pre/code representation.
Links & further reading
- Worker / SSR guidance: /nuxt-ssr
- Installation notes: /guide/installation
Try this — simple CodeBlock render:
vue
<script setup lang="ts">
import type { CodeBlockNodeProps } from 'markstream-vue'
import { CodeBlockNode } from 'markstream-vue'
const node = {
type: 'code_block',
language: 'js',
code: 'console.log(42)',
raw: 'console.log(42)',
} satisfies CodeBlockNodeProps['node']
</script>
<template>
<CodeBlockNode :node="node" />
</template>