この記事では、Webpacker
を使っている Rails アプリからWebpacker
を剥がし、webpack
を使うようにするための手順を書いていく。
Webpacker
を止めたい理由は様々だが、主な理由は以下のような感じだろうか。
- 現時点では
Webpacker
が使用しているwebpack
のバージョンは3.x
で、最新バージョンに追従できていない
webpack
の設定の他に、「Webpacker
の設定」について学ばなければならず、無駄が多い
webpack
を直接触れば済む話を、常にWebpacker
という仲介者を通して作業しなければならない
手軽にフロントエンド開発環境を導入できる、というのがWebpacker
の利点であり、カスタマイズやらアップデートやらが必要になってしまうのならあまりメリットはない、と個人的には感じている。
前回の記事で、Webpacker
と Vue を使ったサンプルアプリを作成した。
numb86-tech.hatenablog.com
このアプリからWebpacker
を削除し、webpack
で Vue コンポーネントなどをビルドできるようにしていく。
この記事のゴールは「ちゃんと動くようにするための仕組みを作る」ことであって、Webpacker
が行っていたことを忠実に再現することはしない。
Webpacker
を剥がすために必要な作業の全体像を掴み、それを記録しておくことがこの記事の意図であり、細部には踏み込まない。
ただ、スタイルシートを別ファイルとして抽出するなどの基本的な動作は踏襲する。
Webpacker
が果たしている主な仕事は以下。
app/javascript/packs
に入っているファイルをエントリファイルとして、public/packs
にダイジェスト付きでビルドする
- マニフェストファイルとして
pubplic/packs/manifest.json
を生成する
webpack-dev-server
を使うことで、差分を検知し、自動的にビルドやブラウザのリロードを行う
javascript_pack_tag
やstylesheet_pack_tag
で、ビルドされたファイルを読み込める
これをwebpack
で実現する。
以下のバージョンで作業している。
Step1 Webpacker の削除
まずは、Webpacker
を削除していく。
Gemfile
からgem 'webpacker'
を削除して、$ bundle
を実行。
$ yarn remove @rails/webpacker
を実行。
これでライブラリを削除できるが、関連ファイルはまだまだ残っているのでこれらを削除していく。
- bin/webpack
- bin/webpack-dev-server
- config/webpack/development.js
- config/webpack/environment.js
- config/webpack/loaders/vue.js
- config/webpack/production.js
- config/webpack/test.js
- config/webpacker.yml
最後に、設定ファイルからWebpacker
に関する記述を削除する。
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 4b8cc17..1311e3e 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -1,6 +1,4 @@
Rails.application.configure do
- # Verifies that versions and hashed value of the package contents in the project's package.json
- config.webpacker.check_yarn_integrity = true
# Settings specified here will take precedence over those in config/application.rb.
# In the development environment your application's code is reloaded on
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 457946a..b4dbe4a 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -1,6 +1,4 @@
Rails.application.configure do
- # Verifies that versions and hashed value of the package contents in the project's package.json
- config.webpacker.check_yarn_integrity = false
# Settings specified here will take precedence over those in config/application.rb.
# Code is not reloaded between requests.
また、Turbolinks
が有効になっている場合は忘れずに無効化しておく。
これが有効になっていると、ページ遷移時に上手く動かなくなる。
Turbolinks
と共存させる方法もあると思うが、ここでは扱わない。
Step2 webpack でビルドできるようにする
この時点では Rails との連携は考えず、まずはapp/javascript/packs
に入っているファイルをビルドできるようにする。
必要なライブラリをインストールする。
$ yarn add -D webpack webpack-cli
$ yarn add -D css-loader file-loader sass-loader node-sass mini-css-extract-plugin vue-loader@latest
$ yarn add -D webpack-manifest-plugin
webpack@4
からは、CSSの抽出にはextract-text-webpack-plugin
ではなくmini-css-extract-plugin
を使うことが推奨されている。
しかし、Webpacker
経由で Vue の開発環境を整えた場合はvue-loader
のv14
が入っており、これがmini-css-extract-plugin
に対応していない。そのため、最新バージョンのvue-loader
を入れる必要がある。
次に、プロジェクトのルートディレクトリ直下にwebpack.config.js
を用意する。
もっとよい書き方があるとは思うし、プロジェクトによってはこれではダメかもしれないが、今回扱っているサンプルアプリの場合はこれで問題なく動く。
Babel の対応は次の Step で行う。
const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';
return {
context: path.resolve(__dirname, 'app/javascript/packs'),
entry: {
application: './application.js',
'application-stylesheet': './application.sass',
hello_vue: './hello_vue.js',
},
output: {
path: path.resolve(__dirname, 'public/packs'),
filename: isProduction ? '[name]-[contentHash].js' : '[name]-[hash].js',
},
module: {
rules: [
{test: /\.(css|sass)$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
outputPath: 'images/',
name: '[name]-[hash].[ext]',
},
},
],
},
{
test: /\.vue$/,
exclude: /node_modules/,
loader: 'vue-loader',
options: {
extractCSS: true,
},
},
],
},
plugins: [
new VueLoaderPlugin(),
new ManifestPlugin(),
new MiniCssExtractPlugin({filename: '[name]-[contentHash].css'}),
],
};
}
あとは、npm スクリプトとして"build": "yarn && webpack --mode=production"
を登録し、$ yarn build
を実行すれば、public/packs
にビルドされる。
Step3 Babel の対応と v7 へのマイグレーション
Rails5.2
のWebpacker
環境で使われる Babel はv6
なので、この機会にv7
に上げておく。
必要なライブラリをインストール。
$ yarn add -D babel-loader @babel/core @babel/preset-env @babel/plugin-syntax-dynamic-import @babel/plugin-proposal-class-properties
次に、マイグレーションを行うが、公式のツールが便利なのでそれを使う。
github.com
このツールを使うことで、必要なライブラリのインストールなどが行われる。
$ npm i -g babel-upgrade
$ babel-upgrade --write --install
.babelrc
の更新も自動的に行われる、はずなのだが、環境によっては実行されなかった。
取り敢えず今回のケースでは以下のようにすればよい。
diff --git a/.babelrc b/.babelrc
index 47cfe92..3233cc0 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,24 +1,31 @@
{
"presets": [
- ["env", {
- "modules": false,
- "targets": {
- "browsers": "> 1%",
- "uglify": true
- },
- "useBuiltIns": true
- }]
+ [
+ "@babel/preset-env",
+ {
+ "modules": false,
+ "targets": {
+ "browsers": "> 1%"
+ }
+ }
+ ]
],
-
"plugins": [
- "syntax-dynamic-import",
- "transform-object-rest-spread",
- ["transform-class-properties", { "spec": true }]
+ "@babel/plugin-syntax-dynamic-import",
+ "@babel/plugin-proposal-object-rest-spread",
+ [
+ "@babel/plugin-proposal-class-properties",
+ {
+ "spec": true
+ }
+ ]
],
-
"env": {
"test": {
- "presets": ["env", "power-assert"]
+ "presets": [
+ "@babel/preset-env",
+ "power-assert"
+ ]
}
}
}
あとはwebpack
でbabel-loader
を使うようにすればよい。
diff --git a/webpack.config.js b/webpack.config.js
index 89d3790..2fcb99d 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -19,6 +19,11 @@ module.exports = (env, argv) => {
},
module: {
rules: [
+ {
+ test: /\.js$/,
+ include: path.resolve(__dirname, 'app'),
+ use: 'babel-loader',
+ },
{test: /\.(css|sass)$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']},
{
test: /\.(png|jpg|gif)$/,
これで、ビルドの際に Babel によるトランスパイルが行われるようになる。
Babel のバージョンをv7
に上げたことで、babel-preset-power-assert
の最新バージョン(v3
)を使えるようになったので、対応しておく。
$ yarn add -D babel-preset-power-assert@3.0.0
Step4 webpack-dev-server を導入
ビルドは出来るようになったので、次は、webpack-dev-server
を使えるようにする。これがないと自動ビルドや自動リロードが出来ないので、開発に支障をきたしてしまう。
Webpacker
をインストールしたときに導入されたwebpack-dev-server
はバージョンが古いので、更新する。
$ yarn add -D webpack-dev-server@latest
コマンドの追加と設定ファイルへの追記を行う。
diff --git a/package.json b/package.json
index 98cde84..b573325 100644
--- a/package.json
+++ b/package.json
@@ -2,6 +2,7 @@
"name": "webpacker-example",
"private": true,
"scripts": {
+ "start": "yarn && webpack-dev-server --mode=development",
"build": "yarn && webpack --mode=production",
"test": "jest"
},
diff --git a/webpack.config.js b/webpack.config.js
index 2fcb99d..4e94f0b 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -52,5 +52,16 @@ module.exports = (env, argv) => {
new ManifestPlugin(),
new MiniCssExtractPlugin({filename: '[name]-[contentHash].css'}),
],
+ devServer: {
+ publicPath: '/packs/',
+ historyApiFallback: true,
+ port: 3035,
+ },
};
}
$ yarn start
を実行し、http://localhost:3035/packs/manifest.json
にアクセスできれば成功である。
Step5 ヘルパータグの実装
次はいよいよ、Rails とのつなぎ込みを行う。
javascript_pack_tag
に代わるヘルパータグを作成する。
app/helpers/webpack_bundle_helper.rb
として、以下のファイルを作成した。
require 'open-uri'
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
def stylesheet_bundle_tag(entry, **options)
path = asset_bundle_path("#{entry}.css")
options = {
href: path
}.merge(options)
stylesheet_link_tag '', **options
end
private
def asset_server
port = Rails.env === 'production' ? '3000' : '3035'
"http://#{request.host}:#{port}/"
end
def pro_manifest
File.read('public/packs/manifest.json')
end
def dev_manifest
OpenURI.open_uri("#{asset_server}packs/manifest.json").read
end
def test_manifest
File.read('public/packs-test/manifest.json')
end
def manifest
return @manifest ||= JSON.parse(pro_manifest) if Rails.env.production?
return @manifest ||= JSON.parse(dev_manifest) if Rails.env.development?
return @manifest ||= JSON.parse(test_manifest)
end
def valid_entry?(entry)
return true if manifest.key?(entry)
raise BundleNotFound, "Could not find bundle with name #{entry}"
end
def asset_bundle_path(entry, **options)
valid_entry?(entry)
asset_path("#{asset_server}packs/" + manifest.fetch(entry), **options)
end
end
そしてこれを、テンプレートファイルのなかで使う。
diff --git a/app/views/posts/index.html.erb b/app/views/posts/index.html.erb
index 3bfde83..a418880 100644
--- a/app/views/posts/index.html.erb
+++ b/app/views/posts/index.html.erb
@@ -26,7 +26,7 @@
<%= link_to 'New Post', new_post_path %>
-<%= javascript_pack_tag 'application' %>
-<%= stylesheet_pack_tag 'application' %>
-<%= javascript_pack_tag 'hello_vue' %>
-<%= stylesheet_pack_tag 'hello_vue' %>
+<%= javascript_bundle_tag 'application' %>
+<%= stylesheet_bundle_tag 'application-stylesheet' %>
+<%= javascript_bundle_tag 'hello_vue' %>
+<%= stylesheet_bundle_tag 'hello_vue' %>
$ bin/rails s
と$ yarn start
を同時に使って、http://localhost:3000/
にアクセスしてみる。
プロダクション環境を確認したい場合は、$ yarn build
したうえで$ bin/rails s -e production
を実行する。
概ね動いているが、app/javascript/app.vue
のなかで使っている画像が表示されていない。
最後の仕上げとして、この問題を修正する。
Step6 Vueのコンポーネントで使っている画像を表示できるようにする
表示されていない画像だが、確認してみると、http://localhost:3000/images/usa-xxxxx.png
を参照している。
だがこのパスは誤りであり、以下のパスが正しい。
この問題はプロキシを作成して対応することにした。
まず、rack-proxy
をインストール。
Gemfile
にgem 'rack-proxy'
と追記して、$ bundle
を実行すればよい。
次に、lib/tasks/assets_path_proxy.rb
という名前でプロキシを作成。
require 'rack/proxy'
class AssetsPathProxy < Rack::Proxy
def perform_request(env)
if env['PATH_INFO'].include?("/images/")
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'] = "/packs/images/" + env['PATH_INFO'].split("/").last
super
else
@app.call(env)
end
end
end
そして、設定ファイルにプロキシを使う設定を書き込む。
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 1311e3e..f4a4f26 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -1,3 +1,5 @@
+require_relative '../../lib/tasks/assets_path_proxy'
+
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
@@ -12,6 +14,8 @@ Rails.application.configure do
# Show full error reports.
config.consider_all_requests_local = true
+ config.middleware.use AssetsPathProxy, ssl_verify_none: true
+
# Enable/disable caching. By default caching is disabled.
# Run rails dev:cache to toggle caching.
if Rails.root.join('tmp', 'caching-dev.txt').exist?
diff --git a/config/environments/production.rb b/config/environments/production.rb
index b4dbe4a..dc6fac5 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -1,3 +1,5 @@
+require_relative '../../lib/tasks/assets_path_proxy'
+
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
@@ -14,6 +16,8 @@ Rails.application.configure do
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
+ config.middleware.use AssetsPathProxy, ssl_verify_none: true
+
# Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
# or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
# config.require_master_key = true
これで、表示されるようになる。
2019/1/25 追記
webpack
+ Rails
のウェブアプリを作った。
numb86-tech.hatenablog.com
参考資料