官方文档:http://guides.ruby-china.org/asset_pipeline.html
http://guides.rubyonrails.org/asset_pipeline.html
在 Rails 的早期版本中,所有静态资源文件都放在 public
文件夹的子文件夹中,例如 images
、javascripts
和 stylesheets
子文件夹。当 Rails 开始使用 Asset Pipeline 后,就推荐把静态资源文件放在 app/assets
文件夹中,并使用 Sprockets 中间件处理这些文件。
当然,静态资源文件仍然可以放在 public
文件夹及其子文件夹中。只要把 config.public_file_server.enabled
选项设置为 true
,Rails 应用或 Web 服务器就会处理 public
文件夹及其子文件夹中的所有静态资源文件。但对于需要预处理的文件,都应该放在 app/assets
文件夹中。
在生产环境中,Rails 默认会对 public/assets
文件夹中的文件进行预处理,经过预处理的静态资源文件将由 Web 服务器直接处理,app/assets
文件夹中的文件不会直接交由 Web 服务器处理。
1 针对控制器的静态资源文件
使用生成器生成控制器时,Rails 会同时为控制器生成以下文件:
- JavaScript 文件,如果 Gemfile 中包含了
coffee-rails
gem,那么生成的是 CoffeeScript 文件 - CSS 文件,如果 Gemfile 中包含了
sass-rails
gem,那么生成的是 SCSS 文件
在生成脚手架时,Rails 还会生成
-
scaffolds.css
文件,如果 Gemfile 中包含了sass-rails
gem,那么生成的是scaffolds.scss
文件
针对控制器的 JavaScript 文件和 CSS 文件也可以只在相应的控制器中引入:
<%= javascript_include_tag params[:controller] %> 或 <%= stylesheet_link_tag params[:controller] %>
此时,千万不要使用 require_tree
指令,否则就会重复包含这些静态资源文件。
在进行静态资源文件预编译时,请确保针对控制器的静态文件是在按页加载时进行预编译的。默认情况下,Rails 不会自动对 .coffee
和 .scss
文件进行预编译。
要使用 CoffeeScript,就必须安装支持 ExecJS 的运行时。macOS 和 Windows 已经预装了此类运行时。
通过在 config/application.rb
配置文件中添加下述代码,可以禁止生成针对控制器的静态资源文件:
config.generators do |g|
g.assets false
end
2 静态资源文件的组织方式
应用的 Asset Pipeline 静态资源文件可以储存在三个位置:app/assets
、lib/assets
和 vendor/assets
。
app/assets
用于储存应用自有的静态资源文件,例如自定义图像、JavaScript 文件和 CSS 文件。lib/assets
用于储存自有代码库的静态资源文件,这些代码库或者不适合放在当前应用中,或者需要在多个应用间共享。vendor/assets
用于储存第三方代码库的静态资源文件,例如 JavaScript 插件和 CSS 框架。如果第三方代码库中引用了同样由 Asset Pipeline 处理的静态资源文件(图像、CSS 文件等),就必须使用asset_path
这样的辅助方法重新编写相关代码。
lib/assets
和 vendor/assets
文件夹中的静态资源文件,但是这两个文件夹不再是预编译数组的一部分。2.1 搜索路径
当清单文件或辅助方法引用了静态资源文件时,Sprockets 会在静态资源文件的三个默认存储位置中进行查找。
这三个默认存储位置分别是 app/assets
文件夹的 images
、javascripts
和 stylesheets
子文件夹,实际上这三个文件夹并没有什么特别之处,所有的 app/assets/*
文件夹及其子文件夹都会被搜索。
例如,下列文件:
app/assets/javascripts/home.js lib/assets/javascripts/moovinator.js vendor/assets/javascripts/slider.js vendor/assets/somepackage/phonebox.js
在清单文件中可以像下面这样进行引用:
//= require home //= require moovinator //= require slider //= require phonebox
这些文件夹的子文件夹中的静态资源文件:
app/assets/javascripts/sub/something.js
可以像下面这样进行引用:
//= require sub/something
通过在 Rails 控制台中检查 Rails.application.config.assets.paths
变量,我们可以查看搜索路径。
除了标准的 app/assets/*
路径,还可以在 config/application.rb
配置文件中为 Asset Pipeline 添加其他路径。例如:
config.assets.paths << Rails.root.join("lib", "videoplayer", "flash")
app/assets
中的文件优先级最高,将会遮盖 lib
和 vendor
文件夹中的同名文件。千万注意,在清单文件之外引用的静态资源文件必须添加到预编译数组中,否则无法在生产环境中使用。
2.2 使用索引文件
对于 Sprockets,名为 index
(带有相关扩展名)的文件具有特殊用途。
例如,假设应用中使用的 jQuery 库及多个模块储存在 lib/assets/javascripts/library_name
文件夹中,那么 lib/assets/javascripts/library_name/index.js
文件将作为这个库的清单文件。在这个库的清单文件中,应该按顺序列出所有需要加载的文件,或者干脆使用require_tree
指令。
在应用的清单文件中,可以把这个库作为一个整体加载:
//= require library_name
这样,相关代码总是作为整体在应用中使用,降低了维护成本,并使代码保持简洁。
3 创建指向静态资源文件的链接
Sprockets 没有为访问静态资源文件添加任何新方法,而是继续使用我们熟悉的 javascript_include_tag
和 stylesheet_link_tag
辅助方法:
<%= stylesheet_link_tag "application", media: "all" %> <%= javascript_include_tag "application" %>
如果使用了 Rails 默认包含的 turbolinks
gem,并使用了 data-turbolinks-track
选项,Turbolinks 就会检查静态资源文件是否有更新,如果有更新就加载到页面中:
<%= 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" %>
如果在应用中启用了 Asset Pipeline,并且未在当前环境中禁用 Asset Pipeline,那么这个图像文件将由 Sprockets 处理。如果图像的位置是 public/assets/rails.png
,那么将由 Web 服务器处理。
如果文件请求包含 SHA256 哈希值,例如 public/assets/rails-f90d8a84c707a8dc923fca1ca1895ae8ed0a09237f6992015fef1e11be77c023.png
,处理的方式也是一样的。
Sprockets 还会检查 config.assets.paths
中指定的路径,其中包括 Rails 应用的标准路径和 Rails 引擎添加的路径。
也可以把图像放在子文件夹中,访问时只需加上子文件夹的名称即可:
<%= image_tag "icons/rails.png" %>
如果对静态资源文件进行了预编译,那么在页面中链接到并不存在的静态资源文件或空字符串将导致该页面抛出异常。因此,在使用 image_tag
等辅助方法处理用户提供的数据时一定要小心。
3.1 CSS 和 ERB
Asset Pipeline 会自动计算 ERB 的值。也就是说,只要给 CSS 文件添加 .erb
扩展名(例如 application.css.erb
),就可以在 CSS 规则中使用 asset_path
等辅助方法。
.class { background-image: url(<%= asset_path 'image.png' %>) }
上述代码中的 asset_path
辅助方法会返回指向图像真实路径的链接。图像必须位于静态文件加载路径中,例如 app/assets/images/image.png
,以便在这里引用。如果在 public/assets
文件夹中已经存在此图像的带指纹的版本,那么将引用这个带指纹的版本。
要想使用 data URI(用于把图像数据直接嵌入 CSS 文件中),可以使用 asset_data_uri
辅助方法:
#logo { background: url(<%= asset_data_uri 'logo.png' %>) }
asset_data_uri
辅助方法会把正确格式化后的 data URI 插入 CSS 源代码中。
注意,关闭标签不能使用 -%>
形式。
3.2 CSS 和 Sass
在使用 Asset Pipeline 时,静态资源文件的路径都必须重写,为此 sass-rails
gem 提供了 -url
和 -path
系列辅助方法(在 Sass 中使用连字符,在 Ruby 中使用下划线),用于处理图像、字体、视频、音频、JavaScript 和 CSS 等类型的静态资源文件。
image-url("rails.png")
会返回url(/assets/rails.png)
image-path("rails.png")
会返回"/assets/rails.png"
或使用更通用的形式:
asset-url("rails.png")
返回url(/assets/rails.png)
asset-path("rails.png")
返回"/assets/rails.png"
3.3 JavaScript/CoffeeScript 和 ERB
只要给 JavaScript 文件添加 .erb
扩展名(例如 application.js.erb
),就可以在 JavaScript 源代码中使用 asset_path
辅助方法:
$('#logo').attr({ src: "<%= asset_path('logo.png') %>" });
上述代码中的 asset_path
辅助方法会返回指向图像真实路径的链接。
同样,只要给 CoffeeScript 文件添加 .erb
扩展名(例如 application.coffee.erb
),就可以在 CoffeeScript 源代码中使用 asset_path
辅助方法:
$('#logo').attr src: "<%= asset_path('logo.png') %>"
4 清单文件和指令
Sprockets 使用清单文件来确定需要包含和处理哪些静态资源文件。这些清单文件中的指令会告诉 Sprockets,要想创建 CSS 或 JavaScript 文件需要加载哪些文件。通过这些指令,可以让 Sprockets 加载指定文件,对这些文件进行必要的处理,然后把它们连接为单个文件,最后进行压缩(压缩方式取决于 Rails.application.config.assets.js_compressor
选项的值)。这样在页面中只需处理一个文件而非多个文件,减少了浏览器的请求次数,大大缩短了页面的加载时间。通过压缩还能使文件变小,使浏览器可以更快地下载。
4.1 javascript
在默认情况下,新建 Rails 应用的 app/assets/javascripts/application.js
文件包含下面几行代码:
// ... //= require jquery //= require jquery_ujs //= require_tree .
在 JavaScript 文件中,Sprockets 指令以 //=.
开头。上述代码中使用了 require
和 require_tree
指令。require
指令用于告知 Sprockets 哪些文件需要加载。这里加载的是 Sprockets 搜索路径中的 jquery.js
和 jquery_ujs.js
文件。我们不必显式提供文件的扩展名,因为 Sprockets 假定在 .js
文件中加载的总是 .js
文件。
require_tree
指令告知 Sprockets
以递归方式包含指定文件夹中的所有 JavaScript 文件。在指定文件夹路径时,必须使用相对于清单文件的相对路径。也可以通过 require_directory
指令包含指定文件夹中的所有 JavaScript 文件,此时将不会采取递归方式。
清单文件中的指令是按照从上到下的顺序处理的,但我们无法确定 require_tree
指令包含文件的顺序,因此不应该依赖于这些文件的顺序。如果想要确保连接文件时某些 JavaScript 文件出现在其他 JavaScript 文件之前,可以在清单文件中先行加载这些文件。注意,require
系列指令不会重复加载文件。
4.2 css
在默认情况下,新建 Rails 应用的 app/assets/stylesheets/application.css
文件包含下面几行代码:
/* ... *= require_self *= require_tree . */
无论新建 Rails 应用时是否使用了 --skip-sprockets
选项,Rails 都会创建 app/assets/javascripts/application.js
和 app/assets/stylesheets/application.css
文件。因此,之后想要使用 Asset Pipeline 非常容易。
我们在 JavaScript 文件中使用的指令同样可以在 CSS 文件中使用,此时加载的是 CSS 文件而不是 JavaScript 文件。在 CSS 清单文件中,require_tree
指令的工作原理和在 JavaScript 清单文件中相同,会加载指定文件夹中的所有 CSS 文件。
上述代码中使用了 require_self
指令,用于把当前文件中的 CSS 代码(如果存在)插入调用这个指令的位置。
和使用 require_tree
指令相比,使用 @import "*"
和 @import "**/*"
的效果完全相同,都能加载指定文件夹中的所有文件。
我们可以根据需要使用多个清单文件。例如,可以用 admin.js
和 admin.css
清单文件分别包含应用管理后台的 JS 和 CSS 文件。
CSS 清单文件中指令的执行顺序类似于前文介绍的 JavaScript 清单文件,尤其是加载的文件都会按照指定顺序依次编译。例如,我们可以像下面这样把 3 个 CSS 文件连接在一起:
/* ... *= require reset *= require layout *= require chrome */
5 预处理
静态资源文件的扩展名决定了预处理的方式。在使用默认的 Rails gemset 生成控制器或脚手架时,会生成 CoffeeScript 和 SCSS 文件,而不是普通的 JavaScript 和 CSS 文件。在前文的例子中,生成 projects
控制器时会生成 app/assets/javascripts/projects.coffee
和 app/assets/stylesheets/projects.scss
文件。
在开发环境中,或 Asset Pipeline 被禁用时,会使用 coffee-script
和 sass
gem 提供的处理器分别处理相应的文件请求,并把生成的 JavaScript 和 CSS 文件发给浏览器。当 Asset Pipeline 可用时,会对这些文件进行预处理,然后储存在 public/assets
文件夹中,由 Rails 应用或 Web 服务器处理。
通过添加其他扩展名,可以对文件进行更多预处理。对扩展名的解析顺序是从右到左,相应的预处理顺序也是从右到左。例如,对于 app/assets/stylesheets/projects.scss.erb
文件,会先处理 ERB,再处理 SCSS,最后作为 CSS 文件处理。同样,对于app/assets/javascripts/projects.coffee.erb
文件,会先处理 ERB,再处理 CoffeeScript,最后作为 JavaScript 文件处理。
记住预处理顺序很重要。例如,如果我们把文件名写为 app/assets/javascripts/projects.erb.coffee
,就会先处理 CoffeeScript,这时一旦遇到 ERB 代码就会出错。