项目地址: dianahtml
文档地址: http://muyunyun.cn/diana/前端
为啥已经有如此多的前端工具类库还要本身造轮子呢?我的认为有如下几个观点吧:node
抛开内部方法(写相应的专题效果可能会更好,因此这里先略过),下面分享一些开发 diana 库 时的一些心得:webpack
├── 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
目录结构也随着方法的增多在不停迭代当中,建议直接到库中查看最新的目录结构。git
相应地,具体的方法会随着时间迭代,因此首先推荐查看文档,点击以下图的 Ⓢ 就能查看源码。es6
咱们能够经过以下方法来判断模块当前是运行在 Node.js 仍是浏览器中,而后使用不一样的方式实现咱们的功能。github
// 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 都已经支持这个字段了。web
给 browser 字段提供一个文件路径做为在浏览器端使用时的模块入口,但须要注意的是,打包工具会优先使用 browser 字段指定的文件路径做为模块入口,因此你的 main 字段 和 module 字段会被忽略,可是这会致使打包工具不会优化你的代码。详细信息请参考这个问题。编程
在 diana 库 为了在不一样环境中使用适当的文件,在 package.json 中进行了以下声明: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 了。
另外为了使 diana 库 的打包文件兼容 node 端、以及浏览器端的引用,选择了 UMD 规范进行打包,那么为何要选择 UMD 规范呢?让咱们看下如下几种规范之间的异同:
CommonJs 是服务器端模块的规范,Node.js 采用了这个规范
。这些规范涵盖了模块、二进制、Buffer、字符集编码、I/O流、进程环境、文件系统、套接字、单元测试、服务器网关接口、包管理等。
根据 CommonJS 规范,一个单独的文件就是一个模块。加载模块使用 require
方法,该方法读取一个文件并执行,最后返回文件内部的 exports
对象。
CommonJS 加载模块是同步的。像 Node.js 主要用于服务器的编程,加载的模块文件通常都已经存在本地硬盘,因此加载起来比较快,不用考虑异步加载的方式,因此 CommonJS 规范比较适用。但若是是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。因此就有了 AMD、CMD 解决方案。
// AMD 默认推荐的是 define(['./a', './b'], function(a, b) { a.doSomething() b.doSomething() ... })
// CMD define(function(require, exports, module) { var a = require('./a') a.doSomething() var b = require('./b') b.doSomething() ... })
UMD 是 AMD 和 CommonJS 的结合。由于 AMD 是以浏览器为出发点的异步加载模块,CommonJS 是以服务器为出发点的同步加载模块,因此人们想出了另外一个更通用的模式 UMD,来解决跨平台的问题。
diana 库 选择了以 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 种类型,
最初的版本, 仅仅用到 mocha 进行测试 *.test.js 文件,而后在 codecov 获得测试覆盖率。
若是仅仅测试 es五、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 行,之后有机会再捣鼓吧。(心累) ... }) }
本文围绕 diana 库 对造轮子的意义,模块兼容性,测试用例进行了思考总结。后续会对该库流程自动化以及性能上作些分享。
该库参考学习了不少优秀的库,感谢 underscore、outils、ec-do、30-seconds-of-code 等库对个人帮助。
最后欢迎各位大佬在 issues 尽情吐槽。