vue組件庫用markdown生成文檔


前言:

開發 vue 組件庫需要提供組件的使用文檔,最好是有渲染到瀏覽器的 demo 實例,既能操作又能查看源代碼。markdown 作為常用的文檔編寫載體,如果能在里面直接寫 vue 組件,同時編寫使用說明就再好不過。流行的組件庫 element-ui 的文檔就是用 markdown 寫出來的,看了看其處理 md 的程序后,自己也決定寫一個類似的處理程序,研究一下其中的細節。

技術點

1.markdown-it

處理 markdown 最常用的工具是 markdown-it,它能把我們寫的 markdown 文件轉換為 html。類似於 babel,markdown 也有自己的插件系統,通過設置或者編寫自定義插件改變渲染的路徑。

2.webpack-loader

處理 md 文件可以使用自定義 webpack-loader 來處理,先把 md 內容轉為合適 html,然后再給 vue-loader 處理。

3.cheerio

使用 markdown-it 把 md 內容轉為 html 之后,需要操作 html,cherrio 以類似 jquery 的方式操作 html,簡單方便。

4.hljs

代碼需要高亮渲染,hijs 的功能就是將代碼處理成 html,通過樣式使其高亮顯示出來。

步驟

1.配置 webpack 解析 md

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

2.markdown-loader 的入口

module.exports = function(source) {
    this.cacheable && this.cacheable();
    const { resourcePath = "" } = this;
    const fileName = path.basename(resourcePath, ".md");
    // @符號在markdown中是特殊符號
    source = source.replace(/@/g, "__at__");

    var content = parser.render(source).replace(/__at__/g, "@");

    var result = renderMd(content, fileName);

    return result;
};

3.添加插件 markdown-it-container

markdown-it-container 是一個插件,使用這個插件之后就可以在 markdown 中添加自己的標識,然后就能自定義處理標識里面的內容。在這里可以在把代碼塊放到標識內部,主要是防止 markdown-it 把 vue 組件轉成 html,由自己處理這些代碼,最終返回想要的內容。

::: demo
​`html <i class="kv-icon-close fs-24"></i> <i class="kv-icon-link fs-24"></i> ​`
:::

上面就是插件的用法,demo 由自己定義,初始注入的代碼如下:

parser.use(require("markdown-it-container"), "demo", {
    validate(params) {
        return params.trim().match(/^demo\s*(.*)$/);
    },
    // 把demo代碼放到div.kv-demo里面
    render(tokens, idx) {
        const m = tokens[idx].info.trim().match(/^demo\s*(.*)$/);
        if (tokens[idx].nesting === 1) {
            const content =
                tokens[idx + 1].type === "fence" ? tokens[idx + 1].content : "";
            // 先把demo中的代碼放到demo-block的之中,然后程序繼續render fence,按照上面的fence規則渲染出代碼部分,作為隱藏的查看代碼。
            return `<demo-block><div  class="kv-demo">${content}</div>`;
        }
        return "</demo-block>";
    },
});

render 方法仿照的是 npm 包里的例子。其中的 tokens 是 AST 節點,可以從這個網址看到 markdown-it 解析的 AST,對照着做判斷。

根據自己的理解,因為 html 是有起始標簽和結束標簽,markdown-it 的 render 也是成對的,也就是在標記的起始和結束都會調用 render 方法,所以在 demo 起始的時候返回了一個起始<demo-block> (demo-block是個全局定義的 vue 組件),這里的content后來變成了vue的組件。

繼續處理 demo 標識內部``` 代碼標識,代碼標識在 markdown-it 中有自己的 rules (rules.fence)來處理,結果就是高亮顯示。我們的目標不僅僅如此,還需要讓這部分代碼有一個顯示隱藏的效果,所以
需要加一個vue的slot標識。

// 先保存下來
const defaultRender = parser.renderer.rules.fence;
parser.renderer.rules.fence = (tokens, idx, options, env, self) => {
    const token = tokens[idx];
    // 判斷該 fence 是否在 :::demo 內
    const prevToken = tokens[idx - 1];
    const isInDemoContainer =
        prevToken &&
        prevToken.nesting === 1 &&
        prevToken.info.trim().match(/^demo\s*(.*)$/);
    if (token.info === "html" && isInDemoContainer) {
        return `<template slot="highlight">${defaultRender(tokens, idx, options, env, self)}</template>`;
    }

    return `<div class="code-common">${defaultRender(
        tokens,
        idx,
        options,
        env,
        self
    )}</div>`;
};

需要注意highlight不會把代碼中的{{tab}} 這種插值轉譯,如果直接給vue渲染會報錯,所以需要添加一個v-pre的指令,需要包裝原始的rules.fence:

const ensureVPre = function (markdown) {
  if (markdown && markdown.renderer && markdown.renderer.rules) {
    const rules = ['code_inline', 'code_block', 'fence']
    const rendererRules = markdown.renderer.rules
    rules.forEach(function (rule) {
      if (typeof rendererRules[rule] === 'function') {
        const saved = rendererRules[rule]
        rendererRules[rule] = function () {
          return saved.apply(this, arguments).replace(/(<pre|<code)/g, '$1 v-pre')
        }
      }
    })
  }
}
ensureVPre(parser)

做完以上部分之后,md 的內容會被渲染成代碼片斷,內部包含普通的 html 標簽和 vue 組件標簽,大概如下:

<div>一些文字</div>
<demo-block>
    <div class="kv-demo">
        <ul class="icon-list">
            <li v-for="name in icons" :key="name">
                <span>
                    <i :class=" iconPre+ name"></i>
                    {{'kv-' + name}}
                </span>
            </li>
        </ul>
        <script>
            export default {
                data() {
                    return {
                        icons: require("../icon.json"),
                        iconPre: "kv-icon-",
                    };
                },
            };
        </script>

        <style lang="scss">
            .demo-icon {
              	.....
               }
        </style>
    </div>
    <template slot="highlight">
        ......
    </template>
</demo-block>

組裝成 vue 模板

這個代碼和 vue 的組件的代碼不一致,是無法解析的,需要修正一下。

另外,一篇文檔中會有多個 demo 即多個 export default,解決方案就是把各個 demo 提取成組件,注冊當前文檔這個 vue 組件中,把 demo 的部分替換組件的名字。

第一部分:組裝當前文檔為 vue 組件 ,同時掛載提取出來 demo 組件https://github.com/blank-x/kv/blob/master/build/markdown-loader/index.js#L15

var renderMd = function (html,fileName) {
	......
}

第二部分:提取其中的 demo 為組件,https://github.com/blank-x/kv/blob/master/build/markdown-loader/index.js#L57)

var renderVueTemplate = function (content) {
	......
}

結果類似於如下:

<template>
  <div class="demo-">
    <demo-block>
      <template slot="source">
        <kv-demo0></kv-demo0>
      </template>
      <template slot="highlight">
        <pre v-pre><code class="html">......</code></pre>
      </template>
    </demo-block>
   .......
    <demo-block>
      <template slot="source">
        <kv-demo1></kv-demo1>
      </template>
      <template slot="highlight">
        <pre v-pre><code class="html"><span class="hljs-tag">.......</code></pre>
      </template>
    </demo-block>
  </div>
</template>
<script>
export default {
  name: "component-doc0",
  components: {
    "kv-demo0": {
      template: `<div class="kv-demo0"><kv-tag>標簽一</kv-tag></div>`
    },
    "kv-demo1": {
      template: `<div class="kv-demo1">
      							<kv-tag :key="tag.name" v-for="tag in dynamicTags"
      									closable :disable-transitions="false" @close="handleClose(tag)" :type="tag.color">
                    {{tag.name}}
                    </kv-tag>
                </div>`,
      data() {
        return {
          dynamicTags: [{
              name: "標簽一",
              color: "primary"
          }]
        };
      },
      methods: {
        handleClose(tag) {
          this.dynamicTags.splice(this.dynamicTags.indexOf(tag), 1);
        }
      }
    }
  }
};
</script>
<style lang="scss"  >
  .kv-tag {
    margin-right: 8px;
  }
</style>

組件 kv-demo0 和 kv-demo1 在 components 中定義;

在 demo 內部的 scss 會被提出來,放到了外層 vue 組件中,如果需要修改樣式,可以參考如下寫法:

.demo-tag .kv-demo1{
	//
}
.demo-tag .kv-demo0{
	//
}
tag  // md的名字
demo0  // 頁面內第幾個demo

未解決的問題

每一個 demo 中 script 標簽和 export 之間的代碼被丟棄。如果需要引入其他文件,可以在 data 中通過 require 引入;

最后

本代碼僅為練手使用,未在實際開發中使用,如有不正之處望指正。
地址https://github.com/blank-x/kv


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM