代码高亮
为代码块添加语法高亮,提升代码可读性和展示效果。
Shiki
基本信息
- 简介: 基于 TextMate 语法和 VS Code 主题的代码高亮库,提供精准的语法高亮
- 官网: https://shiki.style
- GitHub: https://github.com/shikijs/shiki
- npm: https://www.npmjs.com/package/shiki
特点
- ✅ 使用 VS Code 的语法引擎,高亮精准度高
- ✅ 支持 100+ 种编程语言
- ✅ 内置多种 VS Code 主题
- ✅ 支持行高亮、差异高亮
- ✅ 零运行时依赖,生成纯 HTML + CSS
- ✅ 支持 SSR(服务端渲染)
- ✅ 支持自定义主题和语言
- ✅ TypeScript 类型支持完善
安装
bash
pnpm add shikibash
bun add shikibash
npm install shikibash
yarn add shiki基础用法
基本高亮
typescript
import { codeToHtml } from 'shiki'
const code = `
function hello() {
console.log('Hello, Shiki!')
}
`
const html = await codeToHtml(code, {
lang: 'javascript',
theme: 'vitesse-dark'
})
console.log(html)在 Vue 中使用
vue
<template>
<div v-html="highlightedCode" class="rounded-lg overflow-auto"></div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { codeToHtml } from 'shiki'
const highlightedCode = ref('')
const code = `
const greeting = 'Hello, World!'
console.log(greeting)
`
onMounted(async () => {
highlightedCode.value = await codeToHtml(code, {
lang: 'typescript',
theme: 'github-dark'
})
})
</script>多主题支持
typescript
import { codeToHtml } from 'shiki'
const code = 'const a = 1'
const html = await codeToHtml(code, {
lang: 'javascript',
themes: {
light: 'github-light',
dark: 'github-dark'
}
})
// 生成的 HTML 包含两套主题的样式
// 可以通过 CSS 媒体查询切换对应的 CSS:
css
@media (prefers-color-scheme: dark) {
.shiki.github-light {
display: none;
}
}
@media (prefers-color-scheme: light) {
.shiki.github-dark {
display: none;
}
}进阶用法
行高亮
typescript
import { codeToHtml } from 'shiki'
const code = `
function add(a, b) {
return a + b
}
const result = add(1, 2)
console.log(result)
`
const html = await codeToHtml(code, {
lang: 'javascript',
theme: 'nord',
decorations: [
{
// 高亮第 2-3 行
start: { line: 1, character: 0 },
end: { line: 2, character: 0 },
properties: { class: 'highlighted' }
}
]
})配合 CSS:
css
.highlighted {
background-color: rgba(255, 255, 0, 0.1);
display: block;
margin: 0 -1rem;
padding: 0 1rem;
}差异高亮
typescript
import { codeToHtml } from 'shiki'
const code = `
function hello() {
console.log('Hello')
console.log('World')
}
`
const html = await codeToHtml(code, {
lang: 'javascript',
theme: 'github-dark',
decorations: [
{
start: { line: 1, character: 0 },
end: { line: 1, character: 100 },
properties: { class: 'diff-remove' }
},
{
start: { line: 2, character: 0 },
end: { line: 2, character: 100 },
properties: { class: 'diff-add' }
}
]
})CSS 样式:
css
.diff-remove {
background-color: rgba(244, 63, 94, 0.15);
display: block;
}
.diff-add {
background-color: rgba(34, 197, 94, 0.15);
display: block;
}代码编辑器集成
vue
<template>
<div class="relative w-full">
<textarea
v-model="code"
@input="handleCodeChange"
class="absolute inset-0 w-full h-full opacity-0 z-10 font-mono"
></textarea>
<div
v-html="highlightedCode"
class="relative z-0 pointer-events-none"
></div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { codeToHtml } from 'shiki'
import { debounce } from 'lodash-es'
const code = ref('console.log("Hello")')
const highlightedCode = ref('')
const updateHighlight = async (newCode: string) => {
highlightedCode.value = await codeToHtml(newCode, {
lang: 'javascript',
theme: 'vitesse-dark'
})
}
// 防抖处理,避免频繁高亮
const handleCodeChange = debounce(async () => {
await updateHighlight(code.value)
}, 300)
// 初始化
updateHighlight(code.value)
</script>自定义主题
typescript
import { codeToHtml } from 'shiki'
const customTheme = {
name: 'custom-theme',
colors: {
'editor.background': '#1e1e1e',
'editor.foreground': '#d4d4d4'
},
tokenColors: [
{
scope: ['comment'],
settings: {
foreground: '#6A9955'
}
},
{
scope: ['string'],
settings: {
foreground: '#CE9178'
}
},
{
scope: ['keyword'],
settings: {
foreground: '#569CD6'
}
}
]
}
const html = await codeToHtml('const a = 1', {
lang: 'javascript',
theme: customTheme
})完整示例
点击查看完整的代码展示组件
vue
<template>
<div class="border border-gray-200 rounded-lg overflow-hidden">
<div class="flex gap-2.5 p-3 bg-gray-50 border-b border-gray-200">
<select
v-model="selectedLang"
@change="updateHighlight"
class="px-3 py-1.5 border border-gray-300 rounded bg-white cursor-pointer"
>
<option value="javascript">JavaScript</option>
<option value="typescript">TypeScript</option>
<option value="vue">Vue</option>
<option value="python">Python</option>
<option value="go">Go</option>
</select>
<select
v-model="selectedTheme"
@change="updateHighlight"
class="px-3 py-1.5 border border-gray-300 rounded bg-white cursor-pointer"
>
<option value="github-dark">GitHub Dark</option>
<option value="github-light">GitHub Light</option>
<option value="vitesse-dark">Vitesse Dark</option>
<option value="nord">Nord</option>
<option value="one-dark-pro">One Dark Pro</option>
</select>
<button
@click="copyCode"
class="px-3 py-1.5 border border-gray-300 rounded bg-white cursor-pointer hover:bg-gray-100"
>
{{ copied ? '已复制' : '复制代码' }}
</button>
</div>
<div v-html="highlightedCode" class="overflow-auto max-h-[500px] [&_pre]:m-0 [&_pre]:p-4"></div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { codeToHtml } from 'shiki'
const selectedLang = ref('javascript')
const selectedTheme = ref('github-dark')
const highlightedCode = ref('')
const copied = ref(false)
const code = ref(`
function fibonacci(n) {
if (n <= 1) return n
return fibonacci(n - 1) + fibonacci(n - 2)
}
const result = fibonacci(10)
console.log('Fibonacci(10):', result)
`)
const updateHighlight = async () => {
try {
highlightedCode.value = await codeToHtml(code.value, {
lang: selectedLang.value,
theme: selectedTheme.value
})
} catch (error) {
console.error('高亮失败:', error)
}
}
const copyCode = async () => {
try {
await navigator.clipboard.writeText(code.value)
copied.value = true
setTimeout(() => {
copied.value = false
}, 2000)
} catch (error) {
console.error('复制失败:', error)
}
}
onMounted(() => {
updateHighlight()
})
</script>最佳实践
1. 性能优化
使用单例模式:
typescript
import { createHighlighter } from 'shiki'
let highlighter: Awaited<ReturnType<typeof createHighlighter>> | null = null
export async function getHighlighter() {
if (!highlighter) {
highlighter = await createHighlighter({
themes: ['github-dark', 'github-light'],
langs: ['javascript', 'typescript', 'vue', 'python']
})
}
return highlighter
}typescript
import { getHighlighter } from './highlighter'
const highlighter = await getHighlighter()
const html = highlighter.codeToHtml(code, {
lang: 'javascript',
theme: 'github-dark'
})2. 按需加载语言
typescript
import { createHighlighter } from 'shiki'
const highlighter = await createHighlighter({
themes: ['github-dark'],
langs: [] // 初始不加载任何语言
})
// 按需加载
await highlighter.loadLanguage('javascript')
await highlighter.loadLanguage('python')3. 服务端渲染
typescript
// 在服务端预渲染
import { codeToHtml } from 'shiki'
export async function generateCodeHTML(code: string, lang: string) {
return await codeToHtml(code, {
lang,
theme: 'github-dark'
})
}4. 错误处理
typescript
import { codeToHtml } from 'shiki'
async function highlightCode(code: string, lang: string) {
try {
return await codeToHtml(code, {
lang,
theme: 'github-dark'
})
} catch (error) {
console.error('高亮失败:', error)
// 降级处理:返回纯文本
return `<pre><code>${escapeHtml(code)}</code></pre>`
}
}
function escapeHtml(text: string) {
return text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}5. 缓存策略
typescript
const codeCache = new Map<string, string>()
async function highlightWithCache(code: string, lang: string, theme: string) {
const key = `${lang}-${theme}-${code}`
if (codeCache.has(key)) {
return codeCache.get(key)!
}
const html = await codeToHtml(code, { lang, theme })
codeCache.set(key, html)
return html
}Highlight.js
基本信息
- 简介: 轻量级的代码高亮库,支持自动语言检测
- 官网: https://highlightjs.org
- GitHub: https://github.com/highlightjs/highlight.js
- npm: https://www.npmjs.com/package/highlight.js
特点
- ✅ 支持 190+ 种编程语言
- ✅ 自动语言检测
- ✅ 90+ 种配色方案
- ✅ 轻量级,核心库仅 ~12KB (gzipped)
- ✅ 支持 Node.js 和浏览器
- ✅ 无依赖
- ✅ 支持自定义语言和样式
- ✅ 活跃的社区维护
安装
bash
pnpm add highlight.jsbash
bun add highlight.jsbash
npm install highlight.jsbash
yarn add highlight.js基础用法
基本高亮
typescript
import hljs from 'highlight.js'
import 'highlight.js/styles/github-dark.css'
const code = `
function hello() {
console.log('Hello, Highlight.js!')
}
`
const highlighted = hljs.highlight(code, {
language: 'javascript'
}).value
console.log(highlighted)在 Vue 中使用
vue
<template>
<div>
<pre><code
ref="codeRef"
class="language-javascript"
>{{ code }}</code></pre>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import hljs from 'highlight.js'
import 'highlight.js/styles/atom-one-dark.css'
const codeRef = ref<HTMLElement>()
const code = `
const greeting = 'Hello, World!'
console.log(greeting)
`
onMounted(() => {
if (codeRef.value) {
hljs.highlightElement(codeRef.value)
}
})
</script>自动语言检测
typescript
import hljs from 'highlight.js'
const code = 'const a = 1'
// 自动检测语言
const result = hljs.highlightAuto(code)
console.log('检测到的语言:', result.language)
console.log('高亮结果:', result.value)按需引入语言
typescript
// 只引入需要的语言,减小打包体积
import hljs from 'highlight.js/lib/core'
import javascript from 'highlight.js/lib/languages/javascript'
import python from 'highlight.js/lib/languages/python'
import 'highlight.js/styles/github-dark.css'
hljs.registerLanguage('javascript', javascript)
hljs.registerLanguage('python', python)
const code = 'console.log("Hello")'
const highlighted = hljs.highlight(code, { language: 'javascript' }).value进阶用法
全局自动高亮
vue
<template>
<div class="content" v-html="htmlContent"></div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import hljs from 'highlight.js'
import 'highlight.js/styles/monokai.css'
const htmlContent = ref(`
<pre><code class="language-javascript">
function test() {
console.log('test')
}
</code></pre>
`)
onMounted(() => {
// 高亮所有代码块
document.querySelectorAll('pre code').forEach((block) => {
hljs.highlightElement(block as HTMLElement)
})
})
</script>行号支持
vue
<template>
<div class="flex bg-[#1e1e1e] rounded-lg overflow-hidden">
<div class="flex flex-col py-4 px-2 bg-[#252525] text-[#858585] text-right select-none font-mono text-sm leading-6">
<span v-for="n in lineCount" :key="n" class="block">{{ n }}</span>
</div>
<pre class="m-0 flex-1"><code
ref="codeRef"
class="language-javascript block p-4"
>{{ code }}</code></pre>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import hljs from 'highlight.js'
import 'highlight.js/styles/github-dark.css'
const codeRef = ref<HTMLElement>()
const code = `function hello() {
console.log('Hello')
return true
}
const result = hello()`
const lineCount = computed(() => code.split('\n').length)
onMounted(() => {
if (codeRef.value) {
hljs.highlightElement(codeRef.value)
}
})
</script>代码高亮插件
typescript
// 自定义插件:添加复制按钮
import hljs from 'highlight.js'
hljs.addPlugin({
'after:highlightElement': ({ el, result }) => {
const wrapper = document.createElement('div')
wrapper.className = 'code-wrapper'
const copyBtn = document.createElement('button')
copyBtn.className = 'copy-button'
copyBtn.textContent = '复制'
copyBtn.onclick = () => {
navigator.clipboard.writeText(el.textContent || '')
copyBtn.textContent = '已复制'
setTimeout(() => {
copyBtn.textContent = '复制'
}, 2000)
}
el.parentNode?.insertBefore(wrapper, el)
wrapper.appendChild(copyBtn)
wrapper.appendChild(el)
}
})自定义语言定义
typescript
import hljs from 'highlight.js/lib/core'
// 定义自定义语言
hljs.registerLanguage('mylang', (hljs) => {
return {
keywords: 'if else while for function',
contains: [
hljs.QUOTE_STRING_MODE,
hljs.C_LINE_COMMENT_MODE,
hljs.C_BLOCK_COMMENT_MODE,
{
className: 'number',
begin: '\\b\\d+(\\.\\d+)?'
}
]
}
})
const code = 'function test() { if (true) { } }'
const result = hljs.highlight(code, { language: 'mylang' })完整示例
点击查看完整的代码查看器组件
vue
<template>
<div class="border border-[#3a3a3a] rounded-lg overflow-hidden bg-[#282c34]">
<div class="flex justify-between items-center bg-[#21252b] border-b border-[#3a3a3a] p-2 px-3">
<div class="flex gap-1">
<button
v-for="file in files"
:key="file.name"
:class="[
'px-3 py-1.5 bg-transparent border-none cursor-pointer rounded transition-all',
currentFile === file.name
? 'bg-[#282c34] text-[#61afef]'
: 'text-[#abb2bf] hover:bg-[#2c313a]'
]"
@click="currentFile = file.name"
>
{{ file.name }}
</button>
</div>
<button
@click="copyCurrentCode"
class="px-3 py-1 bg-[#3a3f4b] border border-[#4b5263] text-[#abb2bf] rounded cursor-pointer text-xs hover:bg-[#4b5263]"
>
{{ copied ? '✓ 已复制' : '复制' }}
</button>
</div>
<div class="flex overflow-x-auto">
<div class="flex flex-col py-4 px-2 bg-[#21252b] text-[#5c6370] text-right select-none font-mono text-sm leading-6 min-w-[40px]">
<span v-for="n in currentLineCount" :key="n" class="block">{{ n }}</span>
</div>
<pre class="m-0 flex-1 bg-[#282c34]"><code
ref="codeRef"
:class="`language-${currentLanguage} block p-4 font-mono text-sm leading-6`"
>{{ currentCode }}</code></pre>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import hljs from 'highlight.js/lib/core'
import javascript from 'highlight.js/lib/languages/javascript'
import typescript from 'highlight.js/lib/languages/typescript'
import css from 'highlight.js/lib/languages/css'
import 'highlight.js/styles/atom-one-dark.css'
hljs.registerLanguage('javascript', javascript)
hljs.registerLanguage('typescript', typescript)
hljs.registerLanguage('css', css)
interface CodeFile {
name: string
language: string
code: string
}
const files = ref<CodeFile[]>([
{
name: 'index.ts',
language: 'typescript',
code: `import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')`
},
{
name: 'App.vue',
language: 'javascript',
code: `<template>
<div class="app">
<h1>Hello Vue!</h1>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const message = ref('Hello')
</script>`
},
{
name: 'style.css',
language: 'css',
code: `.app {
padding: 20px;
font-family: sans-serif;
}
h1 {
color: #42b983;
}`
}
])
const currentFile = ref('index.ts')
const codeRef = ref<HTMLElement>()
const copied = ref(false)
const currentFileData = computed(() =>
files.value.find(f => f.name === currentFile.value)!
)
const currentCode = computed(() => currentFileData.value.code)
const currentLanguage = computed(() => currentFileData.value.language)
const currentLineCount = computed(() => currentCode.value.split('\n').length)
const highlightCode = () => {
if (codeRef.value) {
hljs.highlightElement(codeRef.value)
}
}
const copyCurrentCode = async () => {
try {
await navigator.clipboard.writeText(currentCode.value)
copied.value = true
setTimeout(() => {
copied.value = false
}, 2000)
} catch (error) {
console.error('复制失败:', error)
}
}
watch(currentFile, () => {
setTimeout(highlightCode, 0)
})
onMounted(() => {
highlightCode()
})
</script>最佳实践
1. 按需加载
typescript
// 只加载需要的语言
import hljs from 'highlight.js/lib/core'
import javascript from 'highlight.js/lib/languages/javascript'
import typescript from 'highlight.js/lib/languages/typescript'
hljs.registerLanguage('javascript', javascript)
hljs.registerLanguage('typescript', typescript)2. 性能优化
typescript
// 使用 Web Worker 处理大文件
const worker = new Worker('/highlight-worker.js')
worker.postMessage({
code: largeCodeString,
language: 'javascript'
})
worker.onmessage = (e) => {
const highlighted = e.data
// 更新 UI
}3. 主题切换
vue
<script setup lang="ts">
import { ref, watch } from 'vue'
const theme = ref<'light' | 'dark'>('dark')
watch(theme, (newTheme) => {
// 动态加载主题
const link = document.getElementById('hljs-theme') as HTMLLinkElement
if (link) {
link.href = newTheme === 'dark'
? '/node_modules/highlight.js/styles/atom-one-dark.css'
: '/node_modules/highlight.js/styles/atom-one-light.css'
}
})
</script>4. 安全处理
typescript
import hljs from 'highlight.js'
// 转义 HTML,防止 XSS
function escapeHtml(text: string) {
return text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
}
const userCode = getUserInput() // 用户输入的代码
const safeCode = escapeHtml(userCode)
const highlighted = hljs.highlight(safeCode, { language: 'javascript' }).value5. 语言别名
typescript
import hljs from 'highlight.js/lib/core'
import javascript from 'highlight.js/lib/languages/javascript'
hljs.registerLanguage('javascript', javascript)
hljs.registerLanguage('js', javascript) // 添加别名
hljs.registerAliases('jsx', { languageName: 'javascript' })对比总结
Shiki vs Highlight.js
| 特性 | Shiki | Highlight.js |
|---|---|---|
| 高亮精度 | ⭐⭐⭐⭐⭐ 使用 VS Code 引擎 | ⭐⭐⭐⭐ 基于正则表达式 |
| 主题 | VS Code 主题 | 90+ 预设主题 |
| 语言支持 | 100+ | 190+ |
| 包体积 | 较大 (~500KB) | 小 (~12KB core) |
| 运行时 | 零运行时 (生成 HTML) | 需要运行时高亮 |
| SSR | ✅ 完美支持 | ⚠️ 需要特殊处理 |
| 自动检测 | ❌ 不支持 | ✅ 支持 |
| 性能 | 构建时高亮快 | 运行时高亮快 |
| 学习曲线 | 中等 | 简单 |
使用建议
选择 Shiki 的场景:
- 需要最精准的语法高亮
- 使用 SSR/SSG(如 VitePress、Nuxt)
- 喜欢 VS Code 主题风格
- 代码在构建时已知
选择 Highlight.js 的场景:
- 需要自动语言检测
- 运行时动态高亮
- 对包体积敏感
- 需要简单快速集成
- 用户生成的内容高亮
相关资源
Shiki:
Highlight.js: