この記事では、Webpacker
を使っている Rails アプリからWebpacker
を剥がし、webpack
を使うようにするための手順を書いていく。
Webpacker
を止めたい理由は様々だが、主な理由は以下のような感じだろうか。
- 現時点では
Webpacker
が使用しているwebpack
のバージョンは3.x
で、最新バージョンに追従できていない webpack
の設定の他に、「Webpacker
の設定」について学ばなければならず、無駄が多いwebpack
を直接触れば済む話を、常にWebpacker
という仲介者を通して作業しなければならない- カスタマイズやマイグレーションの際に、考慮しなければならない要素が増えてしまう
手軽にフロントエンド開発環境を導入できる、というのがWebpacker
の利点であり、カスタマイズやらアップデートやらが必要になってしまうのならあまりメリットはない、と個人的には感じている。
前回の記事で、Webpacker
と Vue を使ったサンプルアプリを作成した。
このアプリから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
で実現する。
以下のバージョンで作業している。
- Ruby 2.5.3
- Ruby on Rails 5.2.2
- webpack 4.28.3
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
次に、マイグレーションを行うが、公式のツールが便利なのでそれを使う。
このツールを使うことで、必要なライブラリのインストールなどが行われる。
$ 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' # webpackによるビルドファイル読み込み用ヘルパー 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) # async と defer を両方指定した場合、ふつうは async が優先されるが、 # defer しか対応してない古いブラウザの挙動を考えるのが面倒なので、両方指定は防いでおく 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 # webpack-dev-serverから直接取得する 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
を参照している。
だがこのパスは誤りであり、以下のパスが正しい。
環境 | パス |
---|---|
デベロップメント | http://localhost:3035/packs/images/usa-xxxxx.png |
プロダクション | http://localhost:3000/packs/images/usa-xxxxx.png |
この問題はプロキシを作成して対応することにした。
まず、rack-proxy
をインストール。
Gemfile
にgem 'rack-proxy'
と追記して、$ bundle
を実行すればよい。
次に、lib/tasks/assets_path_proxy.rb
という名前でプロキシを作成。
require 'rack/proxy' # Vue のコンポーネントのなかで利用している画像にアクセスできるようにするためのプロキシ 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