前一篇文章實現了按需加載封裝我的的組件庫功能,有了組件庫,固然還要有配套說明文檔,這樣使者用起來才更方便。打包完成的dist目錄是最終可放到服務器中,直接訪問到文檔的喲。
項目github地址:https://github.com/yuanalina/installAsRequiredcss
上篇文章中,執行打包命令會將項目打包至lib下,打包完成的目錄結構是適合直接發佈爲npm包,使用時使用import等引入的。其中並無html文件入口,因此要有說明文檔,直接在瀏覽器中可訪問,還須要從新配置打包。html
1、package.json增長打包命令"build_example": "node build/build.js examples"vue
2、在src同級增長examples目錄,存放文檔相關文件node
examples目錄中:一、assets目錄存放靜態資源依賴,二、components存放vue組件,三、docs目錄存放.md文件,說明文檔,四、main.js會做爲打包的入口,在這裏引入項目的組件、第三方依賴:element-ui、路由配置等,五、route.js路由配置,六、index.html做爲打包的html模版,七、App.vuewebpack
3、webpack相關配置git
在build目錄中增長webpack.prod.examples.conf.js文件,配置打包example。這個文件是vue-cli生成項目中的webpack.prod.conf.js稍做修改,改動部分:github
一、增長output出口配置,因爲以前在config中將這個值設置成了../lib,這裏把值設置爲../dist,將examples打包後輸出到distweb
二、設置打包入口爲examples下的main.jsvue-router
三、設置html模版爲./examples/index.htmlvue-cli
另外在build/build.js中,須要判斷example參數,更改一下output出口路徑,如圖:
編寫說明文檔,最直觀的仍是使用markdown編寫,看了elementUI的實現方案,決定按elementUI的技術方案去實現。特別說明:本文中有部分實現是copy了elementUI的代碼實現的。後面會特別指出
npm i highlight -D //安裝語法高亮 npm i markdown-it markdown-it-anchor markdown-it-container -D // 安裝markdown相關依賴 npm i vue-markdown-loader -D //安裝vue-markdown-loader,解析.md文件爲.vue文件
安裝了vue-markdown-loader解析.md文件,在webpack.base.conf.js文件中,須要進行相關的loader配置,這裏的配置相關,都是copy的element-ui中的代碼。改動部分以下:
1、首先增長strip-tags文件到/build目錄中,strip-tags內容以下:
/*! * strip-tags <https://github.com/jonschlinkert/strip-tags> * * Copyright (c) 2015 Jon Schlinkert, contributors. * Licensed under the MIT license. */ 'use strict'; var cheerio = require('cheerio'); 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(); }; exports.fetch = function(str, tag) { var $ = cheerio.load(str, {decodeEntities: false}); if (!tag) return str; return $(tag).html(); };
2、webpack.base.conf.js的改動
一、增長引入strip-tags和markdown-it
const striptags = require('./strip-tags') const md = require('markdown-it')()
二、增長工具函數
const wrap = function(render) { return function() { return render.apply(this, arguments) .replace('<code v-pre class="', '<code class="hljs ') .replace('<code>', '<code class="hljs">') } } function 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 }
三、增長.md相關loader配置,將.md文件解析爲.vue文件,同時,解析處理::: demo :::代碼塊等,解析處理::: demo :::代碼塊爲demo-block vue組件,並傳入對應參數.
{ test: /\.md$/, loader: 'vue-markdown-loader', options: { use: [ [require('markdown-it-container'), 'demo', { validate: function(params) { return params.trim().match(/^demo\s*(.*)$/) }, render: function(tokens, idx) { var m = tokens[idx].info.trim().match(/^demo\s*(.*)$/) if (tokens[idx].nesting === 1) { var description = (m && m.length > 1) ? m[1] : '' var content = tokens[idx + 1].content var html = convert(striptags.strip(content, ['script', 'style'])).replace(/(<[^>]*)=""(?=.*>)/g, '$1') var script = striptags.fetch(content, 'script') var style = striptags.fetch(content, 'style') var jsfiddle = { html: html, script: script, style: style } var descriptionHTML = description ? md.render(description) : '' jsfiddle = md.utils.escapeHtml(JSON.stringify(jsfiddle)) return `<demo-block class="demo-box" :jsfiddle="${jsfiddle}"> <div class="source" slot="source">${html}</div> ${descriptionHTML} <div class="highlight" slot="highlight">` } return '</div></demo-block>\n' } }], [require('markdown-it-container'), 'tip'], [require('markdown-it-container'), 'warning'] ], preprocess: function(MarkdownIt, source) { MarkdownIt.renderer.rules.table_open = function() { return '<table class="table">'; }; MarkdownIt.renderer.rules.fence = wrap(MarkdownIt.renderer.rules.fence) return source; } } }
配置相關的就告一段落了,接下來進入examples中的文檔編寫部分
1、main.js入口文件編寫
在入口文件中,引入相關依賴,項目樣式入口、路由、組件以及element-ui
import Vue from 'vue' import App from './App.vue' import VueRouter from 'vue-router' // 引入組件 import JY from '../src' Vue.use(JY) // 引入element-ui import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI) // 引入demo-block import DemoBlock from './components/demoBlock' Vue.component('demo-block', DemoBlock) // 引入項目樣式入口 import './assets/less/index.less' // 引入路由 import routes from './route' Vue.use(VueRouter) const router = new VueRouter({ routes }) /* eslint-disable no-new */ new Vue({ render(createElement) { return createElement(App) }, router }).$mount('#app')
2、設置路由配置route.js
路由配置時,將路由路徑對應的組件設置爲引入的.md文件
import Install from './docs/install.md' import QuikeStart from './docs/quikeStart.md' import Input from './docs/input.md' const routes = [ { path: '/', component: Install, name: 'default' }, { path: '/guide/install', name: 'Install', component: Install }, { path: '/guide/quikeStart', name: 'quikeStart', component: QuikeStart }, { path: '/input', name: 'input', component: Input } ] export default routes
3、App.vue、以及相關佈局組件
一、App.vue
<template lang="html"> <div style="height:100%"> <el-container style="height:100%"> <el-header height="40"> <header-model></header-model> </el-header> <el-container> <el-aside width="200px"> <menu-model></menu-model> </el-aside> <el-main> <router-view></router-view> </el-main> </el-container> </el-container> </div> </template> <style> /* 引入代碼高亮樣式 */ @import 'highlight.js/styles/color-brewer.css'; </style> <script> import HeaderModel from './components/header' import MenuModel from './components/menu' export default { components: { HeaderModel, MenuModel }, data() { return { } }, methods: { } } </script>
二、header.vue
<template lang="html"> <div class="header-model"> <h1 class="info"> 通用組件庫 </h1> </div> </template> <script> export default { data () { return { } } } </script>
三、menu.vue
<template lang="html"> <div class="menu-model"> <el-menu default-active="1" :unique-opened="true" :default-openeds="['1', '2', '3']" :default-active="defaultActive" :router="true" > <el-submenu index="1"> <template slot="title"> <span>開發指南</span> </template> <el-menu-item-group> <el-menu-item index="/guide/install">安裝</el-menu-item> <el-menu-item index="/guide/quikeStart">快速上手</el-menu-item> </el-menu-item-group> </el-submenu> <el-submenu index="2"> <template slot="title"> <span>通用模塊</span> </template> <el-menu-item-group> <el-menu-item index="/input">Input</el-menu-item> </el-menu-item-group> </el-submenu> </el-menu> </div> </template> <script> export default { data () { return { defaultActive: '/guide/install' } }, created () { const path = this.$route.fullPath this.defaultActive = path == '/' ? '/guide/install' : path }, methods: { } } </script> <style lang="css"> </style>
4、重要組件demoBlock.vue
demoBlock組件是解析.md中的::: demo ::: 代碼塊須要用到的組件,這裏的demoBlock.vue文件是copy的element-ui的代碼後稍做修改
<template> <div class="demo-block" :class="[blockClass, { '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 class="demo-block-control" ref="control" @click="isExpanded = !isExpanded"> <transition name="arrow-slide"> <i :class="[iconClass, { 'hovering': hovering }]"></i> </transition> <transition name="text-slide"> <span v-show="hovering">{{ controlText }}</span> </transition> </div> </div> </template> <style lang="less"> .demo-block { border: solid 1px #ebebeb; border-radius: 3px; transition: .2s; &.hover { box-shadow: 0 0 8px 0 rgba(232, 237, 250, .6), 0 2px 4px 0 rgba(232, 237, 250, .5); } code { font-family: Menlo, Monaco, Consolas, Courier, monospace; } .demo-button { float: right; } .source { padding: 24px; } .meta { background-color: #fafafa; border-top: solid 1px #eaeefb; overflow: hidden; height: 0; transition: height .2s; } .description { padding: 20px; box-sizing: border-box; border: solid 1px #ebebeb; border-radius: 3px; font-size: 14px; line-height: 22px; color: #666; word-break: break-word; margin: 10px; background-color: #fff; p { margin: 0; line-height: 26px; } code { color: #5e6d82; background-color: #e6effb; margin: 0 4px; display: inline-block; padding: 1px 5px; font-size: 12px; border-radius: 3px; height: 18px; line-height: 18px; } } .highlight { pre { margin: 0; } code.hljs { margin: 0; border: none; max-height: none; border-radius: 0; &::before { content: none; } } } .demo-block-control { border-top: solid 1px #eaeefb; height: 44px; box-sizing: border-box; background-color: #fff; border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; text-align: center; margin-top: -1px; color: #d3dce6; cursor: pointer; position: relative; &.is-fixed { position: fixed; bottom: 0; width: 868px; } i { font-size: 16px; line-height: 44px; transition: .3s; &.hovering { transform: translateX(-40px); } } > span { position: absolute; transform: translateX(-30px); font-size: 14px; line-height: 44px; transition: .3s; display: inline-block; } &:hover { color: #409EFF; background-color: #f9fafc; } & .text-slide-enter, & .text-slide-leave-active { opacity: 0; transform: translateX(10px); } .control-button { line-height: 26px; position: absolute; top: 0; right: 0; font-size: 14px; padding-left: 5px; padding-right: 25px; } } } </style> <script type="text/babel"> export default { data() { return { hovering: false, isExpanded: false, fixedControl: false, scrollParent: null, langConfig: { "hide-text": "隱藏代碼", "show-text": "顯示代碼", "button-text": "在線運行", "tooltip-text": "前往 jsfiddle.net 運行此示例" } }; }, props: { jsfiddle: Object, default() { return {}; } }, methods: { scrollHandler() { const { top, bottom, left } = this.$refs.meta.getBoundingClientRect(); this.fixedControl = bottom > document.documentElement.clientHeight && top + 44 <= document.documentElement.clientHeight; }, removeScrollHandler() { this.scrollParent && this.scrollParent.removeEventListener('scroll', this.scrollHandler); } }, computed: { lang() { return this.$route.path.split('/')[1]; }, blockClass() { return `demo-${ this.lang } demo-${ this.$router.currentRoute.path.split('/').pop() }`; }, iconClass() { return this.isExpanded ? 'el-icon-caret-top' : 'el-icon-caret-bottom'; }, controlText() { return this.isExpanded ? this.langConfig['hide-text'] : this.langConfig['show-text']; }, codeArea() { return this.$el.getElementsByClassName('meta')[0]; }, codeAreaHeight() { if (this.$el.getElementsByClassName('description').length > 0) { return this.$el.getElementsByClassName('description')[0].clientHeight + this.$el.getElementsByClassName('highlight')[0].clientHeight + 20; } return this.$el.getElementsByClassName('highlight')[0].clientHeight; } }, watch: { isExpanded(val) { this.codeArea.style.height = val ? `${ this.codeAreaHeight + 1 }px` : '0'; if (!val) { this.fixedControl = false; this.$refs.control.style.left = '0'; this.removeScrollHandler(); return; } setTimeout(() => { this.scrollParent = document.querySelector('.page-component__scroll > .el-scrollbar__wrap'); this.scrollParent && this.scrollParent.addEventListener('scroll', this.scrollHandler); this.scrollHandler(); }, 200); } }, mounted() { this.$nextTick(() => { let highlight = this.$el.getElementsByClassName('highlight')[0]; if (this.$el.getElementsByClassName('description').length === 0) { highlight.style.width = '100%'; highlight.borderRight = 'none'; } }); }, beforeDestroy() { this.removeScrollHandler(); } }; </script>
5、docs中的.md文檔文件
.md文件編寫時有幾個須要注意的地方:
具備交互功能的說明文檔,須要有<script></script>標籤,在標籤元素中定義須要導出的vue實例。
在:::demo ::: 代碼塊中定義的模版<template></template>會做爲導出的vue實例的模版,可是在代碼塊中的<script></script>中的內容僅做爲展現。
.md文件粘貼進來會展現有誤,這裏只進行了截圖,有須要的夥伴能夠進入github查看
6、樣式調整
樣式相關的調整代碼這裏就不單獨列出來講明瞭,須要的夥伴能夠進入github查看
設置webpack.dev.conf.js文件的入口爲./examples/main.js,這樣便可以邊開發組件邊調試,同時也能夠調試到說明文檔。
entry: { app: './examples/main.js' },
本文結束啦~但願對你有所幫助。。學無止境,與諸君共勉~~