跳至主要内容

[Rails] 靜態檔案處理(The AssetPipeline)

keywords: rails, assets, public, static, manifest

Sprockets

透過 Rails 的 pipeline 可以對檔案(例如,SCSS, CoffeeScript, ERB)進行前處理(pre-process),這些是透過 sprockets-rails 這個 gem 完成的。

Sprockets 會打包(編譯、最小化、壓縮)所有的 .js, .css 檔案成為一支 .js.css 檔。在開發環境下,所有的檔名都會加入一段 SHA256 的編碼,若檔案沒有變更,則使用 cache 的資料,減少伺服器的負擔;一旦檔案有變更,這個編碼就會改變,以此避免 cache 問題。

# 檔名
global-908e25f4bf641868d8683022a5b62f54.css

設定打包方式

一般來說我們會在 ./config/application.rb 中看到

# ./config/application.rb
require "sprockets/railtie"

我們可以在 ./config/environments/production.rb 中設定要打包的方法:

# ./config/environments/production.rb

# 如果有安裝 sass-rails 這個 gem,則這會預設作為 css_compressor 的社竟
config.assets.js_compressor = :uglifier
config.assets.css_compressor = :sass

assets 建立與管理

這些要打包的檔案會放在 ./app/assets/ 這個資料夾中;如果是其他不需要打包的檔案,則可以放在 ./public 這個資料夾中,這些檔案會被當作靜態檔案(config.public_file_server.enabled = true

# ./config/environments/production.rb
# 設定能否以靜態檔案的方式存取 public 中的檔案
config.public_file_server.enabled = true

在開發環境下, Rails 預設會將打包好的檔案放到 ./public/assets 中,接著會以靜態檔案的方式存取,因此在開發環境下,是無法直接透過 server 讀取道 app/assets 中的檔案。

Scaffold

在預設的情況下,當我們使用產生器(generate)來建立檔案時,例如,建立 ProjectsController,Rails 會自動在 ./app/assets 產生 JS 檔和 SCSS 檔,這些檔案將可以透過 require_tree 來載入:

# 如果有安裝 coffee-rails 和 sass-rails 的 gem
./app/assets/javascripts/projects.coffee
./app/assets/stylesheets/projects.scss

你也可以在特定的頁面中指定特定要載入的 stylesheets 和 JS 檔,但要留意,當這樣使用時,不要使用 require_tree,否則同樣的 assets 可能會多次載入:

<%= javascript_include_tag params[:controller] %> or <%= stylesheet_link_tag
params[:controller] %>

當只在特定的頁面載入 JS 或 CSS 時,要留意不要使用 require_tree,否則同樣的 assets 可能會多次載入。

避免自動產生 JS 和 CSS 檔

如果不希望在使用產生器時自動產生 JS 和 CSS 檔,可以在 application.rb 中設定:

# ./config/application.rb
#
module Passer
class Application < Rails::Application
config.generators do |g|
# g.stylesheets false
# g.javascripts false
g.assets false
end
end
end

Assets 檔案位置

Pipeline assets 的檔案可以放在下面三個位置 app/assets, lib/assetsvendor/assets

  • app/assets:通常放應用程式自身的檔案,例如自己寫的圖檔、JS 或 CSS。
  • lib/assets:通常放的是自己寫的函式庫(library),通常不限於在這個應用程式中使用。
  • vendor/assets:通常放的第三方的函式庫。

當我們透過 manifest(安裝設定檔)或 helper 要使用 asset 檔案時,Sprockets 會從這三個預設的位置搜尋檔案,任何在 assets/* 裡面的檔案都將被搜尋。舉例來說,當 manifest 檔案要載入下述檔案時:

//= require home
//= require moovinator
//= require slider
//= require phonebox
//= require sub/something

require 也可以直接載入 npm(如果有安裝 webpacker)的套件。 安裝的 gem 會自動被 require,不用重複寫(因此不要安裝用不到的 gem)。

下面這些檔案都會被處理:

# 在 app/assets, lib/assets, vendor/assets 裡面的檔案
app/assets/javascripts/home.js
lib/assets/javascripts/moovinator.js
vendor/assets/javascripts/slider.js
vendor/assets/somepackage/phonebox.js

# assets/* 的檔案都可以被處理到
app/assets/javascripts/sub/something.js

# gems/* 的檔案也會自動被 require 到

可以在下面的檔案中,看到會搜尋的路徑有哪些:

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

如果不確定有檔案載入的順序或有哪些資料夾被載入,可以 //= require xxx  一個不存在的檔案讓它噴錯,就可以看到載入的路徑:

Imgur

使用 index 檔名的檔案

Sprockets 會自動以名為 index 的檔案當作 manifest 檔來加載其他檔案,舉例來說,如果你的 jQuery 函式庫包含許多的模組,儲存在 lib/assets/javascripts/library_name 中,則 lib/assets/javascripts/library_name/index.js 這支檔案會被當作 manifest 檔案,並根據它在加載其他檔案。

透過 Helper 使用 Assets 檔案

透過 pipeline 使用的檔案會由 Sprockets 提供;如果是將檔案放在 public/assets/rails.png 則會是由伺服器直接提供:

  • javascript_include_tag
  • stylesheet_link_tag
  • image_tag
  • asset_path
  • asset_data_uri
<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => "reload" %>
<%= javascript_include_tag "application", "data-turbolinks-track" => "reload" %>

<!-- ./app/assets/images -->
<%= image_tag "rails.png" %>
<style>
.class { background-image: url(<%= asset_path 'image.png' %>) }
.logo { background: url(<%= asset_data_uri 'logo.png' %>) }
</style>

asset-url("rails.png") <!-- url(/assets/rails.png) -->
asset-path("rails.png") <!-- "/assets/rails.png" -->

安裝設定檔(manifest)

Sprockets 使用安裝設定檔(manifest files)來決定要載入哪些 assets。

JavaScript

./app/assets/javascripts/application.js

在 Sprockets 的設定檔中,會使用 //= 作為開頭,並且使用一些指令:

  • require: 告訴 Sprockets 要載入哪支檔案
  • require_tree: 以疊代的方式(recursive)載入特定資料夾內的所有檔案
  • require_directory: 載入特定資料夾內的檔案,但不會在以疊代的方式進入該資料夾中的其他資料夾。

這些指令會從上往下開始執行,但是如果是透過 require_tree 載入的檔案則不會依循任何順序;另外,這些指令不會載到同一之檔案兩次。

CSS

./app/assets/stylesheets/application.css

@import 'bootstrap'; // 載入特定檔案
@import '*';
@import '**/*';

For more information: sass-rails

在開發模式(development)或沒有沒有啟用 asset pipeline 的情況下,這些檔案會透過 coffee-script 和 rails-sass 的 gems 回傳到瀏覽器;當有啟用 asset pipeline 時,這些檔案會先被前處理(preprocessed)然後放到 public/asset 的資料夾中。

如果一支檔案要經過多次不同的前處理,可以使用多個副檔名,會以副檔名 ++由右至左++ 的順序加以編譯,例如,projects.scss.erb 會先經過 ERB 再來是 SCSS 最後以 CSS 檔給瀏覽器;如果是 projects.coffee.erb 會先經過 ERB 再來是 CoffeeScript 最後以 JS 檔給瀏覽器。

開發模式(Development Mode)

在開發模式下,所有的 assets 會根據安裝設定檔(manifest)中所定義的順序,以獨立的檔案呈現:

// app/assets/javascripts/application.js
//= require core
//= require projects
//= require tickets

這樣的檔案在 HTML 獨立呈現:

<script src="/assets/core.js?body=1"></script>
<script src="/assets/projects.js?body=1"></script>
<script src="/assets/tickets.js?body=1"></script>

發佈模式(Production Mode)

在發佈模式下,Rails 會假設 assets 已經被編譯好,並且以靜態檔案的方式由伺服器提供。

<%= javascript_include_tag "application" %>
<%= stylesheet_link_tag "application" %>

在 HTML 中:

<script src="/assets/application-908e25f4bf641868d8683022a5b62f54.js"></script>
<link
href="/assets/application-4dd5b109ee3439da54f5bdfd78a80473.css"
media="screen"
rel="stylesheet"
/>

參考資料