gulpやめました (やっと)。
2020.03.13 更新
以下gulpをやめて、自作のタスクランナー的なものを作った話です。結構長いです。
まず最初にリポジトリへのリンクを載せておきます。
そもそもなぜずっとgulpを使っていたか
Nuxtやcreate react appのようにモダンな環境がある中でそういうものが使えないプロジェクトが多々あるので、割とどんなプロジェクトにも対応できるような汎用的な環境をつくってずっと運用していました。
具体的には以下のような構成です。(一部機能省略)
・ソースディレクトリからgulpのタスクを通して公開ディレクトリに展開
・cleanタスクで公開ディレクトリ内のファイルを消去
・htmlは単純に公開ディレクトリにコピー
・pugはコンパイルして展開
・cssはautoprefixer、minify (options)して展開
・sass/scssはコンパイル → autoprefixer、minifyして展開
・jsはwebpackを通してコンパイルして展開
・それ以外のファイルはそのままコピーして公開ディレクトリに展開 (copyタスク)
・defaultタスクは clean → html, pug, css, sass, js, copyを実行
・watchタスクで更新を監視 → タスク実行 + local server起動
なぜgulpをやめようと思ったか
gulpのタスクは多くのgulpプラグインをインストールすることで成り立っているのですが、それらのgulpプラグインの多くがあまりメンテナンスされておらず、今後だんだん使えなくなると言われているので、そろそろ何かしら対応しないとなと思っていました。
「gulpプラグインの多くがメンテナンスされていない」というのはちゃんと調べたわけではなく、Twitterなどがソースです。まだそこまで実感がないですが、そういう情報が多くなってきたってことは、いずれはそうなるなと思い、今回実行に踏み切りました。
じゃあ全部webpackでやれないか?
今までjsのコンパイルはwebpackを使用していました。正確にはgulp経由でwebpackのタスクを実行していました。
webpackではpugなどsassなどもコンパイルできそうな雰囲気があったので、それならばいっそwebpackだけで完結できないかと思いましたが、いろいろ検証したやめました。webpack-dev-serverもあるし。
webpackはSPA (HTMLもbody内にタグなし、CSS in JSもしくはCSSは1つ、jsファイルも一つのような)を作る場合それだけですべて完結できそうな気がしましたが、「HTMLもページごとに物理的に存在、それに合わせてjsファイルやCSSも複数存在する」ようなWebページをwebpackだけで完結させようとすると、どうしても「ムリヤリ感」が強くてスマートではないなと思ってしまいました。
以下の記事は結構なるほどな感があって参考にしましたが、やはりいろいろ悩んだ結果やめました。
記事のリンクやら自分で書いたメモやらがEvernoteに残されていましたが、 最後に「webpackで全部やるのはだめだ。」と書き残されていました (覚えてない)。
npm-scripts?
「タスクランナーをやめて、npm-scriptsでビルドしよう」的な記事は良く見かけましたが、どれも簡易的というか、結局package.jsonの中に
"scripts": {
"sass": "node-sass ./src/scss --output ./dist/css --output-style compressed"
}
みたいな感じで記述されていることが多く、今まで自分が使っていた汎用的な環境を見通しよく構築するにはもっと調べる必要があるなと思いました。
要は、ソースディレクトリ、出力ディレクトリ、ビルドオプションなどは設定ファイルにまとめつつ共通化し、npm-scriptsの記述はシンプルにできないかなと。
ローカルnpmパッケージを自作
npmパッケージ化せずに、例えば
"scripts": {
"aaa": "./bbb.js"
}
のように直接ビルド処理が書かれたjsファイル作ってnpm-scriptsから実行しても良かったのですが、いい機会だと思ったのでnpmパッケージ化しました。ただし、公開して不特定多数に使ってもらうようなものでなく、オレオレ開発環境なので、ローカルパッケージとして作りました。
ローカルnpmパッケージは package.json に以下のようにディレクトリパスを指定すれば、npm install 時に node_modules ディレクトリにローカルパッケージへのシンボリックリンクを貼ってくれます。
devDependencies: {
"packageName": "file:path/to/local_modules/"
}
2020.03.13 追記
ローカルnpmパッケージはあんまり意味ないのでやめました。
"scripts": {
"start": "node ./dev/utr/utr-cli.js start --env development"
}
上記のように直接 node コマンドでファイルを指定して実行しています。
作ったものは「gulpっぽいもの」
結局どんなものを作ったのかをざっくりいうと、「タスク設定ファイルに定義されたタスクを実行する機能」です。つまりgulpにかなり似せてます。glupも gulpfile.js に定義されたタスクを実行するものでした。
↓最初に載せていますがリポジトリへのリンクです。
※ クロスプラットフォームで動作するように作ったつもりですが、まだMacOSでしか検証できていません。
上記リポジトリには、開発したタスクランナー、タスク設定、サンプルソースファイルなど、開発環境一式が含まれています。ディレクトリ構成は以下の通りです。 (2020.03.13 修正済み)
.
├── dev/ # 開発環境関連のファイルを格納するディレクトリ
│ │
│ ├── utr/ # utr設定関連のファイルを格納するディレクトリ
│ │ │
│ │ ├── lib.js # task.jsで使用している各種処理
│ │ │
│ │ ├── task.js # タスク設定ファイル
│ │ │
│ │ ├── utr-cli.js # utrをラップしたCLI
│ │ │
│ │ └── utr.js # タスクランナー本体
│ │
│ ├── webpack/ # webpack周りの設定ファイル等
│ │
│ ├── config.js # 各種設定が書かれた設定ファイル
│ │
│ └── meta.js # pugコンパイル時に読み込んで変数として展開するデータ
│
│
├── htdocs/ # 公開ディレクトリ
│
├── src/ # ソースファイル格納ディレクトリ
│
├── package.json # package.json
│
└── tsconfig.json # TypeScript設定ファイル
タスクの実態はタスク設定ファイル (dev/utr/task.js)に記述されており、今回作ったタスクランナー自体の機能は非常に少ないです。
dev/utr/utr.js は
タスクランナーの本体です。utr (unshift task runner)と名付けました。
・タスク設定を読み込み
・1つのタスクを実行
・複数のタスクを並列に実行
・複数のタスクを直列に実行
・ログ出力機能
・ローカルサーバー (BrowserSync)機能
dev/utr/utr-cli.js は
コマンドラインから上記のutrをラップし、オプション付きで実行できるutr-cliです。npm-scriptsからこのCLIを通して、実行モード(development or production)を指定して実行します。
CLIをつくるにあたり、オプションの取得やコマンドの定義などが簡単にできる「commander」というものを使用しています。
package.jsonを見ていただけるとわかると思いますが、実行環境(env)を指定してutrを実行しています。 (2020.03.13 修正済み)
"scripts": {
"initProj": "node ./dev/utr/utr-cli.js initProj",
"clean": "node ./dev/utr/utr-cli.js clean",
"start": "node ./dev/utr/utr-cli.js start --env development",
"build": "node ./dev/utr/utr-cli.js build --env development",
"build:prd": "node ./dev/utr/utr-cli.js build --env production",
"watch": "node ./dev/utr/utr-cli.js watch --env development",
"watch:prd": "node ./dev/utr/utr-cli.js watch --env production",
"dev": "yarn start"
},
utr-cli.js を起点として、タスクを実行できる仕組みができたので、実際にタスクも組んでみました。今回はタスク構築にかなり時間を取られました。
タスクの詳しい内容についてはリポジトリのREADMEに書いておいたので、興味ある方はご覧ください。
・・・
続いて、タスクを組んでいく上で困ったことがいくつかあったので挙げてみます。
困ったこと1:
webpackのwatchを走らせたときに、ビルドが走る
webpack.watchですが、ファイルの監視だけを始めてくれればいいのに、最初にビルドが走ってしまいます。これで困っている人がいるらしく、ビルドを走らせないオプションを付けてくれとは言っているようですが、実装されていません。
困ったこと1の解決方法: あきらめる
機能が提供されていないのであきらめました。
自分が定義したbuildタスクでは
・公開ディレクトリ内のファイルを削除
・pugコンパイル
・htmlコピー
・css/sass/scssコンパイル
・webpackによるjsコンパイル
・その他ファイルをコピー
というタスクなので、npm startをした際は build → watchというタスクにしようと思いました。しかし、上記仕様があるので、buildタスクから「webpackによるjsコンパイル」を除いたものを実行し、そのあとwatchタスクを実行すれば、無駄なjsコンパイルがなくなりました。
ただし、watchタスクだけを実行するとjsのコンパイルが走ってしまいます。これはもうしょうがない。あきらめました。
困ったこと2:
pug-inheritanceでエラーが出る
pugの依存関係を取得してくれるpug-inheritanceですが、Vue.jsで使用するような「: (コロン)」から始まる属性値だとエラーになるようです。たとえば
transition(name="fuga" :duration="hoge")
というやつ。属性値を「" (ダブルクォーテーション)」で囲めばとりあえずは大丈夫だけど、気持ち悪い。
transition(name="fuga" ":duration"="hoge")
困ったこと2の解決方法:
Yarnのselective dependency resolutionsを使う
pug-inheritanceが依存するpug-dependencyが依存するpug-lexerがエラーを吐いている事がわかりました。どうやらpug-lexerが古いらしい。試しに手動で少し上のバージョンで上書きして試してみたら、動く。
さて、階層の深い依存パッケージのバージョンを指定してインストールってできるのかなぁと調べていたら、どうやらYarnでは標準機能であるらしい。
というわけでpackage.jsonに以下を追記。正確にはpug-inheritanceが依存するpug-dependencyが依存するpug-lexerのバージョンを指定。
"resolutions": {
"pug-inheritance/pug-dependency/pug-lexer": "3.1.0"
}
ただしこれは、応急処置でしかないのです。
pug-depencencyがアップデートされたら動かなくなる恐れがあるので、pug-dependencyにプルリクを出したほうが良いかもしれない。でも更新がずっとないのでこうしました。
npmにも似たような機能を提供しているモジュールがあるけど、柔軟に階層を指定してできるのかは不明。未検証。
Nodeインストール時にYarnもインストール
じゃあとりあえずYarnを導入しようってことでこのあたりも導入。具体的には、Node.jsをインストールしたときに、グローバルに最新のYarnも入れてしまおうということです。
ついでに、Node.jsの複数バージョン管理に使っていたndenvがどうやら古いとのことで、nodenvに乗り換えました。もともとanyenvを使用してたので乗り換えは簡単でした。
困ったこと3: 本末転倒になっている
ここまでちゃんと読んでくれた方はうすうす気づいているかと思いますが、「gulpプラグインがメンテされていないからgulpやめる」とか言っているくせに更新日が3年前のパッケージを使ったりしてます。
困ったこと3の解決方法:
いい勉強になったので良しとする
そんなこと言ってたら他人が作ったnpmパッケージを使ってる時点でそういう問題をはらんでいるのと、gulpのブラックボックス的な部分を明示的に自分で組んでいるのでメンテしやすくなったので結果オーライです。
感想 「gulpはすごい」
プラグインで拡張可能にし、タスクの実態は使う人に委ねる、というフレームワークであるgulpはすごいなと、改めて思いました。個人的には全然現役で使えると思います。まだまだ使っている人もたくさんいるはずですし。
・・・
というわけで、gulpやめたのに、結局自分でgulpの簡易版みたいなのを作ってしまった、というお話でした。誰かの役に立つことを祈ります。
今回の調査・検証で得るものは多かったです。しばらくはコレを運用していこうかと思います。