Rails6, Webpack(not webpacker)でJSを最低限動かすまで
Rails6で、フロントエンド周りをデフォルトのWebpackerではなく、Webpackを使おうと思ったので書いた記事です。
使用したバージョン
ruby2.6.3
Rails6.1.0
node14.15.4
webpack5.12.2
初期プロジェクト作成
rails new . --skip-javascript --skip-sprockets --database=mysql
--skip-javascript --skip-sprocketsオプションをつけることでsass-rails, webpacker, turbolinksあたりをインストールしなくなります。
(後にCSSもwebpackでバンドルしようと思っているのでsprocketsもskip)
プロジェクトの中にfrontend
ディレクトリを作っておきます。
以下のコマンドはすべてfrontend
ディレクトリの中で行います。
Webpackでファイルをバンドルできるようにする
Webpackをインストールします。
npm init
質問に適当に答えていき、package.json
を生成します。
まずは最低限webpackを動かせる環境を作ります。
webpackをインストールします。
npm install webpack webpack-cli --save-dev
webpackでバンドルしたいファイルを用意します。
frontend/src/index.js
を作成します。
// frontend/src/index.js
console.count('hello, world')
設定ファイルfrontend/webpack.config.js
を作成します。
// frontend/webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js', // バンドル元となるファイル
output: { // バンドルされたファイルの出力先とファイル名
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
};
webpackコマンドを使えるようにfrontend/package.json
を編集します。
{
"name": "frontend",
"version": "1.0.0",
"description": "",
"scripts": {
"dev": "webpack" // npm runでwebpackを使えるようにする。
},
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^5.12.2",
"webpack-cli": "^4.3.1"
}
}
以下のコマンドでwebpackでバンドルできます。
npm run dev
するとfrontend/dist/
が生成され中にmain.js
が生成されます。
// frontend/dist/main.js
console.count("hello, world");
ここまでで最低限webpackを動かす環境ができました。
バンドルされたファイルをRailsで読み込めるようにする
やることは
- バンドルされたファイルの出力先を
public
ディレクトリにする - キャッシュ対策として、バンドルされるファイルにhashを付与する
- manifest.jsonを生成する
- scriptタグを生成するヘルパー関数を作成する
です。
public
ディレクトリにする
バンドルされたファイルの出力先を出力されたファイルをブラウザから読み込めるようにするためです。
// frontend/webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
- path: path.resolve(__dirname, 'dist'),
+ path: path.resolve(__dirname, '../public/packs'),
},
};
webpackによるファイルの置き場だとわかるようにpublic/packs
を作ってその中に出力するようにします。
キャッシュ対策としてバンドルされるファイルにhashを付与する
開発していて、バンドルされて生成されるファイル名が常に同じだとキャッシュが効いて変更が反映されない場合があります。
そのためファイル名にhashを付与し元のファイルに変更があると違うファイル名になるようにすることで、変更を確実に反映させます。
// frontend/webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
- filename: 'main.js',
+ filename: 'main-[contenthash].js',
path: path.resolve(__dirname, '../public/packs'),
},
};
こうすることでバンドルしたときにmain-98e4726ae81fac80db84.js
と、hashがついた形で出力されるようになります。
manifest.jsonを生成する
これを使います。
例にあるようにこのようなjsonファイルが一緒に生成されます。{
"dist/batman.js": "dist/batman.1234567890.js",
"dist/joker.js": "dist/joker.0987654321.js"
}
このファイルによってRailsがscriptタグを生成するときの埋め込むべきファイル名を知ることができます。
npm install webpack-manifest-plugin --save-dev
// frontend/webpack.config.json
const path = require('path');
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main-[contenthash].js',
path: path.resolve(__dirname, '../public/packs'),
},
plugins: [
new WebpackManifestPlugin({
publicPath: '/packs/' // /packs/main-xxxxxxxx.jsという風にpacks以下に生成される
})
]
};
{
"main.js": "/packs/main-98e4726ae81fac80db84.js"
}
scriptタグを生成するヘルパー関数を作成する
・manifest.jsonの中身を取得
・ハッシュを除いたファイル名をキーにして対応するファイル名を取得
・javascript_include_tagを発行
# helpers/webpack_bundle_helper.rb
module WebpackBundleHelper
class BundleNotFound < StandardError; end
def javascript_webpack_bundle_tag(entry, **options)
path = asset_bundle_path("#{entry}.js")
javascript_include_tag(path, **options)
end
private
MANIFEST_FILE = 'manifest.json'.freeze
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
def manifest
@manifest ||= JSON.parse(File.read(manifest_path))
end
def manifest_path
Rails.root.join('public/packs', MANIFEST_FILE)
end
end
作成したヘルパーをviewにセットします。
# views/layouts/application.html.erb
<%= javascript_webpack_bundle_tag('main') %>
ブラウザで確認してみると以下のようにscriptタグが埋め込まれていて、無事JSを読み込むことができました。
<head>
// 省略
<script src="/packs/main-98e4726ae81fac80db84.js"></script>
//省略
</head>
余談
本番環境だと以下がデフォルトだとfalseなので、public/以下にアクセスできません。
trueになるようにしておく必要があります。public以下はNginxで配信するという場合は必要ないですが。
# config/environments/producition.rb
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
また、webpackコマンドを実行したときに以下の警告が表示されるかと思います。
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
modeを設定していないためです。
webpack.config.jsにmodeを設定するか、
// paclage.json
"scripts": {
"dev": "webpack --mode=development",
コマンドのオプションとして渡す方法があるかと思います。
modeのちがい。
productionの場合ははとにかくバンドルされるファイルサイズを小さくします。動作に必要なものだけ出力される感じです。
対してdevelopmentだと動作に必要なものに加えてデバッグしやすいようにその他の情報も出力されます。
Discussion