前端頁面渲染markDown檔案

行者向陽發表於2018-06-01

在我們開發vue元件的過程中,希望把markdown文件寫的外掛使用說明,也展現到頁面中。那麼具體怎麼做呢?

1、安裝vue-markdown-loader

npm install vue-markdown-loader --save
npm install markdown-it-container --save

2、為webpack配置loader

{
    test: /\.md$/,
    loader: 'vue-markdown-loader',
    options: vueMarkdown
}

3、配置options

vue-markdown-loader配置options。

這一部分的程式碼主要是將markDown的原始碼轉換成,html程式碼。注意裡面有幾處用到了自定義的工具類,在utils.js下,後面會附帶原始碼。

這個options是被作為一個獨立的模組使用的,建議複製出來後以一個獨立的檔案儲存在build資料夾中。

const striptags = require('./strip-tag');
const vueMarkdown = {
  preprocess: (MarkdownIt, source) => {
    MarkdownIt.renderer.rules.table_open = function () {
      return '<table class="table">'
    }
    MarkdownIt.renderer.rules.fence = utils.wrapCustomClass(MarkdownIt.renderer.rules.fence)

    // ```code`` 給這種樣式加個class code_inline
    const code_inline = MarkdownIt.renderer.rules.code_inline
    MarkdownIt.renderer.rules.code_inline = function(...args){
      args[0][args[1]].attrJoin('class', 'code_inline')
      return code_inline(...args)
    }
    return source
  },
  use: [
    [require('markdown-it-container'), 'demo', {
      validate: params => params.trim().match(/^demo\s*(.*)$/),
      render: function(tokens, idx) {
        var m = tokens[idx].info.trim().match(/^demo\s*(.*)$/);
        if (tokens[idx].nesting === 1) {
          var desc = tokens[idx + 2].content;
          const html = utils.convertHtml(striptags(tokens[idx + 1].content, 'script'))
          // 移除描述,防止被新增到程式碼塊
          tokens[idx + 2].children = [];
          return `<demo-block>
                        <div slot="desc">${html}</div>
                        <div slot="highlight">`;
        }
        return '</div></demo-block>\n';
      }
    }],
    [require('markdown-it-container'), 'tip'],
    /* or */
    [require('markdown-it-container'), 'warning']
  ]
};
exports.vueMarkdown = vueMarkdown;

在這段程式碼頂部,我們引入了striptags。這個模組是用來解析示例程式碼的。strip-tag的具體內容。

const cheerio = require('cheerio')
//cheerio是nodejs的抓取頁面模組,為伺服器特別定製的,快速、靈活、實施的jQuery核心實現。適合各種Web爬蟲程式。

module.exports = (str, tags) => {
  const $ = cheerio.load(str, { decodeEntities: false })

  if (!tags || tags.length === 0) {
    return str
  }

  tags = !Array.isArray(tags) ? [tags] : tags
  let len = tags.length

  while (len--) {
    $(tags[len]).remove()
  }
  return $.html()
}

在webpack的build目錄下的utils.js中新增下面的兩個方法

/**
 * 增加 hljs 的 classname
 */
exports.wrapCustomClass = function (render) {
  return function (...args) {
    return render(...args)
      .replace('<code class="', '<code class="hljs ')
      .replace('<code>', '<code class="hljs">')
  }
};

/**
 * Format HTML string
 */
exports.convertHtml = function (str) {
  return str.replace(/(&#x)(\w{4});/gi, $0 => String.fromCharCode(parseInt(encodeURIComponent($0).replace(/(%26%23x)(\w{4})(%3B)/g, '$2'), 16)))
};

4、配置展示mark-down文件的vue頁面

準備一個名叫demo-block的vue檔案,放在你喜歡的地方。然後註冊全域性元件。

注意,在註冊全域性元件時,一定是叫demo-block。

import demoBlock from './components/demo-block.vue';
Vue.component('demo-block', demoBlock);

demo-block.vue檔案的原始碼。

<template>
    <div
            class="demo-block"
            :class="[{ 'hover': hovering }]"
            @mouseenter="hovering = true"
            @mouseleave="hovering = false">
        <slot name="source"></slot>
        <div class="meta" ref="meta">
            <div class="description" v-if="$slots.default">
                <slot></slot>
            </div>
            <slot name="highlight"></slot>
        </div>
    </div>
</template>

<script type="text/babel">

export default {
  data () {
    return {
      hovering: false,
    }
  },
}
</script>

5、設定路由

大家會發現,每一個markddow文件對應一個路由,所以我們切換markDown的時候,其實是切換的是子路由頁面。

import Vue from 'vue';
import Router from 'vue-router';
import loadMap from '@/components/loadMap.vue';

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'loadMap',
      component: loadMap,
      redirect:{ name : 'home' },
      children: [
        {
          path: '/home',
          name: 'home',
          component: r => require.ensure([], () => r(require('../docs/readme.md')))
        },
        {
          path: '/test',
          name: 'test',
          component: r => require.ensure([], () => r(require('../docs/hello.md')))
        }
      ]
    }
  ]
})

6、編寫一個模板文件

@這是我們展示的說明文件
**三維測量 measure**
### 基本用法

* @param type 測量型別
* line 直線測量
* area 面積測量
* vertical 垂直測量
* horizontal 水平測量

-----
@這部分是頁面上可以互動的程式碼

<div class="measure-ct">
            <button @click="measure('line')">直線測量</button>
            <button @click="measure('area')">面積測量</button>
            <button @click="measure('vertical')">垂直測量</button>
            <button @click="measure('horizontal')">水平測量</button>
            <button @click="measureCancel">清除測量結果</button>
        </div>
        <br>

::: demo

@這部分是我們展示的示例程式碼
```shell
import {cw5} from 'cw3d/index.js';
cw5.measure.measureMent(type)
```
```html
<template>
    <div>
        <div class="measure-ct">
            <button @click="measure('line')">直線測量</button>
            <button @click="measure('area')">面積測量</button>
            <button @click="measure('vertical')">垂直測量</button>
            <button @click="measure('horizontal')">水平測量</button>
            <button @click="measureCancel">取消測量模式</button>
        </div>
    </div>
</template>
<script>
import {cw5} from 'cw3d/index.js';
export default {
  name: 'measure',
  methods: {
    measure(type){
      cw5.measure.measureMent(type)
    },
    measureCancel(){
      cw5.measure.measureCancel()
    }
  }
}
</script>

```
:::

7、優化樣式—-引入文件模組的css檔案

這個css檔案有兩個版本,一個lass、一個css。通常css引入就可以。

可以在任意需要css渲染的檔案中,引入這個css檔案。

<style>
    @import "../assets/docs.css";
    .example-block{
        display: flex;
    }
</style>

要引入的docs.css檔案。

body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, form, fieldset, legend, button, input, textarea, th, td,
iframe {
  margin: 0;
  padding: 0;
  border: 0;
  vertical-align: baseline;
}
body {
  font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', SimSun, sans-serif;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  background-color: #f8f8f8;
}
a {
  color: #4078c0;
  text-decoration: none;
}
button, input, select,
textarea {
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
  color: inherit;
}
ul, ol {
  list-style: none;
}
code.hljs {
  line-height: 1.5;
  font-family: Source Code Pro, Menlo, Monaco, Consolas, Courier, monospace;
  font-size: 12px;
  padding: 20px;
  background-color: #2a3138;
  border: solid 1px #E5E5E5;
  margin-bottom: 25px;
  border-radius: 4px;
  -webkit-font-smoothing: auto;
}
.clearfix::before {
  display: table;
  content: "";
}
.clearfix::after {
  display: table;
  content: "";
  clear: both;
}
.main-content {
  width: 50%;
  margin: 50px 20px 40px;
  border: 1px solid #f2f2f2;
}
.main-content .page-tip {
  position: absolute;
  left: 50%;
  top: 60px;
  transform: translatex(-50%);
  height: 60px;
  line-height: 60px;
  font-size: 24px;
  color: #0067ed;
}
.page-container {
  background-color: #fff;
  position: relative;
  display: flex;
  width: 100%;
  overflow: hidden;
}
.page-content {
  box-sizing: border-box;
  flex: 1;
}
.page-content section {
  padding: 0 40px;
}
.page-content section > h1,
.page-content section > h2,
.page-content section > h3,
.page-content section > h4,
.page-content section > h5,
.page-content section > h6 {
  color: #333;
  line-height: 1.5;
  margin: 20px 0;
  font-weight: normal;
}
.page-content section > h1:hover a,
.page-content section > h2:hover a,
.page-content section > h3:hover a,
.page-content section > h4:hover a,
.page-content section > h5:hover a,
.page-content section > h6:hover a {
  opacity: .4;
}
.page-content section > h1 a,
.page-content section > h2 a,
.page-content section > h3 a,
.page-content section > h4 a,
.page-content section > h5 a,
.page-content section > h6 a {
  float: left;
  margin-left: -20px;
  opacity: 0;
  cursor: pointer;
}
.page-content section > h1 a:hover,
.page-content section > h2 a:hover,
.page-content section > h3 a:hover,
.page-content section > h4 a:hover,
.page-content section > h5 a:hover,
.page-content section > h6 a:hover {
  opacity: .4;
}
.page-content section > h1 {
  font-size: 34px;
}
.page-content section > h2 {
  font-size: 28px;
}
.page-content section > h3 {
  font-size: 22px;
}
.page-content section > h4 {
  font-size: 18px;
}
.page-content section > h5 {
  font-size: 16px;
}
.page-content section > h6 {
  font-size: 14px;
  color: #666;
}
.page-content section > p {
  font-size: 14px;
  line-height: 20px;
  color: #666;
  margin: 14px 0;
}
.page-content section > p a {
  color: #38f;
}
.page-content section > ul li,
.page-content section > ol li {
  color: #666;
  font-size: 14px;
  line-height: 20px;
  margin: 10px 0 10px 20px;
  padding-left: 20px;
  position: relative;
}
.page-content section > ul li a,
.page-content section > ol li a {
  color: #38f;
}
.page-content section > ul li::before,
.page-content section > ol li::before {
  content: '';
  position: absolute;
  width: 8px;
  height: 8px;
  box-sizing: border-box;
  border: 2px solid #999;
  border-radius: 50%;
  top: 6px;
  left: 0;
}
.page-content section > ul li li,
.page-content section > ol li li {
  margin-left: 0;
}
.page-content p > code,
.page-content .table code,
.page-content li > code {
  background-color: #F2F2F2;
  display: inline-block;
  border: 1px solid #E5E5E5;
  border-radius: 3px;
  padding: 1px 3px;
  color: #333;
  margin: 0 2px;
}
.table {
  border-collapse: collapse;
  width: 100%;
  background-color: #fff;
  color: #333;
  font-size: 14px;
  margin-bottom: 45px;
}
.table th {
  text-align: left;
  border: 1px solid #E5E5E5;
  background-color: #F2F2F2;
  padding: 10px;
}
.table th:first-child {
  padding-left: 10px;
}
.table td {
  border: 1px solid #E5E5E5;
  padding: 10px;
}
.l {
  float: left;
}
.r {
  float: right;
}
.gray {
  color: #999;
}
.placeholder {
  font-size: 14px;
  color: #cccccc;
}
.vui-intro {
  text-align: center;
}
.vui-intro__logo img {
  margin: 0 auto;
}
.vui-intro__title {
  font-size: 45px;
  line-height: 60px;
  font-weight: 400;
  font-family: monospace;
}
.vui-intro__subtitle {
  font-size: 15px;
  color: #455a64;
}

相關文章