Help us understand the problem. What is going on with this article?

Webpacker使うなら最低限これだけは知っておいてほしいこと

言いたいこと

webpackをあまり知らない人がwebpackerを用いることで簡単に導入できるけど以下のことはちゃんと知っておいてほしかったり、考えてほしい。(切実)

  • ビルドはRubyからコマンド実行してるけど、実際に発行しているnpm scriptはwebpack(-dev-sever) --config config/webpack/(development|test|production).jsなんで覚えておけ。
  • デフォルトでmultiple entryになっているのでSplitChunksPluginCommonsChunkPluginの設定はとりあえず入れておけ。
  • 本当にwebpackerは必要ですか?時期が来たら捨てろ。

webpackerの落とし穴

昨今では当たり前となっているwebpackの導入をwebpackをあまり理解していない人が簡単に入れれるという特徴がある。このwebpackをあまり理解していない人はまずいので導入当初によくおきた問題と解消方法をツラツラ書いていきます。

Rubyからしかビルド方法を知らない

まず、webpackのビルドを簡単に行えるように準備してくれています。方法は3つくらい

  1. bin/webpack(-dev-server)を実行するとビルド
  2. bundle exec rake webpacker:compileのrakeタスクでビルド
  3. bundle exec rake assets:precompileをしてもビルド

詳しくは説明しませんが、上2つはwebpackerが提供しているコマンドもしくはrakeタスクです。最後はwebpackerassets:precompileをフックして、2つ目の自身が作ったrakeタスクを実行することでビルドするということが定義されていることによる実行です。

こうみると何も知らない人からすれば(webpackによる)JavaScriptのビルドはRubyから実行しないといけないということになるのですが、そうではありません。
実際webpackerのソースを見ればわかるのですが、npm scriptを実行してるだけなのです。
書かれているソースはこれ

webpacker/lib/webpacker/webpack_runner.rb
require "shellwords"
require "webpacker/runner"

module Webpacker
  class WebpackRunner < Webpacker::Runner
    def run
      env = { "NODE_PATH" => @node_modules_path.shellescape }
      cmd = [ "#{@node_modules_path}/.bin/webpack", "--config", @webpack_config ] + @argv

      Dir.chdir(@app_path) do
        exec env, *cmd
      end
    end
  end
end

見ての通り、ただnpm scriptを実行しているので、これさえわかっておけばRubyがないとビルドできない!てことはなくなる。
実際に叩かれるnpm scriptはこれです。

./node_modules/.bin/webpack --config config/webpack/development.js

docker-compose(複数コンテナ)での開発環境設定

次にRubyがなくてもJavaScriptをビルドできるってことでバックエンドとフロントエンドのコンテナを開発することができます。
が、webpackerの処理には便利なようでちょっと癖のあるものがあります。

assets:precompile同様に開発環境ではリクエスト時に未ビルドもしくは変更済みJavaScriptファイルをビルドする

Dockerとか使わない単一のローカル開発ならばいいんでしょうが、先に書いたバックとフロントのDockerコンテナを分けたdocker-composeを開発する際は邪魔です。

じゃあ、こいつはどういう条件で動いているかというとこいつです

webpacker/lib/webpacker/manifest.rb
class Webpacker::Manifest
  class MissingEntryError < StandardError; end

...
  def lookup(name)
    compile if compiling?
    find name
  end
...
  private
    def compiling?
      config.compile? && !dev_server.running?
    end
...
end

要は指定されたファイルを返す処理の際に都度ビルド(config.compile?)が設定されており、webpack-dev-server(!dev_server.running?)が動いていない時にコンパイルしたファイルを返すという処理になっています。

1つ目の条件である都度ビルド(config.compile?)の設定ですが、これはwebpacker.ymlのこの設定のことです。

default: &default
  source_path: app/javascript
...
development:
  <<: *default
  compile: true # < This!

このyamlファイルはNodeスクリプトが読みこんで実行することがあるため、動的に設定することはちょっと面倒です。なのでこの部分の変更は諦めるか思い切ってfalseにしてもいいと思います。

2つ目の条件であるwebpack-dev-server(!dev_server.running?)が動いるという条件でなんとかします。そもそもwebpack-dev-serverが動いているというのはどうやって見ているかというとwebpack-dev-serverのホストとポート設定でTCP接続できるかどうかで判定しています。

webpacker/lib/webpacker/dev_server.rb
class Webpacker::DevServer
...
  def running?
    if config.dev_server.present?
      Socket.tcp(host, port, connect_timeout: connect_timeout).close
      true
    else
      false
    end
  rescue
    false
  end
...
  def host
    fetch(:host)
  end

  def port
    fetch(:port)
  end
...
  private
    def fetch(key)
      ENV["WEBPACKER_DEV_SERVER_#{key.upcase}"] || config.dev_server.fetch(key, defaults[key])
    end
...
end

ここのホストとポートは同じくwebpacker.ymlから取得します。

webpacker.yml
  dev_server:
    https: false
    host: localhost
    port: 3035

しかし、よく見ると環境変数の値が設定されているとそれを優先して使用する処理になっているのでこれをうまく使い、docker-composeではdocker networkをうまく使い、WEBPACKER_DEV_SERVER_HOSTを設定することでバックエンドコンテナからフロントエンド(webpack-dev-server)コンテナへの接続を可能とします。

docker-compose.yml
version: '3'

services:
  backend:
    command: 'bundle exec rails s -b 0.0.0.0'
    environment:
      - WEBPACKER_DEV_SERVER_HOST: frontend
  frontend:
    command: './node_modules/.bin/webpack-dev-server --config config/webpack/development --host 0.0.0.0 --port 3035'

これによりバックエンドコンテナでのビルドを抑制することができます。

webpack-dev-serverがクソ重くなる

原因はただのメモリ使いすぎです。画像は仕方ないとしてもwebpack-dev-serverはビルドしたものをすべてメモリに保持します。webpackerはデフォルトでmultiple entryになっているため、エントリーのファイル数が増えれば増えるほどこの傾向が出てきます。
特にJavaScriptではエントリーのファイルにimportしたファイルがどんどん結合されてファイルが肥大化することによりメモリ使用量が増えます。また、ファイルが肥大化することによって実際のwebページに訪れた人にも大きいJavaScriptファイルをダウンロードさせることになることとなってしまう可能性があります。
webpack(v4)ではSplitChunksPluginというものがあり、共通で使用するpackageのJavaScriptファイルを纏める機能があります。詳しくはググるなり以下を参照して下さい。後述するCommonsChunkPluginはv4では廃止です。
https://webpack.js.org/plugins/split-chunks-plugin/
まだwebpack(er)(v3)を使ってる方はCommonsChunkPluginを使って纏めます。
https://webpack.js.org/plugins/commons-chunk-plugin/

ではwebpackerではどうするかですが、v4とv3で対応は異なります。

webpacker v4

まず、v4ですがドキュメントに書いてるので以下のように変更して下さい。

config/webpack/environment.js
const { environment } = require('@rails/webpacker')

+ // デフォルトのセッティングでいいならこの行を足すだけで有効になる。
+ environment.splitChunks()

+ // カスタムなセッティングを入れるなら以下のようにファンクションで処理をする
+ //environment.splitChunks((config) => Object.assign({}, config, { optimization: { splitChunks: false }}))

module.exports = environment

この設定を入れたら、JavaScriptファイルを読み込むヘルパーメソッドは javascript_packs_with_chunks_tag を使用するようにします。

<%# 従来のjavascript_pack_tagでは必要ファイルがすべてロードされないため、javascript_packs_with_chunks_tagを使用する %>
<%#= javascript_pack_tag 'hoge' %>
<%= javascript_packs_with_chunks_tag 'hoge' %>

webpacker v3

まだ、v3を使ってる方はこちらに書いてるに書いてるので以下のように変更して下さい。

config/webpack/environment.js
  const { environment } = require('@rails/webpacker')
+ const webpack = require('webpack')
+
+ environment.plugins.append(
+   'CommonsChunkVendor',
+    new webpack.optimize.CommonsChunkPlugin({
+      name: 'vendor',
+      minChunks: (module) => {
+        // this assumes your vendor imports exist in the node_modules directory
+        return module.context && module.context.indexOf('node_modules') !== -1
+      }
+    })
+  )

  module.exports = environment

使用する方法は以下のようにvender.jsに纏まるので、以下のように足します。

app/view/layouts/application.html.erb
  <!DOCTYPE html>
  <html>
    <head>
+     <%= javascript_pack_tag "vendor" %>
      <%= javascript_pack_tag 'entry_file_name' %>
    </head>

まとめ

ここまで読むとわかると思いますが、ここまでするならwebpacker入れるよりwebpackで純粋にビルドすればいいという雰囲気になってきますよね。webpackerでwebpackのコンフィグ変えるの面倒だし
使う道具はその特性をちゃんと理解するといいです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした