vue展示md文件,前端读取展示markdown文件
Vue 中加载并渲染 Markdown 文件的两种方案(含批量生成侧边栏)
场景:项目里要做文档展示页 / 帮助中心 / 更新日志,内容以
.md文件维护,前端负责渲染成页面。常见两种思路:
- 方案一:构建时编译 —— webpack 配 markdown-loader,
require('./xxx.md')直接把 md 编译成 HTML 字符串打进 bundle,配合v-html渲染。缺点:md 改一次就得重新 build。- 方案二:运行时渲染 —— md 当静态资源放静态目录,运行时 axios 拉原文,交给 vue-markdown 组件渲染。md 改完刷新即见,不用重新构建。
方案一:markdown-loader + require + v-html(构建时编译)
1. 安装 loader
npm install --save markdown-loader html-loader
注意补上了 html-loader:markdown-loader 的输出是 HTML 字符串,不是合法的 JS 模块,需要串一层 html-loader 把它变成 module.exports = "<html 字符串>",require 回来的才是可用字符串。
2. webpack 配置
这一步原文漏了,没有它 require('./xxx.md') 会直接报错。vue-cli 2 写在 build/webpack.base.conf.js 的 module.rules 里;vue-cli 3+ 写在 vue.config.js:
// vue.config.js
module.exports = {
chainWebpack: (config) => {
config.module
.rule('markdown')
.test(/\.md$/)
.use('html-loader')
.loader('html-loader')
.end()
.use('markdown-loader')
.loader('markdown-loader');
},
};
3. 引入并渲染
const htmlMD = require('./xxx.md'); // 编译期已转好的 HTML 字符串
export default {
data() {
return { htmlMD };
},
};
<div v-html="htmlMD"></div>
特点:
- md 在打包时就变成了 HTML,运行时零解析开销;
- 代价是 md 内容全部进 bundle,且每次改 md 都要重新打包——适合内容基本固定的文档(协议、说明页)。
原文在方案一里写的"也可以通过 ajax 获取资源对象"其实行不通:axios 拿到的是 md 原文(纯文本),
v-html渲染出来就是一坨没解析的#和**。要运行时拉取 + 渲染,请直接看方案二。
方案二:vue-markdown + axios(运行时渲染)
1. 安装组件
npm install --save vue-markdown
原文误写成 vue-loader——那是解析 .vue 单文件组件的 loader,跟 md 渲染无关;这一步也不需要 markdown-loader。
2. 注册组件
import Vue from 'vue';
import VueMarkdown from 'vue-markdown';
Vue.component('vue-markdown', VueMarkdown);
// 或只在页面内用:components: { VueMarkdown }
3. 拉取并渲染
md 文件放静态资源目录(vue-cli 2 的 static/、vue-cli 3+ 的 public/),保证浏览器能直接按 URL 访问到:
export default {
data() {
return { mdSource: '' };
},
mounted() {
axios.get('/static/docs/xxx.md').then((response) => {
this.mdSource = response.data; // md 原文
});
},
};
<vue-markdown :source="mdSource"></vue-markdown>
特点:
- md 就是普通静态文件,改完刷新即见,不用重新构建;
- 运行时多一次请求 + 前端解析开销;
- 按需加载,不会把所有文档打进 bundle。
代码高亮(两种方案通用)
默认渲染出来的代码块没有配色,套一个 highlight.js 即可:
npm install --save highlight.js
import hljs from 'highlight.js';
import 'highlight.js/styles/github.css';
// vue-markdown 透传 markdown-it 的配置
Vue.use(VueMarkdown, {
highlight(str, lang) {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(str, { language: lang }).value;
}
return str;
},
});
再引一个 github-markdown-css,整篇文档的排版就贴近 GitHub 的阅读体验了。
XSS 提醒
v-html 和 markdown 渲染组件都不会自动消毒 HTML。渲染本地自己维护的文档没问题;如果 md 来源包含用户输入(评论、投稿),务必先过 DOMPurify(或给 markdown-it 配白名单)再插入,否则 <script> / <iframe> 注入就是这么来的。
补充:require.context 批量读取 md,自动生成侧边栏
文档多的时候不可能一个个手写 require / URL。webpack 的 require.context 可以一次拿到整个目录的 md 文件清单:
// 拉取 @/static/docs 目录(含子目录)下所有 .md 文件
const filesMD = require.context('@/static/docs', true, /\.md$/);
// keys() 形如:['./basics/getting-started.md', './basics/install.md', './advanced/plugin.md']
const listDatas = filesMD.keys().map((path) => {
const segments = path.replace(/^\.\/|\.md$/g, '').split('/');
return {
name: segments[segments.length - 1], // 文件名(不含后缀),作菜单标题
path, // context 的 key,取内容靠它
dir: segments.slice(0, -1).join('/'), // 所在子目录,用于分组建树
children: [],
showChild: false,
};
});
对原文的三处修正:
- 原文
listItemS[1]只取路径的第二段,文件一进子目录名字就取错了;这里改为取最后一段; - 原文
@/../static/xxxxMD这种绕出src的写法依赖项目结构:vue-cli 2 的static/在根目录(可写相对路径../../static/docs);vue-cli 3+ 的静态资源在public/,而public/下的文件不走 webpack,require.context够不着——要么把文档目录挪进src,要么改为枚举 URL 方式(方案二); - 变量名
tmepListDatas少了个字母,顺手改成listDatas。
把平铺清单按目录组装成二级侧边栏:
function buildSidebar(list) {
const groups = {};
list.forEach((item) => {
const dir = item.dir || '未分类';
(groups[dir] = groups[dir] || []).push(item);
});
return Object.keys(groups).map((dir) => ({
name: dir,
showChild: false,
children: groups[dir],
}));
}
点击菜单项时取内容渲染:
// 方案一:require.context 拿到的就是编译好的模块(HTML 字符串)
handleClick(item) {
this.htmlMD = filesMD(item.path);
}
// 方案二:require.context 只用来建菜单,内容仍走 axios 按 URL 拉原文
handleClick(item) {
const url = `/static/docs/${item.path.replace('./', '')}`;
axios.get(url).then((res) => (this.mdSource = res.data));
}
webpack 5 里
require.context依然可用;换到 Vite 对应的写法是import.meta.glob('/src/static/docs/**/*.md', { eager: true }),思路一致。
两种方案怎么选
| 方案一:markdown-loader | 方案二:vue-markdown + axios | |
|---|---|---|
| 编译时机 | 构建时(打进 bundle) | 运行时(请求 + 前端解析) |
| md 改动后 | 需要重新 build | 刷新即见 |
| 请求开销 | 无(已打进 JS) | 每篇一次请求 |
| 包体积 | 所有 md 都算进 bundle | 只拉当前这篇 |
| 适用场景 | 内容固定、篇数少的说明页 | 文档多、更新频繁的帮助中心 |
一句话:内容不常动选方案一省事,内容频繁更新选方案二免打包;文档量大都可以配合 require.context 自动建菜单。
后话:本文是 Vue 2 + webpack 时代的做法,vue-markdown 组件目前已停止维护。Vue 3 项目可以直接用
markdown-it+v-html(记得消毒),或开箱即用的@kangc/v-md-editor,思路与本文一致。
欢迎关注博主:微信公众号交流,不定时更新前端资源
浙公网安备 33010602011771号