Edited at

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

More than 3 years have passed since last update.

※ 書きかけです


パッケージ依存管理ツール 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 の変換をしてくれる。