開源庫架構實戰——從0到1搭建屬於你本身的開源庫

需求分析

最近在 H5 開發與 APP 客戶端工程師的聯調過程當中, 常常須要實現一些經常使用的移動端事件封裝成接口提供給客戶端,例如用戶的單擊 tap 事件、雙擊事件、長按事件以及拖動事件。但因爲瀏覽器默認只提供了 touchstarttouchmovetouchend 三個原生事件,在實際的開發過程當中,咱們經常使用的解決方案即是經過監聽touchstarttouchend 事件配合定時器來實現咱們的自定義移動端事件,爲了實現經常使用自定義事件的複用,咱們對其進行了封裝,並提供方便用戶使用的工具函數,這也是咱們實現 mt-events 的初衷。javascript

mt-events 全名是 Mobile Terminal Events。最初咱們對這個庫的定位是但願封裝一些經常使用的移動端事件來方便用戶進行更爲便捷的移動端開發,例如雙擊事件、長按事件、滑動事件等等。後來,隨着項目的迭代,mt-events 的功能更傾向往前端事件綁定工具的趨勢發展,由於咱們集成了事件委託等,您能夠像使用 JQuery 的 on 方法那樣使用咱們的 mt-events,更加便捷事件綁定和委託,讓移動端事件如原生事件般友好。 這是咱們項目的 Github 地址:github.com/jerryOnlyZR…html

接下來,咱們將帶您體驗一款工具庫的搭建流程,ES6 新特性 Map、Proxy、Reflect 以及 WeakMap 在咱們的工具庫中發揮的做用,以及咱們開源的工具庫mt-events所擁有的魅力。前端


mt-events 初探

先看看 mt-events 這款工具庫具備哪些特性:vue

  • 廣泛性:封裝經常使用的移動端事件
    • 單擊
    • 雙擊
    • 長按
    • 滑動
    • 拖拽
    • 縮放
    • 旋轉
  • 便捷性:在全局掛載工具函數,綁定事件如 $.on() 般絲滑;封裝事件代理,只須要像 JQuery 那樣傳入被代理元素的選擇器便可。
  • 新語法:使用 Map 映射事件回調,便捷事件移除;使用 WeakMap 實現 DOM 元素與回調的弱引用,預防內存泄漏。
  • 輕量級:代碼壓縮 + gzip,只有大約 2KB

那麼,咱們又該如何使用它呢?這裏提供了兩種引用工具庫的方式,最經常使用的固然是從 HTML 裏使用 script 引入:java

<script src="http://mtevents.jerryonlyzrj.com/mtevents.min.js"></script>
複製代碼

而後,咱們的工具函數 mtEvents 將會被掛載在 window 對象上,您能夠在瀏覽器的開發者工具裏的 console 面板輸入並執行 mtEvents,若是打印出以下文本說明您已經成功引入咱們的工具庫了:node

mtEvents-console

或者您是 VUE 等前端框架的開發者,您也能夠經過 npm 依賴的方式引入咱們的工具,咱們的工具庫會跟隨您們的 VUE 文件被打包進 bundle 裏。webpack

首先,將咱們的工具庫以上線依賴的形式安裝:nginx

npm i mt-events --save
複製代碼

而後就能夠在咱們的 .vue 等文件裏直接引入使用:git

//test.vue
<script> const mtEvents = require('mt-events') export default { ..., mounted(){ mtEvents('#bindTarget', 'click', e => console.log('click')) } } </script>
複製代碼

具體的使用方法,您能夠參照咱們 Github 爲您提供的用戶文檔哦~github

如何搭建一款屬於咱們本身的開源庫

選擇一款合適的測試工具

​ 沒有通過測試的代碼不具有任何說服性。相信你們在瀏覽別人開源的工具庫代碼時,都能在根目錄下見到一個名爲 test 的文件夾,其中就放置着項目的測試文件。特別對於工具庫來講,測試更是一個不可或缺的環節。

​ 市面上的測試工具種類繁多,例如 Jest,Karma,Mocha,Tape等,並不須要侷限與哪一款,下面咱們對這幾種框架進行了一些對比。

  • Jest
    • facebook 開源的 js 單元測試框架
    • 集成 JSDOM,mt-events 庫主要適用於移動端,集成 JSDOM 可以讓咱們更好地去模擬移動端事件
    • 基於 Istanbul 的測試覆蓋率工具,會在項目下生產一個 coverage 目錄,內附一個優雅的測試覆蓋率報告,讓咱們能夠清晰看到優雅的測試狀況
    • 開箱即用,配置不多,只須要 npm 命令安裝便可運行,UI 層面清晰,並且操做簡單
    • 基於並行測試多文件,在大項目中的運行速度很快
    • 內置 Jasmine 語法,以及添加了不少新特性
    • 內置 auto mock,自帶 mock API
    • 支持斷言和仿真,不須要引入第三方斷言庫
    • 在隔離環境下測試,支持快照測試
    • 較多用於 React 項目(但普遍支持各類項目)
    • 比較新,社區還不是很成熟
  • Karma
    • Google Angular 團隊開源的 JavaScript測試執行過程管理工具
    • 配置簡單方便
    • 強大適配器,能夠在 karma 上面配置 jasmine,mocha 等單元測試框架
    • 可提供真實的模擬環境,能夠在 chrome,firefox 等各類瀏覽器環境進行配置
    • 開發者能夠本身把控整個自動化測試流程,實現更加自動化,當咱們編輯保存的時候,便可運行所有的測試用例
    • 高擴展性,支持插件開發
    • 支持 ci 服務
    • 執行速度快
    • 支持遠程控制以及支持調試
  • Mocha
    • 學習成本比較高,但隨之帶來的是它能提供更好的靈活性和可擴展性
    • 社區成熟,在社區上能夠找到各類的特殊場景下可用的插件或者擴展
    • 須要較多的配置,配置相對比較麻煩
    • 自身集成度不高,須要和第三方庫結合(一般是 Enzyme 和 Chai)纔能有斷言、mocks、spies 的功能
    • 默認建立全局的測試結構
    • 終端顯示友好
    • 目前使用最普遍的庫
  • Tape
    • 開發者只須要用 node 執行一個 js 腳本,直接調用 API 便可
    • 最精簡,體積最小,提供簡單的結構和斷言
    • 只提供最底層最基礎的 API
    • 沒有定義全局變量,開發者能夠隨意更改測試代碼
    • 不須要 CLI 客戶端環境,只須要可以運行 js 環境,便可運行 Tape

綜上所述,Jest 開箱即用;若須要爲大型項目配備足以快速上手的框架,建議使用Karma;Mocha 用的人最多,社區最成熟,靈活,可配置性強易拓展;Tape 最精簡,提供最基礎的東西最底層的API。

下面咱們舉個例子如何使用 Jest

  • 安裝Jest
$ npm i jest -D
複製代碼
  • 添加配置文件:
// jest.config.js # 在 jest.config.js 配置測試用例路徑,以及覆蓋率輸出文檔的目錄等等信息
module.exports = {
    testURL: 'http://localhost',
    testMatch: ['<rootDir>/test/*.js'],             // 測試文件匹配路徑(拿到根目錄下test文件夾裏的全部JS文件)
    coverageDirectory: '<rootDir>/test/coverage',   // 測試覆蓋率文檔生成路徑
    coverageThreshold: {			    // 測試覆蓋率經過閾值
        global: {
            branches: 90,
            functions: 90,
            lines: 90,
            statements: 90
        }
    }
}
複製代碼
  • 配置package測試scripts
// package.json
{
    ...,
    "scripts": {
        ...,
        "test": "jest"
    }
}
複製代碼
  • 編寫測試用例
// test/index.js                              # 編寫測試用例
describe('test dbtap events', () => {         # 測試組
	test('test 1+1', () => {	      # 測試用例
        expect(1+1).toBe(2)           	      # 斷言
	})
})
複製代碼
  • 執行測試
$ npm t
複製代碼
  • 結果輸出

test-result

這就是配置測試的基本流程。

使用 eslint 規範團隊代碼

在團隊開發的工做中,代碼維護所佔的時間比重每每大於新功能的開發。所以制定符合團隊的代碼規範是相當重要的,這樣不只僅能夠很大程度地避免基本語法錯誤,也保證了代碼的可讀性,方便維護。

程序是寫給人讀的,只是偶爾讓計算機執行一下。 --Donald Knuth

衆所周知,eslint 是一個開源的 JavaScript 代碼檢查工具,能夠用來校驗咱們的代碼,給代碼定義一個規範,團隊成員按照這個代碼規範進行開發,這保證了代碼的規範。使用 eslint 能夠帶來不少好處,能夠幫助咱們避免一些低級錯誤,可能一個小小的語法問題,讓您定位了好久才發現問題所在,並且在團隊合做的過程當中,能夠保證你們都按照同一種風格去開發,這樣更方便你們看懂彼此的代碼,提升開發效率。

另外,eslint 的初衷是爲了讓開發者建立本身的代碼檢測規則,使其能夠在編碼過程當中發現問題,擴展性強。爲了方便使用,eslint 也內置了一些規則,也能夠在這基礎上去增長自定義規則。

eslint --init  
複製代碼

選擇您最熟悉的構建工具

bundle-tools

在開發階段咱們常常會使用一些語法糖像ES6的新特性來方便咱們的開發,或者 ES6 Modules 來銜接咱們的模塊化工做,可是有些新特性是 Node.js 或者瀏覽器還未能支持的,因此咱們須要對開發代碼進行編譯及打包,爲了提煉自動化工程,咱們能夠選擇許多優良的自動化構建工具,例如前端巨頭 Webpack,或是流式構建工具 Gulp,亦或是具備優良 Tree-shaking 特性的Rollup,每款構建工具都有本身的閃光點,咱們能夠根據業務需求選擇最合適的構建工具。 構建工具作的事情就是將一系列流程用代碼去實現,自動化地去執行一系列複雜的操做,最終實現將源代碼轉換成能夠執行的 JavaScript、CSS、HTML 代碼。構建工具層出不窮,例如 Grunt,Gulp,Webpack,Rollup 等等。下面咱們對這幾種工具進行一些對比。

  • Grunt
    • Grunt 有大量可複用的插件,封裝成經常使用的構建任務
    • 靈活性高
    • 集成度不高,配置麻煩,沒法作到開箱即用
    • 至關於 Npm scripts 的進化版
    • 模式與Webpack相似,隨着Webpack的不斷迭代以及功能的完善,能夠優先考慮使用Webpack
  • Gulp
    • 基於流的自動化構建工具
    • 能夠管理任務和執行任務
    • 能夠監聽文件的變化以及讀寫文件,流式處理任務
    • 能夠搭配其餘工具一塊兒使用
    • 集成度不高,配置麻煩,沒法作到開箱即用
  • Webpack
    • 一款打包模塊化的 JavaScript 工具
    • 經過 loader 轉換文件,經過 Plugin 注入鉤子,最後輸出由多個模塊組合成的文件。
    • 專一處理模塊化的項目,不適用於非模塊化項目
    • 豐富完整,同時也可經過 Plugin 擴展
    • 開箱即用,開發體驗不錯
    • 社區成熟活躍,能夠在社區中找到各類特殊場景的插件擴展
  • Rollup
    • 相似 webpack 但專一於 ES6 模塊的打包工具
    • 針對 ES6 源碼進行 Tree Shaking,移除只被定義但沒有被使用的代碼
    • 針對 ES6 源碼進行 Scope Hoisting,以減小輸出文件的大小和提高運行性能
    • 配置和使用簡單,但不如 webpack 那麼完善
    • 社區生態鏈還不夠成熟,不少特殊場景下沒法找到解決方案

咱們的 mt-events 項目選擇了 Rollup 和 Webpack 兩款構建工具是由於咱們須要對「同構」後的JS代碼裁剪分支,所以咱們須要利用 Rollup 優良的 Tree-shaking 特性;而且爲了上線 min.js 文件的壓縮打包,咱們使用 Webpack 來方便咱們的構建工做。

配置 JSDoc 爲後來之人掃清障礙

​ 項目的維護工做是延伸項目生命週期的最關鍵手段,閱讀別人的源碼相信對你們來講都是一件費力的事情,特別是當原做者不在您身邊或者沒法給您提供任何信息的時候,那就更是悲從中來。因此,書寫完善的註釋是開發過程當中須要養成的良好習慣。爲了提高代碼的可維護性,咱們都會在主幹代碼上完善咱們的註釋,而且,市面上有一款工具,它可以自動將咱們的註釋轉化成 API 文檔,生成可視化頁面,聽起來是很神奇吧,先彆着急,聽我娓娓道來。

​ 這款工具名爲 JSDoc,它是一款根據 Javascript 文件中註釋信息,生成 JavaScript 應用程序或庫、模塊的 API 文檔的工具。JSDoc 分析的源代碼是咱們書寫的符合 Docblock 格式的代碼註釋,它會智能幫咱們生成美觀的 API 文檔頁面,咱們要作的,只是簡單的跑一句jsdoc命令就能夠了。

下面是 mt-events 的 API 文檔頁面(很美觀不是嗎?這些都是JSDoc自動生成的):

mtEvents-docs

​ 簡約的風格讓人看起來心曠神怡,想一想若是有後來的維護者想要快速瞭解您的項目的大致架構和具體方法的功能,獻上這樣一份開發者文檔可不是要比直接丟給他一份源代碼要來的好得多對吧。

使用 Git 鉤子對提交的代碼進行 lint 和測試

爲了確保線上的代碼不被污染,咱們配置了eslint,因此在團隊裏每位成員push代碼以前,都須要進行一次lint和test,這樣才能確保線上代碼的整潔性和有效性,可是這一繁瑣的工做可否自動化去完成呢?

解決方案就是使用git鉤子來實現自動化lint和test:

  1. 首先,咱們先安裝git的鉤子管理工具——husky:

    npm install -D husky
    複製代碼
  2. 接着,在咱們項目的package.json裏添加咱們的鉤子命令:

    在mt-events項目裏,咱們在commit鉤子上執行lint,在push鉤子上執行test,配置以下:

    {
      ...,
      "scripts": {
        ...,
        "precommit": "lint-staged",
        "prepush": "npm t"
      }
    }
    複製代碼
  3. 優化 :只lint改動的文件—— lint-staged

    每次鉤子執行lint都須要遍歷全部的文件,不免拉低效率,lint-staged這款插件就優化了這一個問題,自帶diff的特性讓eslint只去lint變動文件,您只須要在package.json裏添加相關配置便可。mt-events示例:

    首先,先安裝lint-staged依賴:

    npm install -D lint-staged
    複製代碼

    接着,在package裏添加相應配置:

    {
      ...,
      "lint-staged": {
        "core/*.js": [ 						// lint監聽的變動文件
          "prettier --write",				// 自動格式化全部代碼
          "eslint --fix",					// lint並自動修改不符合規範的代碼
          "git add"						    // 將全部的修改添加進暫存池
        ]
      }
    }
    複製代碼

    配上鉤子以後,咱們就能看到這樣的額輸出結果了:

    • commit鉤子

    commit-husky

    • push鉤子

    push-husky

讓持續集成工具幫您實現自動化部署

每次咱們在本地跑完構建生成了上線文件以後,咱們都須要經過scp或者rsync等方式上傳到咱們的服務器上,每次若是都須要手動執行相關命令完成上線操做確定是違背了咱們工程自動化的思想,爲了實現自動化部署,咱們可使用持續集成工具來協助咱們完成上線操做。

市面上成熟的持續集成工具也很多,可是口碑最盛的也當屬 Travis CIJenkins 了。做爲Github的標配,Travis CI 在開源領域有着不可顛覆的地位,若是咱們是在Github上對項目進行版本控制管理,選擇這款工具天然再合適不過了。Jenkins由於內容較多,這裏就不作過多介紹了,本文的重點,主要是談談Travis CI在咱們的自動化工程中該如何運用。

ci-tools

Travis CI 的特性:
  • Travis CI 提供的是持續集成服務,它僅支持 Github,不支持其餘代碼託管。
  • 它須要綁定 Github 上面的項目,還須要該項目含有構建或者測試腳本。
  • 只要有新的代碼,就會自動抓取。而後,提供一個虛擬機環境,執行測試,完成構建,還能部署到服務器。
  • 只要代碼有變動,就自動運行構建和測試,反饋運行結果。確保符合預期之後,再將新代碼集成到主幹。
  • 每次代碼的小幅變動,就能看到運行結果,從而不斷累積小的變動,而不是在開發週期結束時,一會兒合併一大塊代碼,這大大提升了開發 mt-events 庫的效率,只要一更新,用戶便可拉取到最新的 js 代碼。這就是增量上線。

其實Travis CI的使用方法能夠簡單的歸納爲3步,就像官網首頁的那樣圖片介紹的同樣:

travis-guide

  1. 在 Travis CI 的儀表盤裏勾選您須要持續集成的項目
  2. 在您的項目根目錄下添加一個名爲 .travis.yml 的配置文件
  3. 最後您要作的,就是 push 您的代碼,而後靜觀其變

其實難點也就是 .travis.yml 配置文件的書寫和具體持續集成的梳理的,先 po 一張咱們項目的配置文件:

language: node_js               # 項目語言,node 項目就按照這種寫法就OK了
node_js:
- 8.11.2 			# 項目環境
cache:				# 緩存 node_js 依賴,提高第二次構建的效率
 directories:
 - node_modules
before_install:                 # 這些是咱們加密密鑰後自動生成,兩行命令的做用就是獲得一個有效密鑰
- openssl aes-256-cbc -K $encrypted_81d1fc7fdfa5_key -iv $encrypted_81d1fc7fdfa5_iv
 -in mtevents_travis_key.enc -out mtevents_travis_key -d
- chmod 600 mtevents_travis_key
after_success:			# 構建成功後的自定義操做
- npm run codecov		# 生成 Github 首頁的 codecov 圖標
- scp -i mtevents_travis_key -P $DEPLOY_PORT -o stricthostkeychecking=no -r dist/mtevents.min.js
  $DEPLOY_USER@$DEPLOY_HOST:/usr/local/nginx/html   		#將生成的上線文件 scp 到服務器
複製代碼

先梳理一下持續集成的流程,首先,咱們更新開源項目而後 push,Travis 會監聽到咱們的 push 操做並自動拉取項目代碼到 Travis 的虛擬機上,執行構建流程。思路就是這樣,其實咱們使用 Shelljs 也能實現一個簡單的持續集成工具。

一般,咱們在CI大型項目例如網站、Web APP 之類的項目時,更多地會使用 rsync 命令代替咱們暴力的 scp,由於 scp 會上傳全部的文件,而 rsync 自帶 diff 功能,因此功能如其名,它的做用就是「同步」變動文件,這樣能極大提高咱們的CI效率。可是因爲咱們的工具庫項目只有一個 min.js 文件,因此 scp 就已經足夠解決問題了。

爲您的項目添加開源許可證

每一個開源項目都須要配置一份合適的開源許可證來告知全部瀏覽過咱們的項目的用戶他們擁有哪些權限,具體許可證的選取能夠參照阮一峯前輩繪製的這張圖表:

licenses

那咱們又該怎樣爲咱們的項目添加許可證了?其實 Github 已經爲咱們提供了很是簡便的可視化操做: ​ 咱們平時在逛 github 網站的時候,發現很多項目都在 README.md 中添加徽標,對項目進行標記和說明,這些小圖標給項目增色很多,不只簡單美觀,並且還包含清晰易懂的信息。

  1. 打開咱們的開源項目並切換至 Insights 面板
  2. 點擊 Community 標籤
  3. 若是您的項目沒有添加 License,在 Checklist 裏會提示您添加許可證,點擊 Add 按鈕就進入可視化操做流程了

添加一些您喜歡的 Icon 來修飾您的項目吧

​ 當咱們花費了不少精力去構建完善咱們的項目後,但願有更多的人來關注以及使用咱們的項目。此時咱們如何更好地向其餘人展現本身的項目呢?給本身的項目添加一些好看的徽標是一種不錯的選擇,讓人耳目一新。

​ 點開 mt-events 的README文件,您能夠看到在開頭部分有不少漂亮的小圖標,不少大型項目都會使用這些小圖標來裝飾本身的項目,既能展現項目的一些主要信息,也能體現項目的專業性。

readme-icons

​ 那麼,咱們又該如何爲咱們本身的開源項目添加這樣的小圖標呢?GitHub 小圖標的官方網站是 shields.io/ ,能夠在上面選擇喜歡的徽標來爲本身的項目潤色,常見的徽標主要有持續集成狀態,代碼測試覆蓋率,項目版本信息,項目下載量,開源協議類型,項目語言等,下面根據咱們項目簡單羅列幾個圖標講一講如何生成。

add-licenses

  • 持續集成狀態

    • 持續集成按照前面的模塊推薦使用 Travis CI,在項目中添加一個 .travis.yml 配置文件,告訴 Travis CI 怎樣對您的項目進行編譯或測試,具體配置關注上一個模塊。

    • 而後徽標圖片地址是

      http://img.shields.io/travis/{GitHub 用戶名}/{項目名稱}.svg
      複製代碼

      將上面 URL 中的 {GitHub 用戶名} 和 {項目名稱} 替換爲本身項目的便可,最後能夠將集成完成後的 markdown 代碼貼在本身的項目上

    • 效果圖是:

      Travis (.org)

  • 測試覆蓋率

辛辛苦苦把項目的測試覆蓋率提升到了100%,不把它show出來確定很憋屈吧。若是您但願在您的Github上添加項目測試覆蓋率小圖標,這裏咱們推薦使用 codecov 這套解決方案(圖片來自官網截圖)。

codecov-index

您要作的,只是像在Travis CI裏添加項目那樣把您須要跑收集測試覆蓋率的項目添加進codecov的儀表盤,而後在您的項目裏安裝codecov依賴:

$ npm install codecov --save-dev
複製代碼

codecov的原理就是在您執行完項目測試以後,它會自動去尋找並收集項目內的測試覆蓋率文檔,而後呈如今頁面上,並生成小圖標,因此,您只要在項目測試以後執行codecov命令就好了。由於咱們的codedev是安裝在本地,因此咱們須要進入package.json內配置一下咱們的codecov執行命令:

// package.json
{
    ...,
    "scripts": {
        ...,
        "codecov": "codecov"
    }
}
複製代碼

如今,您終於知道咱們的.travis.yml配置文件裏的npm run codecov是作什麼用了的吧~

codecov

接下來,就能夠把咱們的效果圖添加進Github首頁了。

  • 項目版本信息

    • 項目版本信息,是根據不一樣的發佈工具來制定的。shields.io/#/examples/… 在這個網站上能夠找到不一樣的發佈工具的徽標圖片地址。

    • 這裏以咱們的庫作示例,以 npm 方式發佈出去的,因此圖標的地址就是:

      https://img.shields.io/npm/v/{項目名稱}.svg
      複製代碼
    • 效果圖是:

      npm

  • 項目下載量

    • 項目被下載的次數,是根據不一樣的平臺獨立統計的。shields.io/#/examples/… 在這個網站上能夠找到各類統計平臺的徽標圖片地址。

    • 這裏以咱們的庫作示例,以 npm 方式發佈出去的,且以每週下載量的維度來看:

      https://img.shields.io/npm/dw/{您的項目}.svg 
      複製代碼
    • 效果圖是:

      npm

mt-events從0到1

目錄結構

mt-events
├── core                   # 源代碼文件夾
│   ├── event.js           # 自定義事件處理句柄生成器,包含長按,雙擊,滑動,拖拽事件
│   ├── index.js           # mtEvents 類以及綁定,移除事件方法
│   ├── proxy.js           # 事件代理 Proxy 生成器
│   ├── touch.js           # 模擬瀏覽器原生 touch 事件,供test使用,未對外發布
│   ├── weakmap.js         # 創建用戶定義回調與事件綁定元素的弱引用,預防內存泄漏
├── dist
│   ├── mtevents.min.js    # mt-events 工具庫最終生成的 JS 上線壓縮文件
├── docs                   
│   ├── developer          # 爲開發者提供的mt-events開發文檔,使用命令`$npm run docs`便可生成
│   ├── user               # 爲用戶提供的mt-events的中英文使用文檔
├── lib                    # 上線待構建代碼臨時文件夾
│   ├── event.js           
│   ├── index-Browser.js   # 上線壓縮JS源文件
│   ├── index-npm.js       # npm package入口文件
│   ├── proxy.js                    
│   ├── weakmap.js                  
├── test
│   ├── coverage           # 測試覆蓋率參考文件
│   ├── index.js           # 測試用例
├── .travis.yml            # Travis-ci配置文件
├── jest.config.js         # Jest 配置文件
├── package.json           
├── rollup.config.js       # rollup 配置文件
├── webpack.config.js      # webpack配置文件 
複製代碼

這是一份平平無奇的項目目錄,你們必定能看到不少熟悉的字眼,咱們都對其中的文件的用途進行了解釋說明,具體關鍵細節和重點,咱們會在後文中提煉出來。

工程化實踐

images

工具選型

構建: webpack4 Rollup
測試工具: Jest
持續集成: Travis CI 
API 文檔生成工具: JSDoc
代碼規範: eslint  prettier  lint-staged
項目版本控制工具: git
複製代碼
JavaScript 模塊打包器 Rollup

​ Rollup 已被許多主流的 JavaScript 庫使用,它對代碼模塊使用新的標準化格式,這些標準都包含在 JavaScript 的 ES6 版本中,這可讓您自由無縫地使用您須要的 lib 中最有用的獨立函數。Rollup 還幫助 mt-events實現了簡單的「同構」,經過區分用戶的引用方式,咱們將上線文件區分爲 index-npm.js 和 index-Browser.js 文件,既能夠經過 script 在 HTML 引入,也可使用 npm 方式 require 依賴。

​ 除了使用 ES6 模塊,Rollup 獨樹一幟的 Tree Shaking 特性,能夠靜態分析導入模塊,移除冗餘,幫助咱們完成了代碼無用分支的裁剪:

// index.js 
if (process.env.PLATFORM === 'Browser') {
  window.mtEvents = mtEventsFun
} else {
  module.exports = mtEventsFun
}
// rollup.config.js 
export default {
    entry: './core/index.js',
    output: {
        file: `lib/index-${platform}.js`,
        format: 'cjs'
    },
    plugins: [
        replace({
            "process.env.PLATFORM": JSON.stringify(platform)
        }),
        copy({
            './core/events.js': 'lib/events.js',
            './core/proxy.js': 'lib/proxy.js',
            './core/weakmap.js': 'lib/weakmap.js',
        })
    ]
};

// package.json 根據傳入的參數生成對應的 index-npm.js 和 index-Browser.js 文件
// 在相應的 index-${platform}.js 文件移除沒用到的代碼
{
    "build:browser": "rollup --config --platform Browser",
    "build:npm": "rollup --config --platform npm"
}

// index-npm.js
{
  module.exports = mtEventsFun;
}

// index-Browser.js
{
  window.mtEvents = mtEventsFun;
}
複製代碼
單元測試工具 Jest

​ 隨着項目迭代的過程,依賴人工去迴歸測試容易出錯和遺漏,爲了保證 mt-events 庫的質量,以及實現自動化測試,咱們引入了 Jest,由於它集成了 JSDOM,用它模擬咱們的事件庫在瀏覽器環境中執行的效果再合適不過了。而且 Jest 容易上手,開箱即用,幾乎零配置,功能全面。

​ 可是在測試的開始階段就遇到了一個問題,在瀏覽器原生移動端事件中,並無一個像 click() 那樣的方法能夠供咱們直接調用來模擬事件觸發,這個問題又該如何解決呢?

​ 利用掛載在全局的 TouchEvent 構造函數,咱們嘗試着建立用戶的 touch 事件,最終實踐證實,這個方法可行,下方即是咱們模擬touch事件的核心代碼:

// touch.js
createTouchEvent (type) {
    return new window.TouchEvent(type, {
        bubbles: true,
        cancelable: true
    })
}
dispatchTouchEvent (eventTarget, event) {
    if (typeof eventTarget === 'string') {
        eventTarget = document.querySelector(eventTarget)
    }
    eventTarget.dispatchEvent(event)
    return eventTarget
}
複製代碼

​ 下面是咱們使用 Jest 測試代碼的覆蓋率及結果:

mtEvents-test

持續集成

根據前文提到的配置,咱們就能夠在Travis CI首頁看到咱們的項目的持續集成結果:

travis-result

線上的min.js文件也同時被更新到最新的版本了。

源碼剖析

mt-events 源碼都是按照 ES6 代碼規範來寫,下面從幾個方面來體驗 mt-events 源碼的魅力:

一個既是 Function 又是 Object 的工具函數

​ 如此奇葩的數據類型看起來彷佛很陌生,但我敢保證您以前必定有見過,只是沒注意到它罷了,並且是多年之前咱們最常常打交道的老朋友。還記得 JQuery 裏面的$符號嘛?您必定用過這種寫法去獲取元素 $("#myDom"),也用過掛在 $ 上的 ajax 方法來發送請求就像這樣:$.ajax(...),是否是被我這麼一說突然發現,以前最經常使用的 $ 竟然既是個函數又是個對象,不多見這樣的狀況對吧,其實實現原理很簡單,只須要把類實例的原型掛載到 Function 上就搞定了,之因此這麼作,是爲了讓用戶綁定事件時,直接使用mtEvents這個 Function 就能夠了,就不須要再去拿到 mtEvents 上的 bind 方法了,可以優化體驗。具體實現代碼以下:

// index.js
let mtEvents = new MTEvents()
const mtEventsPrototype = Object.create(MTEvents.prototype)
const mtEventsFun = mtEvents.bind.bind(mtEvents)
Object.setPrototypeOf(mtEventsFun, mtEventsPrototype)
Object.keys(mtEvents).map(keyItem => {
  mtEventsFun[keyItem] = mtEvents[keyItem]
})
複製代碼

mtEvents-bind

移除事件時須要傳遞指針,怎麼讓用戶的回調和咱們綁定在元素上的事件回調造成映射?

​ 在自定義事件中,咱們是經過同時監聽 touchstarttouchend 兩個事件來判斷用戶觸發的事件類型,而且在指定的位置執行用戶傳入的回調。那麼,當用戶須要移除以前綁定的事件時,咱們又該如何處理呢?用戶傳入的確定是須要執行的回調,而不是咱們綁定在元素上的事件回調。

​ 這時候,咱們就須要對用戶傳入的執行回調和咱們綁定在事件監聽上的回調創建映射關係了,這樣咱們就能夠依據用戶傳入的執行回調找到咱們所須要移除的事件綁定回調函數了。對於映射關係,咱們首先想到的確定就是對象了,可是在傳統的 JS 裏,對象的鍵只能是字符串,可是咱們須要讓它是一個函數,這回就該想到咱們 ES6 裏新增的數據類型 Map 了,他的鍵能夠不限於字符串,正合我意。

​ 咱們定義 userCallback2Handler 爲一個 Map,將用戶自定義的 callback 與事件處理器 eventHandler 綁定起來,相應的 remove 的時候也是根據 callback 來進行移除事件綁定,自定義事件中也是同理。

用戶移除 DOM 元素時忘了移除綁定的事件怎麼辦?讓 WeakMap 弱引用和內存泄漏 Say goodbye!

WeakMap 就是爲了解決這個問題而誕生的,它的鍵名所引用的對象都是弱引用,即垃圾回收機制不將該引用考慮在內。所以,只要所引用的對象的其餘引用都被清除,垃圾回收機制就會釋放該對象所佔用的內存。也就是說,一旦再也不須要,WeakMap 裏面的鍵名對象和所對應的鍵值對會自動消失,不用手動刪除引用。 --摘自 阮一峯《ECMAScript 6 入門》

​ weakmap.js 的意義在於創建 DOM 元素與對應 callback 的弱引用,在移除 DOM 元素時綁定在該元素上的回調也會被 GC 回收,這樣就能起到防止內存泄漏的做用。

// weakmap.js

/** * weakMapCreator WeakMap生成器 * @param {HTMLElement} htmlElement DOM元素 * @param {Function} callback 事件監聽回調 * @return {WeakMap} WeakMap實例 */
function weakMapCreator (htmlElement, callback) {
    let weakMap = new WeakMap()
    weakMap.set(htmlElement, callback)
    return weakMap
}
複製代碼

事件委託的代碼每次綁定事件都得寫一次,用 Proxy—Reflect 快速去重

​ 在開發的過程當中咱們發現,爲了實現事件委託相關操做,咱們常常要書寫重複的代碼,爲了下降代碼的重複率,咱們想到了使用 ES6 裏的 Proxy 和 Reflect 對事件回調進行代理,在這過程當中執行事件委託相關操做。

​ 在 proxy.js 源碼中,定義了事件委託處理的方法:_delegateEvent,以及事件委託 Proxy 生成器:delegateProxyCreator,這樣在執行事件監聽回調時,通過咱們的事件委託 Proxy,進行相應的事件委託處理,這樣不只能夠大大減小代碼重複率,使代碼看起來更加精簡美觀,同時這樣定位問題 bug 也變得簡單不少,只須要從根源處去定位 bug 便可。

/** * _delegateEvent 事件代理處理 * @param {String(Selector) | HTMLElement} bindTarget 事件綁定元素 * @param {String(Selector)} delegateTarget 事件代理元素 * @param {Object} target 原生事件對象上的target對象,即(e.target) * @return {Object | null} 若是存在代理,則調用此方法,事件發生在代理對象上則返回代理對象 */
function _delegateEvent (bindTarget, delegateTarget, target) {
  if (!delegateTarget) return null
  const delegateTargets = new Set(document.querySelectorAll(delegateTarget))
  while (target !== bindTarget) {
    if (delegateTargets.has(target)) {
      return target
    } else {
      target = target.parentNode
    }
  }
  return null
}

/** * delegateProxyCreator 事件代理Proxy生成器 * @param {String(Selector) | HTMLElement} bindTarget 事件綁定元素 * @param {String(Selector)} delegateTarget 事件代理元素 * @param {Object} target 原生事件對象 * @param {Function} callback proxy攔截回調 * @return {Function} 過Proxy的callback */
function delegateProxyCreator (bindTarget, delegateTarget, e, callback) {
  const handler = {
    apply (callback, ctx, args) {
      const target = _delegateEvent(bindTarget, delegateTarget, e.target)
      if ((delegateTarget && target) || !delegateTarget) {
        return Reflect.apply(...arguments)
      }
    }
  }
  return new Proxy(callback, handler)
}
複製代碼
相關文章
相關標籤/搜索