15
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ビルドとアセットパイプラインの動作についてまとめてみた

Last updated at Posted at 2023-09-24

はじめに

プログラミング初学者の者です。
Rails7でミニアプリを作成していましたがビルドやアセットパイプラインの働きについて理解ができていなかったためまとめました。
間違いなども多いとは思うのですがご指摘いただけると嬉しいです。

対象者

  • Rails学習中の方

目次

タイトル 備考  
1 ミニアプリの動作環境
2 ビルドとはなんなのか
3 ビルドツールが動作するために必要なもの
4 アセットパイプラインとは
5 アセットパイプラインの各コマンド 
5 私が勘違いしていたこと 
5 開発環境と本番環境でのビルドとプリコンパイル 
1 Rails7で標準となったImport mapsとはなんなのか

ミニアプリの動作環境

バージョンは以下の通りです。
Rails 7.0.4
Dockerを使って開発しました。
また、CSSにはTailwindCSSを採用しています。
TailwindはRails7から採用されたImport maps経由ではなく、ESbuild経由でインストールしました。
以下はそのためのGemです。
gem 'cssbundling-rails'
gem 'jsbundling-rails'

ビルドとはなんなのか

ESbuildやWebpackやBabelなどのビルドツールで行われる圧縮などの動作ことを指します。
主に以下の3つを担当します。

  1. 複数ファイルのソースを1ファイルにまとめる-bundle(バンドル)
  2. 空白などを取り除き圧縮する-minify
  3. 新しい規格で書かれたコードを古い規格に変換する

ここで先ほど挙げた複数ファイルのソースを1ファイルにまとめるバンドルと後で説明するアセットパイプラインのプリコンパイルによるファイルの結合の違いを説明します。
バンドルは、モジュールや外部のライブラリなどのリンク処理を行って動的ファイルから静的ファイルにします。その一方でアセットパイプラインで行われるファイルの結合は異なったローカルファイルのJavaScriptファイル同士を結合して一つのファイルにするという違いがあります。

ビルドのイメージの参考に以下の記事がわかりやすかったです。

ESbuildをビルドツールとして指定した場合は元のファイルの置き場所(バンドル前のエントリーポイントとなるファイル)は、app/assets/javascriptsになります。
そしてビルド後のファイルの置き場所はapp/assets/buildsディレクトリになります。
Webpackを使う場合はapp/javascript/packsとなるようです。

自分のプロジェクトのファイルを確認したところ今回CSSのビルドツールとしてESbuildではなくTailwindが使われているようでした。app/assets/buildsディレクトリに空白などが取り除かれたファイルのapplication.cssがあることが確認できました。
package.jsonに以下の記述があったためだと考えられます。
app/assets/stylesheets/application.tailwind.cssという元のCSSのファイルをminifyしたものがapp/assets/builds/application.cssに配置されるように指定されています。

package.json
"scripts": {
    "build:css": "tailwindcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css --minify",
    "build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=/assets"
  },

app/assets/builds/application.css

/*! tailwindcss v3.3.3 |solid #e5e7eb}:after,:before{--tw-content:""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto S

ビルドツールが動作するために必要なもの

ESbuildなどのビルドツールについて説明しましたが、これらのツールが動くために必要なものを2つ説明します。
1つ目はNode.jsで2つ目はjs-bundlingとcss-bundlingです。

1. Node.jsについて

まずNode.jsについて説明します。
Node.jsの仕組みは主に大きく2つに分けられます。

  1. JavaScriptをブラウザ以外で実行するためのランタイム環境としての役割
    ランタイム環境とはコンピュータプログラムが実行されるための実行環境のことを指します。
    ESbuildはJavaScriptファイルの圧縮などを行ってくれるGo言語で記述されたツールです。
    そのESbuildがビルドする対象となるJavaScriptはブラウザ環境上で動作するのですが、ESbuildなどのビルドツールがサーバー上でJavaScriptファイルを処理するためにNode.jsが必要となります。
    ESbuild自体はGo言語で書かれているツールですが、その入力と出力はJavaScriptで行われるためJavaSciptの動作環境としてNode.jsが必要となります。

  2. npmを利用してJavaScriptライブラリを簡単にプロジェクトに追加する役割
    パッケージの依存管理ツールとしての役割です。JavaScriptのライブラリをプロジェクトに追加したり管理したりするのに使います。node_moduleディレクトリ上でパッケージが管理されます。

2. js-bundlingとcss-bundlingについて

これらはESbuildなどが動作するために必要なGemになります。
Railsアプリケーション内でESbuildを使う際に、設定やビルドの手順を簡単にするために必要になるらしいです。

アセットパイプラインとは

ここまでESbuildなどのビルドについて説明してきましたがここからはアセットパイプラインについて説明していきたいと思います。

アセットパイプラインとはリクエストがあった際にいちいちブラウザが3つのファイルを要求していたとすると3回サーバーにリクエストを送らなけらばならなくなって面倒なためファイルをビルドしてそれらのローカルのファイルを結合することです。その役割を担うのがSprocketsになります。

ファイルがSprocketsによって結合されるには、どう結合するかという設定が必要になります。
この設定は通常はapp/assets/config/manifest.jsファイルに書かれています。このファイルのことをマニュフェストファイルと呼びます。
このファイルは、アプリケーションで使用するアセットファイルをSprocketsがどのようにコンパイルするかを指定するためのものです。
以下で例のファイルを示します。

//= link_tree ../images
//= link_tree ../builds

これは//= link_tree ../buildsなら
ビルドされた後のapp/assets/builds ディレクトリ内のすべてのファイルをアセットとして結合してブラウザへ送ることを意味します。

このマニュフェストファイルに書いた設定を参考にしてターミナルでrake assets:precompileとコマンドを打つことでプリコンパイルを行うことができます。

rake assets:precompile

結果としてpublic/assetsディレクトリに以下のように結合されたファイルが配置されます。

$ rake assets:precompile

🌼 daisyUI 3.5.1 https://daisyui.com
╰╮
 ╰─ 
.
.

Done in 325ms.
Done in 1.22s.

/sample-app/public/assets/manifest-xxxxxxxxxxxxxxxxxxxxxx.js
/sample-app/public/assets/manifest-oooooooooooooooooooooo.js.gz
.
.

実際アセットパイプラインによってプリコンパイルされたファイルの確認をしてみると以下のようになっていました。

Rails.application.assets_manifest.assets
=> 
{"manifest.js"=>"manifest-xxxxxxxxxxxxxxxxxxxxxxxx.js",
 "application.css"=>"application-oooooooooooooooooo.css",
 "turbo.js"=>"turbo-1111111111111111111111111.js",
 "turbo.min.js"=>"turbo.min-000000000000000000000000000.js"}
.
.

またなぜconfig/manifest.jsの//= link_tree ../buildsでapp/assets/buildsディレクトリを読み込むのかということですが、読み込むファイルはconfig/initializers/assets.rb設定ファイルで設定することができます。
node_module以下のディレクトリをアセットパイプラインを利用して結合するようにしたい場合は以下のように設定ファイルに追記すると読み込んでくれます。

# config/initializers/assets.rb
Rails.application.config.assets.paths << Rails.root.join('node_modules')

アセットパイプラインの各コマンド

以下でrails consoleからアセットパイプラインの設定を確認できるコマンドを紹介します。

1つ目

プリコンパイル対象として登録されているパスを確認できます。

Rails.application.config.assets.paths
pry(main)> Rails.application.config.assets.paths
=> ["sample_app/app/assets/builds",
 "/sample_app/app/assets/config",
 "/sample_app/app/assets/images",
 "/sample_app/app/assets/javascripts",
 "/sample_app/app/assets/stylesheets",
 "/usr/local/bundle/gems/cocoon-1.2.15/app/assets/javascripts",
 "/usr/local/bundle/gems/jquery-rails-4.5.0/vendor/assets/javascripts",
 "/usr/local/bundle/gems/font-awesome-rails-4.7.0.8/app/assets/fonts",
 "/usr/local/bundle/gems/font-awesome-rails-4.7.0.8/app/assets/stylesheets",
 "/usr/local/bundle/gems/actiontext-7.0.4/app/assets/javascripts",
 "/usr/local/bundle/gems/actiontext-7.0.4/app/assets/stylesheets",
 "/usr/local/bundle/gems/actioncable-7.0.4/app/assets/javascripts",
 "/usr/local/bundle/gems/activestorage-7.0.4/app/assets/javascripts",
 "/usr/local/bundle/gems/actionview-7.0.4/lib/assets/compiled",
 #<Pathname:ルートディレクトリ/node_modules>,# 設定でパスを追加することも可能です
 "/usr/local/bundle/gems/bootstrap-sass-3.4.1/assets/stylesheets",
 "/usr/local/bundle/gems/bootstrap-sass-3.4.1/assets/javascripts",
 "/usr/local/bundle/gems/bootstrap-sass-3.4.1/assets/fonts",
 "/usr/local/bundle/gems/bootstrap-sass-3.4.1/assets/images"]

2つ目

以下はプリコンパイルの対象となっているファイルです。

Rails.application.config.assets.precompile
Rails.application.config.assets.precompile
=> 
["manifest.js",
 "turbo.js",
 "turbo.min.js",
 "turbo.min.js.map",
 "actiontext.js",
 "trix.js",
 "trix.css",
 "es-module-shims.js",
 "es-module-shims.min.js",
 "es-module-shims.js.map",
 "stimulus.js",
 "stimulus.min.js",
 "stimulus.min.js.map",
 "activestorage",
 "activestorage.esm",
 "actioncable.js",
 "actioncable.esm.js"]

3つ目

以下はプリコンパイルされてどのファイルができたかということを示すコマンドです。
このファイルはブラウザのソースから確認できます。

Rails.application.assets_manifest.assets
Rails.application.assets_manifest.assets
=> 
{"manifest.js"=>"manifest-xxxxxxxxxxxxxxxxxxxxxxxx.js",
 "application.css"=>"application-oooooooooooooooooo.css",
 "turbo.js"=>"turbo-1111111111111111111111111.js",
 "turbo.min.js"=>"turbo.min-000000000000000000000000000.js"}
.
.

私が勘違いしていたこと

  1. アセットパイプラインによるプリコンパイルではローカルのファイルの結合しか行っていないと思っていました。今回rake assets:precompileコマンドを実行したときにpackage.jsonにyarn buildが設定されていないことでエラーが起きたのでビルドもプリコンパイル時におそらく行っているのだと思います。しかしプリコンパイル時のビルドはESbuildで行われるのではなくSprocketsで行われるのではないかと思っていたので疑問に思っています。
    package.jsonにyarn buildのスクリプトを追記して再度コマンドを実行したところエラーが解消されました。

  2. 開発環境でもプリコンパイルされたファイルを参照しているのかと思っていましたがrake assets:precompileのコマンドを実行するか設定を変更しない限りはbin/devした後のビルドされた後のファイルをブラウザが参照していました。

開発環境と本番環境でのビルドとプリコンパイル

ビルドやアセットファイルの結合を促すコマンドがどこに設定されているか気になったので調べてみました。
開発環境と本番環境について私が作ったプロジェクトでどのようにビルドやアセットパイプラインが設定されているかを確認していきたいと思います。

1. 本番環境

まずデプロイ後に実行されるファイルであるentorypoint.shファイルにプリコンパイルのコマンドを設定しました。

entorypoint.sh
if [ "$RAILS_ENV" = "production" ]; then
bundle exec rails assets:clobber
bundle exec rails assets:precompile

bundle exec rails assets:precompileはconfig/manifest.rbに従ってアセットを結合するコマンドです。

プリコンパイルのコマンドは他にも以下の方法で実行することができます。

  1. ターミナル上で手動でrake assets:precompileを実行する
  2. config/environments/production.rbでリクエスト時にコンパイルされるようにtrueに設定する。
# config/environments/production.rb
config.assets.compile = true

また本番環境でプリコンパイルされたファイルはホスト名/assets以下のパスを入力するとWebで検索した際にプリコンパイルされたファイルが表示されます。そのときpublic 配下のファイルを公開する設定をしていないとエラーになります。

開発環境

私は開発環境ではファイルに変更があるたびにbin/devしてビルドを行いそれをブラウザに反映させていました。
bin/devコマンドの元を辿っていきたいと思います。

#bin/dev
.
.
exec foreman start -f Procfile.dev "$@"
# Procfile.dev
bin/rails server
css: yarn build:css --watch
# package.json
"scripts": {
    "build:css": "tailwindcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css --minify",
    "build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=/assets"
.

ビルドはbin/devで行われ、アセットパイプラインによるプリコンパイルはbin/devでは行われないみたいです。開発環境では、おそらくapp/assets/buildsディレクトリのビルド後のファイルが反映されているのだと思います。
application.html.erbでは以下のようになっており、app/assets/builds/application.cssを読み込んでいることがわかります。

<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>

ちなみに本番環境でrake assets:precompileコマンドを実行しアセットパイプラインでプリコンパイルした後はローカルのファイルの変更があっても、public/assetsディレクトリの変更を優先するのでローカル環境でbin/devしてできたビルド後のファイルを反映させたい時は以下コマンドを実行したら反映されるようになります。

rails assets:clobber

Rails7で標準となったImport mapsとはなんなのか

Rails7では、標準ではWebpackといったビルドツールを使わず、代わりにImport mapsを使って処理を行っています。
importmap-railsを使うことで、Node.jsを使ったJavaScriptファイルのビルドが不要となり、Node.jsのインストールもデフォルトでは行わないようになっているらしいです。

今後勉強していきたいと思います。

Rails 7 : rails newのフロントエンド関連オプションの組み合わせを調べてみた
Rails 7.0 で標準になった importmap-rails とは何なのか?
Rails7のnewコマンドのオプションが多すぎて分からなくなった時のために(フロントエンド多め)
Rails 7: importmap-rails + tailwindcss-railsでnode.jsが不要な理由
Rails7 のフロントエンド関連の Gem を分類

15
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?