📋 Markdown-it 迁移报告及安装教程
本文档详细记录了纸鸢博客从 marked 迁移到 markdown-it 的完整过程,包括迁移原因、技术方案、安装步骤和配置说明。
📌 目录
迁移概述
迁移信息
| 项目 | 内容 |
|---|---|
| 迁移日期 | 2026-04-06 |
| 原解析器 | marked (v17.0.5) |
| 新解析器 | markdown-it + 插件生态 |
| 代码高亮 | highlight.js → Shiki |
| 影响范围 | 文档渲染页面 ([...slug].astro) |
迁移目标
- ✅ 支持 VitePress 风格的自定义容器语法 (
::: tip/::: warning等) - ✅ 提升代码高亮质量和语言支持
- ✅ 获得更丰富的 Markdown 扩展能力
- ✅ 保持现有功能的兼容性
迁移原因
1. 功能扩展需求
marked 的局限性:
- 原生不支持自定义容器语法
- 代码高亮需要额外配置
- 插件生态相对有限
markdown-it 的优势:
- 丰富的插件生态系统
- 原生支持容器扩展
- 更灵活的渲染控制
2. VitePress 兼容性
纸鸢博客希望支持类似 VitePress 的文档体验,特别是:
::: tip 提示
这是提示信息
:::
::: warning 警告
这是警告信息
:::
3. 代码高亮升级
从 highlight.js 迁移到 Shiki:
- 更准确的语法解析
- 更好的主题支持
- 与 VS Code 一致的着色体验
技术方案对比
方案对比表
| 特性 | marked + highlight.js | markdown-it + Shiki |
|---|---|---|
| 容器语法 | ❌ 不支持 | ✅ 原生支持 |
| 代码高亮 | highlight.js | Shiki (VS Code 同款) |
| 锚点生成 | 手动实现 | markdown-it-anchor 插件 |
| 主题切换 | 手动配置 | 自动亮/暗主题 |
| 语言支持 | 有限 | 180+ 语言 |
| 插件生态 | 较少 | 丰富 |
最终技术栈
markdown-it (核心解析器)
├── markdown-it-container (自定义容器)
├── markdown-it-anchor (标题锚点)
├── @shikijs/markdown-it (代码高亮)
└── shiki (语法高亮引擎)
安装步骤
第一步:安装依赖包
在项目根目录执行以下命令:
npm install markdown-it markdown-it-container markdown-it-anchor @shikijs/markdown-it shiki
安装包说明:
| 包名 | 版本 | 用途 |
|---|---|---|
markdown-it |
latest | Markdown 解析器核心 |
markdown-it-container |
latest | 自定义容器插件 |
markdown-it-anchor |
latest | 标题锚点生成 |
@shikijs/markdown-it |
latest | Shiki 代码高亮集成 |
shiki |
latest | 语法高亮引擎 |
第二步:更新导入语句
打开 src/pages/[...slug].astro,替换原有的导入:
原代码(marked):
/* 📝 Markdown 解析相关 */
import { Marked } from 'marked';
import { markedHighlight } from 'marked-highlight';
import hljs from 'highlight.js';
新代码(markdown-it):
/* 📝 Markdown 解析相关 */
import MarkdownIt from 'markdown-it';
import markdownItContainer from 'markdown-it-container';
import markdownItAnchor from 'markdown-it-anchor';
import { fromHighlighter } from '@shikijs/markdown-it';
import { createHighlighter } from 'shiki';
第三步:配置 markdown-it
在 [...slug].astro 文件中,找到 Markdown 解析部分,替换为以下配置:
/* 💕 创建 Shiki 高亮器 */
const highlighter = await createHighlighter({
themes: ['github-light', 'github-dark'],
langs: [
'javascript', 'typescript', 'jsx', 'tsx', 'vue', 'html',
'css', 'scss', 'json', 'markdown', 'bash', 'powershell',
'python', 'java', 'rust', 'go', 'sql', 'yaml', 'toml',
'xml', 'dockerfile', 'nginx', 'php', 'ruby', 'c', 'cpp',
'csharp', 'kotlin', 'swift', 'dart', 'lua', 'perl', 'r',
'shellscript', 'vim', 'docker', 'apache', 'graphql',
'regex', 'astro'
],
});
/* 🔧 配置 markdown-it */
const md = new MarkdownIt({
html: true,
linkify: true,
typographer: true,
});
/* 🎨 使用 Shiki 进行代码高亮 */
md.use(fromHighlighter(highlighter, {
themes: {
light: 'github-light',
dark: 'github-dark',
},
}));
/* 🔗 添加锚点支持 */
md.use(markdownItAnchor, {
permalink: false,
slugify: (s: string) => s.trim().toLowerCase().replace(/\s+/g, '-').replace(/[^\w\-]/g, ''),
});
/* 📦 添加容器支持 - tip */
md.use(markdownItContainer, 'tip', {
render: (tokens: any[], idx: number) => {
const token = tokens[idx];
const title = token.info.trim().slice(3).trim() || '提示';
if (token.nesting === 1) {
return `<div class="custom-block tip"><p class="custom-block-title">${title}</p>\n`;
} else {
return '</div>\n';
}
},
});
/* 📦 添加容器支持 - warning */
md.use(markdownItContainer, 'warning', {
render: (tokens: any[], idx: number) => {
const token = tokens[idx];
const title = token.info.trim().slice(7).trim() || '警告';
if (token.nesting === 1) {
return `<div class="custom-block warning"><p class="custom-block-title">${title}</p>\n`;
} else {
return '</div>\n';
}
},
});
/* 📦 添加容器支持 - danger */
md.use(markdownItContainer, 'danger', {
render: (tokens: any[], idx: number) => {
const token = tokens[idx];
const title = token.info.trim().slice(6).trim() || '危险';
if (token.nesting === 1) {
return `<div class="custom-block danger"><p class="custom-block-title">${title}</p>\n`;
} else {
return '</div>\n';
}
},
});
/* 📦 添加容器支持 - info */
md.use(markdownItContainer, 'info', {
render: (tokens: any[], idx: number) => {
const token = tokens[idx];
const title = token.info.trim().slice(4).trim() || '信息';
if (token.nesting === 1) {
return `<div class="custom-block info"><p class="custom-block-title">${title}</p>\n`;
} else {
return '</div>\n';
}
},
});
/* 📦 添加容器支持 - details */
md.use(markdownItContainer, 'details', {
render: (tokens: any[], idx: number) => {
const token = tokens[idx];
const title = token.info.trim().slice(7).trim() || '详情';
if (token.nesting === 1) {
return `<details class="custom-block details"><summary>${title}</summary>\n`;
} else {
return '</details>\n';
}
},
});
// 🔽使用 markdown-it 解析 Markdown 为 HTML
const rawHtml = md.render(rawContent);
第四步:添加样式
在 src/components/doczy/_index.scss 中添加颜色变量:
/* ========================================
🎴 自定义容器颜色变量 - VitePress 风格
======================================== */
/* ☀️ Info 信息容器 - 蓝色系 */
$container-info-border: #0ea5e9;
$container-info-title: #0284c7;
$container-info-bg-start: rgba(14, 165, 233, 0.08);
$container-info-bg-end: rgba(56, 189, 248, 0.05);
$container-info-dark-title: #38bdf8;
$container-info-dark-bg-start: rgba(14, 165, 233, 0.15);
$container-info-dark-bg-end: rgba(56, 189, 248, 0.08);
/* 💡 Tip 提示容器 - 绿色系 */
$container-tip-border: #10b981;
$container-tip-title: #059669;
$container-tip-bg-start: rgba(16, 185, 129, 0.08);
$container-tip-bg-end: rgba(52, 211, 153, 0.05);
$container-tip-dark-title: #34d399;
$container-tip-dark-bg-start: rgba(16, 185, 129, 0.15);
$container-tip-dark-bg-end: rgba(52, 211, 153, 0.08);
/* ⚠️ Warning 警告容器 - 橙色系 */
$container-warning-border: #f59e0b;
$container-warning-title: #d97706;
$container-warning-bg-start: rgba(245, 158, 11, 0.08);
$container-warning-bg-end: rgba(251, 191, 36, 0.05);
$container-warning-dark-title: #fbbf24;
$container-warning-dark-bg-start: rgba(245, 158, 11, 0.15);
$container-warning-dark-bg-end: rgba(251, 191, 36, 0.08);
/* 🚨 Danger 危险容器 - 红色系 */
$container-danger-border: #ef4444;
$container-danger-title: #dc2626;
$container-danger-bg-start: rgba(239, 68, 68, 0.08);
$container-danger-bg-end: rgba(248, 113, 113, 0.05);
$container-danger-dark-title: #f87171;
$container-danger-dark-bg-start: rgba(239, 68, 68, 0.15);
$container-danger-dark-bg-end: rgba(248, 113, 113, 0.08);
/* 📋 Details 详情容器 */
$container-details-bg-start: rgba(248, 249, 250, 0.8);
$container-details-bg-end: rgba(255, 255, 255, 0.6);
$container-details-hover-bg: rgba(0, 0, 0, 0.02);
$container-details-dark-bg-start: rgba(30, 30, 46, 0.8);
$container-details-dark-bg-end: rgba(40, 40, 70, 0.6);
$container-details-dark-hover-bg: rgba(255, 255, 255, 0.05);
/* 🎴 自定义容器基础样式 */
$container-bg: rgba(255, 255, 255, 0.7);
$container-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
$container-shadow-hover: 0 6px 20px rgba(0, 0, 0, 0.1);
$container-dark-bg: rgba(30, 30, 46, 0.6);
$container-dark-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
$container-dark-shadow-hover: 0 6px 20px rgba(0, 0, 0, 0.3);
在 src/components/doczy/docindex.scss 中添加容器样式(使用上述变量)。
第五步:构建测试
执行构建命令验证配置:
npm run build
如果构建成功,说明迁移完成!
配置详解
markdown-it 基础配置
const md = new MarkdownIt({
html: true, // 允许 HTML 标签
linkify: true, // 自动转换 URL 为链接
typographer: true, // 启用排版增强
});
Shiki 代码高亮配置
const highlighter = await createHighlighter({
themes: ['github-light', 'github-dark'], // 主题
langs: ['javascript', 'typescript', ...], // 语言列表
});
md.use(fromHighlighter(highlighter, {
themes: {
light: 'github-light',
dark: 'github-dark',
},
}));
容器插件配置
容器插件的 render 函数接收两个参数:
tokens: Token 数组idx: 当前 token 索引
md.use(markdownItContainer, 'tip', {
render: (tokens, idx) => {
const token = tokens[idx];
const title = token.info.trim().slice(3).trim() || '提示';
if (token.nesting === 1) {
// 容器开始标签
return `<div class="custom-block tip"><p class="custom-block-title">${title}</p>\n`;
} else {
// 容器结束标签
return '</div>\n';
}
},
});
容器语法使用
基础语法
::: type 标题
内容
:::
支持的类型
| 类型 | 语法示例 | 用途 |
|---|---|---|
| info | ::: info 信息 |
一般性信息 |
| tip | ::: tip 提示 |
技巧建议 |
| warning | ::: warning 警告 |
潜在问题 |
| danger | ::: danger 危险 |
严重后果 |
| details | ::: details 详情 |
可折叠内容 |
使用示例
::: tip 💡 最佳实践
这是提示内容,支持 **Markdown** 语法。
:::
::: warning ⚠️ 注意事项
这是警告内容。
:::
::: details 点击展开
这是可折叠的详情内容。
- 列表项 1
- 列表项 2
:::
样式定制
修改颜色变量
编辑 src/components/doczy/_index.scss:
/* 修改 info 容器的边框颜色 */
$container-info-border: #3b82f6; // 改为蓝色
/* 修改 tip 容器的标题颜色 */
$container-tip-title: #10b981; // 改为绿色
修改容器样式
编辑 src/components/doczy/docindex.scss:
.custom-block {
// 修改圆角
border-radius: 12px;
// 修改内边距
padding: 20px 24px;
}
.custom-block.info {
// 自定义 info 容器样式
border-left-width: 6px;
}
常见问题
Q1: 构建时报错 “Language xxx not found”
原因: Shiki 未加载该语言
解决: 在 createHighlighter 的 langs 数组中添加该语言
const highlighter = await createHighlighter({
themes: ['github-light', 'github-dark'],
langs: [
// 添加缺失的语言
'jsx', 'tsx', 'astro', ...
],
});
Q2: 容器语法不生效
原因: 容器插件未正确注册
解决: 检查 [...slug].astro 中是否正确注册了容器类型
// 确保每种类型都注册了
md.use(markdownItContainer, 'tip', { ... });
md.use(markdownItContainer, 'warning', { ... });
md.use(markdownItContainer, 'danger', { ... });
md.use(markdownItContainer, 'info', { ... });
md.use(markdownItContainer, 'details', { ... });
Q3: 代码高亮样式异常
原因: Shiki 主题配置不正确
解决: 确保主题名称正确
md.use(fromHighlighter(highlighter, {
themes: {
light: 'github-light', // 确保主题存在
dark: 'github-dark',
},
}));
Q4: 如何添加新的容器类型?
解决: 复制现有容器配置,修改类型名称
md.use(markdownItContainer, 'note', {
render: (tokens, idx) => {
const token = tokens[idx];
const title = token.info.trim().slice(4).trim() || '笔记';
if (token.nesting === 1) {
return `<div class="custom-block note"><p class="custom-block-title">${title}</p>\n`;
} else {
return '</div>\n';
}
},
});
然后在样式文件中添加 .custom-block.note 的样式。
迁移总结
完成的工作
- ✅ 成功替换 marked 为 markdown-it
- ✅ 集成 Shiki 代码高亮
- ✅ 实现 5 种自定义容器类型
- ✅ 添加完整的样式支持
- ✅ 适配亮/暗色模式
文件变更
| 文件 | 变更类型 | 说明 |
|---|---|---|
package.json |
修改 | 添加新依赖 |
[...slug].astro |
修改 | 替换 Markdown 解析逻辑 |
_index.scss |
修改 | 添加容器颜色变量 |
docindex.scss |
修改 | 添加容器样式 |
🕊️ 白木 原创开发 🔗 gl.baimu.live