开发一个文件下载的node模块
node 模块也就是 node 的包,常用来被其他 node 程序调用来完成一些功能。 例如 shelljs 这个模块,可以用来实现 shell 命令的执行,例如 request 这个模块可以帮助你进行 http 请求的处理,比如 cheerio 这个包可以用来实现非浏览器环境的 jquery 风格 dom 查询。
今天,我打算开发一款 node 模块,它提供了一个 API,可以帮助别人进行文件下载并提供下载进度信息。最终代码在这里: https://github.com/cuiyongjian/fetch-file
脚手架类型
我们开发 js 项目,目前来看一般有 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 | src; |
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 | { |
当然,别忘记 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 | { |
在根目录下的 index.js 中,使用 require('babel-polyfill')
加载 ES 新标准的语言 API 垫片。
package.json 配置
1 | { |
实战开始
基于 lime-cli 生成一个 nodelib-template 类型的项目骨架,然后再开始。
首先请确保你安装了我写的 lime-cli 脚手架生成工具。如果没有,请执行:
1 | npm i -g lime-cli |
然后安装nodelib-template这个脚手架。
1 | lime new nodelib-template |
安装完成之后,再执行 npm i
来安装其各种依赖和开发依赖。
1 | npm install |
测试
我们使用抹茶 mocha 进行测试代码。
为了让测试代码可以基于 ES6 编写,我们可以利用 mocha 的 compiler 特性,给 mocha 执行测试代码的时候加入一个 babel 预编译器。
通过babel 官网教程 我们知道,需要使用 babel-register 作为 mocha 的 compiler。
1 | npm install --save-dev mocha |
配置 mocha 脚本:
1 | { |
我们都知道 babel-register 的作用,其实 mocha 也是用它来提供了加载 js 代码(require(xxx)
)时候的 ES6 支持。
由于你可能在测试代码中用来 ES6 的一些 API 特性,所以可能会用到 polyfill,故更完整的应该这样:
1 | { |
由于我们需要测试 lib 下的编译后文件,而不是测试源码,所以测试命令还需要修改下让他预先执行编译:
1 | { |
最新版本已经替换为 babel-register 这个单独的库,且废弃了使用–compiler,转而直接全部使用 --require
, 且要设置 glob 类型的测试目录和文件名咯。所以最终是这样:
1 | "scripts": { |
直接执行 mocha,他默认就会测试当前目录下 test 下的 js 文件,不会递归往下查找,只测试 test 下的第一层。递归的话要用 mocha —recursive
. 或者按照官方建议使用 blob: test/**/*.js
发布
源码管理时,lib 目录不需要管理。发布 npm 包时,src 目录不需要发布。所以我们需要使用 .gitignore
和 .npmignore
来完成这件事。
.gitignore 的设置如下:
1 | *.log |
.npmignore 的设置如下:
1 | src |
另外,我们在往 npm 仓库 publish 包时,可以基于 npm 脚本特性实现发布时自动编译:
1 | { |