v7.1.3.2
更多資訊請參閱 rubyonrails.org: 更多 Ruby on Rails

資產管線

本指南涵蓋資產管道。

閱讀本指南後,您將會知道

1 什麼是 Asset Pipeline?

Asset Pipeline 提供一個處理 JavaScript 和 CSS 資源交付的架構。這透過利用 HTTP/2 等技術和串接、縮小的技巧來完成。最後,它允許你的應用程式自動與其他 Gem 的資源結合。

Asset Pipeline 是由 importmap-railssprocketssprockets-rails Gem 實作,並且預設啟用。你可以在建立新應用程式的同時,透過傳遞 --skip-asset-pipeline 選項來停用它。

$ rails new appname --skip-asset-pipeline

本指南專注於僅使用 sprockets 處理 CSS 和 importmap-rails 處理 JavaScript 的預設 Asset Pipeline。這兩者的主要限制是沒有轉譯支援,因此你無法使用 Babel、TypeScript、Sass、React JSX 格式或 Tailwind CSS 等功能。如果你需要為 JavaScript/CSS 轉譯,我們鼓勵你閱讀 其他函式庫區段

2 主要功能

Asset Pipeline 的第一個功能是在每個檔名中插入 SHA256 指紋,以便檔案由網路瀏覽器和 CDN 快取。當你變更檔案內容時,此指紋會自動更新,這會使快取失效。

Asset Pipeline 的第二個功能是在提供 JavaScript 檔案時使用 import maps。這讓你可以在不需轉譯和綑綁的情況下,使用為 ES 模組 (ESM) 建置的 JavaScript 函式庫來建置現代化應用程式。反過來,這消除了對 Webpack、yarn、node 或 JavaScript 工具鏈任何其他部分的需求

資產管線的第三個功能是將所有 CSS 檔案串接成一個主要的 .css 檔案,然後縮小或壓縮。如您稍後在本指南中所學,您可以自訂此策略以依您喜歡的方式分組檔案。在生產環境中,Rails 會在每個檔名中插入 SHA256 指紋,以便檔案由網路瀏覽器快取。您可以透過變更此指紋來使快取失效,這會在您變更檔案內容時自動發生。

資產管線的第四個功能是它允許透過較高階的語言編碼 CSS 的資產。

2.1 什麼是指紋,我為什麼應該關心?

指紋是一種技術,它讓檔案的名稱依賴於檔案的內容。當檔案內容變更時,檔名也會變更。對於靜態或不常變更的內容,這提供了一個簡單的方法來判斷兩個版本的檔案是否相同,即使是在不同的伺服器或部署日期之間。

當檔名是唯一的且基於其內容時,可以設定 HTTP 標頭來鼓勵各處的快取(無論是在 CDN、ISP、網路設備或網路瀏覽器中)保留自己的內容副本。當內容更新時,指紋會變更。這將導致遠端用戶端要求新的內容副本。這通常稱為快取清除

Sprockets 用於指紋的技術是在名稱中插入內容的雜湊,通常在結尾。例如,CSS 檔案 global.css

global-908e25f4bf641868d8683022a5b62f54.css

這是 Rails 資產管線採用的策略。

指紋預設在開發和生產環境中啟用。您可以透過 config.assets.digest 選項在您的組態中啟用或停用它。

2.2 什麼是匯入地圖,我為什麼應該關心?

匯入地圖讓您可以使用對應到版本化/雜湊化檔案的邏輯名稱來匯入 JavaScript 模組 – 直接從瀏覽器。因此,您可以使用為 ES 模組 (ESM) 製作的 JavaScript 函式庫來建置現代 JavaScript 應用程式,而不需要轉譯或綑綁。

使用這種方法,您將傳送許多小型 JavaScript 檔案,而不是一個大型 JavaScript 檔案。感謝 HTTP/2,它在初始傳輸期間不再造成重大效能損失,而且事實上由於更好的快取動態,從長遠來看提供了顯著的優點。

3 如何將 Import Maps 用作 JavaScript Asset Pipeline

Import Maps 是預設的 JavaScript 處理器,產生 import maps 的邏輯由 importmap-rails gem 處理。

Import maps 僅用於 JavaScript 檔案,且無法用於 CSS 傳遞。查看 Sprockets 區段 以了解 CSS。

您可以在 Gem 首頁上找到詳細的使用說明,但了解 importmap-rails 的基礎知識非常重要。

3.1 運作方式

Import maps 本質上是一種字串替換,用於所謂的「裸模組規格」。它們允許您標準化 JavaScript 模組輸入的名稱。

例如,這樣的輸入定義,它在沒有 import map 的情況下無法運作

import React from "react"

您必須這樣定義它才能讓它運作

import React from "https://ga.jspm.io/npm:[email protected]/index.js"

這裡出現 import map,我們定義 react 名稱固定在 https://ga.jspm.io/npm:[email protected]/index.js 位址。有了這些資訊,我們的瀏覽器便接受簡化的 import React from "react" 定義。將 import map 視為程式庫來源位址的別名。

3.2 用法

使用 importmap-rails,您可以建立 importmap 組態檔,將程式庫路徑固定到一個名稱

# config/importmap.rb
pin "application"
pin "react", to: "https://ga.jspm.io/npm:[email protected]/index.js"

所有已設定的匯入映射都應該透過新增 <%= javascript_importmap_tags %> 來附加在應用程式的 <head> 元素中。javascript_importmap_tags 會在 head 元素中呈現一堆指令碼

  • 包含所有已設定匯入映射的 JSON
<script type="importmap">
{
  "imports": {
    "application": "/assets/application-39f16dc3f3....js"
    "react": "https://ga.jspm.io/npm:[email protected]/index.js"
  }
}
</script>
  • Es-module-shims 扮演著多重載入的角色,確保舊瀏覽器支援 import maps
<script src="/assets/es-module-shims.min" async="async" data-turbo-track="reload"></script>
  • app/javascript/application.js 載入 JavaScript 的入口點
<script type="module">import "application"</script>

3.3 透過 JavaScript CDN 使用 npm 套件

您可以使用 bin/importmap 指令,此指令是 importmap-rails 安裝的一部分,用於釘選、取消釘選或更新匯入映射中的 npm 套件。binstub 使用 JSPM.org

它的運作方式如下

$ bin/importmap pin react react-dom
Pinning "react" to https://ga.jspm.io/npm:[email protected]/index.js
Pinning "react-dom" to https://ga.jspm.io/npm:[email protected]/index.js
Pinning "object-assign" to https://ga.jspm.io/npm:[email protected]/index.js
Pinning "scheduler" to https://ga.jspm.io/npm:[email protected]/index.js

bin/importmap json

{
  "imports": {
    "application": "/assets/application-37f365cbecf1fa2810a8303f4b6571676fa1f9c56c248528bc14ddb857531b95.js",
    "react": "https://ga.jspm.io/npm:[email protected]/index.js",
    "react-dom": "https://ga.jspm.io/npm:[email protected]/index.js",
    "object-assign": "https://ga.jspm.io/npm:[email protected]/index.js",
    "scheduler": "https://ga.jspm.io/npm:[email protected]/index.js"
  }
}

正如您所見,當透過 jspm 預設值解析時,兩個套件 react 和 react-dom 會解析為總共四個依賴項。

現在,您可以在 application.js 入口點中使用它們,就像使用任何其他模組一樣

import React from "react"
import ReactDOM from "react-dom"

您也可以指定特定版本進行釘選

$ bin/importmap pin [email protected]
Pinning "react" to https://ga.jspm.io/npm:[email protected]/index.js
Pinning "object-assign" to https://ga.jspm.io/npm:[email protected]/index.js

甚至移除釘選

$ bin/importmap unpin react
Unpinning "react"
Unpinning "object-assign"

對於具有獨立「生產」(預設值)和「開發」建置的套件,您可以控制套件的環境

$ bin/importmap pin react --env development
Pinning "react" to https://ga.jspm.io/npm:[email protected]/dev.index.js
Pinning "object-assign" to https://ga.jspm.io/npm:[email protected]/index.js

釘選時,您還可以選擇其他支援的 CDN 提供者,例如 unpkgjsdelivrjspm 是預設值)

$ bin/importmap pin react --from jsdelivr
Pinning "react" to https://cdn.jsdelivr.net/npm/[email protected]/index.js

不過,請記住,如果您將釘選從一個提供者切換到另一個提供者,您可能必須清理第一個提供者新增的依賴項,而第二個提供者並未使用。

執行 bin/importmap 以查看所有選項。

請注意,此指令只是一個方便的包裝器,用於將邏輯套件名稱解析為 CDN URL。您也可以自己查詢 CDN URL,然後釘選它們。例如,如果您想對 React 使用 Skypack,您只需將下列內容新增到 config/importmap.rb 即可

pin "react", to: "https://cdn.skypack.dev/react"

3.4 預載釘選模組

為了避免瀑布效應(瀏覽器必須逐一載入檔案,才能到達最深層的巢狀匯入),importmap-rails 支援 modulepreload 連結。釘選模組可以透過將 preload: true 附加到釘選來預載。

預先載入應用程式中使用的函式庫或架構是一個好主意,因為這會告訴瀏覽器盡快下載它們。

範例

# config/importmap.rb
pin "@github/hotkey", to: "https://ga.jspm.io/npm:@github/[email protected]/dist/index.js", preload: true
pin "md5", to: "https://cdn.jsdelivr.net/npm/[email protected]/md5.js"
# app/views/layouts/application.html.erb
<%= javascript_importmap_tags %>

# will include the following link before the importmap is setup:
<link rel="modulepreload" href="https://ga.jspm.io/npm:@github/[email protected]/dist/index.js">
...

參閱 importmap-rails 儲存庫以取得最新文件。

4 如何使用 Sprockets

將應用程式資源公開到網路的簡陋方法是將它們儲存在 public 資料夾的子目錄中,例如 imagesstylesheets。手動執行此操作會很困難,因為大多數現代網路應用程式都需要以特定方式處理資源,例如壓縮和將指紋新增到資源。

Sprockets 旨在自動預處理儲存在已設定目錄中的資源,並在處理後將它們公開在 public/assets 資料夾中,並具有指紋辨識、壓縮、原始碼對應產生和其他可設定功能。

資源仍可以放置在 public 階層中。當 config.public_file_server.enabled 設為 true 時,public 下的任何資源都將由應用程式或網路伺服器作為靜態檔案提供。您必須為在提供服務之前必須經過預處理的檔案定義 manifest.js 指令。

在生產環境中,Rails 預設會將這些檔案預編譯到 public/assets。然後,網路伺服器會將預編譯的副本作為靜態資源提供。app/assets 中的檔案永遠不會在生產環境中直接提供。

4.1 清單檔案和指令

使用 Sprockets 編譯資源時,Sprockets 需要決定要編譯哪些頂層目標,通常是 application.css 和影像。頂層目標定義在 Sprockets manifest.js 檔案中,預設如下所示

//= link_tree ../images
//= link_directory ../stylesheets .css
//= link_tree ../../javascript .js
//= link_tree ../../../vendor/javascript .js

它包含指令 - 指示 Sprockets 在建構單一 CSS 或 JavaScript 檔案時需要載入哪些檔案。

這表示要包含在 ./app/assets/images 目錄或任何子目錄中找到的所有檔案的內容,以及在 ./app/javascript./vendor/javascript 中直接識別為 JS 的任何檔案。

它會載入 ./app/assets/stylesheets 目錄中的任何 CSS(不包含子目錄)。假設你在 ./app/assets/stylesheets 資料夾中有 application.cssmarketing.css 檔案,它將允許你使用 <%= stylesheet_link_tag "application" %><%= stylesheet_link_tag "marketing" %> 從你的檢視載入那些樣式表。

你可能會注意到我們的 JavaScript 檔案預設不會從 assets 目錄載入,這是因為 ./app/javascriptimportmap-rails 寶石的預設進入點,而 vendor 資料夾是儲存下載的 JS 套件的地方。

manifest.js 中,你也可以指定 link 指令來載入特定檔案,而不是整個目錄。link 指令需要提供明確的檔案副檔名。

Sprockets 會載入指定的檔案,必要時處理它們,將它們串接成一個單一檔案,然後壓縮它們(根據 config.assets.css_compressorconfig.assets.js_compressor 的值)。壓縮會縮小檔案大小,讓瀏覽器可以更快下載檔案。

4.2 控制器特定資產

當你產生一個腳手架或控制器時,Rails 也會為該控制器產生一個層疊樣式表檔案。此外,在產生腳手架時,Rails 會產生檔案 scaffolds.css

例如,如果你產生一個 ProjectsController,Rails 也會在 app/assets/stylesheets/projects.css 新增一個新檔案。預設情況下,這些檔案會透過 manifest.js 檔案中的 link_directory 指令,立即準備好讓你的應用程式使用。

你也可以選擇只在各自的控制器中包含控制器特定的樣式表檔案,方法如下:

<%= stylesheet_link_tag params[:controller] %>

執行此操作時,請確保你沒有在 application.css 中使用 require_tree 指令,因為這可能會導致你的控制器特定資源被包含多次。

4.3 資源組織

管道資源可以放置在應用程式的三個位置之一:app/assetslib/assetsvendor/assets

  • app/assets 是用於應用程式擁有的資源,例如自訂圖片或樣式表。

  • app/javascript 是用於你的 JavaScript 程式碼

  • vendor/[assets|javascript] 是用於外部實體擁有的資源,例如 CSS 架構或 JavaScript 函式庫。請記住,具有對其他檔案 (例如圖片、樣式表等) 參考的第三方程式碼,需要重新編寫才能使用像 asset_path 這樣的輔助函數。

其他位置可以在 manifest.js 檔案中設定,請參閱 清單檔案和指令

4.3.1 搜尋路徑

當從清單或輔助函數參照檔案時,Sprockets 會在 manifest.js 中指定的所有位置中搜尋它。你可以透過在 Rails 主控台中檢查 Rails.application.config.assets.paths 來檢視搜尋路徑。

4.3.2 使用索引檔案作為資料夾的代理

Sprockets 使用名為 index(具有相關副檔名)的檔案,用於特殊目的。

例如,如果您有一個包含許多模組的 CSS 函式庫,儲存在 lib/assets/stylesheets/library_name 中,則檔案 lib/assets/stylesheets/library_name/index.css 可作為此函式庫中所有檔案的清單。此檔案可以按順序包含所有必要檔案的清單,或包含一個簡單的 require_tree 指令。

這也與檔案 public/library_name/index.html 可透過對 /library_name 的要求來存取的方式有點類似。這表示您無法直接使用索引檔案。

可以在 .css 檔案中存取整個函式庫,如下所示

/* ...
*= require library_name
*/

這簡化了維護,並透過允許在包含在其他地方之前將相關程式碼分組,保持程式碼的整潔。

Sprockets 沒有新增任何新方法來存取您的資源 - 您仍然使用熟悉的 stylesheet_link_tag

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

如果您使用 turbo-rails 寶石(預設包含在 Rails 中),則包含 data-turbo-track 選項,這會導致 Turbo 檢查資源是否已更新,如果是,則將其載入頁面

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

在一般檢視中,您可以像這樣存取 app/assets/images 目錄中的影像

<%= image_tag "rails.png" %>

只要在您的應用程式中啟用了管道(且未在目前的環境內容中停用),此檔案就會由 Sprockets 提供。如果檔案存在於 public/assets/rails.png 中,則由網路伺服器提供。

或者,對具有 SHA256 雜湊的檔案(例如 public/assets/rails-f90d8a84c707a8dc923fca1ca1895ae8ed0a09237f6992015fef1e11be77c023.png)的要求會以相同的方式處理。這些雜湊是如何產生的,將在稍後本指南的 生產中 部分中說明。

如果需要,也可以將影像整理到子目錄中,然後透過在標籤中指定目錄的名稱來存取影像

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

如果您正在預編譯您的資產(請參閱以下在生產中),連結到不存在的資產會在呼叫頁面中引發例外。這包括連結到空白字串。因此,請小心使用 image_tag 和其他具有使用者提供資料的輔助程式。

4.4.1 CSS 和 ERB

資產管線會自動評估 ERB。這表示如果您將 erb 副檔名新增到 CSS 資產(例如 application.css.erb),則輔助程式(例如 asset_path)會在您的 CSS 規則中提供

.class { background-image: url(<%= asset_path 'image.png' %>) }

這會寫入所參照特定資產的路徑。在此範例中,在其中一個資產載入路徑(例如 app/assets/images/image.png)中有一個圖片是有意義的,這會在此參照。如果此圖片已在 public/assets 中作為指紋檔案提供,則會參照該路徑。

如果您想要使用資料 URI(將圖片資料直接嵌入 CSS 檔案的方法),您可以使用 asset_data_uri 輔助程式。

#logo { background: url(<%= asset_data_uri 'logo.png' %>) }

這會將格式正確的資料 URI 插入 CSS 來源。

請注意,結束標籤不能是 -%> 樣式。

4.5 在找不到資產時引發錯誤

如果您正在使用 sprockets-rails >= 3.2.0,您可以設定在執行資產查詢且找不到任何內容時會發生什麼事。如果您關閉「資產備援」,則在找不到資產時會引發錯誤。

config.assets.unknown_asset_fallback = false

如果啟用「資產備援」,則在找不到資產時,會輸出路徑,且不會引發錯誤。資產備援行為預設為停用。

4.6 關閉摘要

您可以透過更新 config/environments/development.rb 來關閉摘要,以包含

config.assets.digest = false

當此選項為 true 時,系統會為資源網址產生摘要。

4.7 開啟原始碼對應

您可以透過更新 config/environments/development.rb 來開啟原始碼對應,加入

config.assets.debug = true

當偵錯模式開啟時,Sprockets 會為每個資源產生一個原始碼對應。這讓您可以在瀏覽器的開發人員工具中個別偵錯每個檔案。

資源會在伺服器啟動後第一次要求時編譯並快取。Sprockets 會設定 must-revalidate Cache-Control HTTP 標頭,以減少後續要求的請求負擔 - 在這些要求中,瀏覽器會收到 304 (未修改) 回應。

如果清單中的任何檔案在要求之間變更,伺服器會回應新的編譯檔案。

5 在生產環境中

在生產環境中,Sprockets 會使用上述的指紋辨識機制。預設情況下,Rails 會假設資源已經預先編譯,而且會由您的網路伺服器作為靜態資源提供。

在預先編譯階段,會從編譯檔案的內容產生 SHA256,並在寫入磁碟時插入檔名中。這些指紋辨識名稱會由 Rails 輔助程式取代清單名稱。

例如

<%= stylesheet_link_tag "application" %>

會產生類似這樣的結果

<link href="/assets/application-4dd5b109ee3439da54f5bdfd78a80473.css" rel="stylesheet" />

指紋辨識行為由 config.assets.digest 初始化選項控制 (預設為 true)。

在正常情況下,不應變更預設的 config.assets.digest 選項。如果檔名中沒有摘要,而且已設定遠端未來標頭,遠端用戶端將永遠不會知道在內容變更時重新擷取檔案。

5.1 預先編譯資源

Rails 內建一個指令,用於編譯資源清單和管線中的其他檔案。

已編譯的資源會寫入 config.assets.prefix 中指定的位址。預設為 /assets 目錄。

您可以在部署期間於伺服器上呼叫此指令,直接在伺服器上建立資源的已編譯版本。請參閱下一個區段,以取得關於在本地端編譯的資訊。

指令為

$ RAILS_ENV=production rails assets:precompile

這會將 config.assets.prefix 中指定的資料夾連結到 shared/assets。如果您已經使用這個共用資料夾,則您需要撰寫自己的部署指令。

這個資料夾在部署之間共用非常重要,這樣參照舊的已編譯資源的遠端快取頁面,在快取頁面的使用期間仍然可以運作。

務必指定預期的已編譯檔名,其結尾為 .js.css

此指令也會產生一個 .sprockets-manifest-randomhex.json(其中 randomhex 是 16 位元的隨機十六進位字串),其中包含一個清單,列出您所有的資源及其各自的指紋。Rails 輔助方法使用這個清單,以避免將對應的要求傳回 Sprockets。一個典型的清單檔案看起來像這樣

{"files":{"application-<fingerprint>.js":{"logical_path":"application.js","mtime":"2016-12-23T20:12:03-05:00","size":412383,
"digest":"<fingerprint>","integrity":"sha256-<random-string>"}},
"assets":{"application.js":"application-<fingerprint>.js"}}

在您的應用程式中,清單中會列出更多檔案和資源,<fingerprint><random-string> 也會產生。

清單的預設位置是 config.assets.prefix 中指定位置的根目錄(預設為 '/assets')。

如果在製作環境中缺少預先編譯的檔案,您會收到一個 Sprockets::Helpers::RailsHelper::AssetPaths::AssetNotPrecompiledError 例外,指出缺少的檔案名稱。

5.1.1 遠期到期標頭

預先編譯的資源存在於檔案系統中,並由您的網路伺服器直接提供。它們預設沒有遠期標頭,因此若要獲得指紋辨識的好處,您必須更新您的伺服器設定,以新增這些標頭。

適用於 Apache

# The Expires* directives requires 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 "";
}

5.2 本機預先編譯

有時,您可能不想或無法在生產伺服器上編譯資產。例如,您對生產檔案系統的寫入存取權限有限,或者您計畫頻繁部署而不對資產進行任何變更。

在這種情況下,您可以在本機預先編譯資產,也就是說,在推送到生產環境之前,將一組已編譯完成且可供生產環境使用的資產新增到您的原始程式碼儲存庫。這樣一來,就不需要在每次部署時在生產伺服器上分別預先編譯這些資產。

如上所述,您可以使用下列指令執行此步驟

$ RAILS_ENV=production rails assets:precompile

請注意以下注意事項

  • 如果預先編譯的資產可用,它們將會提供,即使它們不再與原始(未編譯)資產相符,即使是在開發伺服器上也是如此。

    為確保開發伺服器始終即時編譯資產(從而始終反映程式碼的最新狀態),開發環境必須設定為將預先編譯的資產保存在與生產環境不同的位置。否則,任何預先編譯供生產環境使用的資產都會在開發環境中取代對它們的要求(您對資產進行的後續變更將不會反映在瀏覽器中)。

    您可以透過將下列程式碼行新增到 config/environments/development.rb 來執行此動作

    config.assets.prefix = "/dev-assets"
    
  • 部署工具(例如 Capistrano)中的資產預先編譯任務應停用。

  • 您的開發系統上必須有必要的壓縮器或縮小器。

您也可以設定 ENV["SECRET_KEY_BASE_DUMMY"] 來觸發使用隨機產生的 secret_key_base,該 secret_key_base 儲存在暫存檔案中。這在預先編譯生產環境資產時很有用,因為這是一個建置步驟的一部分,否則不需要存取生產機密。

$ SECRET_KEY_BASE_DUMMY=1 bundle exec rails assets:precompile

5.3 即時編譯

在某些情況下,您可能希望使用即時編譯。在此模式中,管道中所有對資產的要求都由 Sprockets 直接處理。

若要啟用此選項,請設定

config.assets.compile = true

在第一次要求時,資產會根據 資產快取儲存 中所述進行編譯和快取,而輔助程式中使用的清單名稱會變更,以包含 SHA256 hash。

Sprockets 也會將 Cache-Control HTTP 標頭設定為 max-age=31536000。這會向伺服器和用戶端瀏覽器之間的所有快取發出訊號,表示此內容(已提供的檔案)可快取 1 年。這會減少伺服器對此資產的要求數量;此資產很有可能存在於本機瀏覽器快取或某些中間快取中。

此模式使用較多記憶體,執行效能較預設值差,並不建議使用。

5.4 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 的資產在地理位置上更接近瀏覽器,因此要求會更快,而且由於您的伺服器不需要花費時間提供資產,因此它可以專注於盡可能快地提供應用程式程式碼。

5.4.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' %>

5.4.2 自訂 CDN 快取行為

CDN 的運作方式是快取內容。如果 CDN 的內容過時或錯誤,那麼它會傷害您的應用程式,而不是幫助它。本節的目的在於說明大多數 CDN 的一般快取行為。您的特定供應商的行為可能略有不同。

5.4.2.1 CDN 要求快取

雖然 CDN 被描述為對快取資產有益,但它實際上快取整個請求。這包括資產主體以及任何標頭。最重要的標頭是 Cache-Control,它告訴 CDN(和網路瀏覽器)如何快取內容。這表示,如果有人要求一個不存在的資產,例如 /assets/i-dont-exist.png,而您的 Rails 應用程式傳回 404,那麼您的 CDN 很可能會快取 404 頁面,如果存在有效的 Cache-Control 標頭。

5.4.2.2 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 或他們可能新增的任何其他標頭。

5.4.2.3 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 會傳遞給所有未來尋找此資源的瀏覽器。然後,瀏覽器便知道它可以在很長一段時間內儲存此資源,而無需重新要求它。

5.4.2.4 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(而且您確實需要這麼做),那麼請務必在變更資源時使快取失效。例如,當將影像中的笑臉從黃色變更為藍色時,您希望網站的所有訪客都能看到新的藍色笑臉。當使用具有 Rails 資源管線的 CDN 時,config.assets.digest 預設設定為 true,以便在變更每個資源時,它都會有不同的檔案名稱。這樣一來,您就永遠不必手動使快取中的任何項目失效。透過改用不同的唯一資源名稱,您的使用者可以取得最新的資源。

6 自訂管線

6.1 CSS 壓縮

壓縮 CSS 的其中一個選項是 YUI。YUI CSS 壓縮器提供最小化功能。

下列程式行會啟用 YUI 壓縮,並需要 yui-compressor 這個 gem。

config.assets.css_compressor = :yui

6.2 JavaScript 壓縮

JavaScript 壓縮的可能選項有 :terser:closure:yui。它們分別需要使用 terserclosure-compileryui-compressor 這些 gem。

terser 寶石為例。此寶石將 Terser(為 Node.js 編寫)包裝在 Ruby 中。它會移除空白和註解、縮短區域變數名稱,並執行其他微型最佳化,例如在可能的情況下將 ifelse 陳述式變更為三元運算子,來壓縮您的程式碼。

下列程式行呼叫 terser 以進行 JavaScript 壓縮。

config.assets.js_compressor = :terser

您需要一個 ExecJS 支援的執行時間,才能使用 terser。如果您使用 macOS 或 Windows,則您的作業系統中已安裝 JavaScript 執行時間。

當您透過 importmap-railsjsbundling-rails 寶石載入您的資產時,JavaScript 壓縮也適用於您的 JavaScript 檔案。

6.3 壓縮您的資產

預設情況下,系統會產生已編譯資產的 gzip 版本,以及非 gzip 版本的資產。gzip 資產有助於減少透過網路傳輸的資料。您可以透過設定 gzip 旗標來設定此功能。

config.assets.gzip = false # disable gzipped assets generation

請參閱您的網路伺服器的文件,以取得有關如何提供 gzip 資產的說明。

6.4 使用您自己的壓縮器

CSS 和 JavaScript 的壓縮器設定值也接受任何物件。此物件必須具有 compress 方法,該方法將字串作為唯一引數,並且必須傳回字串。

class Transformer
  def compress(string)
    do_something_returning_a_string(string)
  end
end

若要啟用此功能,請將新物件傳遞給 application.rb 中的設定值選項

config.assets.css_compressor = Transformer.new

6.5 變更 assets 路徑

Sprockets 預設使用的公開路徑為 /assets

這可以變更為其他路徑

config.assets.prefix = "/some_other_path"

如果您正在更新未曾使用資產管線且已使用此路徑的較舊專案,或者您希望將此路徑用於新資源,這是一個方便的選項。

6.6 X-Sendfile 標頭

X-Sendfile 標頭是指示網路伺服器忽略應用程式的回應,並改為從磁碟提供指定檔案。此選項預設為關閉,但如果伺服器支援,則可以啟用。啟用後,這會將提供檔案的責任傳遞給網路伺服器,這會比較快。請參閱 send_file,了解如何使用此功能。

Apache 和 NGINX 支援此選項,可以在 config/environments/production.rb 中啟用。

# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX

如果您要升級現有應用程式並打算使用此選項,請務必僅將此組態選項貼到 production.rb 和您定義具有生產行為的任何其他環境(不是 application.rb)。

如需進一步詳細資訊,請參閱生產網路伺服器的文件。

7 資產快取儲存

預設情況下,Sprockets 會將資產快取在開發和生產環境中的 tmp/cache/assets 中。這可以如下所示變更

config.assets.configure do |env|
  env.cache = ActiveSupport::Cache.lookup_store(:memory_store,
                                                { size: 32.megabytes })
end

停用資產快取儲存

config.assets.configure do |env|
  env.cache = ActiveSupport::Cache.lookup_store(:null_store)
end

8 將資產新增到您的寶石

資產也可以來自寶石形式的外部來源。

一個很好的例子是 jquery-rails 寶石。此寶石包含繼承自 Rails::Engine 的引擎類別。透過這樣做,Rails 會收到通知,表示此寶石的目錄可能包含資產,而此引擎的 app/assetslib/assetsvendor/assets 目錄會新增到 Sprockets 的搜尋路徑中。

9 將您的函式庫或寶石設為預處理器

Sprockets 使用處理器、轉換器、壓縮器和匯出器來擴充 Sprockets 功能。請參閱 Extending Sprockets 以進一步了解。在這裡,我們註冊了一個預處理器,將註解新增到 text/css(.css)檔案的結尾。

module AddComment
  def self.call(input)
    { data: input[:data] + "/* Hello From my sprockets extension */" }
  end
end

現在您有一個修改輸入資料的模組,現在是時候將它註冊為 MIME 類型的預處理器。

Sprockets.register_preprocessor 'text/css', AddComment

10 個替代函式庫

多年來,處理資產有多種預設方法。網路不斷演進,我們開始看到越來越多以 JavaScript 為主的應用程式。在 Rails Doctrine 中,我們相信 菜單是 Omakase,因此我們專注於預設設定:Sprockets 與 Import Maps

我們知道沒有適用於所有可用 JavaScript 和 CSS 架構/擴充套件的一體適用解決方案。Rails 生態系統中還有其他綑綁函式庫,在預設設定不足的情況下,這些函式庫應該能賦予您權限。

10.1 jsbundling-rails

jsbundling-rails 是一種 JavaScript 執行時間依賴的替代方法,可用於使用 Bunesbuildrollup.jsWebpack 綑綁 JS 的 importmap-rails 方式。

此 gem 在 package.json 中提供一個建置工作,用於監控變更並在開發中自動產生輸出。對於生產,它會自動將 javascript:build 工作掛接到 assets:precompile 工作,以確保已安裝所有套件依賴項,並已為所有進入點建置 JavaScript。

何時使用,而不是使用 importmap-rails 如果您的 JavaScript 程式碼依賴於轉譯,因此如果您使用 BabelTypeScript 或 React JSX 格式,則 jsbundling-rails 是正確的方法。

10.2 Webpacker/Shakapacker

Webpacker 是 Rails 5 和 6 的預設 JavaScript 預處理器和綑綁器。它現在已停用。一個稱為 shakapacker 的繼任者存在,但並非由 Rails 團隊或專案維護。

與清單中的其他程式庫不同,webpacker/shakapacker 完全獨立於 Sprockets,且可以處理 JavaScript 和 CSS 檔案。

請閱讀 與 Webpacker 比較 文件,以了解 jsbundling-railswebpacker/shakapacker 之間的差異。

10.3 cssbundling-rails

cssbundling-rails 允許使用 Tailwind CSSBootstrapBulmaPostCSSDart Sass 來組合和處理您的 CSS,然後透過資源管道傳遞 CSS。

它的運作方式類似於 jsbundling-rails,因此會透過 yarn build:css --watch 程序將 Node.js 相依性新增到您的應用程式,以在開發階段重新產生您的樣式表,並在生產階段連接到 assets:precompile 任務。

與 Sprockets 有什麼不同?Sprockets 本身無法將 Sass 轉譯為 CSS,需要 Node.js 從您的 .sass 檔案產生 .css 檔案。產生 .css 檔案後,Sprockets 才可以將它們傳遞給您的客戶端。

cssbundling-rails 依賴 Node 來處理 CSS。dartsass-railstailwindcss-rails 套件使用 Tailwind CSS 和 Dart Sass 的獨立版本,表示沒有 Node 相依性。如果您使用 importmap-rails 來處理 JavaScript,並使用 dartsass-railstailwindcss-rails 來處理 CSS,您可以完全避免 Node 相依性,進而得到一個較不複雜的解決方案。

10.4 dartsass-rails

如果您想在應用程式中使用 Sassdartsass-rails 可以取代舊版的 sassc-rails 套件。dartsass-rails 使用 Dart Sass 實作,而不是 sassc-rails 使用的 LibSass(已於 2020 年棄用)。

sassc-rails 不同,新 gem 並未直接與 Sprockets 整合。請參閱 gem 主頁 以取得安裝/移轉說明。

熱門的 sassc-rails gem 自 2019 年起已停止維護。

10.5 tailwindcss-rails

tailwindcss-railsTailwind CSS v3 架構的獨立可執行版本 的包裝 gem。當在 rails new 指令中提供 --css tailwind 時,用於新應用程式。提供 watch 程序,在開發過程中自動產生 Tailwind 輸出。在生產環境中,它會掛接到 assets:precompile 任務。

回饋

歡迎您協助提升本指南的品質。

如果您發現任何錯字或事實錯誤,請協助我們修正。首先,您可以閱讀我們的 文件貢獻 部分。

您也可能會發現不完整或過時的內容。請務必為 main 新增任何遺漏的文件。請務必先查看 Edge Guides,確認問題是否已在主分支中修正。查看 Ruby on Rails 指南指南 以了解樣式和慣例。

如果您發現需要修正的事項,但無法自行修補,請 開啟問題

最後,歡迎在 官方 Ruby on Rails 論壇 討論任何與 Ruby on Rails 文件相關的事項。