2021-10-09

RailsからWebpackerを外してpureなwebpack構成にしてみる その3

前回、無事に脱Webpackerして開発を進めること自体は問題なくできるようになった
しかし、ホットリロードが使えないのは開発体験としてはかなり辛いので、今回はホットリロードも使えるようにしていく

脱Webpacker後のホットリロード対応

webpack --watch ではファイルの変更を検知して再ビルドはしてくれるが画面のリロードまではしてくれないので、やはりwebpack-dev-server を使って実現する
実はsimpackerのリポジトリにwebpack-dev-serverを使った構成のサンプルがあるのでそちらを参考にさせていただくことにする

webpack-dev-server がないと始まらないので yarn add --dev webpack-dev-server で入れ、あとはwebpack.config.jsにdevServer向けの設定を書くだけでOK
webpack-dev-server は今年8月にメジャーバージョンが4に上がり、simpackerのサンプル通りでは動かない箇所があるので、その辺をmigrationガイドを参考に書き換える
ということで書き換えたwebpack.config.jsは以下の通り(必要なところだけ記載)

const { NODE_ENV } = process.env
const isProd = NODE_ENV === "production"

module.exports = {
  mode: isProd ? "production" : "development",
  entry: {...},
  output: {
    ...,
    // 8080はwebpack-dev-serverがデフォルトで使用するポート
    publicPath: isProd ? "/packs/" : "//localhost:8080/packs/",
  },
  resolve: {...},
  module: {...},
  plugins: [...],
  devServer: {
    host: "localhost",
    headers: {
      // CORSのアレ => https://hakozaru.com/posts/http-cors
      "Access-Control-Allow-Origin": "*",
    },
    devMiddleware: {
      publicPath: "/packs/",
    },
    static: {
      // サーバーの起点となるディレクトリ(監視するディレクトリ)
      // outputオプションのpathと同じディレクトリが指定されるはず
      directory: path.resolve(__dirname, "public/packs"),
    }
  },
}

outputのpublicPathをwebpack-dev-serverを起動した時に使われるパスに変更し、devServerの設定を追加しただけ
devServerのpublicPathは devMiddleware オプションの配下となり、directoryは static オプションの配下の設定となった点が変わっている部分
(isProdの部分はsimpackerから拝借した)

あとはdev-serverの起動コマンドをpackage.jsonに書けばOK(watchは使わないので消した)
起動はwebpack経由でするのが公式で推奨されているっぽいのでそちらに従う

"scripts": {
  "dev": "webpack --progress --color --mode=development",
  "dev-server": "webpack serve --mode=development"
}

これで全ての準備が完了したので bin/rails syarn dev-server を実行すれば、ホットリロードな環境でWebpackerに依存せず、快適にフロントエンド開発ができるようになった
(コミット => https://github.com/hakozaru/remove_webpacker_sample/commit/df77d5dfbb7a45cd30691a2a00c4a3943d9026f5)

なお、Webpackerはプロセスを2つ立ち上げなくてもrails sだけでビルドをいい感じにやってくれていたが、どうせdocker-compose使うしそこまではやらなくても開発に支障はないと思うのでやっていない

ついでにCSSと画像、SVGもwebpack管理にする

実際の開発ではCSSとか画像も管理すると思うので一応やっておく

  • 画像
    • webpack4までは file-loader を入れて import Img from '../images/hogehoge.png' のようにしていたが、webpack5からはtypeオプションで asset/resource とすることで同じ挙動にすることができるようになった(file-loader は不要)
    • TSのエラーが出るので declare module '*.png' を定義した ~~~.d.ts を適当なフォルダに用意することをお忘れなく
    • https://github.com/hakozaru/remove_webpacker_sample/commit/ec6990be44fa2d5c792434983cabd31cd106dbb1 (file-loaderを使った方法。asset/resourceはCSS対応時に行う)
  • SVG
  • CSS(Sass)
    • 流石にピュアなCSSは今時キツイので、Sassを使えるようにしておく
    • sass-loader style-loader css-loader sass を入れる(sass-loaderはsassに依存しているので必要)
    • optionにソースマップを付与しておくと便利
    • CSSのbackground-imageで指定するpngは、asset/resourceで処理するように変更しないと表示できなかった(本当はfile-loaderでもできるはずなんだけど原因がわからず)
    • https://github.com/hakozaru/remove_webpacker_sample/commit/c5d7b0bfa6a8763477648c3bdf6b83cee6d7e5fc

うーんちょっとローダーの役割を完全に理解できていない感があるので、どこかでもっとちゃんと調べておこう
やっぱwebpack難しいぃ〜

2021/10/11追記

↑で原因不明だった、CSSのbackground-imageで指定した画像が表示できなかった件を調べた結果
webpack5からデフォルトで使えるようになっているAsset Modulesという機能(asset/resource とか)が file-loader などのアセット向けローダーと重複していたせいでうまく動作していなかった模様

公式ページの説明に従って以下のように画像向けのローダ設定を書き換えたら無事に動作した

{
  test: /\.(png)$/,
  use: [
    {
      loader: 'file-loader',
      options: {
        name: './images/[name].[ext]'
      }
    }
  ],
  // ↓のtypeとdependencyが必要らしい
  type: 'javascript/auto',
  dependency: { not: ['url'] }
},

https://github.com/hakozaru/remove_webpacker_sample/commit/5e9b7335fb60ab0a15f2f0d27e4fae6fb9932199
置き換えられるならwebpack5からはローダー外してビルトインのasset/****を使うほうが良さそう

参考記事