Webpackerを導入してから外すまでをふりかえる

こんにちは、@mugi_unoです。

MisocaでjQuery製のフロントエンドコードを書き換え続けていた結果、技術書典6に参加することになりました。現在必死で書いております。

farewell_webpacker

さて、先日とあるブランチがmasterにマージされ、リリースされました。

f:id:mugi1:20190213111057p:plain

farewell : ごきげんよう!、さらば!
farewellの意味・使い方・読み方 | Weblio英和辞書

farewell_webpacker です。

長い間フロントエンドのビルドにはRailsのGemであるWebpackerを使ってきましたが、現在は完全に依存を外しており、純粋なwebpackビルドを行う形に書き換えました。

正直なところ、フロントエンド界隈からは否定的な意見を聞くことも多いWebpackerですが、実際にある程度の期間プロダクションで利用してみて、良いところも辛いところも両方感じることができたので、今回はそのあたりをご紹介したいと思います。

Webpacker導入に至った経緯

Webpackerを導入した際にもこのブログに記事を書いています。

tech.misoca.jp

当時は次のような課題を抱えていました。

  • browserify-railsによるビルドが致命的に遅い
  • ほぼnpmによるパッケージ管理が行われていない
  • アセットパイプラインを使うものと使わないものが混在

とはいえ、それとは別にコードベースにも

  • jQueryべったり
  • Vueが0.12
  • テストコードが存在しない

といった問題を抱えていて、フロントエンドが絡む機能開発に支障が出ていました。 そのため「まずは手っ取り早く基盤周りをいい感じにしたい」という考えから、導入コストの低かったWebpackerを採用することにしました。

🤝 Webpackerのよかったところ

初期導入の敷居が低い

Railsとフロントエンドのビルド周りを組み合わせるのはそれなりに手間です。また、Misocaの場合はフロントエンド専任のエンジニアがいるわけではなく、基本的には誰でもバックエンド・フロントエンドを通してコードを書きます。逆に言うと、フロントエンドのみに全労力を注ぐことが難しいため、シンプルで理解しやすいものが望ましいと考えました。

フロントエンドに限った話ではありませんが、環境構築を行う際には「その後にちゃんとメンテナンスしていけるか?」も重要な観点です。流行っている・使いたい、といった理由だけで導入すると後で痛い目を見る可能性もあります。(流行っている技術を使わないほうがいいということではありません。私も誰も使ってないようなイケてるフレームワークとかガンガン突っ込みたい人です。)

Webpackerが理解しやすいかと言われると正直思うところはあるのですが、少なくともGem側で道を示してくれており「レールに乗って導入しています」というのは情報共有の面では大きいメリットがあります。

また、Webpackerが提供するインストールタスクを利用することで各種設定もすぐに完了するため、「とにかくwebpackを利用したビルド環境をサクっと整えて他の作業に注力したい」という場合には大いに役立ちます。

レールに乗っている限りはコードに集中できる

Webpackerの提供するレールに乗っている限りはフロントエンド側はコードを書くことだけに集中できます。 マイグレーションが発生した場合も、基本的にはCHANGELOGの内容などを参考にしていけばさほど難しくありません。 フロントエンドが好きだったり得意なメンバー以外からすると、フロントエンドのビルド・環境設定周りは苦痛が伴うこともあるため、そこをGem側でケアしてくれるのはとても助かります。

Railsアクセス時の自動ビルドがうれしい

Webpackerはビルド時にコードのハッシュ値を保存しており、アクセス時にコードに差分が発生していると自動的にビルドをしてくれます。 (デフォルトでは開発時のみのオプションです)

「あれ、なんか動かない〜」→「フロントエンド側ビルド忘れてました」 というケースは結構発生するので、それを回避できるのは地味に便利で快適です。

🤔 Webpackerのつらかったところ

レールから外れたカスタマイズがしづらい

レールに乗っている間は快適ですが、そこから外れようとすると厳しい面が出てきます。 たとえば、webpackではwebpack.config.jsというファイルに LoaderやPluginを利用して設定を追加していくのが一般的な方法かと思いますが、 Webpackerの場合は、独自でラップされたクラスを経由してカスタマイズする必要があります。 たとえば、次のようなイメージです。

const { environment } = require('@rails/webpacker')

environment.loaders.append('json', {
  test: /\.json$/,
  use: 'json-loader'
})

environment.loaders.prepend('json', jsonLoader)

module.exports = environment

webpack.config.jsのお作法を覚えなくても良いというメリットはありますが、 そのぶん独自のカスタマイズ方法を把握することになるため、 すでにwebpackに関してある程度の知見がある場合にはコストに感じるかもしれませんし、 最初からwebpack.config.jsの書き方覚えたほうが手っ取り早いのでは?と思ってしまう部分はあります。

また、Webpackerが提供していない範囲でwebpackのカスタマイズが必要になってくると、結局はwebpack.config.js相当のものを引き剥がして書き変える必要が出てきます。

実際には次のようなコードを通してしました。

const webpack = require('webpack');
const merge = require('webpack-merge');

module.exports = function(environment) {
  const defaultConfig = environment.toWebpackConfig();
  const webpackConfig = merge(defaultConfig, {
    /* webpackの設定を独自でカスタマイズ */
  });

  return Object.assign(environment, {
    webpackConfig,
    toWebpackConfig() {
      return this.webpackConfig;
    }
  });
};

このあたりのカスタマイズにはWebpackerが内部で保持しているコードを把握しておく必要があり、 「レールに乗っていればOK!」という状態からはかけ離れてしまいます。

依存関係が見えづらい

Webpackerを使うことで、たとえばbabelを利用したECMAScriptの変換やCSSのビルドといったことは簡単に実現できます。

しかし、そのために必要な依存は@rails/webpackerの内部で閉じられているため、実際のところ何を利用しているのかが若干見えづらいことがあります。

そのため、例えばwebpackプラグインを追加しようと思った際に「すでにWebpacker内部で依存に追加されていないか?」「Webpacker内部での依存バージョンは何か?」といった情報を把握する必要が出てくるため、確認すべきものが多くてつらい、といったことが多々あります。

内部のモジュールのバージョンに引っ張られる

Babelを例に挙げると、@rails/webpacker#v3.5.5 であれば、内部で利用しているBabelは6.x系ですが、本エントリ投稿時点ではBabelはv7.3.3が最新バージョンです。 6.x→7.xの時点で必要となるパッケージ構造などに大きな変更があり、関連して、Babelを利用する他パッケージではBabel6.xとBabel7.xによって設定や挙動に変化があるものが少なくありません。

たとえば、テストフレームワークのJestはBabel6.xのサポートを打ち切っています。 https://jestjs.io/docs/en/getting-started#babel-6

こういった影響を受けて「Webpacker側のパッケージが更新されるまでは、独自で入れたいパッケージが更新できない・あるいは追加できない」といったケースが発生することがありました。(強引にやっていくこともできそうでしたが、メンテナンスコストが増大しそうだったので避けました。)

🎓Webpackerを卒業することにした

さまざまなメリット・デメリットを考えた結果、Webpackerを卒業して純粋なwebpackビルドに置き換えることにしました。

Webpackerの他に追加しているパッケージが増えてきた

テスト環境の整備などを経て、導入初期と比較するとWebpacker以外で独自で導入しているフレームワークが増えてきました。先に挙げた依存関係やバージョンの問題から、「使いたいが使えない・使いづらい」というシチュエーションが発生してきました。

開発環境のDocker化が進んだ

Webpackerの自動ビルドがとても便利で、Rails以外にwebpackプロセスを独自で立ち上げなくて良いのは大きなメリットだったのですが、最近ではDockerによる開発環境の整備が進んでおり、docker-composeを利用することでwebpackプロセスなどは特に意識せずともコマンド一発で起動することが可能になってきました。

マイグレーションコストが高くなってきてしまった

独自でのパッケージの追加や、力技でのカスタマイズを加えていったため、マイグレーションが発生した場合に、CHANGELOGなどを見るだけでは安心できない状態になっていました。 また、Webpacker外のパッケージを更新する際もWebpackerとの影響を考慮する必要があり、気を払わなければいけない対象が多く、それであれば独自でwebpackを構成してしまって自分たちで依存モジュールを完全に掌握できている方が安心だな、という考えに至りました。

結局Webpackerを採用したのはどうだった?

個人的な感想は「採用してよかった」と思っています。

今現在で言えばマッチしない状況があるのは事実ですが、導入初期のころから振り返ってみると、確実に僕たちを助けてくれた存在だったと思います。

初期の設定をWebpackerに任せたことにより、すぐにVue.jsの更新・CoffeeScriptからの脱却・テスト環境の整備といった、プロダクト的にもっと重要な他の改善作業に注力することができましたし、その間はwebpackのことを考えなくて良かったのはとても助かりました。

また、つらかった部分を幾つか挙げましたが、これらはほぼすべてこちら側でカスタマイズしたことに起因して発生しているものであり、本来Webpackerが推奨しているものとはズレており、Webpacker自体の問題とは言えません。先に述べた恩恵のことを考えると、入れ替えるコストを考えても遥かにメリットのほうが多かったと思います。

つまり、プロダクト側の状況が変化したことでWebpackerを使うフェーズからは卒業したんだな、と思っています。Webpackerに限った話ではありませんが、フレームワークにはプロダクトとの相性や、マッチする時期、旬みたいなものがあるので、定期的に見直して最新化していくのは重要ですよね。

というわけでWebpacker、今までありがとう!

📢採用

Misocaでは旬を追いかけるエンジニアを募集しています!

www.wantedly.com