ByteNoteByteNote

字节笔记本

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()

相关文章

分享: