Rails: Sprockets->Propshaftアップグレードガイド(翻訳)
Propshaftの適用範囲はSprocketsよりも狭いので、Propshaftへの移行にはjsbundling-rails gemとcssbundling-rails gemも利用する必要があります。本ガイドでは、プロジェクトがRails 6.1以後の以下の規約に沿っていることを前提としています。
- JavaScriptのバンドルにwebpackerを利用している
- CSSのバンドルにsass-railsを利用している
- アセットのダイジェスト化にsprocketsを利用している
最後に、npx 7.1.0以降をインストールする必要もあります。
PropshaftはRails 7に依存しているので、移行を開始する前にアプリケーションをRails 7にアップグレードしておく必要があります。
1. Webpackerをjsbundling-railsに移行する
最初に以下の手順を進めます。
- Gemfileの
webpacker
をjsbundling-rails
に置き換える ./bin/bundle install
を実行する./bin/rails javascript:install:webpack
を実行するconfig/initializers/assets.rb
ファイルを削除するbin/webpack
ファイルを削除するbin/webpack-dev-server
ファイルを削除するconfig/webpack
フォルダを削除する(注: カスタム設定はすべて新しいwebpack.config.js
に移行すること)config/webpacker.yml
ファイルを削除する- コード内の
javascript_pack_tag
をすべてjavascript_include_tag
に置き換え、defer: true
を追加する
上の手順が完了すると、プロジェクトにさまざまなファイルが追加され、既存ファイルの一部も更新されていることがわかります。
新規ファイル: bin/devとProcfile.dev
./bin/dev
は、foremanとProcfile.dev
を用いて1つのターミナルから2つのプロセスを起動するシェルスクリプトです。後者のProcfile.dev
ファイルは、バンドルおよびJavaScriptファイルの変更監視を行うwebpack-dev-server
を置き換えます。
package.jsonにbuild
属性が追加される
これは、yarn build
でJavaScriptファイルをバンドルするのに使うコマンドです。
新規ファイル: webpack.config.js
このファイルはwebpacker
ではgem内に隠されていましたが、直接編集可能になりました。config/webpackディレクトリにカスタム設定を配置していた場合は、このファイルに設定を移動できます。エントリポイントを複数持つプロジェクトの場合は、以下のようにentry
属性を調整する必要があります。
module.exports = {
entry: {
application: "./app/javascript/application.js",
admin: "./app/javascript/admin.js"
}
}
app/assets/manifest.jsにlink_tree
ディレクティブが追加される
これは、assets:precompile
の実行中にapp/assets/buildsディレクトリ内のファイルをインクルードするようSprocketsに指示します。yarn build
でバンドルしたファイルはこのapp/assets/buildsディレクトリに置かれるので、これらもリポジトリにコミットしてください。アセットを整理するときに誤って削除しないようにしましょう。
babelの扱いについて
babelによるトランスパイルを引き続き利用したい場合は、手動で設定する必要があります。最初に、webpack.config.jsファイルを開いて以下を追加します。
module.exports = {
module: {
rules: [
{
test: /\.(js)$/,
exclude: /node_modules/,
use: ['babel-loader']
}
]
}
}
次にpackages.jsonファイルを開いて以下を追加します。
"babel": {
"presets": [
"./webpack.babel.js"
]
}
最後に、webpackers babel presetファイルをダウンロードしてファイル名をwebpack.babel.jsに変更し、packages.jsonと同じディレクトリに配置します。
モジュールの解決について
Webpackerは、モジュール解決の対象にsource_path
(デフォルトはapp/javascript/)を含めていたので、import 'channels'
などのステートメントによってapp/javascript/channels/がインポートされていました。jsbundling-rails
への移行後はこのように動作しません。この振る舞いを維持したい場合は、webpack.config.jsファイルを手動で更新して以下を含める必要があります。
module.exports = {
// ...
resolve: {
modules: ["app/javascript", "node_modules"],
},
//...
}
または、以下のように相対インポートを使うようにモジュールを変更する方法もあります。
- import 'channels'
+ import './channels'
JavaScriptからSassやSCSSを抽出する
Webpackerでは、webpacker.ymlファイル内のextract_css
を有効にすることで、JavaScriptからSassやSCSSを抽出可能です。これによって、たとえばimport '../scss/application.scss
のようにJavaScript内のソースファイルもインクルードできます。
この機能を残したい場合は、以下の手順を実行します。
yarn add mini-css-extract-plugin sass sass-loader css-loader
を実行する- webpack.config.jsファイルを更新して
mini-css-extract-plugin
をrequire
し、ローダーを設定する(以下の例を参照)。
webpack.config.jsファイルの例:
const path = require("path")
const webpack = require("webpack")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module.exports = {
mode: "production",
devtool: "source-map",
entry: {
application: "./app/javascript/application.js"
},
resolve: {
modules: ["app/javascript", "node_modules"],
},
output: {
filename: "[name].js",
sourceMapFilename: "[file].map",
path: path.resolve(__dirname, "app/assets/builds"),
},
plugins: [
new MiniCssExtractPlugin(),
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1
})
],
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
},
],
},
}
2. sass-railsをcssbundling-railsに移行する
メモ: これまでWebpackerのextract_css
を用いてCSSをビルドしていて、sass-rails
をrequire
していなかった場合は、このセクションの手順を省略できます。
最初に以下の手順を実行します。
- Gemfileに
cssbundling-rails
を追加する ./bin/bundle install
を実行する./bin/rails css:install:sass
を実行する
手順が完了すると、いくつかのファイルが更新されていることがわかります。
Procfile.devファイルに新しいプロセスが追加される
JavaScriptのプロセスの場合と同様に、この新しいプロセスもCSSファイルのバンドルと変更監視を行います。
package.jsonにbuild:css
属性が追加される
これは、yarn build
でCSSファイルをバンドルするのに使うコマンドです。
app/assets/manifest.jsからlink_tree
ディレクティブが削除される
CSSファイルはapp/assets/buildディレクトリに配置されるようになったので、Sprocketsはapp/assets/stylesheetsフォルダのことを気にする必要がなくなりました。他にもCSSファイル用のlink_tree
がある場合は、それらも削除してください。
エントリポイントを複数設定する
Sprocketsがコンパイルするのは、manifest.jsファイル内に記載されているrootディレクトリ内のファイルだけですが、yarn build
で使うsassパッケージもサブフォルダをチェックするので、scssファイルで@import
や変数などの機能を使っている場合はコンパイルエラーが発生する可能性もあります。このため、アプリに複数のエントリポイントがある場合は追加の作業が必要です。
app/asset/stylesheetsフォルダ内が以下のような構成になっているとします。
stylesheets/admin.scss
stylesheets/admin/source_1.scss
stylesheets/admin/source_2.scss
stylesheets/application.scss
stylesheets/application/source_1.scss
stylesheets/application/source_2.scss
最初に、エントリポイントを以下のように他のファイルと別ディレクトリに配置し、新しい構成に合わせて@import
を調整します。
stylesheets/entrypoints/admin.scss
stylesheets/entrypoints/application.scss
stylesheets/sources/admin/source_1.scss
stylesheets/sources/admin/source_2.scss
stylesheets/sources/application/source_1.scss
stylesheets/sources/application/source_2.scss
次に、packages.jsonファイル内のbuild
属性を調整します。
"build:css": "sass ./app/assets/stylesheets/entrypoints:./app/assets/builds --no-source-map --load-path=node_modules"
非推奨警告
Sassで除算などの機能を使っていると非推奨警告が表示される可能性もありますが、修正方法は警告メッセージに表示されます。修正方法がわからない場合はSassの公式ドキュメントで詳細をチェックしてください。
3. SprocketsをPropshaftに移行する
最初に以下の手順を実行します。
- Gemfileから
sprockets
、sprockets-rails
、sass-rails
を削除し、propshaft
を追加する ./bin/bundle install
を実行する- config/application.rbファイルを開いて
config.assets.paths << Rails.root.join('app','assets')
を削除する - asset/config/manifest.jsファイルを削除する
- CSSファイル内のアセットヘルパー(
image_url
、font_url
をすべて標準のurls
に置き換える - Railsフレームワークのインポートに
rails/all
を使わずに個別の機能をインポートしている場合は、require "sprockets/railtie"
を削除する
アセットのパス
Propshaftは、プロジェクトおよびGemfile内にあるすべてのgemについて、探索パス内にある以下のフォルダを自動的にインクルードします。
vendor/assets
lib/assets
app/assets
インクルードされているファイルをすべて表示するには、rakeタスクのreveal
が利用できます。
rake assets:reveal
アセットヘルパー
Sprocketsと異なり、Propshaftはasset_path
やasset_url
やimage_url
といったアセットヘルパーに依存していません。代わりにPropshaftは、CSSファイル内にあるすべての url
関数を探索し、それらの関数が参照するアセットのダイジェストをインクルードするよう調整します。
以下のように、使っているCSSファイルをくまなく調べて必要な調整を行ってください。
- background: image_url('hero.jpg');
+ background: url('/hero.jpg');
ここで、Propshaftではパスが/
で始まっているのに、Sprocketsの場合はパスが/
で始まっていないことにご注意ください。これは、後者では絶対パスが使われ、前者では相対パスが使われるためです。この違いをよりよく理解するために、以下のディレクトリ構造で考えます。
assets/stylesheets/theme/main.scss
assets/images/hero.jpg
Sprocketsの場合、main.scss
はhero.jpgを以下のように参照します。
background: image_url('hero.jpg')
しかしPropshaftのurl
で同じパスを指定すると、theme/hero.jpgが見つからないというエラーになります。その理由は、Propshaftがすべてのパスを「処理中のファイルからの相対パス」として仮定するためです。ここではthemeフォルダ内にあるCSSファイルを処理していたので、hero.jpgファイルを同じフォルダ内で探索します。
パスの冒頭に/
を追加することで、このパスが絶対パスであると解釈するようPropshaftに指定できます。この変更によってアップグレードの手間が増えますが、そのおかげでFontAwesomeなどの外部ライブラリやBootstrapテーマなどがすぐ使えるようになります。
アセットのコンテンツ
アプリの画像をできるだけ素早く表示したいときに、小さなSVGファイルや低解像度版の画像をインライン化しておくというパターンがよく使われます。Propshaftでは、以下のコード行があらゆる環境で動作します。
Rails.application.assets.load_path.find('logo.svg').content
development環境でのプリコンパイル
Propshaftは、developmentモードで動的アセットリゾルバを使います。ただしローカルでassets:precompile
を実行すると、Propshaftが静的アセットリゾルバを使うように切り替わります。そうなるとアセットの変更が反映されなくなるので、アセットを変更するたびにアセットのプリコンパイルを実行しなければならなくなります1。ここがSprocketsと異なる点です。
関連記事
The post Rails: Sprockets->Propshaftアップグレードガイド(翻訳) first appeared on TechRacho.
概要
MITライセンスに基づいて翻訳・公開いたします。