最近,當我把 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
的項目地址,算是廣告一波。