Vue加载.md格式组件核心代码

参考vue-markdown-loader
参考Element源码系列——Vue加载Markdown格式组件上篇
(如有侵权,请联系本人,会尽快修改)
愿景:开发一套内部ui组件库,适用于B、S
不废话,直接开始
1.项目是基于ts、vue2.x

2.安装相关依赖

markdown-it 渲染 markdown 基本语法
markdown-it-anchor 为各级标题添加锚点
markdown-it-container 用于创建自定义的块级容器
vue-markdown-loader 核心loader
transliteration 中文转拼音
cheerio 服务器版jQuery
highlight.js 代码块高亮实现

3.工具类-strip-tags.ts

'use strict';
var cheerio = require('cheerio'); // 服务器版的jQuery
/**
 * 在生成组件效果展示时,解析出的VUE组件有些是带''
 * @return {[String]}             e.g ''
 */
exports.strip = function(str, tags) {
     
  var $ = cheerio.load(str, {
     decodeEntities: false});
  if (!tags || tags.length === 0) {
     
    return str;
  }
  tags = !Array.isArray(tags) ? [tags] : tags;
  var len = tags.length;
  while (len--) {
     
    $(tags[len]).remove();
  }
  return $.html(); // cheerio 转换后会将代码放入中
};

/**
 * 获取标签中的文本内容
 * @param  {[String]} str e.g '

header

' * @param {[String]} tag e.g 'h1' * @return {[String]} e.g 'header' */
exports.fetch = function(str, tag) { var $ = cheerio.load(str, { decodeEntities: false}); if (!tag) return str; return $(tag).html(); }; /** * 由于cheerio在转换汉字时会出现转为Unicode的情况,所以我们编写convert方法来保证最终转码正确 * @param {[String]} str e.g 成功 * @return {[String]} e.g 成功 */ exports.convert = (str) => { str = str.replace(/(&#x)(\w{4});/gi, function($0) { return String.fromCharCode(parseInt(encodeURIComponent($0).replace(/(%26%23x)(\w{4})(%3B)/g, '$2'), 16)); }); return str; } /** * 由于v-pre会导致在加载时直接按内容生成页面.但是我们想要的是直接展示组件效果,通过正则进行替换 * hljs是highlight.js中的高亮样式类名 * @param {[type]} render e.g '' | '' * @return {[type]} e.g '' */ exports.wrap = (render) => { return function() { return render.apply(this, arguments) .replace(', ') .replace('', ''); }; }

4.vue.config.js 配置

const hljs = require('highlight.js');
const slugify = require('transliteration').slugify
const striptags = require('./md-loader/strip-tags.ts'); // 引入工具类
const md = require('markdown-it')();

const vueMarkdown = {
     
  raw: true,
  // 定义处理规则
  preprocess: function(MarkdownIt, source) {
     
    // 对于markdown中的table,
    MarkdownIt.renderer.rules.table_open = function() {
     
      return '';};// 对于代码块去除v-pre,添加高亮样式
    MarkdownIt.renderer.rules.fence = striptags.wrap(MarkdownIt.renderer.rules.fence);// ```code`` 给这种样式加个class code_inlineconst code_inline = MarkdownIt.renderer.rules.code_inline
    MarkdownIt.renderer.rules.code_inline=function(...args){
     
      args[0][args[1]].attrJoin('class','code_inline')returncode_inline(...args)}return source;},
  use:[[require('markdown-it-anchor'),{
     
      level:2,// 添加超链接锚点的最小标题级别, 如: #标题 不会添加锚点
      slugify: slugify,// 自定义slugify, 我们使用的是将中文转为汉语拼音,最终生成为标题id属性
      permalink:true,// 开启标题锚点功能
      permalinkBefore:true// 在标题前创建锚点}],// 'markdown-it-container'的作用是自定义代码块[require('markdown-it-container'),'demo',{
     // 当我们写::: demo :::这样的语法时才会进入自定义渲染方法
      validate:function(params){
     return params.trim().match(/^demo\s*(.*)$/);},// 自定义渲染方法,这里为核心代码
      render:function(tokens, idx){
     var m = tokens[idx].info.trim().match(/^demo\s*(.*)$/);// nesting === 1表示标签开始if(tokens[idx].nesting ===1){
     // 获取正则捕获组中的描述内容,即::: demo xxx中的xxxvar description =(m && m.length >1)? m[1]:'';// 获得内容var content = tokens[idx +1].content;// 解析过滤解码生成html字符串var html = striptags.convert(striptags.strip(content,['script','style'])).replace(/(<[^>]*)=""(?=.*>)/g,'$1');// 获取script中的内容var script = striptags.fetch(content,'script');// 获取style中的内容var style = striptags.fetch(content,'style');// 组合成prop参数,准备传入组件var jsfiddle ={
      html: html, script: script, style: style };// 是否有描述需要渲染var descriptionHTML = description
            ? md.render(description):'';// 将jsfiddle对象转换为字符串,并将特殊字符转为转义序列
          jsfiddle = md.utils.escapeHtml(JSON.stringify(jsfiddle));// 起始标签,写入demo-block模板开头,并传入参数return`${
       jsfiddle}">
                    
${ html}
${ descriptionHTML}
`; } // 否则闭合标签 return '
\n'
; } }], [require('markdown-it-container'), 'tip'], [require('markdown-it-container'), 'warning'] ] } module.exports = { chainWebpack: config => { config.module.rule('md') .test(/\.md$/) .use('vue-loader') .loader('vue-loader') .options({ compilerOptions: { preserveWhitespace: false, }, }) .end() .use('vue-markdown-loader') .loader('vue-markdown-loader/lib/markdown-compiler') .options(vueMarkdown) } }

5.使用到的demo-block.vue

<template>
  <div class="docs-demo-wrapper">
      <div :style="{height: isExpand ? 'auto' : '0'}" class="demo-container">
        <div span="14">
          <div class="docs-demo docs-demo--expand">
            <div class="highlight-wrapper">
              <slot name="highlight"></slot>
            </div>
          </div>
        </div>
      </div>
    <span class="docs-trans docs-demo__triangle" @click="toggle">{
     {
     isExpand ? '隐藏代码' : '显示代码'}}</span>
  </div>
</template>

<script lang='ts'>
  import {
     Component, Vue} from 'vue-property-decorator'
   @Component({
      
   })
   export default class extends Vue {
     
     private isExpand: boolean = false
     private toggle() {
     
       this.isExpand = !this.isExpand;
     }
   }
</script>
<style lang="less" type="text/less">
  .demo-container {
     
    transition: max-height .3s ease;
    overflow: hidden;
  }
  .docs-demo {
     
    width: 100%;
    height: auto;
    box-sizing: border-box;
    font-size: 14px;
    background-color: #F7F7F7;
    border: 1px solid #e2ecf4;
    border-top: none;
    pre code {
     
      font-family: Consolas,Menlo,Courier,monospace;
      line-height: 22px;
      border: none;
    }
  }
  .docs-trans {
     
    width: 100%;
    text-align: center;
    display: inline-block;
    color: #C5D9E8;
    font-size: 12px;
    padding: 10px 0;
    background-color: #FAFBFC;
  }

  .docs-demo__code,
  .highlight-wrapper,
  .docs-demo__meta {
     
    padding: 0 20px;
    overflow-y: auto;
  }

  .docs-demo__code {
     
    border-bottom: 1px solid #eee;
  }
  .docs-demo.docs-demo--expand .docs-demo__meta {
     
    border-bottom: 1px dashed #e9e9e9;
  }

  .docs-demo.docs-demo--expand .docs-demo__triangle {
     
    transform: rotate(180deg);
  }

  .highlight-wrapper {
     
    display: none;

    p,
    pre {
     
      margin: 0;
    }

    .hljs {
     
      padding: 0;
    }
  }

  .docs-demo.docs-demo--expand .highlight-wrapper {
     
    display: block;
  }

  .docs-demo__code__mobi {
     
    height: 620px;
    margin: 20px 0;
  }

  .docs-demo__code__mobi__header {
     
    border-radius: 4px 4px 0 0;
    background: -webkit-linear-gradient(rgba(55,55,55,.98),#545456);
    background: linear-gradient(rgba(55,55,55,.98),#545456);
    text-align: center;
    padding: 8px;

    img {
     
      width: 100%;
    }

    .url-box {
     
      height: 28px;
      line-height: 28px;
      color: #fff;
      padding: 0 3px;
      background-color: #a2a2a2;
      margin: 10px auto 0;
      border-radius: 4px;
      white-space: nowrap;
      overflow-x: auto;
    }
  }

  .docs-demo__code__mobi__content {
     
    iframe {
     
      width: 100%;
      border: 0;
      height: 548px;
    }
  }
</style>

6.main.ts

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

7.编写组件button.md
Vue加载.md格式组件核心代码_第1张图片
8.展示如图
Vue加载.md格式组件核心代码_第2张图片

你可能感兴趣的:(前端,vue,markdown)