更多内容请访问 rubyonrails.org:

资产管道

本指南解释了如何处理基本的资产管理任务。

阅读本指南后,您将了解

  • 什么是资产管道。
  • Propshaft 的主要功能,以及如何设置它。
  • 如何从 Sprockets 迁移到 Propshaft。
  • 如何使用其他库进行更高级的资产管理。

1. 什么是资产管道?

Rails 资产管道是一个旨在组织、缓存和提供静态资产(如 JavaScript、CSS 和图像文件)的库。它简化并优化了这些资产的管理,以提高应用程序的性能和可维护性。

Rails 资产管道由 Propshaft 管理。Propshaft 的构建是为了适应一个转译、打包和压缩对于基本应用程序不再那么重要的时代,这得益于更好的浏览器支持、更快的网络和 HTTP/2 功能。

Propshaft 专注于基本的资产管理任务,并将更复杂的任务(例如 JavaScript 和 CSS 打包和缩小)留给专门的工具,例如 jsbundling-railscssbundling-rails,它们可以单独添加到您的应用程序中。Propshaft 专注于指纹识别,并强调为资产生成基于摘要的 URL,允许浏览器缓存它们,从而最大限度地减少复杂编译和打包的需求。

Propshaft gem 在新应用程序中默认启用。如果由于某种原因,您想在设置过程中禁用它,可以使用 --skip-asset-pipeline 选项

$ rails new app_name --skip-asset-pipeline

在 Rails 8 之前,资产管道由 Sprockets 提供支持。您可以在 Rails 指南的早期版本中阅读有关 Sprockets 资产管道的信息。您还可以探索资产管理技术的演变,了解 Rails 资产管道是如何随着时间推移而演变的。

2. Propshaft 功能

Propshaft 期望您的资产已经是浏览器就绪的格式——例如纯 CSS、JavaScript 或预处理图像(如 JPEG 或 PNG)。它的任务是有效地组织、版本化和提供这些资产。在本节中,我们将介绍 Propshaft 的主要功能以及它们的工作原理。

2.1. 资产加载顺序

使用 Propshaft,您可以通过明确指定每个文件并手动组织它们,或者确保它们按正确的顺序包含在您的 HTML 或布局文件中来控制依赖文件的加载顺序。这确保了依赖项在不依赖自动化依赖管理工具的情况下得到管理和加载。以下是一些管理依赖项的策略

  1. 手动按正确的顺序包含资产

    在您的 HTML 布局(通常是 Rails 应用程序的 application.html.erb)中,您可以通过按特定顺序单独包含每个文件来指定 CSS 和 JavaScript 文件的确切加载顺序。例如

    <!-- application.html.erb -->
    <head>
     <%= stylesheet_link_tag "reset" %>
     <%= stylesheet_link_tag "base" %>
     <%= stylesheet_link_tag "main" %>
    </head>
    <body>
     <%= javascript_include_tag "utilities" %>
     <%= javascript_include_tag "main" %>
    </body>
    

    这很重要,例如,如果 main.js 依赖于 utilities.js 首先加载。

  2. 在 JavaScript 中使用模块 (ES6)

    如果您的 JavaScript 文件中存在依赖项,ES6 模块可以提供帮助。通过使用 import 语句,您可以明确控制 JavaScript 代码中的依赖项。只需确保您的 JavaScript 文件在 HTML 中使用 <script type="module"> 设置为模块

    // main.js
    import { initUtilities } from "./utilities.js";
    import { setupFeature } from "./feature.js";
    
    initUtilities();
    setupFeature();
    

    然后在您的布局中

    <script type="module" src="main.js"></script>
    

    这样,您就可以在 JavaScript 文件中管理依赖项,而无需依赖 Propshaft 来理解它们。通过导入模块,您可以控制文件的加载顺序并确保满足依赖项。

  3. 必要时合并文件

    如果您有几个 JavaScript 或 CSS 文件必须始终一起加载,您可以将它们合并到一个文件中。例如,您可以创建一个 combined.js 文件,该文件从其他脚本导入或复制代码。然后,只需在您的布局中包含 combined.js 即可避免处理单个文件排序。这对于始终一起加载的文件很有用,例如一组实用函数或一组特定组件的样式。虽然这种方法适用于小型项目或简单的用例,但对于大型应用程序来说,它可能会变得繁琐且容易出错。

  4. 使用打包器打包您的 JavaScript 或 CSS

    如果您的项目需要像依赖链或 CSS 预处理这样的功能,您可能需要考虑与 Propshaft 一起使用高级资产管理

    jsbundling-rails 这样的工具将 Bunesbuildrollup.jsWebpack 集成到您的 Rails 应用程序中,而 cssbundling-rails 可用于处理使用 Tailwind CSSBootstrapBulmaPostCSSDart Sass 的样式表。

    这些工具通过处理复杂的处理来补充 Propshaft,而 Propshaft 则有效地组织和提供最终资产。

2.2. 资产组织

Propshaft 在 app/assets 目录中组织资产,其中包括 imagesjavascriptsstylesheets 等子目录。您可以将 JavaScript、CSS、图像文件和其他资产放入这些目录中,Propshaft 将在预编译过程中管理它们。

您还可以通过修改 config/initializers/assets.rb 文件中的 config.assets.paths 来指定 Propshaft 要搜索的其他资产路径。例如

# Add additional assets to the asset load path.
Rails.application.config.assets.paths << Emoji.images_path

Propshaft 将使所有来自配置路径的资产可供服务。在预编译过程中,Propshaft 将这些资产复制到 public/assets 目录中,确保它们已准备好用于生产环境。

资产可以通过 asset_pathimage_tagjavascript_include_tag 和其他资产帮助标签,通过它们的逻辑路径引用。在生产环境中运行 assets:precompile 后,这些逻辑引用将使用 .manifest.json 文件自动转换为它们的指纹路径。

可以从这个过程中排除某些目录,您可以在指纹识别部分阅读更多相关信息。

2.3. 指纹识别:基于摘要的 URL 版本控制

在 Rails 中,资产版本控制使用指纹识别将唯一标识符添加到资产文件名中。

指纹识别是一种使文件名依赖于其内容的技术。文件的内容摘要被生成并附加到文件名中。这确保了当文件内容发生变化时,其摘要(以及因此,其文件名)也随之变化。此机制对于有效缓存资产至关重要,因为当资产内容发生变化时,浏览器将始终加载资产的更新版本,从而提高性能。对于静态或不经常更改的内容,这提供了一种简单的方法来判断两个文件版本是否相同,即使是在不同的服务器或部署日期之间。

2.3.1. 资产摘要

资产组织部分所述,在 Propshaft 中,config.assets.paths 中配置的所有路径中的资产都可供服务,并将复制到 public/assets 目录中。

指纹识别后,像 styles.css 这样的资产文件名将重命名为 styles-a1b2c3d4e5f6.css。这确保了如果 styles.css 更新,文件名也会更改,从而强制浏览器下载最新版本,而不是使用可能过时的缓存副本。

2.3.2. 清单文件

在 Propshaft 中,.manifest.json 文件在资产预编译过程中自动生成。此文件将原始资产文件名映射到其指纹版本,确保正确的缓存失效和高效的资产管理。位于 public/assets 目录中的 .manifest.json 文件帮助 Rails 在运行时解析资产路径,允许它引用正确的指纹文件。

.manifest.json 包括 application.jsapplication.css 等主要资产以及图像等其他文件的条目。以下是 JSON 可能的样子示例

{
  "application.css": "application-6d58c9e6e3b5d4a7c9a8e3.css",
  "application.js": "application-2d4b9f6c5a7c8e2b8d9e6.js",
  "logo.png": "logo-f3e8c9b2a6e5d4c8.png",
  "favicon.ico": "favicon-d6c8e5a9f3b2c7.ico"
}

当文件名唯一且基于其内容时,可以设置 HTTP 头以鼓励各地缓存(无论是在 CDN、ISP、网络设备还是网络浏览器中)保留自己的内容副本。当内容更新时,指纹会改变。这将导致远程客户端请求新的内容副本。这通常被称为缓存清除。

2.3.3. 视图中的摘要资产

您可以使用标准的 Rails 资产帮助程序(如 asset_pathimage_tagjavascript_include_tagstylesheet_link_tag 等)在视图中引用摘要资产。

例如,在您的布局文件中,您可以像这样包含样式表

<%= stylesheet_link_tag "application", media: "all" %>

如果您正在使用 turbo-rails gem(Rails 默认包含),您可以包含 data-turbo-track 选项。这会导致 Turbo 检查资产是否已更新,如果更新,则将其重新加载到页面中

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

您可以像这样访问 app/assets/images 目录中的图像

<%= image_tag "rails.png" %>

当资产管道启用时,Propshaft 将提供此文件。如果文件存在于 public/assets/rails.png,它将由 Web 服务器提供。

或者,如果您正在使用指纹资产(例如,rails-f90d8a84c707a8dc923fca1ca1895ae8ed0a09237f6992015fef1e11be77c023.png),Propshaft 也将正确提供这些资产。指纹在预编译过程中自动应用。

图像可以组织到子目录中,您可以通过在标签中指定目录来引用它们

<%= image_tag "icons/rails.png" %>

最后,您可以在 CSS 中像这样引用图像

background: url("/bg/pattern.svg");

Propshaft 将自动将其转换为

background: url("/assets/bg/pattern-2169cbef.svg");

如果您正在预编译资产(参见生产部分),链接到不存在的资产将导致调用页面引发异常。这包括链接到空字符串。使用 image_tag 和其他带有用户提供数据的帮助程序时要小心。这确保了浏览器始终获取正确版本的资产。

2.3.4. JavaScript 中的摘要资产

在 JavaScript 中,您需要使用 RAILS_ASSET_URL 宏手动触发资产转换。以下是一个示例

export default class extends Controller {
  init() {
    this.img = RAILS_ASSET_URL("/icons/trash.svg");
  }
}

这将转换为

export default class extends Controller {
  init() {
    this.img = "/assets/icons/trash-54g9cbef.svg";
  }
}

这确保了在您的 JavaScript 代码中使用了正确、摘要化的文件。

如果您正在使用 Webpackesbuild 等打包器,您应该让打包器处理摘要过程。如果 Propshaft 检测到文件名中已存在摘要(例如,script-2169cbef.js),它将跳过再次摘要文件以避免不必要的重新处理。

对于使用 导入映射 管理资产,Propshaft 确保在预编译过程中正确处理导入映射中引用的资产并将其映射到其摘要路径。

2.3.5. 绕过摘要步骤

如果您需要引用相互引用的文件(例如 JavaScript 文件及其源映射),并且想要避免摘要过程,您可以手动预摘要这些文件。Propshaft 将模式为 -[digest].digested.js 的文件识别为已摘要的文件,并将保留其稳定的文件名。

2.3.6. 从摘要中排除目录

您可以通过将某些目录添加到 config.assets.excluded_paths 中,从而将其从预编译和摘要过程中排除。例如,如果您使用 app/assets/stylesheets 作为 Dart Sass 等编译器的输入,并且不希望这些文件成为资产加载路径的一部分,这将非常有用。

config.assets.excluded_paths = [Rails.root.join("app/assets/stylesheets")]

这将阻止指定目录被 Propshaft 处理,同时仍然允许它们成为预编译过程的一部分。

3. 使用 Propshaft

从 Rails 8 开始,Propshaft 默认包含。要使用 Propshaft,您需要正确配置它并以 Rails 可以高效地提供它们的方式组织您的资产。

3.1. 设置

请按照以下步骤在您的 Rails 应用程序中设置 Propshaft

  1. 创建一个新的 Rails 应用程序

    $ rails new app_name
    
  2. 组织您的资产

    Propshaft 期望您的资产位于 app/assets 目录中。您可以将资产组织到子目录中,例如用于 JavaScript 文件的 app/assets/javascripts、用于 CSS 文件的 app/assets/stylesheets 和用于图像的 app/assets/images

    例如,您可以在 app/assets/javascripts 中创建一个新的 JavaScript 文件

    // app/assets/javascripts/main.js
    console.log("Hello, world!");
    

    并在 app/assets/stylesheets 中创建一个新的 CSS 文件

    /* app/assets/stylesheets/main.css */
    body {
      background-color: red;
    }
    
  3. 在应用程序布局中链接资产

    在您的应用程序布局文件(通常是 app/views/layouts/application.html.erb)中,您可以使用 stylesheet_link_tagjavascript_include_tag 助手包含您的资产

    <!-- app/views/layouts/application.html.erb -->
    <!DOCTYPE html>
    <html>
      <head>
        <title>MyApp</title>
        <%= stylesheet_link_tag "main" %>
      </head>
      <body>
        <%= yield %>
        <%= javascript_include_tag "main" %>
      </body>
    </html>
    

    此布局在您的应用程序中包含 main.css 样式表和 main.js JavaScript 文件。

  4. 启动 Rails 服务器

    $ bin/rails server
    
  5. 预览您的应用程序

    打开您的网络浏览器并导航到 https://:3000。您应该会看到您的 Rails 应用程序以及包含的资产。

3.2. 开发

Rails 和 Propshaft 在开发和生产环境中的配置不同,以便在无需手动干预的情况下实现快速迭代。

3.2.1. 无缓存

在开发环境中,Rails 配置为绕过资产缓存。这意味着当您修改资产(例如 CSS、JavaScript)时,Rails 将直接从文件系统提供最新版本。无需担心版本控制或文件重命名,因为缓存已完全跳过。每次重新加载页面时,浏览器都会自动获取最新版本。

3.2.2. 资产自动重新加载

单独使用 Propshaft 时,它会在每次请求时自动检查 JavaScript、CSS 或图像等资产的更新。这意味着您可以编辑这些文件,重新加载浏览器,并立即看到更改,而无需重新启动 Rails 服务器。

当与 Propshaft 一起使用 JavaScript 打包器(例如 esbuildWebpack)时,工作流有效地结合了这两种工具

  • 打包器会监视 JavaScript 和 CSS 文件的更改,将它们编译到适当的构建目录中,并使文件保持最新。
  • Propshaft 确保在每次发出请求时将最新的编译资产提供给浏览器。

对于这些设置,运行 ./bin/dev 会启动 Rails 服务器和资产打包器的开发服务器。

无论哪种情况,Propshaft 都能确保您的资产更改在浏览器页面重新加载后立即反映出来,而无需重新启动服务器。

3.2.3. 使用文件观察器提高性能

在开发环境中,Propshaft 在每个请求之前使用应用程序的文件观察器(默认为 ActiveSupport::FileUpdateChecker)检查是否有任何资产已更新。如果您的资产数量众多,您可以通过使用 listen gem 并在 config/environments/development.rb 中配置以下设置来提高性能

config.file_watcher = ActiveSupport::EventedFileUpdateChecker

这将减少检查文件更新的开销并提高开发期间的性能。

3.3. 生产

在生产环境中,Rails 会启用缓存来提供资产以优化性能,确保您的应用程序能够高效地处理高流量。

3.3.1. 生产环境中的资产缓存和版本控制

指纹识别部分所述,当文件内容更改时,其摘要也会更改,因此浏览器会使用文件的更新版本。而如果内容保持不变,浏览器将使用缓存版本。

3.3.2. 预编译资产

在生产环境中,预编译通常在部署期间运行,以确保提供最新版本的资产。Propshaft 明确设计为不提供完整的转译器功能。但是,它确实提供了一个输入 -> 输出编译器设置,默认情况下用于将 CSS 中的 url(asset) 函数调用转换为 url(digested-asset),源映射注释也是如此。

要手动运行预编译,您可以使用以下命令

$ RAILS_ENV=production rails assets:precompile

执行此操作后,加载路径中的所有资产都将在预编译步骤中复制(或在使用高级资产管理时编译)并带上摘要哈希。

此外,您可以设置 ENV["SECRET_KEY_BASE_DUMMY"] 以触发使用随机生成的 secret_key_base,该密钥存储在临时文件中。这对于作为构建步骤的一部分预编译生产资产非常有用,而该构建步骤 otherwise 不需要访问生产机密。

$ RAILS_ENV=production SECRET_KEY_BASE_DUMMY=1 rails assets:precompile

默认情况下,资产从 /assets 目录提供。

在开发模式下运行预编译命令会生成一个名为 .manifest.json 的标记文件,它告诉应用程序可以提供已编译的资产。因此,您对源资产所做的任何更改都不会反映在浏览器中,直到预编译的资产更新。如果您的资产在开发模式下停止更新,解决方案是删除 public/assets/ 中的 .manifest.json 文件。您可以使用 rails assets:clobber 命令删除所有预编译的资产和 .manifest.json 文件。这将强制 Rails 动态重新编译资产,反映最新更改。

始终确保预期的编译文件名以 .js.css 结尾。

3.3.2.1. 远期过期头

预编译的资产存在于文件系统上,并由您的 Web 服务器直接提供。它们默认没有远期过期头,因此要获得指纹识别的好处,您必须更新您的服务器配置以添加这些头。

对于 Apache

# The Expires* directives require the Apache module
# `mod_expires` to be enabled.
<Location /assets/>
  # Use of ETag is discouraged when Last-Modified is present
  Header unset ETag
  FileETag None
  # RFC says only cache for 1 year
  ExpiresActive On
  ExpiresDefault "access plus 1 year"
</Location>

对于 NGINX

location ~ ^/assets/ {
  expires 1y;
  add_header Cache-Control public;

  add_header ETag "";
}

3.3.3. CDN

CDN 代表 内容分发网络,它们主要用于在全球范围内缓存资产,以便当浏览器请求资产时,缓存副本将在地理上靠近该浏览器。如果您在生产环境中直接从 Rails 服务器提供资产,最佳实践是在您的应用程序前端使用 CDN。

使用 CDN 的常见模式是将您的生产应用程序设置为“源”服务器。这意味着当浏览器从 CDN 请求资产并且缓存未命中时,它将从您的服务器获取文件然后缓存它。例如,如果您在 example.com 上运行 Rails 应用程序,并在 mycdnsubdomain.fictional-cdn.com 配置了 CDN,那么当向 mycdnsubdomain.fictional-cdn.com/assets/smile.png 发出请求时,CDN 将向您的服务器查询一次 example.com/assets/smile.png 并缓存请求。下次向 CDN 发出相同 URL 的请求将命中缓存副本。当 CDN 可以直接提供资产时,请求永远不会触及您的 Rails 服务器。由于 CDN 的资产在地理上更接近浏览器,请求速度更快,并且由于您的服务器不需要花费时间提供资产,它可以专注于提供应用程序代码。

3.3.3.1. 设置 CDN 以提供静态资产

要设置 CDN,您的应用程序需要在互联网上以公开可用的 URL 运行,例如 example.com。接下来,您需要从云托管提供商注册 CDN 服务。执行此操作时,您需要将 CDN 的“源”配置指向您的网站 example.com。请查阅您的提供商文档,了解如何配置源服务器。

您提供的 CDN 应该会为您分配一个自定义的应用程序子域名,例如 mycdnsubdomain.fictional-cdn.com(请注意,fictional-cdn.com 在本文撰写时不是有效的 CDN 提供商)。现在您已经配置了 CDN 服务器,您需要告诉浏览器使用您的 CDN 来获取资产,而不是直接使用您的 Rails 服务器。您可以通过配置 Rails 将您的 CDN 设置为资产主机,而不是使用相对路径来完成此操作。要在 Rails 中设置您的资产主机,您需要在 config/environments/production.rb 中设置 config.asset_host

config.asset_host = "mycdnsubdomain.fictional-cdn.com"

您只需提供“主机”,即子域名和根域名,无需指定协议或“方案”,例如 http://https://。当请求网页时,默认情况下,生成的资产链接中的协议将与网页的访问方式匹配。

您还可以通过环境变量设置此值,以使运行站点的暂存副本更容易

config.asset_host = ENV["CDN_HOST"]

您需要在服务器上将 CDN_HOST 设置为 mycdnsubdomain.fictional-cdn.com 才能使其工作。

配置好服务器和 CDN 后,来自以下助手的资产路径

<%= asset_path('smile.png') %>

将呈现为完整的 CDN URL,例如 http://mycdnsubdomain.fictional-cdn.com/assets/smile.png(为便于阅读省略摘要)。

如果 CDN 有 smile.png 的副本,它会将其提供给浏览器,而源服务器甚至不会知道它被请求了。如果 CDN 没有副本,它会尝试在“源”example.com/assets/smile.png 查找,然后将其存储以备将来使用。

如果您只想从 CDN 提供某些资产,您可以为资产帮助程序使用自定义的 :host 选项,它会覆盖 config.action_controller.asset_host 中设置的值。

<%= asset_path 'image.png', host: 'mycdnsubdomain.fictional-cdn.com' %>
3.3.3.2. 自定义 CDN 缓存行为

CDN 通过缓存内容工作。如果 CDN 包含过时或错误的内容,那么它正在损害而不是帮助您的应用程序。本节的目的是描述大多数 CDN 的一般缓存行为。您的特定提供商的行为可能略有不同。

CDN 请求缓存

虽然 CDN 被描述为善于缓存资产,但它实际上缓存了整个请求。这包括资产的主体以及任何标头。其中最重要的是 Cache-Control,它告诉 CDN(和网络浏览器)如何缓存内容。这意味着如果有人请求不存在的资产,例如 /assets/i-dont-exist.png,并且您的 Rails 应用程序返回 404,那么如果存在有效的 Cache-Control 标头,您的 CDN 很可能会缓存 404 页面。

CDN 标头调试

检查 CDN 中标头是否正确缓存的一种方法是使用 curl。您可以从您的服务器和 CDN 请求标头以验证它们是否相同

$ curl -I http://www.example/assets/application-
d0e099e021c95eb0de3615fd1d8c4d83.css
HTTP/1.1 200 OK
Server: Cowboy
Date: Sun, 24 Aug 2014 20:27:50 GMT
Connection: keep-alive
Last-Modified: Thu, 08 May 2014 01:24:14 GMT
Content-Type: text/css
Cache-Control: public, max-age=2592000
Content-Length: 126560
Via: 1.1 vegur

对比 CDN 副本

$ curl -I http://mycdnsubdomain.fictional-cdn.com/application-
d0e099e021c95eb0de3615fd1d8c4d83.css
HTTP/1.1 200 OK Server: Cowboy Last-
Modified: Thu, 08 May 2014 01:24:14 GMT Content-Type: text/css
Cache-Control:
public, max-age=2592000
Via: 1.1 vegur
Content-Length: 126560
Accept-Ranges:
bytes
Date: Sun, 24 Aug 2014 20:28:45 GMT
Via: 1.1 varnish
Age: 885814
Connection: keep-alive
X-Served-By: cache-dfw1828-DFW
X-Cache: HIT
X-Cache-Hits:
68
X-Timer: S1408912125.211638212,VS0,VE0

请查阅您的 CDN 文档,了解他们可能提供的任何额外信息,例如 X-Cache 或他们可能添加的任何额外标头。

CDN 和 Cache-Control 头

Cache-Control 头描述了如何缓存请求。当不使用 CDN 时,浏览器将使用此信息来缓存内容。这对于未修改的资产非常有用,因此浏览器无需在每个请求上重新下载网站的 CSS 或 JavaScript。通常我们希望我们的 Rails 服务器告诉我们的 CDN(和浏览器)资产是“公共”的。这意味着任何缓存都可以存储请求。我们还通常希望设置 max-age,这是缓存在使缓存失效之前存储对象的时间。max-age 值以秒为单位设置,最大可能值为 31536000,即一年。您可以在 Rails 应用程序中通过设置

config.public_file_server.headers = {
  "Cache-Control" => "public, max-age=31536000"
}

现在,当您的应用程序在生产环境中提供资产时,CDN 将存储该资产长达一年。由于大多数 CDN 还会缓存请求的标头,此 Cache-Control 将传递给所有未来请求此资产的浏览器。然后浏览器知道它可以长时间存储此资产,而无需重新请求它。

CDN 和基于 URL 的缓存失效

大多数 CDN 会根据完整的 URL 缓存资产内容。这意味着对

http://mycdnsubdomain.fictional-cdn.com/assets/smile-123.png

的请求将与对

http://mycdnsubdomain.fictional-cdn.com/assets/smile.png

的请求是完全不同的缓存。如果您想在 Cache-Control 中设置远期 max-age(您确实想这样做),那么请确保在更改资产时使缓存失效。例如,当将图像中的笑脸从黄色更改为蓝色时,您希望所有网站访问者都能看到新的蓝色笑脸。当 CDN 与 Rails 资产管道一起使用时,config.assets.digest 默认设置为 true,以便每个资产更改时都具有不同的文件名。这样您就无需手动使缓存中的任何项目失效。通过使用不同的唯一资产名称,您的用户将获得最新的资产。

4. 从 Sprockets 到 Propshaft

4.1. 资产管理技术的演变

在过去的几年里,网络的演变导致了显著的变化,这些变化影响了 Web 应用程序中资产的管理方式。其中包括

  1. 浏览器支持:现代浏览器改进了对新特性和语法的支持,减少了转译和 Polyfills 的需求。
  2. HTTP/2:HTTP/2 的引入使得并行提供多个文件变得更容易,减少了打包的需求。
  3. ES6+:大多数现代浏览器都支持现代 JavaScript 语法 (ES6+),减少了转译的需求。

因此,由 Propshaft 提供支持的资产管道不再默认包含转译、打包或压缩。然而,指纹识别仍然是不可或缺的一部分。您可以在下面阅读有关资产管理技术演变以及它们如何引导 Sprockets 到 Propshaft 变化的更多信息。

4.1.1. 转译 ❌

转译涉及将代码从一种语言或格式转换为另一种语言或格式。

例如,将 TypeScript 转换为 JavaScript。

const greet = (name: string): void => {
  console.log(`Hello, ${name}!`);
};

转译后,此代码变为

const greet = (name) => {
  console.log(`Hello, ${name}!`);
};

过去,像 SassLess 这样的预处理器对于 CSS 功能(如变量和嵌套)至关重要。如今,现代 CSS 原生支持这些功能,减少了转译的需求。

4.1.2. 打包 ❌

打包将多个文件组合成一个,以减少浏览器渲染页面所需的 HTTP 请求数量。

例如,如果您的应用程序有三个 JavaScript 文件

  • menu.js
  • cart.js
  • checkout.js

打包会将这些文件合并成一个 application.js 文件。

// app/javascript/application.js
// Contents of menu.js, cart.js, and checkout.js are combined here

这对于 HTTP/1.1 至关重要,它限制每个域的并发连接数为 6-8 个。使用 HTTP/2,浏览器可以并行获取多个文件,这使得打包对于现代应用程序来说不那么关键了。

4.1.3. 压缩 ❌

压缩以更高效的格式对文件进行编码,以在交付给用户时进一步减小其大小。一种常见技术是 Gzip 压缩

例如,一个 200KB 的 CSS 文件在 Gzip 压缩后可能只剩下 50KB。浏览器在接收到此类文件后会自动解压缩,从而节省带宽并提高速度。

然而,随着 CDN 自动压缩资产,手动压缩的需求有所减少。

4.2. Sprockets vs. Propshaft

4.2.1. 加载顺序

在 Sprockets 中,您可以将文件链接在一起以确保它们按正确的顺序加载。例如,一个依赖于其他文件的主 JavaScript 文件将由 Sprockets 自动管理其依赖项,确保所有内容都按正确的顺序加载。而 Propshaft 不会自动处理这些依赖项,而是让您手动管理资产加载顺序

4.2.2. 版本控制

Sprockets 通过在资产更新时向文件名追加哈希来简化资产指纹识别,确保正确的缓存失效。使用 Propshaft,您需要手动处理某些方面。例如,虽然资产指纹识别有效,但您可能需要使用打包器或手动触发 JavaScript 文件的转换,以确保文件名正确更新。阅读更多关于 Propshaft 中的指纹识别

4.2.3. 预编译

Sprockets 处理明确包含在 bundle 中的资产。相比之下,Propshaft 会自动处理位于指定路径中的所有资产,包括图像、样式表、JavaScript 文件等,而无需显式打包。阅读更多关于资产摘要的信息。

4.3. 迁移步骤

Propshaft 比 Sprockets 故意更简单,这可能使得从 Sprockets 迁移需要大量工作。如果您依赖 Sprockets 进行 TypeScript 或 Sass 的转译,或者您正在使用提供此功能的 gems,则尤其如此。在这种情况下,您需要停止转译或切换到基于 Node.js 的转译器,例如 jsbundling-railscssbundling-rails 提供的那些。在高级资产管理部分阅读更多相关信息。

但是,如果您已经使用基于 Node 的设置来打包 JavaScript 和 CSS,Propshaft 应该无缝集成到您的工作流中。由于您不需要额外的工具进行打包或转译,Propshaft 将主要处理资产摘要和提供。

迁移的一些关键步骤包括

  1. 使用以下命令删除一些 gems

    bundle remove sprockets
    bundle remove sprockets-rails
    bundle remove sass-rails
    
  2. 从您的项目中删除 config/assets.rbassets/config/manifest.js 文件。

  3. 如果您已经升级到 Rails 8,那么 Propshaft 已经包含在您的应用程序中。否则,使用 bundle add propshaft 进行安装。

  4. 从您的 application.rb 文件中删除 config.assets.paths << Rails.root.join('app', 'assets') 行。

  5. 迁移资产助手,将 CSS 文件中所有资产助手的实例(例如 image_url)替换为标准的 url() 函数,请记住 Propshaft 利用相对路径。例如,image_url("logo.png") 可能变为 url("/logo.png")

  6. 如果您依赖 Sprockets 进行转译,您将需要切换到基于 Node 的转译器,例如 Webpack、esbuild 或 Vite。您可以使用 jsbundling-railscssbundling-rails gems 将这些工具集成到您的 Rails 应用程序中。

有关更多信息,您可以阅读 有关如何从 Sprockets 迁移到 Propshaft 的详细指南

5. 高级资产管理

多年来,处理资产的方法有很多种默认方法,随着网络的发展,我们开始看到更多 JavaScript 繁重的应用程序。在 Rails 原则中,我们相信 菜单是 Omakase,因此 Propshaft 默认专注于为现代浏览器提供生产就绪的设置。

对于各种 JavaScript 和 CSS 框架和扩展,没有一刀切的解决方案。但是,Rails 生态系统中还有其他打包库,在默认设置不足的情况下,它们应该能为您提供帮助。

5.1. jsbundling-rails

jsbundling-rails 是一个 gem,它将现代 JavaScript 打包器集成到您的 Rails 应用程序中。它允许您使用 Bunesbuildrollup.jsWebpack 等工具管理和打包 JavaScript 资产,为寻求灵活性和性能的开发人员提供了一种运行时依赖的方法。

5.1.1. jsbundling-rails 的工作原理

  1. 安装后,它会设置您的 Rails 应用程序以使用您选择的 JavaScript 打包器。
  2. 它在您的 package.json 文件中创建一个 build 脚本来编译您的 JavaScript 资产。
  3. 在开发过程中,build:watch 脚本确保在您进行更改时资产实时更新。
  4. 在生产环境中,该 gem 确保 JavaScript 在预编译步骤中构建并包含,从而减少手动干预。它会挂钩到 Rails 的 assets:precompile 任务,以便在部署期间为所有入口点构建 JavaScript。这种集成确保您的 JavaScript 在最少配置的情况下即可用于生产环境。

该 gem 会自动处理入口点发现——通过遵循 Rails 约定(通常在 app/javascript/ 和配置等目录中查找)来识别要打包的主要 JavaScript 文件。通过遵循 Rails 约定,jsbundling-rails 简化了将复杂的 JavaScript 工作流集成到 Rails 项目中的过程。

5.1.2. 什么时候应该使用它?

jsbundling-rails 是以下 Rails 应用程序的理想选择

  • 需要 ES6+、TypeScript 或 JSX 等现代 JavaScript 功能。
  • 需要利用打包器特定的优化,例如摇树优化、代码分割或缩小。
  • 使用 Propshaft 进行资产管理,并且需要一种可靠的方式将预编译的 JavaScript 与更广泛的 Rails 资产管道集成。
  • 利用依赖构建步骤的库或框架。例如,需要转译的项目(例如使用 BabelTypeScript 或 React JSX 的项目)将从 jsbundling-rails 中受益匪浅。这些工具依赖于构建步骤,该 gem 无缝支持。

通过与 Rails 工具(如 Propshaft)集成并简化 JavaScript 工作流,jsbundling-rails 使您能够构建丰富、动态的前端,同时保持高效并遵循 Rails 约定。

5.2. cssbundling-rails

cssbundling-rails 将现代 CSS 框架和工具集成到您的 Rails 应用程序中。它允许您打包和处理样式表。处理后,生成的 CSS 通过 Rails 资产管道交付。

5.2.1. cssbundling-rails 的工作原理

  1. 安装后,它会设置您的 Rails 应用程序以使用您选择的 CSS 框架或处理器。
  2. 它会在您的 package.json 文件中创建一个 build:css 脚本来编译您的样式表。
  3. 在开发过程中,build:css --watch 任务确保您的 CSS 在您进行更改时实时更新,提供流畅且响应迅速的工作流。
  4. 在生产环境中,该 gem 确保您的样式表已编译并准备好部署。在 assets:precompile 步骤中,它通过 bunyarnpnpmnpm 安装所有 package.json 依赖项,并运行 build:css 任务来处理您的样式表入口点。生成的 CSS 输出随后由资产管道摘要并复制到 public/assets 目录中,就像其他资产管道文件一样。

这种集成简化了准备生产就绪样式表的过程,同时确保所有 CSS 都得到高效管理和处理。

5.2.2. 什么时候应该使用它?

cssbundling-rails 是以下 Rails 应用程序的理想选择

  • 使用 Tailwind CSSBootstrapBulma 等 CSS 框架,这些框架在开发或部署期间需要处理。
  • 需要高级 CSS 功能,例如使用 PostCSSDart Sass 插件进行自定义预处理。
  • 需要将处理后的 CSS 无缝集成到 Rails 资产管道中。
  • 受益于开发过程中样式表的实时更新,而无需最少的手动干预。

注意:与使用独立版 Dart SassTailwind CSSdartsass-railstailwindcss-rails 不同,cssbundling-rails 引入了 Node.js 依赖项。这使得它成为已经依赖 Node 进行 JavaScript 处理(例如使用 jsbundling-rails 等 gem)的应用程序的不错选择。但是,如果您正在使用 importmap-rails 进行 JavaScript 并且更喜欢避免 Node.js,那么 dartsass-railstailwindcss-rails 等独立替代方案提供了更简单的设置。

通过集成现代 CSS 工作流、自动化生产构建并利用 Rails 资产管道,cssbundling-rails 使开发人员能够高效地管理和交付动态样式。

5.3. tailwindcss-rails

tailwindcss-rails 是一个包装 gem,它将 Tailwind CSS 集成到您的 Rails 应用程序中。通过将 Tailwind CSS 与 独立可执行文件 打包,它消除了对 Node.js 或其他 JavaScript 依赖项的需求。这使其成为用于样式化 Rails 应用程序的轻量级高效解决方案。

5.3.1. tailwindcss-rails 的工作原理

  1. 安装后,通过向 rails new 命令提供 --css tailwind,该 gem 会生成一个 tailwind.config.js 文件用于自定义您的 Tailwind 设置,以及一个 stylesheets/application.tailwind.css 文件用于管理您的 CSS 入口点。
  2. 该 gem 不依赖 Node.js,而是使用预编译的 Tailwind CSS 二进制文件。这种独立的方法允许您处理和编译 CSS,而无需向项目添加 JavaScript 运行时。
  3. 在开发过程中,对您的 Tailwind 配置或 CSS 文件的更改会自动检测和处理。该 gem 会重建您的样式表并提供一个 watch 进程,以在开发过程中自动生成 Tailwind 输出。
  4. 在生产环境中,该 gem 会挂钩到 assets:precompile 任务。它会处理您的 Tailwind CSS 文件并生成优化的、生产就绪的样式表,然后将其包含在资产管道中。输出会进行指纹识别并缓存以实现高效交付。

5.3.2. 什么时候应该使用它?

tailwindcss-rails 是以下 Rails 应用程序的理想选择

  • 希望使用 Tailwind CSS,而无需引入 Node.js 依赖项或 JavaScript 构建工具。
  • 需要最小化设置来管理实用优先的 CSS 框架。
  • 需要利用 Tailwind 的强大功能,如自定义主题、变体和插件,而无需复杂的配置。

该 gem 与 Rails 的资产管道工具(如 Propshaft)无缝协作,确保您的 CSS 在生产环境中经过预处理、摘要和高效服务。

5.4. importmap-rails

importmap-rails 实现了在 Rails 应用程序中管理 JavaScript 的无 Node.js 方法。它利用现代浏览器对 ES 模块 的支持,直接在浏览器中加载 JavaScript,而无需打包或转译。这种方法符合 Rails 简单性和约定优于配置的理念。

5.4.1. importmap-rails 的工作原理

  • 安装后,importmap-rails 会将您的 Rails 应用程序配置为使用 <script type="module"> 标签直接在浏览器中加载 JavaScript 模块。
  • JavaScript 依赖项通过 bin/importmap 命令进行管理,该命令将模块固定到 URL,通常托管在像 jsDelivr 这样的 CDN 上,这些 CDN 托管预打包的、浏览器就绪的库版本。这消除了对 node_modules 或包管理器的需求。
  • 在开发过程中,没有打包步骤,因此您的 JavaScript 更新立即可用,从而简化了工作流程。
  • 在生产环境中,该 gem 与 Propshaft 集成,将 JavaScript 文件作为资产管道的一部分提供。Propshaft 确保文件经过摘要、缓存并可用于生产。依赖项经过版本控制、指纹识别并高效交付,无需手动干预。

注意:虽然 Propshaft 确保了正确的资产处理,但它不处理 JavaScript 处理或转换——importmap-rails 假定您的 JavaScript 已经是浏览器兼容的格式。这就是为什么它最适用于不需要转译或打包的项目。

通过消除构建步骤和 Node.js 的需求,importmap-rails 简化了 JavaScript 管理。

5.4.2. 什么时候应该使用它?

importmap-rails 是以下 Rails 应用程序的理想选择

  • 不需要复杂的 JavaScript 功能,例如转译或打包。
  • 使用现代 JavaScript,而不依赖于 Babel 等工具。


回到顶部