[WIP] npm-scripts を使い倒そう!

  • 92
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

※ 書きかけです

パッケージ依存管理ツール npm

npm は、今や Web 開発には欠かすことのできないパッケージ依存管理ツールです。
Browserify, Babel, Gulp, LESS のようなツール、Koa, Express のようなサーバー側のライブラリ、 React, AngularJS のようなクライアント側のライブラリまで揃っています。
サーバー側はもちろん、クライアント側の依存管理も Browserify を併用することでスマートに実現できます。

そんな npm ですが、実はタスク ランナーの機能も持ち合わせています。
Grunt や Gulp の影に隠れて目立たない存在でしたが、Substack の記事からこっち、注目を集め始めています (たぶん)。

タスク ランナー npm

タスク ランナーといえば、Web 界隈では Grunt や Gulp が有名です。

これらは環境構築・ビルド・デプロイ等、目的に応じた定形処理(タスク)を登録しておき、コマンドひとつで実行できるようにしておくツールです。
それに加えて、ツールたちはタスクを定義しやすくする様々な工夫を競っています。

さて、タスク ランナーとしての npm はとてもシンプルです。
package.json (パッケージの情報や依存関係が書かれるファイル) の scripts フィールドに、タスク名とコマンドのペアを記述します。

package.json
{
    "scripts": {
        "hello": "echo \"Hello World!\""
    }
}

定義したタスクはnpm run タスク名で実行できます。

shell
$ npm run hello

> echo "Hello World!"
Hello World!

コマンドの内容は単なる Shell です。
Linux では /bin/sh, Windows では cmd.exe で実行されます。
npm ではたくさんの小さなコマンドライン プログラムが公開されているので、それらを活用して書いていきます。
パッケージ管理ツールとしての側面を活かしたシンプルな作り、というわけですね。

具体例

こんなにシンプルで、果たして使えるのか?

もちろん、プロダクトの方針にも依ると思いますが、私が試してみた感じでは、じゅうぶんに使えると思えました。
いくつか例を見て行きましょう。

※ 以下で紹介するスクリプト群は、基本的に Windows で動作確認しています。
  単発テストに関しては Travis CI 上の Linux でも動作確認しています。

ツールやサーバー側ライブラリの開発

package.json
{
  "scripts": {
    "clean": "rimraf lib",
    "lint": "eslint src",

    "build": "npm-run-all clean lint build:babel",
    "build:babel": "babel src --out-dir lib",

    "test": "npm-run-all build test:mocha",
    "test:mocha": "mocha test/*.js --compilers js:espower-babel/guess --colors",

    "testing": "npm-run-all clean --parallel testing:*",
    "testing:build": "npm run build:babel -- --watch --source-maps-inline",
    "testing:mocha": "npm run test:mocha -- --watch --growl",
  }
}
shell
$ npm i --save-dev babel eslint espower-babel mocha npm-run-all power-assert rimraf

このスクリプト構成例は、clean, lint, build, test, testing タスクを定義します。

  • clean -- ビルド結果のディレクトリを削除します。
  • lint -- ESLint を用いて、ソースコードを静的検証します。
  • build -- Babel を用いて、ES2015 で書かれたソースコードを ES5 にトランスパイルします (その直前に clean, lint を実行します)。
  • test -- Mocha + PowerAssert を用いた自動テストを実行します (その直前に build を実行します)。
  • testing -- test をウォッチ モードで実行します。コードを編集して保存すると即座にビルドされ、テストが再実行され、結果がデスクトップ通知で報告されます。
    • Windows では Growl for Windows を予めインストールしておく必要があります。
    • インストールしなくても、テスト自体は動作します。

もちろん、依存パッケージを --save-dev で保存しておくことで、他の環境では npm i コマンド一発ですべてダウンロードされて使えるようになります。

クライアント側ライブラリの開発

package.json
{
  "scripts": {
    "clean": "rimraf lib dist",
    "lint": "eslint src",

    "build": "npm-run-all clean lint build:*",
    "build:lib": "babel src --out-dir lib --source-maps-inline",
    "build:dist": "mkdirp dist && browserify lib/index.js --debug -t babelify --standalone __LIBRARY_NAME__ > dist/__LIBRARY_NAME__.js",
    "build:dist-min": "mkdirp dist && browserify lib/index.js -t babelify --standalone __LIBRARY_NAME__ | uglifyjs - --compress --mangle > dist/__LIBRARY_NAME__.min.js",

    "test": "npm-run-all clean lint build:lib test:karma",
    "test:karma": "karma start karma.conf.js --single-run",

    "testing": "npm-run-all clean --parallel testing:*",
    "testing:build": "babel src --out-dir lib --watch",
    "testing:karma": "karma start karma.conf.js --auto-watch --reporters growl,progress"
  }
}
karma.conf.js
module.exports = function(config) {
  config.set({
    basePath: "",
    frameworks: ["browserify", "mocha"],
    files: [
      "node_modules/babel/node_modules/babel-core/browser-polyfill.js",
      "test/*.js"
    ],
    browsers: ["Chrome", "Firefox", "IE"],

    preprocessors: {
      "test/*.js": ["browserify"]
    },
    browserify: {
      debug: true,
      extensions: [".js"],
      transform: [
        "babelify",
        "espowerify"
      ]
    }
  });
};
shell
$ npm i --save-dev babel babelify browserify eslint espowerify karma karma-browserify karma-chrome-launcher karma-firefox-launcher karma-ie-launcher karma-mocha mkdirp mocha npm-run-all power-assert rimraf uglify-js

__LIBRARY_NAME__ には、実際の名前を入れてください。

一気に長くなってしまいました。
だいたい BrowserifyKarma のせい。

このスクリプト構成例は、clean, lint, build, test, testing タスクを定義します (前項と同じです)。

  • clean -- ビルド結果のディレクトリを削除します。
  • lint -- ESLint を用いて、ソースコードを静的検証します。
  • build -- この工程は少し複雑です。
    • ふたつのディレクトリ lib, dist を生成します。
    • lib には、Babel を利用して、ライブラリ利用者が Browserify で依存管理をする場合のための成果物を生成します。
    • dist には、Browserify, Babel, UglifyJS を利用して、ブラウザから直接読み込んだり、RequireJS で依存管理をする場合のための成果物を生成します。
  • test -- Karma + Mocha + PowerAssert を用いた自動テストを実行します (その直前に build:lib を実行します)。
  • testing -- test をウォッチ モードで実行します。コードを編集して保存すると即座にビルドされ、テストが再実行され、結果がデスクトップ通知で報告されます。

TODO

  • Travis CI でテストできない
  • testling 試したい
  • 他のランナー (TestemとかProtractorとか) も試したい
  • Isomophicのテスト (ブラウザと Node で同時実行するランナー無いか?)

Webアプリケーション開発

TODO

検証中です。
上記2つの内容と大きくは変わらず、cpx を利用した単純コピーと [node-dev][node-dev] あたりを利用したサーバー実行が入ると予測。
あと API テストの方針とか...

まとめ

TODO

  • ツールはまず CLI を持っている事が多いので、他のタスク ランナーと比較して、専用ラッパーへの依存を減らせる場合がある。
  • npm で単一責務の小さなツールを探して or 作って公開して、パズルのように組み合わせて使うのは楽しい。一部だけ交換するのが楽なので、進化ペースを上昇させられる。
  • Shell は stdin/out による Streams ベースのプラットフォームで、Gulp に似ている。

[おまけ] この記事に登場したパッケージ紹介

npm 一般

  • npm-run-all -- 拙作。複数の npm-scripts を順番 or 並列に実行する CLI.

ファイル操作系

  • mkdirp -- Substack (Browserifyのオーナー) 作。クロスプラットフォームの mkdir -p. ディレクトリが既存の場合は何もしない。
  • rimraf -- Isaacs (npmのオーナー) 作。クロスプラットフォームの rm -rf.
  • cpx -- 拙作。変更監視できる cp コマンド。Glob記法で指定したファイルの変更監視をして、変更時に指定ディレクトリへコピーする (ディレクトリ ツリー構造を保ったまま)。Browserify 互換の変換モジュールを噛ませられる。
  • catw -- Substack 作。変更監視できる cat コマンド。Browserify 互換の変換モジュールを噛ませられる。
  • watch -- Isaacs 作。指定ディレクトリでファイル更新があった際に任意の Shell コマンドを実行させられる。

JavaScript 変換系

  • babel -- 超人 sebmck 作。EcmaScript の標準委員会で検討されている新仕様を ES5 で動くコードに変換してくれるツール。
  • Browserify -- Substack 作。Node.js の require の Lookup の仕様をフロントエンドに持ち込めるツール。
  • UglifyJS -- 有名なミニファイ ツール。未使用コードの削除も行ってくれる (重要)。

テスト系

  • ESLint -- 静的検証ツール。ESTree 仕様に則った抽象構文木を対象とした、さまざまな検証ルールをプラグインできる。組み込みのルールもきめ細やかな設定が可能。ES2015 や JSX にも対応している。
  • Karma -- テストランナー。複数のブラウザを立ち上げてその中でテストを実行し、結果をコンソールに出力してくれる。
  • Mocha -- 人気のテストハーネス。
  • PowerAssert -- twada 作。アサーション ライブラリ。事前の変換によって、アサート式が失敗した時に詳細な情報を出力してくれる。便利。
  • espower-babel -- azu 作。Mocha プラグイン。テストコードに対して Babel + PowerAssert の変換をしてくれる。
  • espowerify -- twada 作。Browserify の変換モジュール。PowerAssert の変換をしてくれる。