kyoncyの日記

Rails6 に Webpack を導入する

June 13, 2020

この記事では、Rails6 から Webpacker を外して Webpack を導入するまでを解説します。 次回の記事で Webpack に React, TypeScript の環境構築について説明します。

Rails6 のセットアップ

Rails のセットアップをする上での詳しい方法はここでは説明しません。 セットアップ方法に関しては https://railsguides.jp/getting_started.html を参照してください。

mysql 派なので --database=mysql としていますが、つけなくても構いません。SQLite になります。

$ rails new app-name --database=mysql
$ rails db:create

検証した Rails, Ruby のバージョンは、

  • Rails: 6.0.3.1
  • Ruby: 2.6.6

としました。 2020/06/13 時点での Ruby の最新バージョンは 2.7.1 なのでアップデートしても良さそうです。 個人的には、Ruby3系で予定されている仕様変更に関する文法を使用しているためにWARNINGが出て、対応していないライブラリがあると面倒だなと思い渋っていますがご了承ください。

Webpacker を外して Webppack の環境構築

Gemfile から webpacker に関する行を削除します。

# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 4.0'

その後 bundle install すればOKです。 webpacker を外しましたが、Webpack の環境構築をしなければなりません。

大まかな流れは以下のようになります。

  • webpack.config.js の作成
  • webpack-manifest-plugin の設定の追加
  • Railsでバンドル後の .js ファイルを読み込むためのヘルパーの作成
  • webpack-dev-server の設定

ルートに frontend ディレクトリを作成して、その中で設定していきます。

webpack.config.js の作成

必要なライブラリをインストールします。

frontend ディレクトリに移動してからです。

$ yarn add -D webpack webpack-cli

frontendディレクトリ内にエントリポイントとなる entries ディレクトリを作成し、適当に application.js を作成します。

そして、 webpack-config.js を作成します。 最小限の設定をいかに示します。

const glob = require('glob');
const path = require('path');

let entries = {}
glob.sync('./entries/*.js').map((file) => {
  let name = file.split('/')[2].split('.')[0];
  entries[name] = file;
})

module.exports = {
  mode: 'development',
  entry: entries,
  output: {
    filename: 'javascripts/[name]-[hash].js',
    path: path.resolve(__dirname, '../public/assets')
  },
};

webpack.config.js の設定した上で以下のコマンド(package.json 内にスクリプトを追加すると良いです)

$ webpack --config webpack.config.js

を実行することで public/assets/javascripts ディレクトリ以下にバンドルされたファイルが生成されます。 Rails側から、バンドル後のファイルを取得しに行くように設定してあげることで Webpack の設定は完了します。

しかし、このままではバンドル後のファイル名に [name]-[hash].js とハッシュ化された文字列が含まれているため Rails 側はファイル名が分かりません。

webpack-manifest-plugin の設定の追加

ここで manifest.json が必要となります。 詳しい説明は https://developer.mozilla.org/ja/docs/Mozilla/Add-ons/WebExtensions/manifest.json に記載しています。 ただ、ここでは application.js を Rails 側から取得するときにパスの解決をする役割を持つものだと理解してください。

manifest.json を生成するために webpack-manifest-plugin の設定をします。

$ yarn add -D webpack-manifest-plugin

webpack.config.js の設定に追記

const ManifestPlugin = require('webpack-manifest-plugin');

module.exports = {
  ...,
  plugins: [
    new ManifestPlugin({
      fileName: 'manifest.json',
      publicPath: "/assets/",
      writeToFileEmit: true,
    }),
  ],
  ...,
}

これでひとまず、 webpack.config.js の設定は完了しました。 次は、バンドル後の .js ファイルを Rails から読み込むためのヘルパーを作成します。

manifest.json は

{
  "application.js": "/assets/javascripts/application-83582db1f2c95fb2e1d8.js",
}

のようになっていれば無事に manifest.json は作成されています。

webpackbundlehelper の作成

ビューでバンドル後の .js ファイルを .erb ファイル内で以下のように指定して取得できるようにします。

<%= javascript_bundle_tag 'application' %>

取得するための app/helpers/webpack_bundle_helper.rb を作成します

module WebpackBundleHelper
  class BundleNotFound < StandardError; end

  def javascript_bundle_tag(entry, **options)
    path = asset_bundle_path("#{entry}.js")

    options = {
      src: path,
      defer: true,
    }.merge(options)

    options.delete(:defer) if options[:async]

    javascript_include_tag "", **options
  end

  private

  MANIFEST_PATH = Rails.root.join('public', 'assets', 'manifest.json')

  def manifest
    @manifest ||= JSON.parse(File.read(MANIFEST_PATH))
  end

  def asset_bundle_path(entry, **options)
    raise BundleNotFound, "Could not find bundle with name #{entry}" unless manifest.key? entry
    asset_path(manifest.fetch(entry), **options)
  end
end

これでヘルパーの作成は完了です。 webpack 実行後、Railsサーバーを起動して application.js 内のコードが実行されていれば成功です。

ここまでで、Webpack が使えるようになりました。 —watch や —inline オプションをつけて起動すれば application.js の変更を検知してホットリロードが有効になります。

しかし、このままでは更新のたび public/assets/javascripts 以下にバンドルされた .js ファイルが生成されます。 .gitignore されてるとはいえ、手作業で削除するのは面倒です。 そのため、次は webpack-dev-server の設定を行います。

webpack-dev-server の設定

webpack-dev-server をインストールします。

$ yarn add -D webpack-dev-server

webpack.config.js に設定を追記します。

module.exports = {
  ...,
  devServer: {
    host: 'localhost',
    port: 3035,
    publicPath: 'http://localhost:3035/assets/',
    contentBase: path.resolve(__dirname, '../public/assets'),
    hot: true,
    disableHostCheck: true,
    historyApiFallback: true
  },
  ...,
}

webpack-dev-server コマンドを実行して立ち上げると ポートが3035番で起動します。 しかし、Railsサーバのポート番号はデフォルトで3000番なのでプロ棋士の設定をする必要があります。

lib/tasks/assets_path_proxy.rb の設定を追加します。 rack-proxy の Gem が必要なので Gemfile に gem 'rack-proxy' を追記して budnle install を実行します。

require "rack/proxy"

class AssetsPathProxy < Rack::Proxy
  def perform_request(env)
    if env["PATH_INFO"].include?("/javascripts/")
      if Rails.env != "production"
        dev_server = env["HTTP_HOST"].gsub(":3000", ":3035")
        env["HTTP_HOST"] = dev_server
        env["HTTP_X_FORWARDED_HOST"] = dev_server
        env["HTTP_X_FORWARDED_SERVER"] = dev_server
      end
      env["PATH_INFO"] = "/assets/javascripts/" + env["PATH_INFO"].split("/").last
      super
    else
      @app.call(env)
    end
  end
end 

その上で AssetsPathProxy を有効化するため config/environments/development.rb に追記します。

require_relative '../../lib/tasks/assets_path_proxy'

Rails.application.configure do
  ...

  config.middleware.use AssetsPathProxy, ssl_verify_none: true
end

これにて Webpack の設定は完了です。 次回は Rails6 + Webpack の環境に React, TypeScript, SCSS Modules を設定する方法について書きます。

文章少なめでコマンドやコードばかりになってしまい申し訳ありません。


きょんしー

社会人2年目の きょんしー🐧です。みんなのコード で働いてます。Twitter, Facebookやってます。React, TypeScript 書いてます。土日は個人開発したり読書したり無人島生活してます。 経歴は Wantedly に記載してます。