最近,当我把 vue-loader 升级到 v15 后发现,本身项目中所使用的一个 vue-markdown-loader 由于兼容问题而无法用了,正当我束手无策的时候,无心间看到 vuepress 中使用了当时还处于 v15.0.0 rc 版本的 vue-loader,仔细研究其源码后发现,vuepress 对于 markdown 的支持至关完善,并且代码也规范易懂。因而心生一计,把里面部分相关的代码拿出来魔改一番,作成一个新的 loader 用到本身的项目……css
为了干这个,首先得理解 webpack 的 loader 的功能和原理。html
loader 用于对模块的源代码进行转换。loader 可使你在 import 或"加载"模块时预处理文件。所以,loader 相似于其余构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 能够将文件从不一样的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至容许你直接在 JavaScript 模块中 import CSS文件!(摘自 webpack 官方中文文档)
因而可知,loader 就像是一个“处理器”,输入特定的内容,处理后进行输出。当必要时,能够把一些合适的 loader 串起来使用,使前一个 loader 的输出变成后一个 loader 的输入,最终获得本身想要的结果。前端
对于本文要提到的 markdown-loader 来讲,它须要进行的处理就是,将 markdown 文件内容解析并包装成一个与 vue 单文件组件内容类似的,而后传给它后面的 vue-loader, 因此一个最简单的 vue markdown-loader 能够长这德性:vue
module.exports = function (src) { const res = ( `<template>\n` + `<h1>hello world</h1>\n` + `</template>` ) return res }
固然,这个 loader 看起来有点儿智障,由于无论传给它什么,最后输出来的都同样的玩意儿。但它确实作一个 loader 一般作的事…… webpack
下面进入正题,要作一个处理 markdown 的 loader 其逻辑要复杂得多,但实质与上面的差很少,首先咱们须要安装一些必要的包。git
yarn add -D markdown-it markdown-it-anchor markdown-it-container markdown-it-emoji markdown-it-table-of-contents
包名称 | 功能说明 |
---|---|
markdown-it | 渲染 markdown 基本语法 |
markdown-it-anchor | 为各级标题添加锚点 |
markdown-it-container | 用于建立自定义的块级容器 |
markdown-it-emoji | 渲染 emoji |
markdown-it-table-of-contents | 自动生成目录 |
highlight.js | 代码高亮 |
在这个 loader 里,传入的是 markdown 文件的源文件,也就是没做任何解析的内容,咱们须要对它进行一些处理,包括解析基本语法、渲染 emoji、添加锚点等处理,并定义一些自定义的块,比较关键的就是,最后要把这些内容包裹到 <template>
标签中,否则接下来处理它们的 vue-loader 处理不了。github
贴上 loader 的代码:web
const fs = require('fs') const path = require('path') const hash = require('hash-sum') const LRU = require('lru-cache') const hljs = require('highlight.js') // markdown-it 插件 const emoji = require('markdown-it-emoji') const anchor = require('markdown-it-anchor') const toc = require('markdown-it-table-of-contents') // 自定义块 const containers = require('./containers') const md = require('markdown-it')({ html: true, // 代码高亮 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 v-pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>' } }) // 使用 emoji 插件渲染 emoji .use(emoji) // 使用 anchor 插件为标题元素添加锚点 .use(anchor, { permalink: true, permalinkBefore: true, permalinkSymbol: '#' }) // 使用 table-of-contents 插件实现自动生成目录 .use(toc, { includeLevel: [2, 3] }) // 定义自定义的块容器 .use(containers) const cache = LRU({ max: 1000 }) module.exports = function (src) { const isProd = process.env.NODE_ENV === 'production' const file = this.resourcePath const key = hash(file + src) const cached = cache.get(key) // 从新模式下构建时使用缓存以提升性能 if (cached && (isProd || /\?vue/.test(this.resourceQuery))) { return cached } const html = md.render(src) const res = ( `<template>\n` + `<div class="content">${html}</div>\n` + `</template>\n` ) cache.set(key, res) return res }
如下为上面代码中引用到的 containers.js 中代码
const container = require('markdown-it-container') module.exports = md => { md .use(...createContainer('tip', 'TIP')) .use(...createContainer('warning', 'WARNING')) .use(...createContainer('danger', 'WARNING')) // explicitly escape Vue syntax .use(container, 'v-pre', { render: (tokens, idx) => tokens[idx].nesting === 1 ? `<div v-pre>\n` : `</div>\n` }) } function createContainer (klass, defaultTitle) { return [container, klass, { render (tokens, idx) { const token = tokens[idx] const info = token.info.trim().slice(klass.length).trim() if (token.nesting === 1) { return `<div class="${klass} custom-block"><p class="custom-block-title">${info || defaultTitle}</p>\n` } else { return `</div>\n` } } }] }
写好了 loader,就能够在 webpack 里使用了,在配置的 module.rules
数组中加入以下规则:数组
{ test: /\.md$/, use: [ { loader: 'vue-loader', // 这里的使用的最新的 v15 版本 options: { compilerOptions: { preserveWhitespace: false } } }, { // 这里用到的就是刚写的那个 loader loader: require.resolve('./markdownLoader') } ] },
而后,就能够在本身的组件中引入 markdown 文件了。假如你有一个名叫 something-cool.md 的文件里有下面这样的内容:缓存
# hello world [[toc]] ## 二级标题1 有钱就是能够随心所欲! :+1: ## 二级标题2 但我特么真的没钱 :cry: ## 二级标题3 ### 三级标题1 ### 三级标题2 :::tip 友情提示 ::: :::danger 卧槽,粗大事了…… :::
在你的 vue 项目中就能够有以下姿式:
<template> <my-markdown/> </template> <script> export default { components: { 'my-markdown': () => import('./something-cool.ms') } } </script>
结果:
说明:上图的渲染结果,涉及到一些 css 样式,本文就不一一列出了,由于很长……
能够看到,写个 loader 其实也是蛮简单的,了解其中原理以后,你甚至能够建立自创格式的文件和扩展名,而后写个 loader 处理/加载这类文件,是否是很骚?!
正如开头提到的,loader 中的代码,借鉴自 vuepress,感谢其开发组人员并尊重其版权,若是你们有兴趣可本身前往查看该项目,本人也并不打算装这个 loader 封装成包并发布,它仅仅是为了本身项目须要折腾的,十分粗陋。
最后,放出本身用到这个 loader
的项目地址,算是广告一波。