タスクランナーとしてのnpm-scripts(パート3)

前回(パート2)の記事では、CSS(PostCSS)とJavaScript(ES6)のビルドタスクと画像の圧縮をまとめて実行するタスク機能の実装まで行いました。

今回パート3では、ファイル変更時にタスクを自動処理させる機能と、ローカルサーバーを立ち上げ、ファイル変更時にブラウザを自動リロードさせる機能を実装します。またCSSとJavaScriptの構文チェック機能も追加したいと思います。

ファイル変更監視

シームレスな開発作業のために、ファイル変更時にこれまで実装したビルドタスクが自動で実行されるようにしましょう。

まず、CSSファイルの変更用に下記のタスクを追加します。

"scripts": {
    "watch:css": "postcss src/css/main.pcss -o dist/css/main.css -w"
},

build:*と同様に、後でまとめて実行するためwatch:cssというタスク名にしておきます。build:cssと異なり、-w(--watch)オプションを追加して監視モードを有効にし、--no-mapオプションを外すことでインラインのソースマップを生成するようにしています。開発中はブラウザの開発ツールでソースマップが確認できます。

次にJavaScriptですが、browserifyには監視モードが無いので、watchifyというツールを導入します。

watchify
ファイル更新時にbrowserifyのビルド処理を実行させるツール。差分のみを処理させることができる。
watchify - npm

watchifyをインストールします。

$ npm i -D watchify

タスクは次のように設定します。

"scripts": {
    "watch:js": "watchify -t babelify src/js/main.js -o 'uglifyjs -c -m > dist/js/main.js' -v"
},

watchifyは出力にパイプをつかうことができないようなので、-o(--output)オプションを使います。-v(--verbose)オプションで処理にかかった時間を計測できます。

画像ファイルの監視には、onchangeを使います。

onchange
ァイルの変更通知を受け取り、コマンドを実行できるツール。
onchange - npm

onchangeをインストールします。

$ npm i -D onchange

タスクは次のように設定します。

"watch:images": "onchange 'src/images' -e '**/*.DS_Store' -- npm run images"

onchangeでは、監視対象のファイルをglobパターンで指定して--の後に実行したいコマンドを記述します。上記コードではsrc/imagesディレクトリが変更された時に、imagesタスクを実行します。OS X環境で生成される.DS_Storeを無視するために、-e(--exclude)オプションを使っています。

BrowserSyncによるローカルサーバー&ブラウザ自動同期

ファイル変更時にビルド処理が自動で行われるようになったので、その処理が完了するタイミングでブラウザをリロードしたり、複数のブラウザ上の表示を同期できるようにしましょう。

定番のBrowserSyncを導入します。

browser-sync
複数環境を自動同期させファイル更新の確認ができるツール。
browser-sync - npm
Browsersync - Time-saving synchronised browser testing

browser-syncをインストールします。

$ npm i -D browser-sync

そしてwatch:serverという名前でタスクを設定します。

"scripts": {
    "watch:server": "browser-sync start -s -f 'dist, **/*.html, !node_modules/**/*'"
},

browser-sync startに続き、-s(--server)オプションで(現在のディレクトリで)ローカルサーバーを立ち上げ、-f(--files)オプションで監視するディレクトリまたはファイルを指定します。今回はdistフォルダ内の全てのアセットファイルと、node_modulesフォルダを除外したすべてのHTMLファイルを監視対象にしました。

前回の記事で紹介したnpm-run-allを使って、全ての監視タスクを一度に実行できるようにしましょう。

"scripts": {
    "watch": "npm-run-all -p watch:*"
},

これでnpm run watchコマンド一発で、変更点を複数環境でチェックしながら開発できるようになりました。

次のようにして、buildタスクとwatchタスクをパッケージインストール時に自動で組み込むようにしておくと便利です。

"scripts": {
    "postinstall": "npm run build && npm run watch"
},

postinstallは、npm install実行後に実行されるコマンドです。

構文チェックツールの導入

可読性や信頼性の高い美しいコードを提供するためには、構文チェックツールは書かせません。構文チェック機能をエディタの拡張機能に組み込んで使うこともあるかもしれませんが、プロジェクトのタスクランナーに組み込むと共同作業において統一されたコードスタイルを保つことができます。

CSSとJavaScriptの構文チェックツールは次のものを使います。

  • stylelint
  • eslint
stylelint
CSSの構文チェックツール。
stylelint - npm
stylelint
eslint
JavaScriptとJSX用の構文チェックツール。
eslint - npm
ESLint - Pluggable JavaScript linter

まとめてインストールします。

$ npm i -D stylelint eslint

CSSの構文チェック

stylelintの設定はpackage.jsonに含めることができますが、異なるプロジェクト間で使いまわせるように、独立した設定ファイルに記述するのがよいでしょう。

.stylelintrcというファイル名で設定ファイルを用意して下さい。JSON形式で設定を書き込むことができます。

プロジェクトに合わせてルールなどを設定します。詳しい説明は公式サイトにあるのでご覧ください。

User guide - stylelint

src/cssフォルダにある.pcssファイルに対して構文チェックを行うタスクを追加します。

"scripts": {
    "lint:css": "stylelint src/css/**/*.pcss"
},

JavaScriptの構文チェック

ESLintの設定ファイルを生成するために、下記コマンドを実行します。

$ ./node_modules/.bin/eslint --init

すると対話形式で初期設定をサポートしてくれます。Googleが共有している代表的な設定を利用することもできます。

? How would you like to configure ESLint? Use a popular style guide
? Which style guide do you want to follow? (Use arrow keys)
❯ Google
  Airbnb
  Standard

最後に生成したい設定ファイルのフォーマットを選びます。

? How would you like to configure ESLint? Use a popular style guide
? Which style guide do you want to follow? Standard
? What format do you want your config file to be in? (Use arrow keys)
❯ JavaScript
  YAML
  JSON

ここではStandard、JavaScriptと選択を進めます。設定完了と共に.eslintrc.jsという設定ファイルが生成され、依存モジュールがインストールされます。

Eslintの設定をさらにカスタマイズする方法はここでは触れませんが、.eslintrc.jsに設定を追加してくことになります。下記リンクに詳しく説明があります。

Configuring ESLint

List of available rules - ESLint

scriptsにタスクを追加しましょう。

"scripts": {
    "lint:js": "eslint src/js/**/*.js"
},

構文チェックのタスクが完成しました。

完成物

完成したサンプルプロジェクトのファイル構成はこのようになりました。

├─ dist
├─ node_modules
├─ src
│   ├─ css
│   │   └─ main.pcss
│   ├─ images
│   └─ js
│         └─ main.js
├─ .eslintrc.js
├─ .stylelintrc
├─ index.html
├─ package-lock.json
├─ package.json
└─ postcss.config.js

package.jsonはこのようになっています。※2017/07/31時点のバージョン

{
  "name": "sample",
  "version": "1.0.0",
  "scripts": {
    "clean": "rimraf dist/{css/*,js/*,images/*}",
    "build:css": "postcss src/css/main.pcss -o dist/css/main.css --no-map",
    "build:js": "mkdirp dist/js && browserify -t babelify src/js/main.js | uglifyjs -c -m > dist/js/main.js",
    "build:images": "imagemin src/images/* -o dist/images",
    "build": "npm run clean & npm-run-all -p build:*",
    "watch:css": "postcss src/css/main.pcss -o dist/css/main.css -w",
    "watch:js": "watchify -t babelify src/js/main.js -o 'uglifyjs -c -m > dist/js/main.js' -v",
    "watch:images": "onchange 'src/images' -e '**/*.DS_Store' -- npm run build:images",
    "watch:server": "browser-sync start -s -f 'dist, **/*.html, !node_modules/**/*'",
    "watch": "npm-run-all -p watch:*",
    "lint:css": "stylelint src/css/**/*.pcss",
    "lint:js": "eslint src/js/**/*.js",
    "postinstall": "npm run build && npm run watch"
  },
  "devDependencies": {
    "babelify": "^7.3.0",
    "browser-sync": "^2.18.13",
    "browserify": "^14.4.0",
    "cssnano": "^3.10.0",
    "eslint": "^4.3.0",
    "eslint-config-standard": "^10.2.1",
    "eslint-plugin-import": "^2.7.0",
    "eslint-plugin-node": "^5.1.1",
    "eslint-plugin-promise": "^3.5.0",
    "eslint-plugin-standard": "^3.0.1",
    "imagemin-cli": "^3.0.0",
    "mkdirp": "^0.5.1",
    "npm-run-all": "^4.0.2",
    "onchange": "^3.2.1",
    "postcss-cli": "^4.1.0",
    "postcss-cssnext": "^3.0.2",
    "postcss-import": "^10.0.0",
    "rimraf": "^2.6.1",
    "stylelint": "^8.0.0",
    "uglify-es": "^3.0.26",
    "watchify": "^3.9.0"
  }
}

3回に分けて、npm-scriptsを使ったタスクランナーの構築について解説しました。npm-scriptsが非常にシンプルで扱いやすく、タスクランナーとして十分な機能を持つツ―ルであることを知っていただけたと思います。

今回作成したサンプルコードは、Githubにて公開しています。