項目位址: diana
文檔位址: http://muyunyun.cn/diana/
造輪子的意義
為啥已經有如此多的前端工具類庫還要自己造輪子呢?個人認為有以下幾個觀點吧:
- 定制性強,能根據自己的需求為主導延伸開發。萬一一不小心還能幫到别人(比如 React 庫);
- 紙上得來終覺淺,很多流行的庫,隻是照着它們的 API 進行使用,其實這些庫裡蘊含着大量的知識、技巧,最好的辦法就是仿照它們來寫些小 demo,進而體會這些庫的精髓;
- 造輪子的過程中能讓自己體會到與平常業務開發不一樣的樂趣;比如和日常業務開發中很大的一個差別是會對測試用例具有比較嚴格的要求;而且寫文檔能力提升了。
- 就先瞎編到這裡了。。。
抛開内部方法(寫相應的專題效果可能會更好,是以這裡先略過),下面分享一些開發
diana 庫時的一些心得:
項目目錄結構
├── LICENSE 開源協定
├── README-zh_en.md 英文說明文檔
├── README.md 中文說明文檔
├── coverage 代碼覆寫率檔案
├── docs 文檔目錄
│ └── static-parts
│ ├── index-end.html 靜态文檔目錄結尾檔案
│ └── index-start.html 靜态文檔目錄開頭檔案
├── karma.conf.js karma 配置檔案
├── lib
│ ├── diana.back.js 服務端引用入口
│ └── diana.js 浏覽器引用入口
├── package.json
├── script
│ ├── build.js 建構檔案
│ ├── check.js 結合 pre-commit 進行 eslint 校驗
│ ├── tag-script.js 自動生成文檔的标簽
│ ├── web-script.js 自動生成文檔
│ ├── webpack.browser.js 浏覽器端 webpack 配置檔案
│ └── webpack.node.js 伺服器端 webpack 配置檔案
├── snippets
├── src
│ ├── browser 浏覽器端方法
│ ├── common 共用方法
│ ├── node node 端方法
│ └── util.js 庫内通用方法
├── tag_database 文檔标簽
└── test 測試檔案
├── browserTest
├── commonTest
├── index.js
└── nodeTest
目錄結構也随着方法的增多在不停疊代當中,建議直接到
庫中檢視最新的目錄結構。
相應地,具體的方法會随着時間疊代,是以首先推薦檢視
文檔,點選如下圖的 Ⓢ 就能檢視源碼。
讓子產品同時在 Node.js 與浏覽器中運作
我們可以通過如下方法來判斷子產品目前是運作在 Node.js 還是浏覽器中,然後使用不同的方式實作我們的功能。
// Only Node.JS has a process variable that is of [[Class]] process
const isNode = Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]'
但如果使用者使用了子產品打包工具,這樣做會導緻 Node.js 與浏覽器的實作方式都會被包含在最終的輸出檔案中。針對這個問題,開源社群提出了在 package.json 中添加 browser 字段的
提議,目前 webpack 和 rollup 都已經支援這個字段了。
給 browser 字段提供一個檔案路徑作為在浏覽器端使用時的子產品入口,但需要注意的是,打包工具會優先使用 browser 字段指定的檔案路徑作為子產品入口,是以你的 main 字段 和 module 字段會被忽略,但是這會導緻打包工具不會優化你的代碼。詳細資訊請參考
這個問題。
在
為了在不同環境中使用适當的檔案,在 package.json 中進行了如下聲明:
"browser": "lib/diana.js",
"main": "lib/diana.back.js", // 或者 "module": "lib/diana.back.js",
這樣一來,在 node 環境中,引用的是
lib/diana.back.js
檔案,在浏覽器環境中,引用的是
lib/diana.js
檔案。然後就能愉快地在浏覽器端和 node 端愉快地使用自己特有的 api 了。
常見子產品規範比較
另外為了使
的打封包件相容 node 端、以及浏覽器端的引用,選擇了 UMD 規範進行打包,那麼為什麼要選擇 UMD 規範呢?讓我們看下以下幾種規範之間的異同:
CommonJS
- CommonJs 是伺服器端子產品的規範,
。這些規範涵蓋了子產品、二進制、Buffer、字元集編碼、I/O流、程序環境、檔案系統、套接字、單元測試、伺服器網關接口、包管理等。Node.js 采用了這個規範
- 根據 CommonJS 規範,一個單獨的檔案就是一個子產品。加載子產品使用
方法,該方法讀取一個檔案并執行,最後傳回檔案内部的require
對象。exports
- CommonJS 加載子產品是同步的。像 Node.js 主要用于伺服器的程式設計,加載的子產品檔案一般都已經存在本地硬碟,是以加載起來比較快,不用考慮異步加載的方式,是以 CommonJS 規範比較适用。但如果是浏覽器環境,要從伺服器加載子產品,這是就必須采用異步模式。是以就有了 AMD、CMD 解決方案。
AMD、CMD
- AMD 是 RequireJS 在推廣過程中對子產品定義的規範化産物。AMD 推崇提前執行。
// AMD 預設推薦的是 define(['./a', './b'], function(a, b) { a.doSomething() b.doSomething() ... })
- CMD 是 SeaJS 在推廣過程中對子產品定義的規範化産物。CMD 推崇依賴就近。
// CMD define(function(require, exports, module) { var a = require('./a') a.doSomething() var b = require('./b') b.doSomething() ... })
UMD
UMD 是 AMD 和 CommonJS 的結合。因為 AMD 是以浏覽器為出發點的異步加載子產品,CommonJS 是以伺服器為出發點的同步加載子產品,是以人們想出了另一個更通用的模式 UMD,來解決跨平台的問題。
選擇了以 umd 方式進行輸出,來看下 UMD 做了啥:
(function (root, factory) {
if (typeof exports === 'object' && typeof module === 'object') { // UMD 先判斷是否支援 Node.js 的子產品(exports)是否存在,存在則使用 CommonJS 模式
module.exports = factory()
} else if (typeof define === 'function' && define.amd) { // 接着判斷是否支援 AMD(define是否存在),存在則使用 AMD 方式加載子產品。
define([], factory)
} else if (typeof exports === 'object') { // CommonJS 的另一種形式
exports['diana'] = factory()
} else
root['diana'] = factory() // Window
})(this, function() {
return module
})
測試踩坑之路
代碼覆寫率
單元測試的代碼覆寫率統計,是衡量測試用例好壞的一個的方法。但凡是線上用的庫,基本上都少不了高品質的代碼覆寫率的檢測。如下圖為 diana 庫的測試覆寫率展示。
可以看到覆寫率分為以下 4 種類型,
- 行覆寫率(line coverage):是否每一行都執行了?
- 函數覆寫率(function coverage):是否每個函數都調用了?
- 分支覆寫率(branch coverage):是否每個if代碼塊都執行了?
- 語句覆寫率(statement coverage):是否每個語句都執行了?
番外:github 上顯示的覆寫率是根據行覆寫率來展示的。
mocha + istanbul
最初的版本, 僅僅用到 mocha 進行測試 *.test.js 檔案,然後在
codecov得到測試覆寫率。
引人 karma
如果僅僅測試 es5、es6 的文法,其實用 mocha 就已經夠用了,但是涉及到測試 Dom 操作的文法等就必須建立一個浏覽器,在上面進行測試。karma 的作用其實就是自動幫我們建立一個測試用的浏覽器環境。
為了讓浏覽器支援 Common.js 規範,中間用了 karma + browserify,盡管測試用例都跑通了,但是最後的代碼覆寫率的檔案裡隻有各個方法的引用路徑。最後隻能又回到 karma + webpack 來,這裡又踩到一個坑,
打包編譯JS代碼覆寫率問題,踩了一些坑後,終于實作了可以檢視編譯前代碼的覆寫率。圖如下:
通過這幅圖我們能清晰地看到源代碼中測試用例跑過各行代碼的次數(左側的數字),以及測試用例沒有覆寫到的代碼(圖中紅色所示)。然後我們就能改善相應的測試用例進而提高測試覆寫率。
配置檔案,核心部分如下:
module.exports = function(config) {
config.set({
files: ['test/index.js'], // 需載入浏覽器的檔案
preprocessors: { // 預處理
'test/index.js': ['webpack', 'coverage']
},
webpack: {
module: {
rules: [{
test: /\.js$/,
use: { loader: 'sourcemap-istanbul-instrumenter-loader' }, // 這裡用 istanbul-instrumenter-loader 插件的 0.0.2 版本,其它版本有坑~
exclude: [/node_modules/, /\.spec.js$/],
}],
}
},
coverageReporter: {
type: 'lcov', // 貌似隻能支援這種類型的讀取
dir: 'coverage/'
},
remapIstanbulReporter: { // 生成 coverage 檔案
reports: {
'text-summary': null,
json: 'coverage/coverage.json',
lcovonly: 'coverage/lcov.info',
html: 'coverage/html/',
}
},
reporters: ['progress', 'karma-remap-istanbul'], // remap-isbanbul 也報了一個未找到 sourcemap 的 error,直接注釋了 remap-istanbul 包的 CoverageTransformer.js 檔案的 169 行,以後有機會再搗鼓吧。(心累)
...
})
}
總結
本文圍繞
對造輪子的意義,子產品相容性,測試用例進行了思考總結。後續會對該庫流程自動化以及性能上做些分享。
該庫參考學習了很多優秀的庫,感謝
underscore、
outils ec-do 30-seconds-of-code等庫對我的幫助。
最後歡迎各位大佬在
issues盡情吐槽。
作者:
牧雲雲出處:
http://www.cnblogs.com/MuYunyun/"本文版權歸作者和部落格園所有,歡迎轉載,轉載請标明出處。
如果您覺得本篇博文對您有所收獲,請點選右下角的 [推薦],謝謝!