本身擼個 vue markdown loader

本身擼個 vue markdown loader

最近,當我把 vue-loader 升級到 v15 後發現,本身項目中所使用的一個 vue-markdown-loader 由於兼容問題而無法用了,正當我束手無策的時候,無心間看到 vuepress 中使用了當時還處於 v15.0.0 rc 版本的 vue-loader,仔細研究其源碼後發現,vuepress 對於 markdown 的支持至關完善,並且代碼也規範易懂。因而心生一計,把裏面部分相關的代碼拿出來魔改一番,作成一個新的 loader 用到本身的項目……css

關於 webpack loader

爲了幹這個,首先得理解 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

在這個 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>

結果:

result

說明:上圖的渲染結果,涉及到一些 css 樣式,本文就不一一列出了,由於很長……

其它

能夠看到,寫個 loader 其實也是蠻簡單的,瞭解其中原理以後,你甚至能夠建立自創格式的文件和擴展名,而後寫個 loader 處理/加載這類文件,是否是很騷?!

正如開頭提到的,loader 中的代碼,借鑑自 vuepress,感謝其開發組人員並尊重其版權,若是你們有興趣可本身前往查看該項目,本人也並不打算裝這個 loader 封裝成包併發布,它僅僅是爲了本身項目須要折騰的,十分粗陋。

最後,放出本身用到這個 loader 的項目地址,算是廣告一波。

we-vue GitHub 地址

we-vue 在線文檔

相關文章
相關標籤/搜索