mcpField
P
propshaft

propshaft

github.com/thinkoodle开发7188 次安装8 周前更新
代码仓库

关于 propshaft

面向 Rails 8 及更高版本应用的 Propshaft 资源管理技能,适合负责静态资源配置和维护的开发者。内容涵盖资源组织、指纹处理、CSS 层以及从 Sprockets 迁移时的差异。

主要功能

  1. 01组织 Rails 静态资源目录
  2. 02说明资源指纹与路径引用
  3. 03使用 @layer 控制 CSS 级联
  4. 04配置样式表加载与资源预编译
  5. 05指导从 Sprockets 迁移

适用场景

  1. 01在 Rails 8 应用中配置资源
  2. 02排查资源路径或指纹问题
  3. 03设置 CDN 或生产环境预编译
  4. 04将应用从 Sprockets 迁移到 Propshaft

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/thinkoodle/rails-skills --skill propshaft

安装后在 Claude Code 中输入/propshaft确认已加载。

附带文件reference.md

详细介绍

Rails Propshaft 资产管道专家

使用 Propshaft 管理 Rails 8+ 应用中的资产——这是一种现代、精简的资产管道,可直接提供文件,无需编译或打包。

何时使用此技能

  • 在 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不适用

Agent 最常犯的错误: 使用 //= require 或 Sprockets 时代的辅助函数。Propshaft 会完全忽略这些内容,它们会以纯文本形式出现在 CSS/JS 中。

理念

  1. 直接提供文件 — 无需编译、打包或转换
  2. 指纹处理是核心任务 — Propshaft 为文件生成摘要以实现缓存失效
  3. CSS @layer 控制层叠 — 不依赖文件加载顺序或清单声明
  4. 仅使用适合浏览器的资产 — Propshaft 需要浏览器可直接使用的 CSS/JS
  5. 简单优先于强大 — 需要打包/转译?添加 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;

这一行即可控制所有层叠优先级。列表中越靠后的层,优先级越高。

将每个文件包装在对应层中
/* 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 后,无论哪个文件先加载,声明的顺序始终生效:

  1. reset — 优先级最低(CSS 重置、box-sizing)
  2. base — 元素默认样式(body、标题、链接)
  3. components — UI 组件(卡片、按钮、表单)
  4. 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

此操作会:

  1. 将加载路径中的所有资源复制到 public/assets/
  2. 为文件名添加指纹(例如,application-a1b2c3.css)
  3. 将 CSS 中的 url() 引用重写为带摘要的路径
  4. 生成 .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"
}

添加新样式表

  1. 创建文件:

    app/assets/stylesheets/components/modal.css
    
  2. 使用适当的 @layer 包裹:

    @layer components {
      .modal { /* ... */ }
      .modal-overlay { /* ... */ }
    }
    
  3. 完成。 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 即可修复。

常见错误

  1. 使用 //= require — Propshaft 会完全忽略 Sprockets 指令
  2. 使用 image-url() 或 asset-url() — 这些是 Sass/Sprockets 辅助方法。请使用普通的 url("/path")
  3. 使用 :all 而不是 :app — :all 会加载你不需要的引擎 CSS
  4. 缺少 @layer 声明 — 没有层时,层叠顺序取决于不可预测的文件加载顺序
  5. 期待 Sass 编译 — Propshaft 提供的是纯 CSS。要使用 Sass,请使用 cssbundling-rails 或 dartsass-rails
  6. 在开发环境中预编译 — 这会创建 .manifest.json 并冻结资源。运行 assets:clobber
  7. CSS 中的 URL 格式错误 — 使用 url("/icons/check.svg"),而不是 url("icons/check.svg")(相对于根目录)
  8. 混淆 Propshaft 和 importmap-rails — Propshaft = CSS/图像/字体。Import maps = JavaScript 模块

从 Sprockets 迁移

完整的迁移检查清单请参见此技能目录中的 reference.md。

快速摘要:

  1. bundle remove sprockets sprockets-rails sass-rails
  2. 删除 config/assets.rb 和 assets/config/manifest.js
  3. bundle add propshaft(或升级到包含 Propshaft 的 Rails 8)
  4. 将 //= require 替换为空 — 文件会自动加载
  5. 将 image-url() / asset-url() 替换为 url("/path")
  6. 将 CSS 中的 asset_path() 替换为 url("/path")
  7. 添加 @layer 声明来控制层叠顺序
  8. 将 Sass 转换为纯 CSS(或添加 dartsass-rails/cssbundling-rails)

评论

支持 Markdown