字
字节笔记本
2026年2月20日
Shiki 代码高亮完全指南:配置、主题与最佳实践
API中转
¥120
Shiki 是一个基于 TextMate 语法的高性能代码高亮库,使用与 VS Code 相同的引擎,支持 100+ 种语言和多种主题。本文详细介绍在 VibeAny 项目中使用 Shiki 的各种配置和最佳实践。
什么是 Shiki
Shiki (式) 是一个语法高亮器,名称来源于日语"式"(しき),意为"公式"或"格式"。它的核心优势:
| 特性 | 说明 |
|---|---|
| VS Code 引擎 | 使用与 VS Code 相同的 TextMate 语法解析 |
| 100+ 语言 | 内置支持绝大多数编程语言 |
| 主题兼容 | 支持所有 VS Code 主题 |
| 多输出格式 | HTML、SVG、终端 ANSI 等 |
| TypeScript | 原生 TS 支持,类型安全 |
基础使用
安装
bash
# npm
npm install shiki
# pnpm (推荐)
pnpm add shiki
# yarn
yarn add shiki基本高亮
typescript
import { codeToHtml } from 'shiki'
const code = `console.log('Hello, Shiki!')`
const html = await codeToHtml(code, {
lang: 'javascript',
theme: 'github-dark'
})
// 输出带语法高亮的 HTML
console.log(html)输出结果:
html
<pre class="shiki github-dark" style="background-color: #24292e" tabindex="0">
<code>
<span class="line">
<span style="color: #79B8FF">console</span>
<span style="color: #E1E4E8">.</span>
<span style="color: #B392F0">log</span>
<span style="color: #E1E4E8">(</span>
<span style="color: #9ECBFF">'Hello, Shiki!'</span>
<span style="color: #E1E4E8">)</span>
</span>
</code>
</pre>核心 API 详解
1. codeToHtml
最常用的 API,将代码转换为高亮 HTML:
typescript
import { codeToHtml } from 'shiki'
const html = await codeToHtml(code, {
// 语言,支持 100+ 种
lang: 'typescript',
// 主题,支持 VS Code 所有主题
theme: 'dracula',
// 可选配置
transformers: [], // 代码转换器
decorations: [], // 行内装饰
})2. createHighlighter
创建高亮器实例,适合多次复用:
typescript
import { createHighlighter } from 'shiki'
// 创建实例时预加载语言和主题
const highlighter = await createHighlighter({
themes: ['github-dark', 'github-light', 'dracula'],
langs: ['javascript', 'typescript', 'python', 'rust'],
})
// 多次使用,无需重复加载
const html1 = highlighter.codeToHtml(code1, {
lang: 'typescript',
theme: 'dracula'
})
const html2 = highlighter.codeToHtml(code2, {
lang: 'python',
theme: 'github-dark'
})
// 动态加载额外语言
await highlighter.loadLanguage('go')
await highlighter.loadTheme('nord')3. 代码块元数据
支持 Markdown 风格的代码块信息:
markdown
```typescript {1,3-5} title="example.ts" showLineNumbers
// 第1行被高亮
const a = 1
// 第3-5行被高亮
const b = 2
const c = 3
const d = 4
```主题配置
内置主题
Shiki 内置了众多流行主题:
typescript
// 深色主题
theme: 'github-dark' // GitHub 深色
theme: 'dracula' // Dracula 经典
theme: 'monokai' // Monokai
theme: 'nord' // Nord 冷色调
theme: 'one-dark-pro' // One Dark Pro
theme: 'tokyo-night' // Tokyo Night
// 浅色主题
theme: 'github-light' // GitHub 浅色
theme: 'github-light-default'
theme: 'vitesse-light' // Vitesse 浅色自定义主题
使用 VS Code 主题 JSON 文件:
typescript
import { createHighlighter } from 'shiki'
import customTheme from './my-theme.json'
const highlighter = await createHighlighter({
themes: [
'github-dark',
customTheme // 自定义主题
],
langs: ['javascript'],
})自定义主题格式(VS Code 主题):
json
{
"name": "My Custom Theme",
"type": "dark",
"colors": {
"editor.background": "#1e1e1e",
"editor.foreground": "#d4d4d4"
},
"tokenColors": [
{
"scope": ["keyword"],
"settings": {
"foreground": "#569CD6",
"fontStyle": "bold"
}
},
{
"scope": ["string"],
"settings": {
"foreground": "#CE9178"
}
}
]
}双主题(深色/浅色切换)
typescript
import { codeToHtml } from 'shiki'
// 生成包含两个主题的 HTML
const html = await codeToHtml(code, {
lang: 'typescript',
themes: {
light: 'github-light',
dark: 'github-dark',
}
})
// 输出包含 CSS 变量的 HTML,可通过媒体查询切换配合 CSS:
css
/* 自动跟随系统主题 */
@media (prefers-color-scheme: light) {
.shiki {
background-color: var(--shiki-light-bg);
color: var(--shiki-light);
}
.shiki span {
color: var(--shiki-light);
}
}
@media (prefers-color-scheme: dark) {
.shiki {
background-color: var(--shiki-dark-bg);
color: var(--shiki-dark);
}
.shiki span {
color: var(--shiki-dark);
}
}高级配置
Transformers(转换器)
Transformers 用于在生成过程中修改代码:
typescript
import { codeToHtml } from 'shiki'
import {
transformerNotationDiff,
transformerNotationHighlight,
transformerNotationWordHighlight,
transformerNotationFocus,
transformerRenderWhitespace
} from '@shikijs/transformers'
const html = await codeToHtml(code, {
lang: 'typescript',
theme: 'github-dark',
transformers: [
// 差异高亮
transformerNotationDiff(),
// 行高亮
transformerNotationHighlight(),
// 单词高亮
transformerNotationWordHighlight(),
// 焦点模式
transformerNotationFocus(),
// 显示空白字符
transformerRenderWhitespace(),
]
})差异高亮示例
markdown
```typescript
function add(a: number, b: number): number {
// [!code ++]
return a + b
}
// [!code --]
const result = add(1, 2)
```行高亮示例
markdown
```typescript
// [!code highlight]
const important = '这行被高亮'
const normal = '普通行'
// [!code highlight:3]
const multiLine = '这三行'
const areHighlighted = '都被'
const together = '高亮'
```行号显示
typescript
import { codeToHtml } from 'shiki'
const html = await codeToHtml(code, {
lang: 'typescript',
theme: 'github-dark',
transformers: [
{
// 自定义行号转换器
code(node) {
const lines = node.children.filter(n => n.type === 'element')
lines.forEach((line, index) => {
line.properties['data-line'] = index + 1
})
}
}
]
})配合 CSS 显示行号:
css
.shiki .line {
display: block;
counter-increment: line;
}
.shiki .line::before {
content: counter(line);
display: inline-block;
width: 2em;
margin-right: 1em;
text-align: right;
color: #666;
}自定义装饰
typescript
import { codeToHtml } from 'shiki'
const html = await codeToHtml(code, {
lang: 'typescript',
theme: 'github-dark',
decorations: [
{
// 起始位置
start: { line: 1, character: 0 },
// 结束位置
end: { line: 1, character: 10 },
// 装饰属性
properties: {
class: 'highlight-line',
'data-comment': '重点代码'
}
}
]
})在 React 中使用
基础组件
tsx
import { codeToHtml } from 'shiki'
import { useEffect, useState } from 'react'
interface CodeBlockProps {
code: string
lang?: string
theme?: string
className?: string
}
export function CodeBlock({
code,
lang = 'typescript',
theme = 'github-dark',
className
}: CodeBlockProps) {
const [html, setHtml] = useState('')
useEffect(() => {
codeToHtml(code, { lang, theme })
.then(setHtml)
}, [code, lang, theme])
return (
<div
className={className}
dangerouslySetInnerHTML={{ __html: html }}
/>
)
}服务端渲染优化
tsx
// app/components/code-block.tsx (Server Component)
import { codeToHtml } from 'shiki'
interface CodeBlockProps {
code: string
lang?: string
theme?: string
}
// 服务端直接渲染,无需客户端加载
export async function CodeBlock({
code,
lang = 'typescript',
theme = 'github-dark'
}: CodeBlockProps) {
const html = await codeToHtml(code, { lang, theme })
return (
<div
className="rounded-lg overflow-hidden"
dangerouslySetInnerHTML={{ __html: html }}
/>
)
}带复制按钮的代码块
tsx
'use client'
import { codeToHtml } from 'shiki'
import { useEffect, useState } from 'react'
import { Check, Copy } from 'lucide-react'
export function CodeBlockWithCopy({
code,
lang = 'typescript'
}: {
code: string
lang?: string
}) {
const [html, setHtml] = useState('')
const [copied, setCopied] = useState(false)
useEffect(() => {
codeToHtml(code, {
lang,
theme: 'github-dark',
transformers: [
// 添加行号
{
code(node) {
const lines = node.children.filter(n => n.type === 'element')
lines.forEach((line, i) => {
line.properties['data-line'] = i + 1
})
}
}
]
}).then(setHtml)
}, [code, lang])
const copyCode = async () => {
await navigator.clipboard.writeText(code)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
return (
<div className="relative group">
<button
onClick={copyCode}
className="absolute top-2 right-2 p-2 rounded bg-gray-700
opacity-0 group-hover:opacity-100 transition-opacity"
>
{copied ? <Check size={16} /> : <Copy size={16} />}
</button>
<div dangerouslySetInnerHTML={{ __html: html }} />
</div>
)
}在 Markdown/MDX 中使用
配置 MDX
typescript
// mdx.config.ts
import { compile } from '@mdx-js/mdx'
import { createHighlighter } from 'shiki'
const highlighter = await createHighlighter({
themes: ['github-dark', 'github-light'],
langs: ['javascript', 'typescript', 'python', 'bash'],
})
export async function compileMDX(source: string) {
return compile(source, {
remarkPlugins: [
// 其他 remark 插件
],
rehypePlugins: [
// 代码高亮插件
[
rehypeShiki,
{
highlighter,
themes: {
light: 'github-light',
dark: 'github-dark',
}
}
]
]
})
}使用 rehype-shiki
typescript
import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import rehypeShiki from '@shikijs/rehype'
import rehypeStringify from 'rehype-stringify'
const processor = unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeShiki, {
theme: 'dracula',
transformers: [
transformerNotationDiff(),
]
})
.use(rehypeStringify)
const html = await processor.process(markdown)Cloudflare Workers 中的特殊配置
问题:WASM 不兼容
Shiki 默认使用 Oniguruma WASM 引擎,在 Workers 环境中会报错:
Error: WASM is not supported in this environment
解决方案:使用 JavaScript 引擎
typescript
// vite.config.ts - SSR stubs 配置
const shikiNoWasm = () => ({
name: 'shiki-no-wasm',
enforce: 'pre',
resolveId(id, importer, { ssr }) {
if (!ssr) return null
// 替换 WASM 引擎为 JavaScript 引擎
if (id.includes('@shikijs/engine-oniguruma')) {
return '\0shiki-js-engine'
}
// 移除语言定义,使用精简版
if (id.includes('shiki/langs/')) {
return '\0shiki-empty-langs'
}
},
load(id) {
if (id === '\0shiki-js-engine') {
return `
export { createJavaScriptRegexEngine } from '@shikijs/engine-javascript'
export const createOnigurumaEngine = createJavaScriptRegexEngine
`
}
if (id === '\0shiki-empty-langs') {
return 'export default []'
}
}
})客户端/服务端分离加载
typescript
// lib/shiki.ts
import { createHighlighter } from 'shiki'
import { createJavaScriptRegexEngine } from '@shikijs/engine-javascript'
// 判断是否在 Workers 环境
const isWorkers = typeof WebAssembly === 'undefined'
export async function getHighlighter() {
if (isWorkers) {
// Workers 环境:使用 JS 引擎
return createHighlighter({
themes: ['github-dark'],
langs: [], // 预加载常用语言
engine: createJavaScriptRegexEngine()
})
}
// 浏览器/Node 环境:使用默认 WASM 引擎
return createHighlighter({
themes: ['github-dark', 'github-light'],
langs: ['javascript', 'typescript', 'python', 'rust', 'go'],
})
}性能优化
1. 语言按需加载
typescript
import { createHighlighter } from 'shiki'
const highlighter = await createHighlighter({
themes: ['github-dark'],
// 只加载需要的语言
langs: ['javascript', 'typescript'],
})
// 动态加载其他语言
await highlighter.loadLanguage('python')
await highlighter.loadLanguage('rust')2. 缓存高亮结果
typescript
import { codeToHtml } from 'shiki'
import { LRUCache } from 'lru-cache'
const cache = new LRUCache<string, string>({
max: 500, // 最多缓存 500 个代码块
ttl: 1000 * 60 * 60 * 24, // 24 小时过期
})
export async function highlightWithCache(
code: string,
options: any
) {
const key = `${options.lang}:${options.theme}:${code}`
if (cache.has(key)) {
return cache.get(key)!
}
const html = await codeToHtml(code, options)
cache.set(key, html)
return html
}3. 服务端预渲染
typescript
// 构建时预渲染代码块
export async function generateStaticParams() {
const docs = await getAllDocs()
for (const doc of docs) {
// 预渲染所有代码块
doc.highlightedCode = await Promise.all(
doc.codeBlocks.map(block =>
codeToHtml(block.code, {
lang: block.lang,
theme: 'github-dark'
})
)
)
}
return docs
}常见问题
语言不支持
typescript
// 检查语言是否支持
import { bundledLanguages } from 'shiki'
console.log(bundledLanguages) // 查看所有支持的语言
// 使用别名
const html = await codeToHtml(code, {
lang: 'js', // 自动映射到 javascript
theme: 'github-dark'
})样式覆盖
css
/* 自定义 Shiki 样式 */
.shiki {
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
}
/* 特定语言的样式 */
.shiki[data-lang="typescript"] {
border-left: 4px solid #3178c6;
}
/* 行高亮 */
.shiki .line.highlight {
background-color: rgba(255, 255, 0, 0.1);
}内存优化
typescript
import { createHighlighter } from 'shiki'
const highlighter = await createHighlighter({
themes: ['github-dark'],
langs: ['javascript'],
})
// 使用完毕后释放资源
highlighter.dispose()相关文章
-
VibeAny 部署到 Cloudflare Workers 完全指南 - 了解完整的部署流程
-
SSR Stubs 技术详解 - 深入了解 SSR stubs 优化原理
-
Mermaid 图表绘制完全指南 - 图表绘制的完整教程
-
Tiptap 富文本编辑器完全指南 - 富文本编辑器的配置和使用
分享: