没有你的日子里,我真的好想你
枯了,年前1月20送修的电脑,一个月了才拿回来🤕

这篇文章加强认知的同时,使用一下star法则,加强表达能力

situation 情景

公司的组件库出第二版了,技术栈是vue2.6+ts,项目写到快要验收的阶段,问题出现了。组件库的文档在直接使用markdown-loader解析md的情况下,出现了md和demo不对应的情况。(有些开发者(好吧就是我自己。。)组件写完,文档写完,发现还要写.vue的demo组件,在本身还有繁杂业务需求的情况下,开始焦躁不已,demo也就草草了事了),对一些复杂组件,一个不和文档对应的demo,跟没有一样。因此,我们把目光瞄向了文档demo对应的element-ui。

task 任务

任务就是创建出一套md和demo对应的组件文档。

action 执行

确认过眼神,我遇上对的loader。看到element用md作为vue-router的路由组件的时候,element-ui的md-loader应该就是我们想要的东西。
md中使用

::: demo
```html
<action-sheet>...</actionsheet>
export default { ... }
\```
:::

包裹的东西最后会转成我们的demo,因此我们只要写一份md就能得到demo和文档了。

md-loader

一个webpack loader说白了其实就是一个接受source的function,函数处理source之后,返回一个新的source交给下个loader处理。

来康康md-loader的处理顺序

1、获取处理好的md
这边分几种情况

  • 普通的md语法 交由markdown-it来解析,得到标签包裹好的文本
  • 对于被:::demo所包裹的sfc语法,进行两步操作。
    • 将这部分代码用<!--element-demo: :element-demo-->包裹,这个属于一个占位符,在我们遍历demo时便是以这个来标识demo代码的起点和终点的
    • 再使用<demo-block>...</demo-block>包裹
    • 得到一份新的md
    • 针对新的md,改变markdown对demo块的处理方式,并放入<template slot="highlight">中,作为代码展示部分
    if (token.info === 'html' && isInDemoContainer) {
      return `<template slot="highlight"><pre v-pre><code class="html">${md.utils.escapeHtml(token.content)}</code></pre></template>`;
    }
    
    • md.render(source) 得到最后的代码块

2、处理代码块,将其变成vue-loader能够处理的模样
其实,如果md中只有一个demo,根本没有必要进行这步处理,将模板直接塞入source,交给vue-loader处理就好了。可是理想很美好,现实很骨感,每个md一般会有多个demo块,如果共用一份状态,必须混合状态,且状态名不能重复,非常不方便,因此md-loader是这样处理的。

const demoComponentName = `element-demo${id}`;
output.push(`<template slot="source"><${demoComponentName} /></template>`);

对每个块插入一个element-demo${id}组件,然后这个组件,就是我们接下来要生成的了,确保了每个demo都是独立互不干扰的。最后这些组件会通过插槽的形式,插入demo-block组件提供的槽位中。

生成element-demo${id}组件的render函数

依赖的两个库分别是@vue/component-compiler-utilsvue-template-compiler

  • let demoComponentContent = genInlineComponentText(html, script); 通过genInlineComponentText函数生成我们所需的组件代码
  • 将合法的options传给@vue/component-compiler-utils中提供的compileTemplate函数
  const finalOptions = {
    source: `<div>${template}</div>`,
    filename: 'inline-component', // TODO:这里有待调整
    compiler
  };
  const compiled = compileTemplate(finalOptions);
  let demoComponentContent = `
    ${compiled.code}
  `;
  demoComponentContent = `(function() {
    ${demoComponentContent}
    ${script}
    return Object.assign({
      render,
      staticRenderFns
    }, democomponentExport)
  })()`;
  return demoComponentContent;
  • 最后的demoComponentContent就是我们要的script了~
    举个例子,最后的demoComponentContent的样子类似这样
"element-demo0": (function () {
    var render = function (_c) { return _c(....) }
    var staticRenderFns = []
    const democomponentExport = {
        data () {
            return {
            value1: false,
            actions1: [
                { value: 1, label: '项目1' }, 
                { value: 2, label: '项目2' }, 
                { value: 3, label: '项目3' }
            ]
            }
        },
        ...options
    }
    return Object.assign({
        render,
        staticRenderFns
    }, democomponentExport)
})

result 结果

将生成的模板,和得到的element-demo${id}组件script一起,插入到新的模板中

// md-loader
  return `
    <template>
      <section class="content wand-doc" :class="'wand-doc-' + $route.fullPath.substring(1)">
        ${output.join('')}
      </section>
    </template>
    ${pageScript}
  `;

下一步便由vue-loader处理了,生成整个路由组件的render函数并在路由访问时执行并挂载到页面上!最后我们看到的文档和demo就是一一对应的了,看起来非常得直观。

完成!