尝试造了个工具类库,名为 Diana
项目地址: 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 字段会被忽略,但是这会导致打包工具不会优化你的代码。详细信息请参考这个问题。
在 diana 库 为了在不同环境中使用适当的文件,在 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 了。
常见模块规范比较
另外为了使 diana 库 的打包文件兼容 node 端、以及浏览器端的引用,选择了 UMD 规范进行打包,那么为什么要选择 UMD 规范呢?让我们看下以下几种规范之间的异同:
CommonJS
CommonJs 是服务器端模块的规范,
Node.js 采用了这个规范
。这些规范涵盖了模块、二进制、Buffer、字符集编码、I/O流、进程环境、文件系统、套接字、单元测试、服务器网关接口、包管理等。根据 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,来解决跨平台的问题。
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 种类型,
- 行覆盖率(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 行,以后有机会再捣鼓吧。(心累)
...
})
}
总结
本文围绕 diana 库 对造轮子的意义,模块兼容性,测试用例进行了思考总结。后续会对该库流程自动化以及性能上做些分享。
该库参考学习了很多优秀的库,感谢 underscore、outils、ec-do、30-seconds-of-code 等库对我的帮助。
最后欢迎各位大佬在 issues 尽情吐槽。
尝试造了个工具类库,名为 Diana的更多相关文章
- Java程序员都应该去使用一下这款强大的国产工具类库
这不是标题党,今天给大家推荐一个很棒的国产工具类库:Hutool.可能有很多朋友已经知道这个类库了,甚至在已经在使用了,如果你还没有使用过,那不妨去尝试一下,我们项目组目前也在用这个.这篇文章来简单介 ...
- Apache—dbutils开源JDBC工具类库简介
Apache—dbutils开源JDBC工具类库简介 一.前言 commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用 ...
- 【Java基础】Java开发过程中的常用工具类库
目录 Java开发过程中的常用工具类库 1. Apache Commons类库 2. Guava类库 3. Spring中的常用工具类 4. 其他工具 参考 Java开发过程中的常用工具类库 1. A ...
- 国人开源了一款小而全的 Java 工具类库,厉害啊!!
最近栈长看到了一款小而全的 Java 工具类库:Hutool,Github 已经接近 14K Star 了,想必一定很优秀,现在推荐给大家,很多轮子不要再造了! Hutool 是什么 Hutool 是 ...
- 编写更少量的代码:使用apache commons工具类库
Commons-configuration Commons-FileUpload Commons DbUtils Commons BeanUtils Commons CLI Commo ...
- WMI使用的WIN32_类库名
WMI使用的WIN32_类库名 包括:硬件类.操作系统类.安装应用程序类.WMI服务管理类.性能计数器类1.硬件类冷却类别Win32_Fan--风扇Win32_HeatPipe--热管Win32_Re ...
- Flutter 常用工具类库common_utils
地址:https://pub.flutter-io.cn/packages/common_utils#-readme-tab- Dart常用工具类库 common_utils 1.TimelineUt ...
- JSON工具类库: alibaba/fastjson 使用记录
JSON工具类库: alibaba/fastjson 使用记录 一.了解JSON JSON标准规范中文文档: http://www.json.org/json-zh.html 最佳实践:http:// ...
- Java_图片处理_02_图片处理工具类库
二.参考文档 1.Java图片处理工具类库
随机推荐
- Delphi工程版本号修改工具
自动修改某目录下符合条件的Delphi工程(dproj)版本号, 支持命令行调用支持通配符忽略文件 -p [Path] 在[Path]路径下查询所有dproj文件(可以为空, 默认路径为程序当前路径) ...
- HTML的用法
今天学习了HTMIL,标签.这个东西吧,没什么很难的,就是得多练多打.今天的一些个人心得: 标签:成对存在的名称 标签注意:1.标签名放在<> 2.标签成对存在的 3.结束标签有斜杠/ 例 ...
- Celery 源码解析七:Worker 之间的交互
前面对于 Celery 的分布式处理已经做了一些介绍,例如第五章的 远程控制 和第六章的 Event机制,但是,我认为这些分布式都比较简单,并没有体现出多实例之间的协同作用,所以,今天就来点更加复杂的 ...
- better-scroll 实现tab栏目滑动当前高亮始终在可视区
https://ustbhuangyi.github.io/better-scroll/doc/zh-hans/#better-scroll better-scroll文档地址 如图 ,是我们要实现的 ...
- java基础回顾(2)
java中只有两种类型:基础类型.引用类型 8中基本类型:byte short int long float double char boolean,其中byte类型取值范围[-2^7~2^7-1] ...
- pytesseract使用
1.安装pip install pytesseract 2.安装tesseract-ocr,下载地址:https://github.com/UB-Mannheim/tesseract/wiki,我安装 ...
- 【C#入门教案-02】用记事本编写第一个C#程序-Hello World
02-用记事本编写第一个C#程序-Hello World 广东职业技术学院 欧浩源 [1]进行.NET程序开发的最基本环境配备 .NET Framework + 代码编辑工具(记事本或Noetpad ...
- QTP日期格式化
'以下函数将日期参数进行格式转化,例如:2017-01-02 Function ShortDateToLongDate(strChangeDate) b=split(strChangeDate, ...
- Django学习(2)数据宝库
数据库是一所大宝库,藏着各种宝贝.一个没有数据库的网站,功能有限.在Django中,支持的数据库有以下四种: SQLite3 MySQL PostgreSQL Oracle 其中SQLite3为Dja ...
- POJ 1273 Drainage Ditches 网络流 FF
Drainage Ditches Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 74480 Accepted: 2895 ...