发布模块

编译

上文已提到,为了让使用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.jsdownload()函数使用的是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

优化

上文例子中需要依赖mochababel两个工具,当我们开发多个项目或将其作为开源项目发布出去时,可能不同的项目所依赖babel的版本是不一样的,为了开发环境一致,一般我们需要在当前项目中执行其开发时所指定的babel版本。

首先执行以下命令安装babel-climocha

$ npm i babel-cli mocha --save-dev

安装完成后,对于上文中使用的babelmocha命令,可以使用./node_modules/.bin/babel./node_modules/.bin/mocha来执行。编辑package.json文件,更改compiletest命令:

{
  "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是可以测试通过的。

results matching ""

    No results matching ""