vue組件封裝的流程

前言

前一段時間利用空閑時間學習了一下vue組件的封裝,也在工作中進行了實踐,將公司常用的一個api抽象成了vue組件,并發布在npm上。之前覺得組件的封裝是一件很困難的事情,通過親身體驗之后,發現確實有很多需要注意的地方,但是當自己真正走完了這個過程之后,回頭看的時候,其實也不過如此。真正困難的其實不是組件封裝的流程與步驟,而是組件的實現思想。但是,對于沒有進行過組件封裝的同學來說,流程和步驟確實也存在著許多坑,但是一旦你趟過去之后,就會非常輕松。

我的學習渠道主要來源于兩個地方,一個是vue官方文檔cookbook中一篇介紹組件封裝的文章,另一個是饑人谷的一門課程。

我將通過一系列文章去講一下整個組件封裝的過程中我是如何做的,文章會圍繞一個簡易組件的封裝過程去寫,這個組件并不具有實際用處,只是一個demo。希望通過幾篇文章,給那些想自己封裝組件的同學做一個參考。

demo地址

https://github.com/zhuweileo/vue-component-demo

需求分析

我們的需求如下:

  • 寫一個button.vue組件

    ps:由于是為了學習封裝步驟,所以這里button組件的功能十分簡單。

  • 將組件發布至npm

    按說單元測試應該在發布之前進行,但是單元測試比較復雜,為了快點得到成就感,所以先走簡單的流程。

  • 對組件進行單元測試

步驟

1.使用webpack打包組件

2.發布到npm

3.單元測試


1.使用webpack打包組件

為什么要打包

可能你會問為什么要對.vue文件進行打包,直接引用.vue文件不可以么?當然可以,但是前提是用戶有自己的打包工具可以處理.vue文件,如果用戶沒有打包工具,你的組件是不是就不能用了呢!

不打包,你只能這么用

import MyComponent from 'my-component';

export default {
  components: {
    MyComponent,
  },
  // rest of the component
}

打包后,你還可以這么用

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
  {{text}}
  <m-button>nio</m-button>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
<script src="../dist/index.js"></script>
<script>
  console.log(MyComponent);
  const {MButton} = MyComponent;
  const app = new Vue({
    el:'#app',
    data:{
      text: 'hello vue!',
    },
    components:{
      'm-button':MButton,
    }
  })
</script>
</body>
</html>

組件的封裝肯定離不開打包工具,打包工具大家最熟悉的一定非webpack莫屬了。其實,在vue官方文檔中的cookbook中,文章的作者推薦使用的 打包工具是Rollup,并附有詳細的配置文件,但是我之前對Rollup不熟悉,就沒有用,有興趣的同學可以自己嘗試。

webpack版本及文件具體內容

webpack版本:4.17.1 (比較新的版本)

webpack.config.js

var path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin')
var webpack = require('webpack')

module.exports = {
    entry: {
        'index': './src/index.js'
    },
    output: {
        path: path.resolve(__dirname, './dist'),
        filename: '[name].js',
        library: 'MyComponent',
        libraryTarget: 'umd'
    },
    devtool: '#eval-source-map',
    resolve: {
        alias: {
            'vue$': 'vue/dist/vue.esm.js'
        },
        extensions: ['.js', '.vue']
    },
    mode: 'production',
    performance: {
        hints: false
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use:{
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env'],
                        "env": {
                            "test": {
                                "plugins": ["istanbul"]
                            }
                        }
                    }
                }
            },
            {
                test: /\.vue$/,
                loader: 'vue-loader',
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: [
                    'vue-style-loader',
                    'css-loader'
                ]
            }
        ]
    },
    plugins: [
        new VueLoaderPlugin()
    ]
};

webpack配置文件詳解

入口文件

    entry: {
        'index': './src/index.js'
    },

入口文件配置比較簡單,關鍵在于入口文件的內容。

index.js

export {default as MButton}  from './MButton.vue'

入口文件的意思就是將MButton.vue文件中的默認導出值,重命名為MButton然后再導出。

(對于export語法不理解的同學,推薦查看阮一峰的es6相關教程

輸出配置

    output: {
        path: path.resolve(__dirname, './dist'), //輸出目錄
        filename: '[name].js', //輸出文件名
        library: 'MyComponent', //輸出的全局變量名稱
        libraryTarget: 'umd',//輸出規范為umd
    },

前兩行配置不解釋,解釋下后兩行

  • library: 'MyComponent'

    MyCompoent是一個全局變量名稱(你自定義),當用戶直接通過script標簽引用你的組件的時候,這個將作為你的組件的命名空間,你的組件的內容會掛載到該全局變量上面,作為它的一個屬性,類似于你使用jquery的時候,會有一個全局的$jquery供你引用。

  • libraryTarget: 'umd',

    使用umd(通用模塊規范)打包你的模塊。umd兼容amd以及cmd模式,并且會導出一個全局變量。這樣使打包后的模塊可以使用各種規范引用,增強模塊的通用性。

    引用webpack官網的解釋:

    libraryTarget: 'umd' - This exposes your library under all the module definitions, allowing it to work with CommonJS, AMD and as global variable. Take a look at the UMD Repository to learn more.

    這么設置可以讓你的庫適應所有的模塊標準,允許別人通過CommonJS、AMD和全局變量的形式去使用它。

    具體什么是umd、amd、cmd大家自行百度吧。

模式

mode: 'production'

webpack4 新增的配置參數,意為webpack將認為該打包是為了生產環境,會將一些默認配置設置為生產環境所需要的,例如默認進行代碼壓縮。

rules

這里是重點,有三個規則

  1. 使用babel處理js,這樣你就可以在vue單文件組件中的script標簽內放心使用es6語法

     {
         test: /\.js$/,
         exclude: /node_modules/,
         use:{
             loader: 'babel-loader',
             options: {
                 presets: ['@babel/preset-env'],
                 "env": {
                     "test": {
                         "plugins": ["istanbul"]
                     }
                 }
             }
         }
     },
    
  2. 使用vue-loader處理.vue文件。在webpack中每一種文件的處理都需要對應的loader,就像css需要css-loader,js文件需要babel-loader,vue文件也不例外。其實vue-loader就是將你寫的單文件組件內的三個標簽,轉化為原生的js,具體原理查看官方文檔

     {
         test: /\.vue$/,
         loader: 'vue-loader',
         exclude: /node_modules/
     },
    
  3. 使用css-loader處理和vue-style-loader處理單文件組件內style便簽內的css樣式

     {
         test: /\.css$/,
         use: [
             'vue-style-loader',
             'css-loader'
         ]
     }
    

使用vue-loader插件

官方說必須使用VueLoaderPlugin配合vue-loader使用,具體為什么我也不清楚。

  plugins: [
    // make sure to include the plugin for the magic
    new VueLoaderPlugin()
  ]

這就是所有的webpack配置,其實還是挺簡單的。


2. 發布到npm上

修改你的package.json文件

   {
      ...
      
         "name": "vue-component-demo",//你的組件的名字
         "version": "0.0.1",//當前版本號
         "description": "vue component demo",//描述
         "main": "dist/index.js",//入口文件
         
         ...
   }
  • 入口參數"main": "dist/index.js",指向的就是我們之前打包好的文件。

    這樣當用戶向下面這樣引入你的組件的時候,打包工具就會直接去"main": "dist/index.js"找文件。

    import {button} from 'vue-component-demo'
    
  • name參數不能和npm上已有的組件名相同,否則發布的時候會報錯,如果不幸有人用了這個組件名,你就需要修改一下,再重復這個流程重新發布就好了。

登錄npm(需要提前注冊一個npm賬號)

   /vue-component-demo (master)
   $ npm adduser
   Username: 
   Password:
   Email: (this IS public) 

發布組件

   /vue-component-demo (master)
   $ npm publish

至此,你的組件就已經發布到npm上了,別人就可以通過npm 安裝你的組件,然后使用。

   npm install vue-component-demo

更新組件

以上是我們發布的第一個版本,如果之后你有修復組件中的bug,或者增強了組件的功能,你就要更新組件,更新組件也很簡單。

  • 更新package.json中的version參數,不能和之前的版本號重復,否則發布不成功。
  • 再執行一次npm publish

3.單元測試

為什么單元測試

單元測試的目的是為了保證組件的的質量(可靠性),畢竟寫組件是為了讓更多的人使用,發布完之后出現一堆bug總是不好的。單元測試,可以讓你每次你修改組件之后,及時發現是否存在bug,保證每次發布的代碼存在較少的bug。

單元測試工具

安裝工具

安裝主要的工具

npm install karma mocha chai @vue/test-utils 

karma 配合chai,mocha等工具時,需要安裝對應的一系列插件,插件比較多沒有都寫出來,具體參考package.json

npm install karma-chai karma-mocha karma-webpack karma-sourcemap-loader...

karma配置

//引入打包用的webpack配置
var webpackConfig = require('./webpack.config.js')

module.exports = function (config) {
    config.set({
        //引入需要使用的工具
        frameworks: ['mocha','sinon-chai','chai-dom','chai',],
        /*
         這個參數決定哪些文件會被放入測試頁面,哪些文件的變動會被karma監聽,以及以服務的形
         式供給
         */
        files: [
            'test/**/*.spec.js'
        ],
        //測試文件會使用webpack進行預處理
        preprocessors: {
            '**/*.spec.js': ['webpack', 'sourcemap']
        },
        //預處理時webpack的配置
        webpack: webpackConfig,
        //使用哪些工具進行測試報告
        reporters: ['spec','coverage'],
        //通過哪些瀏覽器進行測試
        browsers: ['Chrome']
    })
}

寫測試用例

/test/button.spec.js

import MButton from '../src/MButton'
import {mount} from "@vue/test-utils";
import Syn from 'syn'

describe('MButton.vue',function () {

  it('can set type prop',function () {
    const wrapper = mount(MButton,{
      propsData:{
        type: 'warn',
      }
    });
    const vm = wrapper.vm;
    expect(vm.$el.classList.contains('warn')).to.be.true
  })

  it('can click',function (done) {
    const click = sinon.spy();
    const haha = sinon.spy();
    const wrapper = mount(MButton,{
      propsData:{
        type: 'warn',
      },
      listeners:{
        click,
      }
    });

    Syn.click(wrapper.vm.$el,function () {
      sinon.assert.calledWith(click);
      done();
    });
  })

});

describe,it函數

測試用例中的這兩個函數是 mocha 庫中提供的

  • 為什么沒有import,就可以直接使用?
    還記得karma配置文件中的frameworks: ['mocha','sinon-chai','chai-dom','chai',]嗎?
  • 這兩個函數有什么用?
    為你的測試用例劃分區塊,一個describe是一個大區塊,一個it是一個小區塊,兩個函數的第一個參數是對于區塊的描述,第二個參數是一個回調函數,指定區塊的具體測試內容。

mount函數

mount函數是@vue/test-utils庫中的函數

  • 有什么用?

    mount的作用是裝載(運行)你的vue組件,相當于如下代碼。

    const constructor =  Vue.extend(MButton)
    new constructor().$mount()
    

    只有將你的組件運行起來,才可以測試其功能是否正確。

  • @vue/test-utils是什么?

    是vue組件測試輔助庫,使用細節查看@vue/test-utils

expect函數

expect函數來源于chai

  • 有什么用?

    expect期待一個結果 。

    //期待button組件的 dom元素的classList中包含warn是真的
    expect(vm.$el.classList.contains('warn')).to.be.true  
    
  • to,be有什么用?

    沒有任何實質性意義,是為了讓代碼看起來更像一個句子,增強可讀性

sinon

  • 有什么用

    可以用來測試事件是否被觸發

    //聲明一個間諜函數
    const click = sinon.spy();
    
    const wrapper = mount(MButton,{
      propsData:{
        type: 'warn',
      },
      //這里參看  mount 函數的介紹
      listeners:{
        click, //把間諜函數作為click事件的回調函數
      }
    });
    

syn

  • 用什么用

    模擬用戶的交互動作(點擊、拖拽等)

    //模擬click事件,然后期待sinon生成的click函數被調用
    Syn.click(wrapper.vm.$el,function () {
       sinon.assert.calledWith(click);
       done();
    });
    
    
    

運行測試用例

在package.json中加入如下代碼
package.json

 "scripts": {
    "test": "cross-env BABEL_ENV=test karma start --single-run=false", 
    ...
  },

當執行以上腳本之后,karma會自動打開一個瀏覽器窗口,將測試用例都執行一遍,并告訴你哪個測試通過了,哪個沒有通過。如果有測試用例沒通過,你就應該檢查是你的代碼有問題,還是測試用例編寫的不正確,并修復問題,直到所有測試用例都通過,之后你就可以發布你的代碼了。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容

  • 寫在前面的話 閱讀本文之前,先看下面這個webpack的配置文件,如果每一項你都懂,那本文能帶給你的收獲也許就比較...
    不忘初心_9a16閱讀 3,245評論 0 17
  • ## 框架和庫的區別?> 框架(framework):一套完整的軟件設計架構和**解決方案**。> > 庫(lib...
    Rui_bdad閱讀 2,935評論 1 4
  • webpack 是什么? 本質上,webpack 是一個現代 JavaScript 應用程序的靜態模塊打包器(mo...
    IT老馬閱讀 3,333評論 2 27
  • 期中考試,大家一聽這個詞,肯定非常緊張,今天我們迎來了上一年級第一次的正式考試(期中考試),我的心里有點小小...
    董筱萱閱讀 292評論 0 0
  • 昨天女兒開學了,在客車上瞌睡了一會兒,筆記本電腦被偷走了。女兒給邊哭邊給我打電話說情況。我說,寶貝,電腦丟了,你很...
    王瑤燕行閱讀 153評論 0 0