[实践]-开发一个文件下载的node模块

node模块也就是node的包,常用来被其他node程序调用来完成一些功能。 例如shelljs这个模块,可以用来实现shell命令的执行,例如request这个模块可以帮助你进行http请求的处理,比如cheerio这个包可以用来实现非浏览器环境的jquery风格dom查询。

今天,我打算开发一款node模块,它提供了一个API,可以帮助别人进行文件下载并提供下载进度信息。最终代码在这里: https://github.com/cuiyongjian/fetch-file

脚手架类型

我们开发js项目,目前来看一般有3种类型:

  1. 纯前端的库

  2. 前后端都可用的库,比如一些算法相关的

  3. 纯后端的库,比如用到了node特有的API的库

第一种和第二种,其实可以共用前端脚手架。因为一个基于webpack工作流的前端库,其本身就能兼容node环境调用的,因为node环境使用一个库时跟前端webpack工作流使用一个包时的引用方式是一致的,只需要找到该包的入口即可。所以如果是算法类型的模块,这种模块跟平台无关,node和浏览器均可以使用。则你应该利用felib-template去初始化一个前端模板。 在node环境下只需要引用包中的main入口文件,在前端工作流中使用时也是引用src下的入口文件即可,当然前端也可以直接引用编译后的dist下的目标文件。

前端库的另一种使用方法是在浏览器端使用<script>标签的话,这种直接引用webpack编译后的dist目录下的.min.js文件即可。其实大部分情况下是引用CDN上的min.js文件了。

前端库中应该把main文件设置为谁呢?我们一般在包中直接将main文件设置为dist下的UMD类型的编译结果就可以了。或者你可以像Vue.js一样编译一个dist/common.js的版本出来,作为包的main文件。 因为当这个包被npm的方式使用(不管前端还是node后端)时,必然是为了支持commonjs的环境(包括webpack工作流的前端开发或者node后端开发)。

所以,开发前端库的包中如何设置main,我们只考虑目标是commonjs的环境即可。

纯后端库的ES6脚手架

但如果我们仅仅是开发一个node模块,这个模块使用了FS等模块,它注定不是应用在浏览器端的。此时如果使用felib-tempalte前端库的脚手架,显然webpack这些是多余的,也没必要编译到dist中UMD的版本(后端支持commonjs即可)。所以纯node的模块其实更简单,只需要项目中有个package.json就足够了。

不过后端现在为了跟上潮流,也要使用ES6,那么这里就要存在于一个转码的过程。这里,我创建了一个nodelib-tempalte的脚手架类型,可以用来开发纯node端的模块。且使用了babel进行ES6转码,且入口中加载babel-polyfill全局cover了ES6的API。所以可以尽情的使用ES6语法。

脚手架介绍

项目结构如下:

1
2
src
lib

babel配置

关于babel的知识点,可以参考从零开始搭建一个webpack前端类库脚手架[3]-babel

该脚手架中使用babel-cli进行了ES6代码的编译。从而可以使用ES6编写代码,编译为ES5之后再发布和运行。

polyfill

关于polyfill有几种方案,在babel这篇文章中我也分析过了。 其中babel-runtime无法cover代码中类似 [1,2,3].from 这样的。而babel-polyfill全部引入可能会比较臃肿。 最好是能够根据目标平台的版本,来利用 preset-env 配合 useBuiltIns 来设置。

不过由于这是个node项目,所以不需要介意最终代码的臃肿,所以我暂且直接使用preset-es2015以及最新标准进行编译, 且引入了完整polyfill。不过这样其实不是很好的,因为它默认了要把所有代码都编译为es2015, 且node代码运行前就要加载一个polyfill完整版,这样肯定对于第一次启动node模块时有性能损失。由于性能问题不严重,我就暂且采用这种方法了,有兴趣的读者可以将preset-es2015这个预设修改为env并配合useBuiltIns来按需加载polyfill是极好的。

下面是我的.babelrc配置:

1
2
3
{
"presets": ["es2015", "stage-0"]
}

当然,别忘记 npm i babel-preset-es2015 babel-preset-stage-0 --save-dev 安装他们。stage-0表示最新标准的最晚提案,所以其特性已经囊括了stage1, stage2, stage3. 然而我们会发现,其实我的配置就是要使用最新的ES特性,倒不如直接使用babel中的 latest 预设,而现在的babel已经飞起latest,因为 preset-env 的默认情况下就是 latest的效果。

所以,最终,我把babelrc配置为这样:

1
2
3
{
"presets": ["env"]
}

在根目录下的index.js中,使用 require('babel-polyfill') 加载ES新标准的语言API垫片。

package.json配置

1
2
3
{
}

实战开始

基于lime-cli生成一个nodelib-template类型的项目骨架,然后再开始。

首先请确保你安装了我写的lime-cli脚手架生成工具。如果没有,请执行:

1
npm i -g lime-cli

然后安装nodelib-template这个脚手架。

1
lime new nodelib-template

安装完成之后,再执行 npm i 来安装其各种依赖和开发依赖。

1
npm i

测试

我们使用抹茶mocha进行测试代码。

为了让测试代码可以基于ES6编写,我们可以利用mocha 的compiler特性,给mocha执行测试代码的时候加入一个babel预编译器。
通过babel官网教程 我们知道,需要使用babel-register作为mocha的compiler。

1
2
npm install --save-dev mocha
npm install --save-dev babel-register

配置mocha脚本:

1
2
3
4
5
{
"scripts": {
"test": "mocha --compilers js:babel-register"
}
}

我们都知道babel-register的作用,其实mocha也是用它来提供了加载js代码(require(xxx))时候的ES6支持。

由于你可能在测试代码中用来ES6的一些API特性,所以可能会用到polyfill,故更完整的应该这样:

1
2
3
4
5
{
"scripts": {
"test": "mocha --require babel-polyfill --compilers js:babel-register"
}
}

由于我们需要测试lib下的编译后文件,而不是测试源码,所以测试命令还需要修改下让他预先执行编译:

1
2
3
4
5
{
"scripts": {
"test": "npm run build && mocha --compilers js:babel-core/register"
}
}

最新版本已经替换为 babel-register 这个单独的库,且废弃了使用–compiler,转而直接全部使用 --require, 且要设置glob类型的测试目录和文件名咯。所以最终是这样:

1
2
3
4
5
"scripts": {
"build": "babel -d lib src",
"test": "npm run build && mocha --require babel-polyfill --require babel-register \"test/*.js\"",
"prepublish": "npm run build"
},

直接执行mocha,他默认就会测试当前目录下test下的js文件,不会递归往下查找,只测试test下的第一层。递归的话要用 mocha —recursive. 或者按照官方建议使用blob: test/**/*.js

发布

源码管理时,lib目录不需要管理。发布npm包时,src目录不需要发布。所以我们需要使用 .gitignore.npmignore 来完成这件事。

.gitignore的设置如下:

1
2
3
*.log
lib
node_modules

.npmignore的设置如下:

1
src

另外,我们在往npm仓库publish包时,可以基于npm脚本特性实现发布时自动编译:

1
2
3
4
5
6
{
"scripts": {
"prepublish": "npm run build"
}
}
p

Refer

https://cnodejs.org/topic/565c65c4b31692e827fdd00c