We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
本文参考了 Element UI 的 md-loader 源码,为 Element UI 团队表示深深的敬意。
Element UI
md-loader
以下示例是层层递进的,从简单到复杂的顺序进行叙述。另外只有部分代码片段,如果要运行:
vue-cli3
vue create demo-md
演示一个 Vue 工程如何解析一个基本的 Markdown 文件
Demo1.vue
<template> <div class="demo"> <p class="description"> 直接引入 MarkdownIt 插件,调用其函数 md.render 渲染字符串。 </p> <div class="content" v-html="mdResult"></div> </div> </template> <script> import MarkdownIt from 'markdown-it' export default { data () { return { mdResult: '' } }, mounted () { const md = new MarkdownIt() this.mdResult = md.render(` # demo1 ## 二级标题 ### 三级标题 1. 有序列表项1 1. 有序列表项2 * 无序列表项1 * 无序列表项2 * 无序列表项3 `) } } </script>
演示一个 Vue 工程如何加载外部 .md 文件解析并显示到页面上
.md
Demo2.vue
<template> <div class="demo"> <p class="description"> 将外部 MD 文件通过 require 形式引入,必须增加 loader 来处理 </p> <div class="content" v-html="mdResult"></div> </div> </template> <script> export default { data () { return { mdResult: '' } }, mounted () { // OR // import demo2 from './demo2.md' // this.mdResult = demo2 this.mdResult = require('./demo2.md').default } } </script>
demo2.md
# demo2 ## 二级标题 ### 三级标题 1. 有序列表项1 1. 有序列表项2 * 无序列表项1 * 无序列表项2 * 无序列表项3
build/md-loader/index.js
const MarkdownIt = require('markdown-it') const md = new MarkdownIt() module.exports = function (source) { return md.render(source) }
vue.config.js
注意: 一定要结合 raw-loader 使用,详细查看文档。
raw-loader
const path = require('path') // vue.config.js module.exports = { // options... configureWebpack: { module: { rules: [ { test: /\.md$/, use: [ // https://webpack.docschina.org/loaders/raw-loader/ 'raw-loader', { loader: path.resolve(__dirname, './build/md-loader/index.js') } ] } ] } } }
演示如何高亮显示代码块
Demo3.vue
<template> <div class="demo"> <p class="description"> MD 文件中有代码块,需要高亮显示,引入插件 highlight.js </p> <div class="content" v-html="mdResult"></div> </div> </template> <script> export default { data () { return { mdResult: '' } }, mounted () { this.mdResult = require('./demo3.md').default } } </script>
demo3.md
# demo3 这是一个基本的示例说明,使用到了 `highlight.js` 插件 ## 菜单配置说明 ```js // 注释模块 export default [{ id: 1, name: '张三' }]; ```
main.js
import 'highlight.js/styles/tomorrow.css'
const hljs = require('highlight.js') // https://highlightjs.org/ const md = require('markdown-it')({ highlight: function (str, lang) { if (lang && hljs.getLanguage(lang)) { try { return '<pre class="hljs"><code>' + hljs.highlight(lang, str, true).value + '</code></pre>' } catch (__) {} } return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>'; } }) module.exports = function (source) { return md.render(source) }
要能让 vue-router 有能力直接跳转显示一个 Markdown 文件,必须将整个 Markdown 文件转换为一个 Vue 独立组件
vue-router
router/index.js
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) const routes = [ { path: '/', redirect: 'demo4' }, { path: '/demo4', name: 'demo4', component: () => import('./demo4.md') }, { path: '*', redirect: '/' } ] const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) export default router
demo4.md
# demo4 ## npm 安装 推荐使用 npm 的方式安装,它能更好地和 webpack 打包工具配合使用。 ## 示例演示 ### Button ```html <el-button>默认按钮</el-button> <el-button type="primary">主要按钮</el-button> ```
const hljs = require('highlight.js') // https://highlightjs.org/ const md = require('markdown-it')({ highlight: function (str, lang) { if (lang && hljs.getLanguage(lang)) { try { return '<pre class="hljs"><code>' + hljs.highlight(lang, str, true).value + '</code></pre>' } catch (__) {} } return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>'; } }) module.exports = function (source) { return ` <template> <div class="content">${md.render(source)}</div> </template> <script> export default { name: 'demo' } </script> ` }
注意: 不再需要 raw-loader 插件了,因为 .md 文件已经被解析为 vue 组件了,可以通过路由显示,或者作为 components 组件使用
components
const path = require('path') // vue.config.js module.exports = { // options... configureWebpack: { module: { rules: [ { test: /\.md$/, use: [ // https://github.com/vuejs/vue-loader 'vue-loader', // https://webpack.docschina.org/loaders/raw-loader/ // 'raw-loader', { loader: path.resolve(__dirname, './build/md-loader/index.js') } ] } ] } } }
.eslintignore
由于 .md 文件变成了 Vue 组件,所以 Eslint 默认会对其进行代码检查,但是 MD 里面的写法肯定不如标准的 Vue 规范,所以直接忽略掉。
node_modules *.md
要直接预览显示 Markdown 文件中的代码块,只需要2步:
vue-loader
关键点:
markdown-it
vue Component
build/md-loader
demo5.md
注意: 下面示例代码块的写法。
# demo5 ## 示例1 ::: demo 示例1的描述信息 ```html <el-button>默认按钮</el-button> ``` ::: ## 示例2 ::: demo 示例2的描述信息 ```html <template> <el-select v-model="value" placeholder="请选择"> <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"> </el-option> </el-select> </template> <script> export default { data() { return { options: [{ value: '选项1', label: '黄金糕' }, { value: '选项2', label: '双皮奶' }, { value: '选项3', label: '蚵仔煎' }, { value: '选项4', label: '龙须面' }, { value: '选项5', label: '北京烤鸭' }], value: '' } } } </script> ``` :::
router.js
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) const routes = [ { path: '/', redirect: 'demo5' }, { path: '/demo5', name: 'demo5', component: () => import('./demo5.md') }, { path: '*', redirect: '/' } ] const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) export default router
import Vue from 'vue' import App from './App.vue' import router from './router' import 'highlight.js/styles/tomorrow.css' import 'element-ui/lib/theme-chalk/index.css' import ElementUI from 'element-ui' import DemoBlock from './DemoBlock.vue' Vue.config.productionTip = false Vue.use(ElementUI) Vue.component('demo-block', DemoBlock) new Vue({ router, render: h => h(App) }).$mount('#app')
DemoBlock.vue
这个组件在 md-loader 中有所使用,所以先初始化。
<template> <div> <section class="description" v-if="$slots.default"> <slot></slot> </section> <section class="source"> <slot name="source"></slot> </section> </div> </template> <script> export default { name: 'demo-block' } </script>
const hljs = require('highlight.js') // https://highlightjs.org/ const mdContainer = require('markdown-it-container') const parser = require('./parser') const md = require('markdown-it')({ highlight: function (str, lang) { if (lang && hljs.getLanguage(lang)) { try { return '<pre class="hljs"><code>' + hljs.highlight(lang, str, true).value + '</code></pre>' } catch (__) {} } return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>' } }).use(mdContainer, 'demo', { // https://github.com/markdown-it/markdown-it-container#example validate (params) { return params.trim().match(/^demo\s*(.*)$/) }, render (tokens, idx) { const m = tokens[idx].info.trim().match(/^demo\s*(.*)$/) if (tokens[idx].nesting === 1) { const description = m && m.length > 1 ? m[1] : '' const content = tokens[idx + 1].type === 'fence' ? tokens[idx + 1].content : '' return `<demo-block> ${description ? `<div>${md.render(description)}</div>` : ''} <!--demo: ${content}:demo--> ` } return '</demo-block>' } }) module.exports = function (source) { return parser(md, source) }
build/md-loader/parser.js
以下内容大部分出自 Element UI 源码
const { stripScript, stripStyle, stripTemplate, genInlineComponentText } = require('./util') module.exports = function (md, source) { const content = md.render(source) const startTag = '<!--demo:' const startTagLen = startTag.length const endTag = ':demo-->' const endTagLen = endTag.length let componenetsString = '' let id = 0 // demo 的 id let output = [] // 输出的内容 let start = 0 // 字符串开始位置 let styles = [] let commentStart = content.indexOf(startTag) let commentEnd = content.indexOf(endTag, commentStart + startTagLen) while (commentStart !== -1 && commentEnd !== -1) { output.push(content.slice(start, commentStart)) const commentContent = content.slice(commentStart + startTagLen, commentEnd) const html = stripTemplate(commentContent) const script = stripScript(commentContent) const style = stripStyle(commentContent) styles.push(style) let demoComponentContent = genInlineComponentText(html, script) const demoComponentName = `demo${id}` output.push(`<template slot="source"><${demoComponentName} /></template>`) componenetsString += `${JSON.stringify(demoComponentName)}: ${demoComponentContent},` // 重新计算下一次的位置 id++ start = commentEnd + endTagLen commentStart = content.indexOf(startTag, start) commentEnd = content.indexOf(endTag, commentStart + startTagLen) } let pageScript = '' if (componenetsString) { pageScript = `<script> export default { name: 'demo', components: { ${componenetsString} } } </script>` } else if (content.indexOf('<script>') === 0) { start = content.indexOf('</script>') + '</script>'.length pageScript = content.slice(0, start) } output.push(content.slice(start)) return ` <template> <div class="content"> ${output.join('')} </div> </template> ${pageScript} ` }
build/md-loader/util.js
const { compileTemplate } = require('@vue/component-compiler-utils') const compiler = require('vue-template-compiler') function stripScript (content) { const result = content.match(/<(script)>([\s\S]+)<\/\1>/) return result && result[2] ? result[2].trim() : '' } function stripStyle (content) { const result = content.match(/<(style)\s*>([\s\S]+)<\/\1>/) return result && result[2] ? result[2].trim() : '' } // 编写例子时不一定有 template。所以采取的方案是剔除其他的内容 function stripTemplate (content) { content = content.trim() if (!content) { return content } return content.replace(/<(script|style)[\s\S]+<\/\1>/g, '').trim() } function pad (source) { return source .split(/\r?\n/) .map(line => ` ${line}`) .join('\n') } function genInlineComponentText (template, script) { // https://github.com/vuejs/vue-loader/blob/423b8341ab368c2117931e909e2da9af74503635/lib/loaders/templateLoader.js#L46 const finalOptions = { source: `<div>${template}</div>`, filename: 'inline-component', compiler } const compiled = compileTemplate(finalOptions) // tips if (compiled.tips && compiled.tips.length) { compiled.tips.forEach(tip => { console.warn(tip) }) } // errors if (compiled.errors && compiled.errors.length) { console.error( `\n Error compiling template:\n${pad(compiled.source)}\n ${compiled.errors.map(e => ` - ${e}`).join('\n')} ` ) } let demoComponentContent = ` ${compiled.code} ` script = script.trim() if (script) { script = script.replace(/export\s+default/, 'const democomponentExport =') } else { script = 'const democomponentExport = {}' } demoComponentContent = `(function() { ${demoComponentContent} ${script} return { render, staticRenderFns, ...democomponentExport } })()` return demoComponentContent } module.exports = { stripScript, stripStyle, stripTemplate, genInlineComponentText }
以上代码块中,democomponentExport 这个对象比较重要,如果没有的话,嵌在 .md 文件中的代码片段,如果有 <script> 脚本片段无法解析的。
democomponentExport
<script>
(全文完)
The text was updated successfully, but these errors were encountered:
sunmaobin
No branches or pull requests
本文参考了
Element UI
的md-loader
源码,为Element UI
团队表示深深的敬意。以下示例是层层递进的,从简单到复杂的顺序进行叙述。另外只有部分代码片段,如果要运行:
vue-cli3
初始化一个标准工程vue create demo-md
一、工程需要有解析 Markdown 文件的能力
demo-md-1
演示一个 Vue 工程如何解析一个基本的 Markdown 文件
Demo1.vue
demo-md-2
演示一个 Vue 工程如何加载外部
.md
文件解析并显示到页面上Demo2.vue
demo2.md
build/md-loader/index.js
vue.config.js
注意: 一定要结合
raw-loader
使用,详细查看文档。demo-md-3
演示如何高亮显示代码块
Demo3.vue
demo3.md
main.js
build/md-loader/index.js
vue.config.js
注意: 一定要结合
raw-loader
使用,详细查看文档。二、工程需要有能独立访问 Markdown 文件页面的能力
demo-md-4
要能让
vue-router
有能力直接跳转显示一个 Markdown 文件,必须将整个 Markdown 文件转换为一个 Vue 独立组件router/index.js
demo4.md
build/md-loader/index.js
vue.config.js
注意: 不再需要
raw-loader
插件了,因为.md
文件已经被解析为 vue 组件了,可以通过路由显示,或者作为components
组件使用.eslintignore
由于
.md
文件变成了 Vue 组件,所以 Eslint 默认会对其进行代码检查,但是 MD 里面的写法肯定不如标准的 Vue 规范,所以直接忽略掉。node_modules *.md
三、工程需要有直接预览显示 Markdown 文件中代码块的能力
demo-md-5
要直接预览显示 Markdown 文件中的代码块,只需要2步:
vue-loader
转换为一个 Vue Component关键点:
markdown-it
插件,标记 MD 文件中要运行的代码块?vue Component
,然后组织内容变成一个整体 vue 组件?build/md-loader
的写法demo5.md
注意: 下面示例代码块的写法。
router.js
main.js
DemoBlock.vue
这个组件在
md-loader
中有所使用,所以先初始化。build/md-loader/index.js
build/md-loader/parser.js
build/md-loader/util.js
以上代码块中,
democomponentExport
这个对象比较重要,如果没有的话,嵌在.md
文件中的代码片段,如果有<script>
脚本片段无法解析的。vue.config.js
(全文完)
The text was updated successfully, but these errors were encountered: