muyunyun / diana Goto Github PK
View Code? Open in Web Editor NEWfe customized tools:nut_and_bolt:
Home Page: http://muyunyun.cn/diana/
License: MIT License
fe customized tools:nut_and_bolt:
Home Page: http://muyunyun.cn/diana/
License: MIT License
current build is to build typescript file to javascript in dist, then convert dist into lib or es file.
expect one step to convert typescript file to lib or es file.
本文提供了按需加载了几种思路,并给出了相应实践。
为了探究按需加载的本质,选择了对先前造的轮子 diana 进行实验。
import * as _ from 'diana'
打包体积结果如下:
测试的是 diana 0.4.1
import { equal } from 'diana'
打包体积结果如下:
经过测试,发现两种方式打包后的体积都为 21 k,第二种方式仍然将整个包
引入项目中了。可是 lodash 就是这么玩的呀,这和说好的不一样呀,难道是忽视了什么细节么。
下文就来揭开面纱,并动手改造项目,最终目标是用第二种写法实现按需加载,减小打包体积。
按需加载的效果是最终打包的代码里没有未引入的模块,从而优化项目体积。下面给出 3 种可以按需加载的方案。
按需加载的方案一是将每个函数都单独发布一个包,可以在 npm 上查阅 lodash,这种引用方式如下:
import { isEqual } from 'lodash.isequal'
按需加载的方案二是将每一个函数都作为一个单一的模块导出,参照这种思路将 diana 的每个函数暴露在 lib 目录下,部分截图如下:
这时候再来测试下打包体积:
import equal from 'diana/lib/equal'
打包体积结果如下:
可以看到打包体积减小约为原来的 1/7 了,但是这种方案在写法上过于冗长,那要不借助下 babel ?
方案三是在方案二的基础上借助 babel 插件后,写法可以如下:
import { equal } from 'diana'
在 .babelrc
里进行如下配置:
// .babelrc
{
"plugins": [
["on-demand-loading", {"library": "diana"}]
]
}
此时打包体积如下:
实际上,babel 插件 的作用是将 import { equal } from 'diana'
编译成 import equal from 'diana/lib/equal'
。
关于 babel 插件执行机制,可以在babel执行机制中探讨,这里先不展开了。
下载了你的这个项目,但是执行命令报错,并不存在dist包之类的。我想弄一个函数库,请问我应该怎么实现,方便提供个demo不
expect some useful typescript function from type-challenges like ts-toolbelt、utility-types、https://github.com/andnp/SimplyTyped 。
Both react and vue has migrated to rollup as module bundler for these reasons:
For pure JavaScript project, rollup seems to be a better solution. Any ideas migrating?
It seems strange with import n from "tslib"
in lib/diana.js,it maybe cause some bug.
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem.
remove the code import n from "tslib"
.
make this tool library better, the plan as follows:
尝试在 ts 中写了下:
class Demo {
@Test()
submit = 1234
}
function Test() {
return function (target, key, descriptor) {
console.log(descriptor)
}
}
但是这个 descriptor 打印出来的值是 undefined(期待的是里面有 initializer 属性)
后续继续跟进。
为啥已经有如此多的前端工具类库还要自己造轮子呢?个人认为有以下几个观点吧:
抛开内部方法(写相应的专题效果可能会更好,所以这里先略过),下面分享一些开发 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 还是浏览器中,然后使用不同的方式实现我们的功能。
// 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 是服务器端模块的规范,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 得到测试覆盖率。
如果仅仅测试 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 等库对我的帮助。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.