Element 文件中的 Markdown 解析

核桃大號發表於2021-03-01

Element 的文件站是講Markdown解析成vue元件在頁面中渲染出來,轉換過程如下圖所示:

紅框部分勢必要對 Markdown 進行特殊的訂製,訂製過的 Markdown 像下面這樣。


:::demo 要使用 Radio 元件,只需要設定`v-model`繫結變數,選中意味著變數的值為相應 Radio `label`屬性的值,`label`可以是`String`、`Number`或`Boolean`。
```html
<template>
  <el-radio v-model="radio" label="1">備選項</el-radio>
  <el-radio v-model="radio" label="2">備選項</el-radio>
</template>
<script>
  export default {
    data () {
      return {
        radio: '1'
      };
    }
  }
</script>
\`\`\`
:::

需要解析成對應的頁面如下圖:

通過 :::demo 作為頁面中元件例項的標識,這個轉換過程在md-loader中處理。具體element文件站如何實現解析功能的,看看原始碼build檔案下的webpack.demo.js配置md的解析器。

webpack配置

把Markdown解析成vue元件就在webpack配置md的解析loader:

    {
        test: /\.md$/,
        use: [
          {
            loader: 'vue-loader',
            options: {
              compilerOptions: {
                preserveWhitespace: false
              }
            }
          },
          {
            loader: path.resolve(__dirname, './md-loader/index.js')
          }
        ]
      },

從配置檔案中可以看出,Markdown 先經由 md-loader 處理,然後再交由 vue-loader 處理。經過這兩個 loader 的處理後輸出JavaScript在頁面中渲染出來。

md-loader

原始碼中md-loader目錄如下:

├─md-loader
 |      ├─config.js
 |      ├─containers.js
 |      ├─fence.js
 |      ├─index.js
 |      ├─util.js

index.js:

const {
  stripScript,
  stripTemplate,
  genInlineComponentText
} = require('./util');
const md = require('./config');
module.exports = function(source) {
  const content = md.render(source);
  const startTag = '<!--element-demo:';
  const startTagLen = startTag.length;
  const endTag = ':element-demo-->';
  const endTagLen = endTag.length;
  ...

md.render(source)這句程式碼是在markdown-it外掛(將markdown轉換為html外掛)用法很相似,根據const md = require('./config');可以猜測markdown轉換為html的程式碼在config中。

config.js:

const Config = require('markdown-it-chain');
const anchorPlugin = require('markdown-it-anchor');
const slugify = require('transliteration').slugify;
const containers = require('./containers');
const overWriteFenceRule = require('./fence');
const config = new Config();
config
  .options.html(true).end()
  .plugin('anchor').use(anchorPlugin, [
    {
      level: 2,
      slugify: slugify,
      permalink: true,
      permalinkBefore: true
    }
  ]).end()
  .plugin('containers').use(containers).end();
const md = config.toMd();
overWriteFenceRule(md);
module.exports = md;

程式碼中用到很多外掛,我們先百度下這幾個外掛的作用。

markdown-it-chain:npm上markdown-it-chain包的描述是這樣的:

In order to ensure the consistency of the chained API world, webpack-it-chain is developed directly on the basis of webpack-chain and ensures that the usage is completely consistent.Here are some things worth reading that come from webpack-chain:ChainedMapConfig plugins

因為英語不好,我用谷歌翻譯:

為了確保鏈式API世界的一致性,直接在webpack-chain的基礎上開發了webpack-it-chain,並確保用法是完全一致的。

確保鏈式API世界的一致性這句基本沒看懂,可能作者老哥也和我一樣英語不好,但我們可以知道這個外掛是在webpack-it-chain的基礎做的功能完善優化。通過給的markdown-it-chain的例子我們知道 config.js程式碼主要就是在宣告使用markdown-it-chain的。markdown-it-chain的例子程式碼如下:

// Require the markdown-it-chain module. This module exports a single
// constructor function for creating a configuration API.
const Config = require('markdown-it-chain')
 
// Instantiate the configuration with a new API
const config = new Config()
 
// Make configuration changes using the chain API.
// Every API call tracks a change to the stored configuration.
config
  // Interact with 'options' in new MarkdownIt
  // Ref: https://markdown-it.github.io/markdown-it/#MarkdownIt.new
  .options
    .html(true) // equal to .set('html', true)
    .linkify(true)
    .end()
 
  // Interact with 'plugins'
  .plugin('toc')
    // The first parameter is the plugin module, which may be a function
    // while the second parameter is an array of parameters accepted by the plugin.
    .use(require('markdown-it-table-of-contents'), [{
      includeLevel: [2, 3]
    }])
    // Move up one level, like .end() in jQuery.
    .end()
 
  .plugin('anchor')
    .use(require('markdown-it-anchor'), [{
      permalink: true,
      permalinkBefore: true,
      permalinkSymbol: '$'
    }])
    // Apply this plugin before toc.
    .before('toc')
 
// Create a markdown-it instance using the above configuration
const md = config.toMd()
md.render('[[TOC]] \n # h1 \n ## h2 \n ## h3 ')

要知道markdown-it-chain的到底是做什麼的,我去查了下webpack-chain外掛

Use a chaining API to generate and simplify the modification of webpack version 2-4 configurations.
鏈式API用於建立和修改webpack配置

就是提供一些鏈式函式的呼叫方法來修改和建立webpack配置,例子如下:

const Config = require('webpack-chain');

const config = new Config();

config
  .amd(amd)
  .bail(bail)
  .cache(cache)
  .devtool(devtool)
  .context(context)
  .externals(externals)
  .loader(loader)
  .name(name)
  .mode(mode)
  .parallelism(parallelism)
  .profile(profile)
  .recordsPath(recordsPath)
  .recordsInputPath(recordsInputPath)
  .recordsOutputPath(recordsOutputPath)
  .stats(stats)
  .target(target)
  .watch(watch)
  .watchOptions(watchOptions)

至此第一個外掛markdown-it-chain我們知道了他的用處:用鏈式呼叫的方法來建立和修改markdown-it配置,而其中plugin是給markdown-it配置一些外掛。config.js程式碼中

 .plugin('anchor').use(anchorPlugin, [
    {
      level: 2,
      slugify: slugify,
      permalink: true,
      permalinkBefore: true
    }
  ]).end()
  .plugin('containers').use(containers).end();

就是給markdown-it新增markdown-it-anchorcontainers.js外掛。

那麼這裡丟擲一個問題,為什麼使用markdown-it-chain,它帶來的好處是什麼呢?

npm上 webpack-chain的文件是這麼說的:

webpack's core configuration is based on creating and modifying a potentially unwieldy JavaScript object. While this is OK for configurations on individual projects, trying to share these objects across projects and make subsequent modifications gets messy, as you need to have a deep understanding of the underlying object structure to make those changes.
webpack的核心配置基於建立和修改可能難以使用的JavaScript物件。 儘管這對於單個專案上的配置是可以的,但是嘗試在專案之間共享這些物件並進行後續修改會很麻煩,因為您需要對基礎物件結構有深刻的瞭解才能進行這些更改。

大概意思理解了,但因為沒有經常操作webpack的配置所以對嘗試在專案之間共享這些物件並進行後續修改會很麻煩這個點get不到。後續去找資料麻煩的點具體是指的什麼,大家也可以一起討論下。覺得寫的可以點個贊。下篇繼續!

參考資料

相關文章