Element 文档中的 Markdown 解析

Element 的文档站是讲Markdown解析成vue组件在页面中渲染出来,转换过程如下图所示:
Element 文档中的 Markdown 解析

红框部分势必要对 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>
\`\`\`
:::

需要解析成对应的页面如下图:

Element 文档中的 Markdown 解析

通过 :::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不到。后续去找资料麻烦的点具体是指的什么,大家也可以一起讨论下。觉得写的可以点个赞。下篇继续!

参考资料

上一篇:Soul 学习笔记---使用 waf 插件(十九)


下一篇:Chain of Responsibility 职责链模式