合格前端系列第十一彈-揭秘組件庫一二事(中)

摘要:因為知乎文章字數上線,本來該博文是準備分上下篇發布的,結果現在光上篇就得分上中兩篇進行發布

閱讀中篇前請先閱讀上篇

qiangdada:合格前端系列第十彈-揭秘組件庫一二事(上)?

zhuanlan.zhihu.com圖標

六、單元測試

目前為止,我們已經構建好了組件庫需要的新目錄,js 文件和 css 文件的打包我們也改造好了,組件庫開發的前置工作我們已經做好了比較充實的準備,但我們仍需做一些非常重要的前置工作以方便組件庫後續組件的開發和維護。

而對於前端測試,它是前端工程方面的一個重要分支,因此,在我們的組件庫中怎麼能少掉這麼重要的一角呢?對於單元測試,主要分為兩種

  • TDD(Test-Driven Development):測試驅動開發,注重輸出結果。
  • BDD(Behavior Driven Development):行為驅動開發,注重測試邏輯。

在本章節中,我將帶領大家使用基於項目初始化自帶的 Karma + Mocha 這兩大框架對我們的組件庫中的組件進行單元測試。

1、框架簡介

對於 Karma + Mocha 這兩大框架,相信大多數接觸過單元測試的人都不會陌生,但這裡我覺得還是有必要單獨開一小節對著兩大框架進行一個簡單的介紹。

i. Karma 框架

  • Karma 是一個基於 Node.js 的 JavaScript 測試執行過程管理工具(Test Runner)
  • Karma 是一個測試工具,能讓你的代碼在瀏覽器環境下測試
  • Karma 能讓你的代碼自動在多個瀏覽器,比如 chrome,firefox,ie 等環境下運行

為了能讓我們的組件庫中的組件能夠運行在各大主流 Web 瀏覽器中進行測試,我們選擇了 Karma 。最重要的是 Karma 是 vue-cli 推薦的單元測試框架。如果你想了解更多有關 Karma 的介紹,請自行查閱 Karma 官網

ii. Mocha 框架

  • Mocha 是一個 simple,flexible,fun 的測試框架
  • Mocha 支持非同步的測似用例,如 Promise
  • Mocha 支持代碼覆蓋率 coverage 測試報告
  • Mocha 允許你使用任何你想使用的斷言庫,比如 chai 、should.js (BDD風格)、expect.js 等等
  • Mocha 提供了 before(), after(), beforeEach(), 以及 afterEach() 四個鉤子函數,方便我們在不同階段設置不同的操作以更好的完成我們的測試

這裡我介紹一下 mocha 的三種基本用法,以及 describe 的四個鉤子函數(生命周期)

  • describe(moduleName, function): describe 是可嵌套的,描述測試用例是否正確

describe(測試模塊的描述, () => { // ....});

  • it(info, function):一個 it 對應一個單元測試用例

it(單元測試用例的描述, () => { // ....})

  • 斷言庫的用法

expect(1 + 1).to.be.equal(2)

  • describe 的生命周期

describe(Test Hooks, function() { before(function() { // 在本區塊的所有測試用例之前執行 }); after(function() { // 在本區塊的所有測試用例之後執行 }); beforeEach(function() { // 在本區塊的每個測試用例之前執行 }); afterEach(function() { // 在本區塊的每個測試用例之後執行 }); // test cases});

想了解更多 mocha 操作的同學可以點擊下面的鏈接進行查閱

  1. Mocha 官網
  2. 測試框架 Mocha 實例教程

2、單元測試實戰

上面一小節,我給大家簡單介紹了一下 Vue 官方推薦的測試框架 Karma 和 Mocha,也希望大家看到這裡的時候能夠對單元測試及常見測試框架能有個簡單的了解。

i. 對 hello 組件進行單元測試

在單元測試實戰開始前,我們先看看 Karma 的配置,這裡我們直接看 vue-cli 腳手架初始化出來的 karma.conf.js 文件裡面的配置(具體用處我做了注釋)

var webpackConfig = require(../../build/webpack.test.conf)module.exports = function karmaConfig (config) { config.set({ // 瀏覽器 browsers: [PhantomJS], // 測試框架 frameworks: [mocha, sinon-chai, phantomjs-shim], // 測試報告 reporters: [spec, coverage], // 測試入口文件 files: [./index.js], // 預處理器 karma-webpack preprocessors: { ./index.js: [webpack, sourcemap] }, // webpack配置 webpack: webpackConfig, // webpack中間件 webpackMiddleware: { noInfo: true }, // 測試覆蓋率報告 coverageReporter: { dir: ./coverage, reporters: [ { type: lcov, subdir: . }, { type: text-summary } ] } })}

接下來,我們再來對我們自己的 hello 組件進行簡單的測試(只寫一個測試用例),在 test/unit/specs 新建 hello.spec.js 文件,並寫入以下代碼

import Vue from vue // 導入Vue用於生成Vue實例import Hello from packages/hello // 導入組件// 測試腳本裡面應該包括一個或多個describe塊,稱為測試套件(test suite)describe(Hello.vue, () => { // 每個describe塊應該包括一個或多個it塊,稱為測試用例(test case) it(render default classList in hello, () => { const Constructor = Vue.extend(Hello) // 獲得Hello組件實例 const vm = new Constructor().$mount() // 將組件掛在到DOM上 // 斷言:DOM中包含class為v-hello的元素 expect(vm.$el.classList.contains(v-hello)).to.be.true const message = vm.$el.querySelector(.v-hello__message) // 斷言:DOM中包含class為v-hello__message的元素 expect(message.classList.contains(v-hello__message)).to.be.true })})

測試實例寫完,接下來就是進行測試了。執行 npm run test,走你 ~ ,輸出結果

hello.vue ? render default classList in hello

ii. 優化單元測試

從上面 hello 組件的測試實例可以看出,我們需要將組件實例化為一個Vue實例,有時還需要掛載到 DOM 上

const Constructor = Vue.extend(Hello)const vm = new Constructor({ propsData: { message: component }}).$mount()

如果之後每個組件擁有多個單元測試實例,那這種寫法會導致我們最後的測試比較臃腫,這裡我們可以參考 element 封裝好的 單元測試工具 util.js 。我們需要封裝 Vue 在單元測試中常用的一些方法,下面我將列出工具裡面提供的一些方法

/** * 回收 vm,一般在每個測試腳本測試完成後執行回收vm。 * @param {Object} vm */exports.destroyVM = function (vm) {}/** * 創建一個 Vue 的實例對象 * @param {Object|String} Compo - 組件配置,可直接傳 template * @param {Boolean=false} mounted - 是否添加到 DOM 上 * @return {Object} vm */exports.createVue = function (Compo, mounted = false) {}/** * 創建一個測試組件實例 * @param {Object} Compo - 組件對象 * @param {Object} propsData - props 數據 * @param {Boolean=false} mounted - 是否添加到 DOM 上 * @return {Object} vm */exports.createTest = function (Compo, propsData = {}, mounted = false) {}/** * 觸發一個事件 * 註: 一般在觸發事件後使用 vm.$nextTick 方法確定事件觸發完成。 * mouseenter, mouseleave, mouseover, keyup, change, click 等 * @param {Element} elm - 元素 * @param {String} name - 事件名稱 * @param {*} opts - 配置項 */exports.triggerEvent = function (elm, name, ...opts) {}/** * 觸發 「mouseup」 和 「mousedown」 事件,既觸發點擊事件。 * @param {Element} elm - 元素 * @param {*} opts - 配置選項 */exports.triggerClick = function (elm, ...opts) {}

下面我們將使用定義好的測試工具方法,改造 hello 組件的測試實例,將 hello.spec.js 文件進行改造

import { destroyVM, createTest } from ../utilimport Hello from packages/hellodescribe(hello.vue, () => { let vm // 測試用例執行之後銷毀實例 afterEach(() => { destroyVM(vm) }) it(render default classList in hello, () => { vm = createTest(Hello) expect(vm.$el.classList.contains(v-hello)).to.be.true const message = vm.$el.querySelector(.v-hello__message) expect(message.classList.contains(v-hello__message)).to.be.true })})

重新執行 npm run test,輸出結果

hello.vue ? render default classList in hello

iii. 更多單元測試的用法

上面我們介紹了單元測試的部分有關靜態判定的用法,接下來我們將測試一些非同步用例以及一些交互事件。在測試之前,我們需稍微改動一下我們的 hello 組件的代碼,如下

<template> <div class="v-hello" @click="handleClick"> <p class="v-hello__message">hello {{ message }}</p> </div></template><script>export default { name: v-hello, props: { message: String }, methods: { handleClick () { return new Promise((resolve) => { resolve() }).then(() => { this.$emit(click, this is click emit) }) } }}</script>

接下來我們要測試 hello 組件通過 Promise 是否能夠成功將信息 emit 出去,測試案例如下

it(create a hello for click with promise, (done) => { let result vm = createVue({ template: `<v-hello @click="handleClick"></v-hello>`, methods: { handleClick (msg) { result = msg } } }, true) vm.$el.click() // 斷言消息是非同步emit出去的 expect(result).to.not.exist setTimeout(_ => { expect(result).to.exist expect(result).to.equal(this is click emit) done() }, 20)})

重新開始測試,執行npm run test,輸出結果

hello.vue ? render default classList in hello ? create a hello for click with promise

至此,我們便學會了單元測試的配置以及一些常用的用法。如果需要了解更多有關單元測試的細節,請根據我前面提供的鏈接進入更深入的研究

七、文檔官網開發(上)

小夥伴們跟著我將前面5個章節實戰下來,已經將我們組件開發的基本架子給搭建好了。接下來我將帶著大家一起把組件庫中重要成分很高的文檔官網給擼完。

大家應該都知道,好的開源項目肯定是有文檔官網的,所以為了讓我們的 UI 庫也成為優秀中的一員的話,我們也應該擼一個自己文檔官網。

一個好的文檔官網,需要做到兩點。

  1. 將自己的開源項目的 API 梳理清楚,讓使用者能夠用的更舒心
  2. 有示例 demo ,讓使用者能在線就看到效果

由於本博文中,我帶領大家開發的組件庫是適配移動端的,那麼如何讓我們的文檔官網既有 API 文檔的描述,還有移動端示例的 Demo 呢。這就要求我們需要開發兩套頁面進行適配,對此我們需要的做的事有以下幾點:

  • PC 端展示組件 API 文檔
  • 移動端的展示組件 Demo
  • 路由動態生成

在實戰開始前,我們先看下本章節需要用到的目錄結構

├── assets css,圖片等資源都在這├── dist 打包好的文件都在這├── docs PC端需要展示的markdown文件都在這├── pages 移動端所有的demo都在這├── src │ ├── components demo中可以復用的模塊放在這裡面│ ├── index.tpl 頁面入口│ ├── is-mobile.js 判斷設備│ ├── index.js PC端主入口js│ ├── App.vue PC端入口文件│ ├── mobile.js 移動端端主入口js│ ├── MobileApp.vue 移動端入口文件│ ├── nav.config.json 路由控制文件│ ├── router.config.js 動態註冊路由

本章節,主要帶著大家實現 markdown 文件的轉化,以及不同設備的路由適配。

思路捋清後,接下來繼續我們的文檔官網開發實戰吧!

1、markdown 文件轉化

從上面我給出的目錄可以看到,在 docs 文件夾裡面存放的都是 markdown 文件,每一個 markdown 文件都對應一個組件的 API 文檔。我們是想要的結果是,轉化 docs 裡面的每一個 markdown 文件,使其變成一個個 Vue 組件,並將轉化好的 Vue 組件註冊到路由中,讓其可以通過路由對每一個 markdown 文件進行訪問。

對於 markdown 文件解析成 Vue 組件,市場上有很多三方 webpack 插件,當然如果你要是對 webpack 造詣比較深的話,你也可以嘗試自己擼一個。這裡我是直接使用的 餓了么團隊 開發出來的 vue-markdown-loader 。

i. 使用 vue-markdown-loader

第一步,依賴安裝

npm i vue-markdown-loader -D

第二步,在 webpack.base.conf.js 文件中使用 vue-markdown-loader

{ test: /.md$/, loader: vue-markdown-loader, options: { // 阻止提取腳本和樣式標籤 preventExtract: true }}

第三步,try 一 try。先在 docs 裡面添加 hello.md 文件,然後寫入 hello 組件的使用說明

## Hello**Hello 組件,Hello 組件,Hello 組件,Hello 組件**### 基本用法```html<template> <div class="hello-page"> <v-hello message="my component library" @click="handleClick"></v-hello> <p>{{ msg }}</p> </div></template><script>export default { name: hello, data () { return { msg: } }, methods: { handleClick (msg) { this.msg = msg } }}</script>```### Attributes| 參數 | 說明 | 類型 | 可選值 | 默認值 ||---------- |-------- |---------- |------------- |-------- || message | 文本信息 | string | — | — |### Events| 事件名稱 | 說明 | 回調參數 ||---------- |-------- |---------- || click | 點擊操作 | — |

第四步,將 hello.md 註冊到路由中

route.push({ path: /component/hello, component: require(../docs/hello.md)})

最後,訪問頁面。這個時候可以發現 hello.md 的內容已經被轉成 Vue 組件,並且能夠通過路由載入的方式進行訪問,但是頁面卻很醜很醜 ~ 就像這樣

ii. 為 md 加上高亮主題和樣式

當然,出現這種情況不用我說明,大家可能也知道了。對的,解析出來的 markdown 文件這麼丑,只是因為我們既沒有給我們的 markdown 文件加上高亮主題,也沒有設置好文檔頁面的基本樣式而已。所以,接下來,我們需要給我們的 markdown 文件加上漂亮的高亮主題和簡潔的基本樣式。

對於主題,這裡我們將使用 highlight.js 裡面的 atom-one-dark 主題。

第一步,安裝 highlight.js

npm i highlight -D

第二步,在 examples/src/App.vue 引入主題,並且為了設置文檔的基本樣式,我們還需要修改 App.vue 的布局

<template> <div class="app"> <div class="main-content"> <div class="page-container clearfix"> <div class="page-content"> <router-view></router-view> </div> </div> </div> </div></template><script>import highlight.js/styles/atom-one-dark.cssexport default { name: App}</script>

第三步,設置文檔的基本樣式。在 assets 中新建 docs.css,寫入初始樣式,由於代碼量偏多,就不往這裡貼了。大家可自行 copy docs.css 裡面的代碼到本地的 docs.css 文件中,然後在 examples/src/index.js 中進行引入

import ../assets/docs.css

最後,改造 markdown 解析規則,vue-markdown-loader 提供了一個 preprocess 介面給我們自由操作,接下來,我們對解析好的 markdown 文件的結構進行定義吧,在 webpack.base.conf.js 文件中寫入

// 定義輔助函數wrap,將<code>標籤都加上名為hljs的classfunction wrap (render) { return function() { return render.apply(this, arguments) .replace(<code v-pre class=", <code class="hljs ) .replace(<code>, <code class="hljs">) }}// ...{ test: /.md$/, loader: vue-markdown-loader, options: { preventExtract: true, preprocess: function(MarkdownIt, source) { // 為table標籤加上名為table的class MarkdownIt.renderer.rules.table_open = function() { return <table class="table"> }; MarkdownIt.renderer.rules.fence = wrap(MarkdownIt.renderer.rules.fence); return source; } }}

然後,重新訪問 localhost:8080/#/component/hello

OK,我們的 md 文件已經成功解析成 Vue 組件,並有了漂亮的高亮主題和簡潔的基本樣式了 ~

2、不同設備環境下路由的適配

前面我有說過,本文帶領大家開發的組件庫是適配移動端的,所以我們需要做到 PC 端展示文檔,移動端展示 Demo。

在這一小節,我會帶著大家進行不同端路由的適配。當然,這個東西不難,主要是利用 webpack 構建多頁面的特性,那麼具體怎麼做呢?好了,不多扯,咱們直接開始吧

i. 入口文件註冊

第一步,註冊 js 入口文件,在 webpack.base.conf.js 文件中寫入

entry: { // ... vui: ./examples/src/index.js, // PC端入口js vui-mobile: ./examples/src/mobile.js // 移動端入口js}

第二步,註冊頁面入口,在 webpack.base.conf.js 文件中寫入

plugins: [ // ... // PC端頁面入口 new HtmlWebpackPlugin({ chunks: [manifest, vendor, vui], template: examples/src/index.tpl, filename: index.html, inject: true }), // 移動端頁面入口 new HtmlWebpackPlugin({ chunks: [manifest, vendor, vui-mobile], template: examples/src/index.tpl, filename: mobile.html, inject: true })]

ii. 設備環境判定

入口文件註冊完成,接下來我們需要做的是對設備環境進行判定。這裡,我將使用 navigator.userAgent 配合正則表達式的方式判斷我們組件庫運行的環境到底是屬於 PC 端還是移動端?

第一步,在examples/src/is-mobile.js 文件中寫入以下代碼

/* eslint-disable */const isMobile = (function () { var platform = navigator.userAgent.toLowerCase() return (/(android|bbd+|meego).+mobile|kdtunion|weibo|m2oapp|micromessenger|avantgo|bada/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)/|plucker|pocket|psp|series(4|6)0|symbian|treo|up.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i).test(platform) || (/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i).test(platform.substr(0, 4));})()// 返回設備所處環境是否為移動端,值為boolean類型export default isMobile

第二步,在 PC 端 js 入口文件 examples/src/index.js 中寫入以下判定規則

import isMobile from ./is-mobile// 是否為生產環境const isProduction = process.env.NODE_ENV === productionrouter.beforeEach((route, redirect, next) => { if (route.path !== /) { window.scrollTo(0, 0) } // 獲取不同環境下,移動端Demo對應的地址 const pathname = isProduction ? /vui/mobile : /mobile.html // 如果設備環境為移動端,則直接載入移動端Demo的地址 if (isMobile) { window.location.replace(pathname) return } document.title = route.meta.title || document.title next()})

第三步,在移動端 js 入口文件examples/src/mobile.js 中寫入與上一步類似的判定規則

import isMobile from ./is-mobileconst isProduction = process.env.NODE_ENV === productionrouter.beforeEach((route, redirect, next) => { if (route.path !== /) { window.scrollTo(0, 0) } // 獲取不同環境下,PC端對應的地址 const pathname = isProduction ? /vui/mobile : /mobile.html // 如果設備環境不是移動端,則直接載入PC端的地址 if (!isMobile) { window.location.replace(pathname) return } document.title = route.meta.title || document.title next()})

最後,完善 examples/src/mobile.js 文件,和移動端頁面入口 MobileApp.vue 文件

在 examples/src/mobile.js 中寫入以下代碼

import Vue from vueimport VueRouter from vue-routerimport MobileApp from ./MobileAppimport Vui from src/indeximport isMobile from ./is-mobile.jsimport Hello from ../pages/hello.vueimport packages/vui-css/src/index.cssVue.use(Vui)Vue.use(VueRouter)const isProduction = process.env.NODE_ENV === productionconst router = new VueRouter({ base: isProduction ? /vui/ : __dirname, routes: [{ path: /component/hello, component: Hello }]})router.beforeEach((route, redirect, next) => { if (route.path !== /) { window.scrollTo(0, 0) } const pathname = isProduction ? /vui/ : / if (!isMobile) { window.location.replace(pathname) return } document.title = route.meta.title || document.title next()})new Vue({ el: #app-container, router, components: { MobileApp }, template: <MobileApp/>})

在 MobileApp.vue 中寫入

<template> <div class="mobile-container"> <router-view></router-view> </div></template>

接下來,你可以去瀏覽器中試試效果了,看看不同的設備環境是否能展示對應的內容 ~

到這裡,我們本章制定好的計劃便已經全部完成。md 文件的"完美"轉化,以及不同設備環境下路由的適配。文檔官網的開發(上)到這裡就要告一段落了,下一章節,我們將繼續完成文檔官網剩餘的開發工作!

八、文檔官網開發(下)

上一章節,我們已經完成了:

  1. markdown 文件的轉化,並為其加上了漂亮的高亮主題和樣式
  2. 文檔官網在不同的設備環境下的適配

這一章節,我們將完善文檔官網的細節,開發出一個完整的文檔官網。

1、路由管理

從上一章給出的目錄我們可以知道,docs 目錄是用來存放 PC 需要展示的 md 文件的,pages 目錄是用來存放移動端 Demo 文件的。那麼如何讓組件在不同的設備環境下展示其對應的文件呢(PC 端展示組件對應的 md 文件,移動端展示組件對應 vue 文件)?這種情況又該如何合理的管理好我們組件庫的路由呢?接下來,我們就著這些問題繼續下面的開發。這裡肯定會用到 is-mobile.js 去進行設備環境的判定,具體工作大家跟著我慢慢來做

第一步,在 examples/src 下新建文件 nav.config.json 文件,寫入以下內容

{ // 為了之後組件文檔多語言化 "zh-CN": [ { "name": "Vui 組件", "showInMobile": true, "groups": [ { // 管理相同類型下的所有組件 "groupName": "基礎組件", "list": [ { // 訪問組件的相對路徑 "path": "/hello", // 組件描述 "title": "Hello" } ] } ] } ]}

第二步,改善 router.config.js 文件,將其改成一個路由註冊的輔助函數

const registerRoute = (navConfig, isMobile) => { let route = [] // 目前只有中文版的文檔 let navs = navConfig[zh-CN] // 遍歷路由文件,逐一進行路由註冊 navs.forEach(nav => { if (isMobile && !nav.showInMobile) { return } if (nav.groups) { nav.groups.forEach(group => { group.list.forEach(nav => { addRoute(nav) }) }) } else if (nav.children) { nav.children.forEach(nav => { addRoute(nav) }) } else { addRoute(nav) } }) // 進行路由註冊 function addRoute (page) { // 不同的設備環境引入對應的路由文件 const component = isMobile ? require(`../pages${page.path}.vue`) : require(`../docs${page.path}.md`) route.push({ path: /component + page.path, component: component.default || component }) } return route}export default registerRoute

第三步,在 PC 端主入口 js 文件 examples/src/index.js 和移動端主入口 js 文件 examples/src/mobile.js 裡面註冊路由,都寫入以下代碼

import registerRoute from ./router.configimport navConfig from ./nav.configconst routesConfig = registerRoute(navConfig)const router = new VueRouter({ routes: routesConfig})

然後再訪問一下我們現在的組件庫文檔官網

2、PC 端 API 展示

從上一章節的最終效果圖我們可以看出來,PC端分為三個部分,分別為:

  1. 頭部,組件庫的簡單描述,以及項目 github 的鏈接
  2. 左側欄,組件路由及標題展示
  3. 右側欄,組件 API 文檔展示

接下來,讓我們開始來完成PC 端 API 的展示吧

i. 頭部

頭部相對簡單點,我們只需要在 examples/src/components 下新建 page-header.vue 文件,寫入以下內容

<template> <div class="page-header"> <div class="page-header__top"> <h1 class="page-header__logo"> <a href="#">Vui.js</a> </h1> <ul class="page-header__navs"> <li class="page-header__item"> <a href="/" class="page-header__link">組件</a> </li> <li class="page-header__item"> <a href="https://github.com/Brickies/vui" class="page-header__github" target="_blank"></a> </li> <li class="page-header__item"> <span class="page-header__link"></span> </li> </ul> </div> </div></template>

具體樣式,請直接訪問 page-header.vue 進行查看

ii. 左側欄

左側欄,是我們展示組件路由和標題的地方。其實就是對 examples/src/nav.config.json 進行解析並展示。

我們在 examples/src/components 下新建 side-nav.vue 文件,文件正常結構如下

<li class="nav-item"> <a href="javascript:void(0)">Vui 組件</a> <div class="nav-group"> <div class="nav-group__title">基礎組件</div> <ul class="pure-menu-list"> <li class="nav-item"> <router-link active-class="active" :to="/component/hello" v-text="navItem.title">Hello </router-link> </li> </ul> </div></li>

但我們現在要基於目前的結構對 examples/src/nav.config.json 進行解析,完善後的代碼如下

<li class="nav-item" v-for="item in data"> <a href="javascript:void(0)" @click="handleTitleClick(item)">{{ item.name }}</a> <template v-if="item.groups"> <div class="nav-group" v-for="group in item.groups"> <div class="nav-group__title">{{ group.groupName }}</div> <ul class="pure-menu-list"> <template v-for="navItem in group.list"> <li class="nav-item" v-if="!navItem.disabled"> <router-link active-class="active" :to="base + navItem.path" v-text="navItem.title" /> </li> </template> </ul> </div> </template></li>

完整代碼點這裡 side-nav.vue

iii. App.vue

我們把我們寫好的 page-header.vue 和 side-nav.vue 兩個文件在 App.vue 中使用

<template> <div class="app"> <page-header></page-header> <div class="main-content"> <div class="page-container clearfix"> <side-nav :data="navConfig[zh-CN]" base="/component"></side-nav> <div class="page-content"> <router-view></router-view> </div> </div> </div> </div></template><script>import highlight.js/styles/atom-one-dark.cssimport navConfig from ./nav.config.jsonimport PageHeader from ./components/page-headerimport SideNav from ./components/side-navexport default { name: App, components: { PageHeader, SideNav }, data () { return { navConfig: navConfig } }}</script>

然後,再次訪問頁面,結果如圖

3、移動端 Demo

移動端 Demo 和 PC 端原理差不多,都得解析 nav.config.json 文件從而進行展示

i. 移動端首頁組件

目前我們移動端除了主入口頁面 MobileApp.vue 以外,是沒有根目錄組件依賴的,接下來我們將先完成根目錄組件的開發,在 examples/src/components 下新建 demo-list.vue 文件,寫入一些內容

<template> <div class="side-nav"> <h1 class="vui-title"></h1> <h2 class="vui-desc">VUI 移動組件庫</h2> </div></template>

然後我們需要在路由中對其進行引用,在 mobile.js 文件中寫入

import DemoList from ./components/demo-list.vueroutesConfig.push({ path: /, component: DemoList})

然後開始完善 demo-list.vue 文件

<template> <div class="side-nav"> <h1 class="vui-title"></h1> <h2 class="vui-desc">VUI 移動組件庫</h2> <div class="mobile-navs"> <div v-for="(item, index) in data" :key="index"> <div class="mobile-nav-item" v-if="item.showInMobile"> <mobile-nav v-for="(group, s) in item.groups" :group="group" :base="base" :key="s"></mobile-nav> </div> </div> </div> </div></template><script>import navConfig from ../nav.config.json;import MobileNav from ./mobile-nav;export default { data() { return { data: navConfig[zh-CN], base: /component }; }, components: { MobileNav }};</script><style lang="postcss">.side-nav { width: 100%; box-sizing: border-box; padding: 90px 15px 20px; position: relative; z-index: 1; .vui-title, .vui-desc { text-align: center; font-weight: normal; user-select: none; } .vui-title { padding-top: 40px; height: 0; overflow: hidden; background: url(https://raw.githubusercontent.com/xuqiang521/vui/master/src/assets/logo.png) center center no-repeat; background-size: 40px 40px; margin-bottom: 10px; } .vui-desc { font-size: 14px; color: #666; margin-bottom: 50px; }}</style>

這裡我們引用了 mobile-nav.vue 文件,這也是我們接下來要完成的移動端 Demo 列表展示組件

ii. nav 列表

在 examples/src/components 下新建 mobile-nav.vue 文件,解析 nav.config.json 文件,從而進行 Demo 列表展示。

<template> <div class="mobile-nav-group"> <div class="mobile-nav-group__title mobile-nav-group__basetitle" :class="{ mobile-nav-group__title--open: isOpen }" @click="isOpen = !isOpen"> {{group.groupName}} </div> <div class="mobile-nav-group__list-wrapper" :class="{ mobile-nav-group__list-wrapper--open: isOpen }"> <ul class="mobile-nav-group__list" :class="{ mobile-nav-group__list--open: isOpen }"> <template v-for="navItem in group.list"> <li class="mobile-nav-group__title" v-if="!navItem.disabled"> <router-link active-class="active" :to="base + navItem.path"> <p> {{ navItem.title }} </p> </router-link> </li> </template> </ul> </div> </div></template><script>export default { props: { group: { type: Object, default: () => { return []; } }, base: String }, data() { return { isOpen: false }; }};</script>

然後寫入列表樣式

<style lang="postcss">@component-namespace mobile { @b nav-group { border-radius: 2px; margin-bottom: 15px; background-color: #fff; box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1); @e basetitle { padding-left: 20px; } @e title { font-size: 16px; color: #333; line-height: 56px; position: relative; user-select: none; @m open { color: #38f; } a { color: #333; display: block; user-select: none; padding-left: 20px; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); &:active { background: #ECECEC; } > p { border-top: 1px solid #e5e5e5; } } } @e list-wrapper { height: 0; overflow: hidden; @m open { height: auto; } } @e list { transform: translateY(-50%); transition: transform .2s ease-out; @m open { transform: translateY(0); } } li { list-style: none; } ul { padding: 0; margin: 0; overflow: hidden; } }}</style>

接下來,重新訪問 http://localhost:8080/mobile.html ,不出意外你便能訪問到我們預想的結果

到這一步為止,我們「粗陋」的組件庫架子便已經全部搭建完畢。

博文到這裡也差不多要結束了,文章中所有的代碼都已經託管到了 github 上,後續我還會寫一篇文章,帶著大家逐步完善我們組件庫中的一些細節,讓我們的組件庫能夠更加的完美。

github地址:github.com/xuqiang521/p

文章末尾再打一波廣告 ~~~

前端交流群:731175396

美團點評長期招人,如果有興趣的話,歡迎一起搞基,簡歷投遞方式交流群中有說明 ~

小夥伴們你們還在等什麼呢?趕緊先給文章點波贊,然後關注我一波,然後加群和大佬們一起交流啊 ~~~

大佬們快到碗里來 ~


推薦閱讀:

關於Vue組件化的疑惑,這是Vue的缺陷嗎?
有什麼UI組件庫可以兼容三大框架,vue,react,angular嗎?
Vue比React有什麼優點嗎?
有哪些好用的ajax組件?

TAG:前端開發框架和庫 | Vuejs | 單元測試 |