发布模块
编译
上文已提到,为了让使用ES2015编写的代码能在Node.js上正常运行,需要先将其编译成ES5标准的代码,然后还需要在程序入口载入babel-polyfill
模块。
我们可以修改文件package.json
,为其增加compile
命令:
{
"scripts": {
"compile": "babel src -d lib"
}
}
说明:$ babel src -d lib
命令表示lib
目录下的所有文件,并保存到src
目录下。
配置完成后,可以执行$ npm run compile
命令编译试试:
$ npm run compile
> @isnc/es2015_demo@1.0.0 compile /Users/glen/work/tmp/es2015_demo
> babel src -d lib
src/copy.js -> lib/copy.js
src/download.js -> lib/download.js
src/index.js -> lib/index.js
src/utils.js -> lib/utils.js
此时,我们还不能直接载入lib/index.js
文件,因为在此之前需要载入babel-polyfill
模块。编辑文件package.json
,设置模块入口文件:
{
"main": "index.js"
}
说明:使用$ npm init
生成package.json
文件时,main
的默认值即为index.js
,可无需修改。
新建文件index.js
:
require('babel-polyfill');
module.exports = require('./lib').default;
说明:在src/index.js
中download()
函数使用的是export default
输出,所以在Node.js中需要读取模块输出的default
属性。
上文中我们的测试程序是直接载入src
目录下的程序,但模块最终发布的却是编译后的程序,为了避免因babel的Bug而导致编译后的程序与源程序功能有差异,我们的单元测试需要改用编译后的代码。
编辑文件test/test.js
,将引入src
目录的模块:
import download from '../src';
import {randomFilename} from '../src/utils';
改为:
import download from '../';
import {randomFilename} from '../lib/utils';
在编辑package.json
文件,将test
命令改为先执行compile
编译代码后再执行mocha
测试:
{
"scripts": {
"test": "npm run compile && mocha --compilers js:babel-core/register"
}
}
重新执行$ npm test
可看到如下结果:
$ npm test
> es2015_demo@1.0.0 test /private/tmp/es2015_demo
> npm run compile && mocha --compilers js:babel-core/register
> es2015_demo@1.0.0 compile /private/tmp/es2015_demo
> babel src -d lib
src/copy.js -> lib/copy.js
src/download.js -> lib/download.js
src/index.js -> lib/index.js
src/utils.js -> lib/utils.js
es2015_demo
✓ 复制本地文件成功
1 passing (42ms)
发布
在开发项目时,一般都会使用Git这样的源代码版本管理工具。上文例子中,lib
目录的文件是编译生成的,可以不需要纳入到版本管理中。Node.js项目在安装模块时会将其保存到node_modules
目录下,这些内容也是不应该纳入版本管理的。可以添加文件.gitignore
来将其排除:
*.log
node_modules
lib
如果要将模块发布到NPM上,ES2015编写的源程序也是不需要的,可以添加文件.npmignore
来将其排除:
src
在使用$ npm publish
命令发布模块时,可以设置prepublish
命令来让其自动执行编译。编辑文件package.json
,增加以下内容:
{
"scripts": {
"prepublish": "npm run compile"
}
}
现在我们执行$ npm publish
就可以发布模块了:
$ npm publish
> @leizongmin/es2015_demo@1.0.0 prepublish /Users/glen/work/tmp/es2015_demo
> npm run compile
> @leizongmin/es2015_demo@1.0.0 compile /Users/glen/work/tmp/es2015_demo
> babel src -d lib
src/copy.js -> lib/copy.js
src/download.js -> lib/download.js
src/index.js -> lib/index.js
src/utils.js -> lib/utils.js
+ @leizongmin/es2015_demo@1.0.0
优化
上文例子中需要依赖mocha
和babel
两个工具,当我们开发多个项目或将其作为开源项目发布出去时,可能不同的项目所依赖babel
的版本是不一样的,为了开发环境一致,一般我们需要在当前项目中执行其开发时所指定的babel
版本。
首先执行以下命令安装babel-cli
和mocha
:
$ npm i babel-cli mocha --save-dev
安装完成后,对于上文中使用的babel
和mocha
命令,可以使用./node_modules/.bin/babel
和./node_modules/.bin/mocha
来执行。编辑package.json
文件,更改compile
和test
命令:
{
"scripts": {
"compile": "babel src -d lib",
"test": "npm run compile && mocha --compilers js:babel-core/register"
}
}
说明:在package.json
文件的scripts
里面,要执行的命令如果是在./node_modules/.bin
目录内,可以省略./node_modules/.bin/
前缀,比如./node_modules/.bin/mocha
可以简写为
mocha
。
本文示例模块输出的download()
函数使用的是Promise的异步模式,对于习惯使用callback模式的用户,我们也可以通过简单的修改来使其支持callback模式。
编辑文件src/utils.js
,增加callbackify()
函数:
export function callbackify(fn) {
let argc = fn.length;
return (...args) => {
let callback = args[argc];
if (typeof callback !== 'function') callback = null;
return fn(...args)
.then(ret => {
callback && callback(null, ret);
return Promise.resolve(ret);
})
.catch(err => {
callback && callback(err);
return Promise.reject(err);
});
}
}
编辑文件src/index.js
,将其改为以下内容:
import path from 'path';
import mkdirp from 'mkdirp';
import copyFile from './copy';
import downloadFile from './download';
import {randomFilename, isURL, noop, callbackify} from './utils';
export default callbackify(function download(source, target, progress) {
target = target || randomFilename(download.tmpDir);
progress = progress || noop;
return new Promise((resolve, reject) => {
mkdirp(path.dirname(target), err => {
if (err) return callback(err);
resolve((isURL(source) ? downloadFile : copyFile)
(source, target, progress));
});
});
});
说明:callbackify()
函数的作用是返回一个新的函数,这个函数可以支持原函数的Promise模式,同时支持callback模式。
现在再给test/test.js
增加一个测试用例:
it('复制本地文件成功 callback', done => {
let source = __filename;
let target = randomFilename();
let onProgress = false;
download(source, target, (size, total) => {
onProgress = true;
assert.equal(size, total);
assert.equal(total, getFileSize(source));
}, (err, filename) => {
assert.equal(err, null);
assert.equal(onProgress, true);
assert.equal(target, filename);
assert.equal(readFile(source), readFile(target));
done();
});
});
如无意外,重新执行$ npm test
是可以测试通过的。