脱webpack : CLI編

## はじめに

現在フロントエンド開発環境のうち、一部のwebpack依存処理をnode.jsスクリプトに置き換えています。
前回の記事ではejsの変換処理をnode.jsスクリプトに移行しました。

[脱webpack : ejs編](https://qiita.com/masato_makino/items/64809aac9cfb2e739133)

今回は簡単な処理を肩代わりできるCLIモジュールをご紹介します。

## 皆さまへのお願い

**「このモジュールは絶対便利！覚えておけ！損はないから！」**
という一押しモジュールがあったらぜひコメント欄からお知らせください。

## nodeモジュールのインターフェイス

nodeモジュールをユーザーが利用する場合、インターフェイスはAPI経由かCLI経由のどちらかになります。1つのモジュールで両方のインターフェイスを用意しているものもあります。各モジュールがどのインターフェイスを備えているかはREADMEに記述してあります。

### API

APIはモジュールをjavascriptから呼び出すインターフェイスです。まずはpackage.jsonでモジュールを読み込みます。

*▼package.json*

```json
"devDependencies": {
  "glob": "^7.1.2",
},
"scripts": {
  "task": "node task.js",
}
```

npm-scriptsからは`node`コマンドでjsファイルを指定して実行します。`task.js`というファイル名は解説のためにつけたもので、ファイル名は自由に設定できます。

次にpackage.jsonと同じ階層にnpm-scriptsで指定したjsファイルを用意します。

*▼task.js*

```js
"use strict";
const glob = require("glob");
glob(...);
```

jsファイル内でモジュールを`require`で読み込み、初期化したり関数を呼び出すことで機能を利用できます。

### CLI

CLIはターミナルやnpm-scriptsからモジュールを呼び出すインターフェイスです。実行時に引数を与えることでモジュールを操作します。

*▼package.json*

```json
"devDependencies": {
  "cpx": "^1.5.0"
},
"scripts": {
  "copy": "cpx 'src/**/*.{mp4,webm,ico}' ./dist"
}
```

上記の例の場合は、`cpx`というモジュールをnpm-scriptsから直接呼び出しています。引数は`'src/**/*.{mp4,webm,ico}'`と`./dist`の2つを与えています。
別途jsファイルを用意する必要がないので、ビルドタスクの記述を簡潔にできるのが利点です。

## モジュール紹介

本記事ではフロントエンド開発に利用できるCLIモジュールをご紹介します。webpackやgulpを利用しなくても、簡単なタスクならCLIモジュールとnpm-scriptsで代用が可能です。

### cpx

[cpx](https://github.com/mysticatea/cpx)は指定されたファイルを、ディレクトリ構造を保ったまま出力先にコピーします。webpackで[copy-webpack-plugin](https://github.com/webpack-contrib/copy-webpack-plugin)を利用している方は、乗り換え先として検討対象になります。

*▼package.json*

```json
"scripts": {
  "copy:asset": "cpx 'src/**/*.{mp4,webm,ico}' ./dist -w"
}
```

上記のコマンドで

- src配下のmp4,webm,icoのファイルを、dist以下にディレクトリを維持したままコピー。
- 各ファイルに更新があった場合は自動コピー。

となります。

### onchange

[onchange](https://github.com/Qard/onchange)はファイル更新監視モジュールです。webpackでは`--watch`オプションが存在しますが、このオプションが存在しないモジュールを利用するときにonchangeモジュールを併用します。

*▼package.json*

```json
"scripts": {
  "watch:ejs": "onchange './src/**/*.ejs' -- npm run ejs"
}
```

上記の例だと「src配下のejsファイルに更新があった場合『npm run ejs』を実行する」となります。

nodeモジュールは単一目的単機能であることを理想とします。各モジュールは単機能を担当することで、肥大化しすぎてメンテナンスが難しくなったり重複した機能を複数のモジュールで個別に開発してしまうことを避けています。

そのためモジュールは単体ではなく、複数組み合わせて使うことを前提としています。このonchangeモジュールも監視機能のない他のモジュールと併用すると利便性が大きく向上します。

### rimraf / del

[rimraf](https://www.npmjs.com/package/rimraf)はディレクトリを削除するモジュールです。[clean-webpack-plugin](https://github.com/johnagan/clean-webpack-plugin)の乗り換え先として検討対象になります。

*▼package.json*

```json
"scripts": {
  "clean": "rimraf dist"
}
```

上記の例では、プロジェクトルート直下のdistというフォルダーを内容ごと削除します。
rimrafは基本的に単独の対象を削除します。

一方[del-cli](https://github.com/sindresorhus/del-cli)はワイルドカードで削除対象を指定できます。

*▼package.json*

```json
"scripts": {
  "del" : "del 'public/assets/**', '!public/assets/goat.png'"
}
```

上記のコマンドを実行すると

- public/assets/以下をすべて削除する。
- ただしpublic/assets/goat.pngは除く。

という処理になります。どのようなビルドタスクを組み上げるかによって使い分けてください。

### npm-run-all

[npm-run-all](https://github.com/mysticatea/npm-run-all)は複数のnpm-scriptsを直列、並列で実行するためのモジュールです。

*▼package.json*

```json
"scripts": {
  "watch": "npm-run-all -p watch:*",
  "watch:server": "...",
  "watch:sass": "...",
  "watch:autoprefixer": "...",
  "watch:ejs": "...",
  "watch:javascript": "..."
}
```

上記の場合、`watch`を実行すると以下の`watch:*`がすべて並列で実行されます。

npm-scriptsでも、標準の機能で複数のスクリプトを直列、並列で実行できます。たとえば

*▼package.json*

```json
"scripts": {
  "watch": "npm run watch:server && npm run watch:sass",
  "watch:server": "...",
  "watch:sass": "..."
}
```

と記述した場合、`watch:server`と`watch:sass`が直列で実行されます。

npm-run-allの利点として、スクリプトのワイルドカード指定があります。[npm-run-all](https://github.com/mysticatea/npm-run-all)のREADMEから例を引用すると、以下のように記述ができます。

> Before: npm run clean && npm run build:css && npm run build:js && npm run build:html
> After: npm-run-all clean build:\*

スクリプト名を大量に書く必要がなくなるので、記述が簡潔になりpackage.jsonの管理が楽になります。

### husky

[husky](https://github.com/typicode/husky)はgitの各種イベントにフックする処理を簡単に記述できるようにするモジュールです。CLIのモジュールの記事でご紹介するのには少し種類が違うような気もしますが、とても便利なモジュールです。

*▼package.json*

```json
"scripts": {
  ...
},
"husky": {
  "hooks": {
    "pre-commit": "npm run prettyQuick && npm run secrets"
  }
},
```

このようにhuskyではpackage.jsonに`husky`オブジェクトを追加して動作を設定します。上記の例では、gitでcommitする直前に指定されたnpmスクリプトを実行しています。SourceTreeなどのgitクライアントを利用しても問題なくフックします。

各種lintと組み合わせることでコミットされるコードを常にクリーンな状態に保てます。

### pretty-quick

[pretty-quick](https://github.com/azz/pretty-quick)はPrettierのコード整形を、gitのstage上のファイルに行うモジュールです。上記のhuskyと併用することで、常にコードフォーマット済みのファイルがコミットされるようになります。

*▼package.json*

```json
"scripts": {
  "prettyQuick": "pretty-quick --staged"
}
```

## おわりに

nodeモジュールとnpm-scriptsを利用することで、webpackやタスクランナーが担当していた処理のかなりの部分が肩代わりできます。すべてをnpm-scriptsで処理しようとすると、今度はpackage.jsonの肥大化という別の問題が発生してしまいます。適度に担当範囲を切り分けて利用するのがベストです。

また、皆様の
**「このモジュールは使っとけ！使わないと損するから！」**
情報をお待ちしております。

以上、ありがとうございました。