propshaft について
Rails 8 以降のアプリを保守する開発者向けの Propshaft アセット管理スキルです。アセット構成、フィンガープリント、CSS レイヤーに加え、Sprockets からの移行時の違いを説明します。
主な機能
- 01Rails アセットディレクトリの整理
- 02アセットのフィンガープリントとパスの解説
- 03@layer による CSS カスケード制御
- 04スタイルシート読み込みと事前コンパイルの設定
- 05Sprockets 移行の案内
活用シーン
- 01Rails 8 アプリのアセット設定
- 02アセットパスやフィンガープリントの問題調査
- 03CDN 利用や本番用事前コンパイルの設定
- 04Sprockets から Propshaft への移行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ thinkoodle/ rails-skills --skill propshaftインストール後、Claude Code で/propshaftを入力して読み込まれたか確認します。
同梱ファイルreference.md
詳細
Rails Propshaft アセットパイプラインのエキスパート
Propshaft を使用して Rails 8 以降のアプリケーションのアセットを管理します。Propshaft は、コンパイルやバンドルを行わずにファイルを直接配信する、モダンで最小限のアセットパイプラインです。
このスキルを使用する場面
- Rails 8 以降のアプリでアセットを設定または整理するとき
- Sprockets から Propshaft に移行するとき
- アセットパスの破損、スタイルシートの欠落、フィンガープリントの問題を修正するとき
- CDN、インポートマップ、アセットのプリコンパイルを設定するとき
- Propshaft の規約に沿って CSS/JS のファイル構成を整理するとき
重要な基本概念
Propshaft は Sprockets ではありません。 すぐに Sprockets のパターンで考えるのをやめてください。
| Sprockets(旧式 — 使用禁止) | Propshaft(Rails 8 のデフォルト) |
|---|---|
//= require ディレクティブ | 不要 — すべてのファイルが自動的に配信されます |
//= require_tree . | 不要 — ディレクトリが自動的に含まれます |
CSS 内の asset_path("image.png") | CSS 内の url("/image.png") |
Sass ヘルパーの image-url("bg.png") | プレーン CSS の url("/bg.png") |
manifest.js ファイル | 不要 — マニフェストはありません |
| Sass/SCSS のコンパイル | プレーン CSS(または cssbundling-rails を使用) |
| アセットのコンパイル手順 | コンパイルなし — ファイルはそのまま配信されます |
config.assets.compile = true | 該当しません |
エージェントが最もよく犯す間違い: //= require や Sprockets 時代のヘルパーを使うことです。Propshaft はこれらを完全に無視するため、CSS/JS 内に文字列としてそのまま表示されます。
基本方針
- ファイルは直接配信されます — コンパイル、バンドル、変換はありません
- 主な役割はフィンガープリントです — Propshaft はキャッシュを更新できるよう、ファイルのダイジェストを生成します
- カスケードは CSS の
@layerで制御します — ファイルの読み込み順やマニフェストの宣言ではありません - ブラウザーで利用できるアセットのみを使用します — Propshaft が想定するのは、ブラウザーが処理できる CSS/JS です
- 高機能さよりシンプルさを優先します — バンドルやトランスパイルが必要な場合は、jsbundling-rails または cssbundling-rails を追加します
ファイル構成
標準的なディレクトリ構成
app/assets/
├── stylesheets/
│ ├── application.css # Main entry — may just declare @layer order
│ ├── _global.css # Design tokens (CSS custom properties)
│ ├── reset.css # CSS reset
│ ├── base.css # Base element styles
│ ├── utilities.css # Utility classes
│ └── components/
│ ├── buttons.css
│ ├── cards.css
│ ├── forms.css
│ ├── alerts.css
│ └── navigation.css
├── images/
│ ├── logo.svg
│ └── icons/
│ ├── arrow.svg
│ └── check.svg
└── fonts/
├── inter-regular.woff2
└── inter-bold.woff2
主なルール
app/assets/stylesheets/内のすべての.cssファイルが配信可能です- アンダースコアのプレフィックス(
_global.css)は単なる慣例です — Propshaft は特別扱いしません - マニフェストファイルはありません — Sprockets とは異なり、
manifest.jsも//= requireを記述したapplication.cssも必要ありません - サブディレクトリも使えます —
components/cards.cssはcomponents/cards.cssとして配信されます
@layer を使った CSS の整理
レイヤーの順序を宣言する(重要)
application.css または _global.css で、カスケードの順序を宣言します。
/* app/assets/stylesheets/application.css */
@layer reset, base, components, utilities;
この 1 行で、カスケードの優先順位がすべて決まります。後に記述されたレイヤーほど優先されます。
各ファイルをレイヤーで囲む
/* app/assets/stylesheets/reset.css */
@layer reset {
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; }
}
/* app/assets/stylesheets/components/cards.css */
@layer components {
.card {
padding: var(--space-4);
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
}
}
/* app/assets/stylesheets/utilities.css */
@layer utilities {
.hidden { display: none !important; }
.text-center { text-align: center; }
}
@layer が重要な理由
@layer がない場合、CSS の詳細度はファイルの読み込み順に左右されますが、Propshaft はその順序を保証しません。@layer を使えば、どのファイルが先に読み込まれても、宣言した順序が常に優先されます。
reset— 最も低い優先順位(CSS リセット、ボックスサイズ)base— 要素のデフォルト(body、見出し、リンク)components— UI コンポーネント(カード、ボタン、フォーム)utilities— 最も高い優先順位(あらゆるスタイルを上書き)
いずれの @layer にも属さないスタイルは、最も高い詳細度を持ちます — すべてのレイヤーより優先されます。意図的に使用してください。
CSS カスタムプロパティを使ったデザイントークン
/* app/assets/stylesheets/_global.css */
:root {
/* Colors */
--color-primary: #2563eb;
--color-surface: #ffffff;
--color-border: #e5e7eb;
--color-text: #111827;
--color-text-muted: #6b7280;
/* Spacing */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
/* Typography */
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--font-medium: 500;
--font-bold: 700;
/* Borders */
--radius-sm: 0.25rem;
--radius-md: 0.375rem;
--radius-lg: 0.5rem;
}
light-dark() を使ったダークモード:
:root {
color-scheme: light dark;
--color-surface: light-dark(#ffffff, #1f2937);
--color-text: light-dark(#111827, #f9fafb);
}
レイアウトでのスタイルシートの読み込み
:app シンボル(推奨)
<%# Loads ALL stylesheets from app/assets/stylesheets/ %>
<%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>
特定のファイルを読み込む
<%# Load third-party CSS first, then app CSS %>
<%= stylesheet_link_tag "actiontext", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>
:all は絶対に使わない
<%# BAD — loads engine CSS too (e.g., Bulma from mission_control-jobs) %>
<%= stylesheet_link_tag :all %>
<%# GOOD — only your app's stylesheets %>
<%= stylesheet_link_tag :app %>
明示的にファイル順を指定する(必要な場合)
<%= stylesheet_link_tag "reset", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag "base", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag "main", "data-turbo-track": "reload" %>
アセットの参照
ビュー内(ERB)
<%# Images %>
<%= image_tag "logo.svg", alt: "Company Logo" %>
<%= image_tag "icons/arrow.svg", alt: "Arrow", class: "icon" %>
<%# Stylesheets %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%# Favicon %>
<%= favicon_link_tag "favicon.ico" %>
<%# Generic asset path %>
<%= asset_path("document.pdf") %>
CSS 内
/* Use root-relative paths — Propshaft rewrites these to digested URLs */
.hero {
background-image: url("/bg/pattern.svg");
}
.icon-check {
background-image: url("/icons/check.svg");
}
@font-face {
font-family: "Inter";
src: url("/fonts/inter-regular.woff2") format("woff2");
font-weight: 400;
}
Propshaft はプリコンパイル時に、url("/bg/pattern.svg") を url("/assets/bg/pattern-abc123.svg") に自動的に書き換えます。
JavaScript 内
RAILS_ASSET_URL マクロを使用します。
// Propshaft transforms this during precompilation
const trashIcon = RAILS_ASSET_URL("/icons/trash.svg");
// Becomes: "/assets/icons/trash-54g9cbef.svg"
インポートマップを使った JavaScript
Propshaft は CSS/画像/フォントを処理します。JavaScript は importmap-rails(Rails 8 のデフォルト)で管理します。
app/javascript/
├── application.js # Entry point
└── controllers/ # Stimulus controllers
├── hello_controller.js
└── modal_controller.js
<%# In layout — this is importmap-rails, not Propshaft %>
<%= javascript_importmap_tags %>
# config/importmap.rb
pin "application"
pin "@hotwired/turbo-rails", to: "turbo.min.js"
pin "@hotwired/stimulus", to: "stimulus.min.js"
pin_all_from "app/javascript/controllers", under: "controllers"
重要な違い: Propshaft は JS ファイルにフィンガープリントを付与します。インポートマップはモジュール名を URL に解決します。両者は互いを補完します — Propshaft は JS をバンドルも変換もしません。
画像とフォント
画像
app/assets/images/ に配置します。ヘルパーで参照します。
<%= image_tag "logo.svg", alt: "Logo", width: 200 %>
<%= image_tag "icons/edit.svg", class: "icon", alt: "" %>
CSS 内では次のようにします。
.logo { background-image: url("/logo.svg"); }
フォント
app/assets/fonts/ に配置します。@font-face で宣言します。
@font-face {
font-family: "Inter";
font-weight: 400;
font-style: normal;
font-display: swap;
src: url("/fonts/inter-regular.woff2") format("woff2");
}
@font-face {
font-family: "Inter";
font-weight: 700;
font-style: normal;
font-display: swap;
src: url("/fonts/inter-bold.woff2") format("woff2");
}
body {
font-family: "Inter", system-ui, sans-serif;
}
デプロイとプリコンパイル
プリコンパイルコマンド
RAILS_ENV=production rails assets:precompile
または、実際のシークレットを使わずに実行します。
RAILS_ENV=production SECRET_KEY_BASE_DUMMY=1 rails assets:precompile
このコマンドでは、次の処理が行われます。
- ロードパスからすべてのアセットを
public/assets/にコピーします - ファイル名にフィンガープリントを付与します(例:
application-a1b2c3.css) - CSS 内の
url()の参照先を、ダイジェスト付きパスに書き換えます - 元のファイル名とダイジェスト付きファイル名の対応を記した
.manifest.jsonを生成します
CDN の設定
# config/environments/production.rb
config.asset_host = ENV.fetch("CDN_HOST", nil)
# e.g., CDN_HOST=https://cdn.example.com
キャッシュヘッダー(Web サーバー)
Nginx:
location ~ ^/assets/ {
expires 1y;
add_header Cache-Control public;
add_header ETag "";
}
Apache:
<Location /assets/>
Header unset ETag
FileETag None
ExpiresActive On
ExpiresDefault "access plus 1 year"
</Location>
本番環境の Cache-Control
# config/environments/production.rb
config.public_file_server.headers = {
"Cache-Control" => "public, max-age=31536000"
}
スタイルシートの追加
-
ファイルを作成します。
app/assets/stylesheets/components/modal.css -
適切な
@layerで囲みます。@layer components { .modal { /* ... */ } .modal-overlay { /* ... */ } } -
これで完了です。 Propshaft が自動的に読み込みます。マニフェストの更新も、require ディレクティブも不要です。
サードパーティ/エンジンの CSS
CSS を提供する gem(例:Action Text、Trix)を使用する場合:
<%# Load third-party BEFORE :app so your styles can override %>
<%= stylesheet_link_tag "actiontext", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>
アセットパスの追加
# config/initializers/assets.rb
Rails.application.config.assets.paths << Rails.root.join("vendor/assets/stylesheets")
Rails.application.config.assets.paths << Emoji.images_path
ダイジェスト処理からのパスの除外
# config/initializers/assets.rb
# Useful when using cssbundling-rails with Sass source files
config.assets.excluded_paths = [Rails.root.join("app/assets/stylesheets")]
デバッグ
# List all available assets
bin/rails assets:reveal
# In Rails console
Rails.application.assets.resolver.logical_paths.to_a
# Clear precompiled assets (fix stale development assets)
bin/rails assets:clobber
開発環境でよくある問題: アセットが更新されなくなった場合は、開発環境で assets:precompile を実行した可能性があります。public/assets/ 内の .manifest.json によって、Rails はプリコンパイル済みファイルを使用します。rails assets:clobber を実行して解決してください。
よくある間違い
//= requireを使う — Propshaft は Sprockets ディレクティブを一切無視しますimage-url()やasset-url()を使う — これらは Sass/Sprockets のヘルパーです。通常のurl("/path")を使用してください:appの代わりに:allを使う —:allは不要なエンジンの CSS も読み込みます@layer宣言がない — レイヤーがないと、カスケードは予測できないファイルの読み込み順に左右されます- Sass のコンパイルを想定する — Propshaft は通常の CSS を配信します。Sass を使用するには、cssbundling-rails または dartsass-rails を使用してください
- 開発環境でプリコンパイルする — アセットを固定する
.manifest.jsonが作成されます。assets:clobberを実行してください - CSS の URL 形式が間違っている —
url("icons/check.svg")ではなく、url("/icons/check.svg")を使用してください(ルート相対パス) - Propshaft と importmap-rails を混同する — Propshaft は CSS/画像/フォント用、Import Maps は JavaScript モジュール用です
Sprockets からの移行
完全な移行チェックリストについては、このスキルディレクトリの reference.md を参照してください。
概要:
bundle remove sprockets sprockets-rails sass-railsconfig/assets.rbとassets/config/manifest.jsを削除しますbundle add propshaftを実行します(または Propshaft が含まれる Rails 8 にアップグレードします)//= requireを削除します。ファイルは自動的に読み込まれますimage-url()/asset-url()をurl("/path")に置き換えます- CSS 内の
asset_path()をurl("/path")に置き換えます - カスケードを制御するために
@layer宣言を追加します - Sass を通常の CSS に変換します(または dartsass-rails/cssbundling-rails を追加します)