浏览代码

feat: zyl-update

zhenyulei 5 年之前
父节点
当前提交
c26a19d296

+ 19 - 0
loader/md-vue/card-wrapper.js

@@ -0,0 +1,19 @@
+/*
+把<h>标题,替换为:::分割的标题,然后可以定制化的处理h标题
+*/
+module.exports = function cardWrapper(html) {
+  const group = html
+    .replace(/<h3/g, ':::<h3')
+    .replace(/<h2/g, ':::<h2')
+    .split(':::');
+
+  return group
+    .map(fragment => {
+      if (fragment.indexOf('<h3') !== -1) {
+        return `<div class="card my-card">${fragment}</div>`;
+      }
+
+      return fragment;
+    })
+    .join('');
+};

+ 9 - 0
loader/md-vue/highlight.js

@@ -0,0 +1,9 @@
+const hljs = require('highlight.js');
+
+module.exports = function highlight(str, lang) {
+  if (lang && hljs.getLanguage(lang)) {
+    return hljs.highlight(lang, str, true).value;
+  }
+
+  return '';
+};

+ 77 - 0
loader/md-vue/index.js

@@ -0,0 +1,77 @@
+const loaderUtils = require('loader-utils'); //:loader-utils 是一个npm i loader-utils -D 安装的插件,便于获取webpack.config.js 中配置loader的options;
+const MarkdownIt = require('markdown-it'); //渲染 markdown 基本语法
+const markdownItAnchor = require('markdown-it-anchor'); //为各级标题添加锚点
+const frontMatter = require('front-matter'); //就是md文档最上面的内容 类似于docz中的路由/标题的设置部分
+const highlight = require('./highlight');
+const linkOpen = require('./link-open');
+const cardWrapper = require('./card-wrapper');
+const { slugify } = require('transliteration');
+
+function wrapper(content) {
+  content = cardWrapper(content);
+  content = escape(content);
+
+  return `
+import { h } from 'vue';
+
+const content = unescape(\`${content}\`);
+
+export default {
+  mounted() {
+    const anchors = [].slice.call(this.$el.querySelectorAll('h2, h3, h4, h5'));
+
+    anchors.forEach(anchor => {
+      anchor.addEventListener('click', this.scrollToAnchor);
+    });
+  },
+
+  methods: {
+    scrollToAnchor(event) {
+      if (event.target.id) {
+        this.$router.push({
+          path: this.$route.path,
+          hash: '#'+event.target.id
+        })
+      }
+    }
+  },
+
+  render() {
+    return h('section', { innerHTML: content });
+  }
+};
+`;
+}
+
+const parser = new MarkdownIt({
+  html: true,
+  linkify: true,
+  highlight
+}).use(markdownItAnchor, {
+  level: 2, // 添加超链接锚点的最小标题级别, 如: #标题 不会添加锚点
+  slugify // 自定义slugify, 我们使用的是将中文转为汉语拼音,最终生成为标题id属性
+});
+
+module.exports = function(source) {
+  let options = loaderUtils.getOptions(this) || {}; // 获取loader的参数
+  this.cacheable && this.cacheable();
+
+  options = {
+    wrapper,
+    linkOpen: true,
+    ...options
+  };
+
+  let fm;
+
+  if (options.enableMetaData) {
+    fm = frontMatter(source);
+    source = fm.body;
+  }
+
+  if (options.linkOpen) {
+    linkOpen(parser);
+  }
+
+  return options.wrapper(parser.render(source), fm);
+};

+ 18 - 0
loader/md-vue/link-open.js

@@ -0,0 +1,18 @@
+// add target="_blank" to all links
+module.exports = function linkOpen(md) {
+  const defaultRender =
+    md.renderer.rules.link_open ||
+    function(tokens, idx, options, env, self) {
+      return self.renderToken(tokens, idx, options);
+    };
+
+  md.renderer.rules.link_open = function(tokens, idx, options, env, self) {
+    const aIndex = tokens[idx].attrIndex('target');
+
+    if (aIndex < 0) {
+      tokens[idx].attrPush(['target', '_blank']); // add new attribute
+    }
+
+    return defaultRender(tokens, idx, options, env, self);
+  };
+};

文件差异内容过多而无法显示
+ 14686 - 0
package-lock.json


+ 6 - 0
package.json

@@ -59,9 +59,15 @@
     "eslint": "^6.7.2",
     "eslint-plugin-prettier": "^3.1.3",
     "eslint-plugin-vue": "^7.0.0-0",
+    "front-matter": "^4.0.2",
+    "highlight.js": "^10.3.1",
     "husky": "^4.3.0",
     "lint-staged": "^10.5.0",
+    "loader-utils": "^2.0.0",
+    "markdown-it": "^12.0.2",
+    "markdown-it-anchor": "^6.0.0",
     "prettier": "^1.19.1",
+    "transliteration": "^2.2.0",
     "typescript": "~3.9.3"
   },
   "eslintConfig": {

+ 2 - 2
src/config.ts

@@ -8,7 +8,7 @@ export const nav = [
     name: '布局组件',
     packages: [
       {
-        name: 'Button',
+        name: 'button',
         sort: 1,
         cName: '按钮组件',
         type: 'component',
@@ -26,7 +26,7 @@ export const nav = [
     name: '基础组件',
     packages: [
       {
-        name: 'Uploader',
+        name: 'uploader',
         sort: 1,
         cName: '上传组件',
         type: 'component',

+ 23 - 1
src/packages/button/doc.md

@@ -14,9 +14,31 @@ const app = createApp();
 app.use(Button);
 
 ```
+https://www.baidu.com
+
+
+## 测试标题
 
 ### 代码示例
 
 #### 按钮类型
 
-按钮支持 `default`、`primary`、`success`、`warning`、`danger` 五种类型,默认为 default。
+按钮支持 `default`、`primary`、`success`、`warning`、`danger` 五种类型,默认为 default。
+
+|序号|名称|备注|
+|--|--|--|
+|1|小花|等哈阿贾克斯|
+|2|小浪|阿师大丹江口市|
+
+## das标题
+
+### 3123示例
+
+#### 按ewqe型
+
+按钮支持 `default`、`primary`、`success`、`warning`、`danger` 五种类型,默认为 default。
+
+|序号|名称|备注|
+|--|--|--|
+|1|小花|等哈阿贾克斯|
+|2|小浪|阿师大丹江口市|

+ 256 - 0
src/packages/uploader/doc.md

@@ -0,0 +1,256 @@
+# Uploader 上传
+
+> 文件上传组件
+
+### 基本用法
+
+``` javascript
+import { createApp } from 'vue';
+import { Button } from '@nutui/nutui';
+
+const app = createApp();
+app.use(Button);
+
+```
+
+```html
+<nut-uploader
+    name="uploader-demo"
+    :url="url"
+    :isPreview="true"
+    :acceptType = "['image/jpeg', 'image/png', 'image/gif', 'image/bmp']"
+    @start="onStart"
+    @success="onSuccess"
+    @fail="onFail"
+    @progress="onProgress"
+    @preview="onPreview"
+    @showMsg="showMsgFn"
+    typeError="对不起,不支持上传该类型文件!"
+    limitError="对不起,文件大小超过限制!"
+>
+上传
+</nut-uploader>   
+```
+
+```javascript
+export default { 
+  data() {
+    return {
+      url:'https://my-json-server.typicode.com/linrufeng/demo/posts',    
+    };
+  },
+  methods:{
+      onStart(){
+        console.log('上传开始');
+      },
+      onSuccess(file,res){
+        alert('上传成功!');
+      },
+      onFail(file,res){
+        alert('上传失败!');
+      },
+      onProgress(file, loaded, total) {
+        console.log('上传进度:'+parseInt((100 * loaded) / total)+'%');
+      },
+      onPreview(file) {
+        this.previewImg = file;
+      },
+      showMsgFn(msg){
+        alert(msg);
+      },
+  }
+```
+
+### 高级用法
+
+与吐司 **Toast** 组件结合使用
+
+```html
+<nut-uploader
+    name="uploader-demo"
+    :url="url"
+    @success="onSuccess"
+    @fail="onFail"
+    @showMsg="showMsgFn"
+>
+上传
+</nut-uploader>   
+```
+
+```javascript
+export default { 
+  data() {
+    return {
+      url:'https://my-json-server.typicode.com/linrufeng/demo/posts',    
+    };
+  },
+  methods:{
+      onSuccess(file,res){
+        this.$toast.success('上传成功');
+      },
+      onFail(file,res){
+        this.$toast.fail('上传失败!');
+      },
+      showMsgFn(msg){
+        this.$toast.text(msg);
+      },
+  }
+
+```
+
+与按钮 **Button** 组件结合使用
+
+```html
+<nut-uploader
+  :name="name"
+  :url="url"    
+  >
+  <nut-button small>上传</nut-button>
+</nut-uploader>   
+```
+
+自定义 headers & formData 使用
+```html
+<nut-uploader
+  :name="name"
+  :url="url"
+  :headers="headers"
+  :attach="formData"
+  >
+  <nut-button small>上传</nut-button>
+</nut-uploader>   
+```
+```javascript
+export default { 
+  data() {
+    return {
+      url:'https://my-json-server.typicode.com/linrufeng/demo/posts', 
+      name:'testname',
+      headers:{
+        token:'test'
+      },
+      formData:{
+        f1:'test',
+        f2:'test1'
+      },
+    };
+  },
+}
+```
+
+与进度条组件 **Progress** 结合使用
+
+```html
+<nut-uploader
+    :name="name"
+    :url="url"
+    @progress="progress"    
+    > 上传
+</nut-uploader>  
+<nut-progress :percentage="progressNum" :showText="false" strokeWidth="24"/>
+```
+预览上传图片
+```html
+ <nut-uploader
+    :name="name"
+    :url="url"
+    :xhrState="stateNum"
+    :isPreview="true"
+    @success="demoSuccess"
+    @fail="demoFail"
+    @preview="preview"
+    @showMsg="showMsg1"
+  >
+    <nut-button small>上传</nut-button>
+  </nut-uploader>
+```
+```js
+preview(file) {
+  this.previewImg = file;
+},
+```
+
+上传图片前处理图片内容
+
+```html
+ <nut-uploader
+    :beforeUpload="test"
+    :name="name"
+    :url="url"
+    :xhrState="stateNum"
+    :acceptType = "['image/jpeg', 'image/png', 'image/gif', 'image/bmp']"
+    @success="demo1Success"
+    @failure="demo1Fail"
+    @start="demo1UploadStart"
+    @showMsg="showMsg"
+  ><nut-button small>上传图片前处理图片内容</nut-button></nut-uploader>
+```
+```js
+test($ev){   
+  console.log($ev,'可以处理input选择的内容')  
+  return {
+    event:$ev,
+    data:''
+  }
+},
+```
+
+自定义增加上传图片数据
+```html
+<nut-uploader
+    :selfData="selfData"          
+    :name="name"
+    :url="url"
+    :xhrState="stateNum"
+    :acceptType = "['image/jpeg', 'image/png', 'image/gif', 'image/bmp']"
+    @success="demo1Success"
+    @failure="demo1Fail"
+    @start="demo1UploadStart"
+    @showMsg="showMsg"
+  ><nut-button small>自定义增加上传图片数据</nut-button></nut-uploader>
+
+```
+
+
+
+### Prop
+
+| 字段 | 说明 | 类型 | 默认值
+|----- | ----- | ----- | ----- 
+| name | input name的名称 | String | ""
+| url | 上传服务器的接口地址 | String | -
+| isPreview | 是否需要预览 | Boolean | false
+| clearInput | 是否需要清空input内容,设为true支持重复选择上传同一个文件 | Boolean | false
+| maxSize | 可以设定最大上传文件的大小(字节) | Number | 5242880
+| acceptType | 可以上传文件的类型 | Array | ['image/jpeg', 'image/png', 'image/gif', 'image/bmp']
+| attach | 附加上传的信息formData | Object | {}
+| headers | 自定义headers | Object | {}
+| xhrState | 接口响应的成功状态(status)值 | Number | 200
+| typeError | 文件类型错误提示文案 | String | "不支持上传该类型文件"
+| limitError | 文件大小超过限制提示文案 | String | "文件大小超过限制"
+| xmlError | 浏览器不支持本组件时的提示文案 | String | "对不起,您的浏览器不支持本组件!"
+| withCredentials | 支持发送 cookie 凭证信息 | Boolean | fasle
+| beforeUpload | 上传前的函数需要返回一个对象  | Function | {event:$event} $event为点击事件必传
+| selfData | 自定义增加上传的数据 | Object | {}
+
+### Event
+
+| 名称 | 说明 | 回调参数 
+|----- | ----- | ----- 
+| start | 文件上传开始 | -
+| progress | 文件上传的进度 | 上传文件、已上传数据量、总数据量
+| preview | isPreview为true时可通过此方法获文件的Base64编码,一般用于预览 | 文件的Base64编码
+| success | 上传成功 | 文件、responseText
+| failure | 上传失败 | 文件、responseText
+| showMsg | 组件抛出信息的处理函数 | 组件抛出的提示信息
+
+### tips
+
+使用 beforeUpload 一定要返回一个JSON 对象且 event 为必传字段
+```js
+beforeUpload($e){
+  return {
+    event:$e
+  }
+}
+```

+ 23 - 2
src/sites/doc/router.ts

@@ -1,10 +1,22 @@
+// @ts-nocheck
 import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
 import Index from './views/Index.vue';
+
+const pagesRouter: any = [];
+const files = require.context('@/packages', true, /doc\.md$/);
+files.keys().forEach(component => {
+  const componentEntity = files(component).default;
+  pagesRouter.push({
+    path: `/${component.split('/')[1]}`,
+    component: componentEntity
+  });
+});
 const routes: Array<RouteRecordRaw> = [
   {
     path: '/',
     name: 'index',
-    component: Index
+    component: Index,
+    children: pagesRouter
   }
 ];
 // import { nav } from '@/config';
@@ -35,7 +47,16 @@ routes.push({
 
 const router = createRouter({
   history: createWebHashHistory(),
-  routes
+  routes,
+  scrollBehavior(to, from, savedPosition) {
+    if (to.hash) {
+      const id = to.hash.split('#')[1];
+      const ele = document.getElementById(id);
+      setTimeout(() => {
+        ele && ele.scrollIntoView(true);
+      });
+    }
+  }
 });
 
 export default router;

+ 4 - 10
src/sites/doc/views/Index.vue

@@ -17,6 +17,7 @@ import Header from '@/sites/doc/components/Header.vue';
 import Nav from '@/sites/doc/components/Nav.vue';
 import Footer from '@/sites/doc/components/Footer.vue';
 import DemoPreview from '@/sites/doc/components/DemoPreview.vue';
+import '../../../styles/md-style.scss';
 export default defineComponent({
   name: 'doc',
   components: {
@@ -35,10 +36,10 @@ export default defineComponent({
     // 获取路由实例
     const router = useRouter();
 
-    onBeforeRouteUpdate(() => {
+    onBeforeRouteUpdate(to => {
       // 当当前路由发生变化时,调用回调函数
-      // const { origin, pathname } = window.location;
-      // data.demoUrl = `${origin}${pathname.replace('index.html', '')}demo.html#${route.path}`;
+      const { origin, pathname } = window.location;
+      data.demoUrl = `${origin}${pathname.replace('index.html', '')}demo.html#${to.path}`;
     });
 
     return data;
@@ -49,14 +50,7 @@ export default defineComponent({
 <style lang="scss" scoped>
 .doc {
   &-content {
-    height: 100%;
     display: flex;
-
-    &-document {
-      margin: 10px;
-      border: 1px solid red;
-      width: 800px;
-    }
   }
 }
 </style>

+ 83 - 0
src/styles/highlight.scss

@@ -0,0 +1,83 @@
+code {
+  position: relative;
+  display: block;
+  padding: 16px;
+  overflow-x: auto;
+  color: $nutui-doc-code-color;
+  font-weight: 400;
+  font-size: 14px;
+  font-family: $nutui-doc-code-font-family;
+  line-height: 26px;
+  white-space: pre-wrap;
+  word-wrap: break-word;
+  -webkit-font-smoothing: auto;
+  background-color: #fafafa;
+  border-radius: 16px;
+}
+
+pre {
+  margin: 20px 0 0;
+
+  + p {
+    margin-top: 20px;
+  }
+}
+
+.hljs {
+  display: block;
+  padding: 0.5em;
+  overflow-x: auto;
+  background: #fff;
+}
+
+.hljs-subst {
+  color: $nutui-doc-code-color;
+}
+
+.hljs-string,
+.hljs-meta,
+.hljs-symbol,
+.hljs-template-tag,
+.hljs-template-variable,
+.hljs-addition {
+  color: $nutui-doc-green;
+}
+
+.hljs-comment,
+.hljs-quote {
+  color: #999;
+}
+
+.hljs-params,
+.hljs-keyword,
+.hljs-attribute {
+  color: $nutui-doc-purple;
+}
+
+.hljs-deletion,
+.hljs-variable,
+.hljs-number,
+.hljs-regexp,
+.hljs-literal,
+.hljs-bullet,
+.hljs-link {
+  color: #eb6f6f;
+}
+
+.hljs-attr,
+.hljs-selector-tag,
+.hljs-title,
+.hljs-section,
+.hljs-built_in,
+.hljs-doctag,
+.hljs-type,
+.hljs-name,
+.hljs-selector-id,
+.hljs-selector-class,
+.hljs-strong {
+  color: #4994df;
+}
+
+.hljs-emphasis {
+  font-style: italic;
+}

+ 218 - 0
src/styles/md-style.scss

@@ -0,0 +1,218 @@
+@import 'highlight.scss';
+.doc-content-document {
+  position: relative;
+  margin: 10px;
+  width: 800px;
+  .card {
+    margin-bottom: 24px;
+    padding: 24px;
+    background-color: #fff;
+    border-radius: $nutui-doc-border-radius;
+    box-shadow: 0 8px 12px #ebedf0;
+  }
+
+  a {
+    margin: 0 1px;
+    color: $nutui-doc-blue;
+    -webkit-font-smoothing: auto;
+
+    &:hover {
+      color: darken($nutui-doc-blue, 10%);
+    }
+
+    &:active {
+      color: darken($nutui-doc-blue, 20%);
+    }
+  }
+
+  h1,
+  h2,
+  h3,
+  h4,
+  h5,
+  h6 {
+    color: $nutui-doc-black;
+    font-weight: normal;
+    line-height: 1.5;
+
+    &[id] {
+      cursor: pointer;
+    }
+  }
+
+  h1 {
+    margin: 0 0 30px;
+    font-size: 30px;
+    cursor: default;
+  }
+
+  h2 {
+    margin: 45px 0 20px;
+    font-size: 25px;
+  }
+
+  h3 {
+    margin-bottom: 16px;
+    font-weight: 600;
+    font-size: 18px;
+  }
+
+  h4 {
+    margin: 24px 0 12px;
+    font-weight: 600;
+    font-size: 16px;
+  }
+
+  h5 {
+    margin: 24px 0 12px;
+    font-weight: 600;
+    font-size: 15px;
+  }
+
+  p {
+    color: $nutui-doc-text-color;
+    font-size: 15px;
+    line-height: 26px;
+  }
+
+  table {
+    width: 100%;
+    margin-top: 12px;
+    color: $nutui-doc-text-color;
+    font-size: 14px;
+    line-height: 1.5;
+    border-collapse: collapse;
+
+    th {
+      padding: 8px 10px;
+      font-weight: 600;
+      text-align: left;
+
+      &:first-child {
+        padding-left: 0;
+      }
+
+      &:last-child {
+        padding-right: 0;
+      }
+    }
+
+    td {
+      padding: 8px;
+      border-top: 1px solid $nutui-doc-code-background-color;
+
+      &:first-child {
+        padding-left: 0;
+
+        // version tag
+        code {
+          margin: 0;
+          padding: 2px 6px;
+          color: $nutui-doc-blue;
+          font-weight: 600;
+          font-size: 11px;
+          background-color: fade($nutui-doc-blue, 10%);
+          border-radius: 20px;
+        }
+      }
+
+      &:last-child {
+        padding-right: 0;
+      }
+    }
+
+    em {
+      color: $nutui-doc-green;
+      font-size: 14px;
+      font-family: $nutui-doc-code-font-family;
+      font-style: normal;
+      -webkit-font-smoothing: auto;
+    }
+  }
+
+  ul li,
+  ol li {
+    position: relative;
+    margin: 5px 0 5px 10px;
+    padding-left: 15px;
+    color: $nutui-doc-text-color;
+    font-size: 15px;
+    line-height: 26px;
+
+    &::before {
+      position: absolute;
+      top: 0;
+      left: 0;
+      box-sizing: border-box;
+      width: 6px;
+      height: 6px;
+      margin-top: 10px;
+      border: 1px solid $nutui-doc-dark-grey;
+      border-radius: 50%;
+      content: '';
+    }
+  }
+
+  hr {
+    margin: 30px 0;
+    border: 0 none;
+    border-top: 1px solid #eee;
+  }
+
+  p > code,
+  li > code,
+  table code {
+    display: inline;
+    margin: 0 2px;
+    padding: 2px 5px;
+    font-size: 14px;
+    font-family: inherit;
+    word-break: keep-all;
+    background-color: $nutui-doc-background-color;
+    border-radius: 4px;
+    -webkit-font-smoothing: antialiased;
+  }
+
+  p > code {
+    font-size: 14px;
+  }
+
+  section {
+    padding: 24px;
+    overflow: hidden;
+  }
+
+  blockquote {
+    margin: 16px 0 0;
+    padding: 16px;
+    background-color: #ecf9ff;
+    border-radius: $nutui-doc-border-radius;
+  }
+
+  img {
+    width: 100%;
+    margin: 16px 0;
+    border-radius: $nutui-doc-border-radius;
+  }
+
+  &--changelog,
+  &--changelog-v3 {
+    strong {
+      display: block;
+      margin: 24px 0 12px;
+      font-weight: 600;
+      font-size: 15px;
+    }
+
+    h3 {
+      + p code {
+        margin: 0;
+      }
+
+      a {
+        color: inherit;
+        font-size: 20px;
+      }
+    }
+  }
+}

+ 21 - 0
src/styles/variables.scss

@@ -68,3 +68,24 @@ $button-warning-background-color: linear-gradient(
   rgba(255, 190, 13, 1) 100%
 );
 $button-plain-background-color: #fff;
+
+//markdown-add-style
+$nutui-doc-black: #323233;
+$nutui-doc-blue: #1989fa;
+$nutui-doc-purple: #8080ff;
+$nutui-doc-fuchsia: #a7419e;
+$nutui-doc-green: #4fc08d;
+$nutui-doc-text-color: #34495e;
+$nutui-doc-text-light-blue: rgba(69, 90, 100, 0.6);
+$nutui-doc-background-color: #f7f8fa;
+$nutui-doc-grey: #999;
+$nutui-doc-dark-grey: #666;
+$nutui-doc-light-grey: #ccc;
+$nutui-doc-border-color: #f1f4f8;
+$nutui-doc-code-color: #58727e;
+$nutui-doc-code-background-color: #f1f4f8;
+$nutui-doc-code-font-family: 'Source Code Pro', 'Monaco', 'Inconsolata', monospace;
+$nutui-doc-padding: 24px;
+$nutui-doc-row-max-width: 1680px;
+$nutui-doc-nav-width: 220px;
+$nutui-doc-border-radius: 20px;

+ 29 - 23
vue.config.js

@@ -1,7 +1,9 @@
 // vue.config.js
+const path = require('path');
+
 module.exports = {
-  productionSourceMap: process.env.NODE_ENV != "production",
-  publicPath: "./",
+  productionSourceMap: process.env.NODE_ENV != 'production',
+  publicPath: './',
   css: {
     loaderOptions: {
       // 给 sass-loader 传递选项
@@ -17,42 +19,46 @@ module.exports = {
       // `scss` 语法会要求语句结尾必须有分号,`sass` 则要求必须没有分号
       // 在这种情况下,我们可以使用 `scss` 选项,对 `scss` 语法进行单独配置
       scss: {
-        additionalData: `@import "~@/styles/variables.scss";@import "~@/sites/assets/styles/variables.scss";`,
-      },
-    },
+        additionalData: `@import "~@/styles/variables.scss";@import "~@/sites/assets/styles/variables.scss";`
+      }
+    }
   },
   pages: {
     doc: {
-      entry: "src/sites/doc/main.ts",
-      template: "src/sites/doc/index.html",
-      filename: "index.html",
+      entry: 'src/sites/doc/main.ts',
+      template: 'src/sites/doc/index.html',
+      filename: 'index.html',
       // template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title>
-      title: "NutUI",
+      title: 'NutUI',
       // 在这个页面中包含的块,默认情况下会包含
       // 提取出来的通用 chunk 和 vendor chunk。
-      chunks: ["chunk-vendors", "chunk-common", "doc"],
+      chunks: ['chunk-vendors', 'chunk-common', 'doc']
     },
     mobile: {
-      entry: "src/sites/mobile/main.ts",
-      template: "src/sites/mobile/index.html",
-      filename: "demo.html",
+      entry: 'src/sites/mobile/main.ts',
+      template: 'src/sites/mobile/index.html',
+      filename: 'demo.html',
       // template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title>
-      title: "NutUI",
+      title: 'NutUI',
       // 在这个页面中包含的块,默认情况下会包含
       // 提取出来的通用 chunk 和 vendor chunk。
-      chunks: ["chunk-vendors", "chunk-common", "mobile"],
-    },
+      chunks: ['chunk-vendors', 'chunk-common', 'mobile']
+    }
   },
   configureWebpack: {
     optimization: {
-      minimize: process.env.NODE_ENV === "production",
+      minimize: process.env.NODE_ENV === 'production',
       splitChunks: {
-        automaticNameDelimiter: "_",
-      },
-    },
-  },
-  chainWebpack: (config) => {
-    if (process.env.NODE_ENV === "production") {
+        automaticNameDelimiter: '_'
+      }
     }
   },
+  chainWebpack: config => {
+    config.module
+      .rule('md-vue')
+      .test(/\.md$/)
+      .use(path.resolve(__dirname, './loader/md-vue/index.js'))
+      .loader(path.resolve(__dirname, './loader/md-vue/index.js'))
+      .end();
+  }
 };