Asset Pipeline
A language that doesn’t affect the way you think about programming is not worth knowing. - Alan Perlis
Assets指的是JavaScript、Stylesheets和图档等静态档案,这些档案并不会随Requests不同而有所不同。而在Rails目录中,只有public这个目录是公开读取的,所以通常我们会将静态档案都放在public这个目录下,好让浏览器可以直接读取。但是随着JavaScript和Stylesheet档案越来越多时,如何管理这些档案变为一项议题,为了加快浏览器的下载速度,我们会合并JavaScript和Stylesheet档案,来减少浏览器Request下载次数。更进一步的还会压缩这些档案来加速下载时间。像是Yahoo!和Google都有各自开源出自己的压缩工具YUI Compressor和Closure Compiler。
而Rails的Assets pipeline可以让我们突破public目录限制,可以将静态档案依需求放在不同目录下,Rails会帮你组合并压缩起来。特别是有一些Rails的外挂套件需要使用JavaScript等静态档案,在没有这个功能之前,我们必须将JavaScript等档案复制放在public目录下,这样浏览器才能读取的到。
Assets的位置在app/assets/下,首先最重要的就是app/assets/javascripts/application.js和app/assets/stylesheets/application.css,这两个档案看起来充满注解,其实它是个manifest档案,列出了所有要加载的静态档案,这些档案的位置依照惯例放在app/assets或vendor/assets目录下。
让我们先看看application.js
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require_tree .
其中的require jquery
和require jqueryujs
会加载_JQuery和Rails的JQuery adapater,这是因为我们在Gemfile中有装jquery-rails这个套件,所以这里可以读取的到。require turbolinks
在上一章有提到。而requiretree .
会加载这个目录下的所有_JavaScript档案。总之,这个manifest的最后输出结果就是通通压缩成一个application.js档案。
同理application.css也是一样加载所有stylesheets目录下的CSS档案,最后压缩成application.css:
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
* compiled file so the styles you add here take precedence over styles defined in any styles
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
* file per style scope.
*
*= require_tree .
*= require_self
*/
实务上不喜欢用
require_tree
的方式来一次加载全部的 css 和 javascript,而是喜欢逐条require
加载,一来比较清楚,二来也可以确保 css 和 javascript 加载的顺序是正确的。另外也不喜欢用require_self
将 css 写在application.css
里面,我们应该放到另一个档案再require
它即可。
让我们看看View,在Layout档案中:
<%= stylesheet_link_tag "application" %>
<%= javascript_include_tag "application" %>
因为最后输出都压缩成一个档案了,所以这里只需要加载application.css和application.js。
除了require_tree
之外,还有其他的用法,你都可以使用绝对或是相对路径来指定档案位置,副档名可有可无:
require
[路径]:加载某支特定档案,如果这支档案被加载多次,Sprockets 也会很聪明的只帮你加载一次。require_tree
[路径] : 会将路径下包含子目录的档案全部加载。require_self
[路径] : 告诉 Sprockets 再加载其他的档案前,先将自己的内容插入。
你可以看 Sprockets 的官方说明 来获得更多的资讯。
另外,在Rails中的assets目录其实有三个:
app/assets
(放置我们自己为了自己的程式所写的 js、css 或是 images)lib/assets
(可以放我们自己写的 js 和 css library)vendor/assets
(放一些我们从别的地方借用的 assets,例如说一些 jQuery 的套件)
这三个目录,在默认情况下这三个资料夹的东西是共通的(因为都会被打包成一个档案),你可以把你的 rails app 跑起来后在 http://localhost:3000/assets/application.js
中看到你所有的 js 都在这支档案中,css 同理亦然,你可以在 terminal 中输入 Rails.application.config.assets.paths
来查看所有的 assets 路径。你可以发现,除了原本我们刚刚说的三个 assets 目录之外,还出现了包含在我们 GemFile 中的 jquery,这代表你的 assets 现在也可以包成 gem 来用,如果你有很多个 projects 常重复使用一些共通的 assets,不妨考虑包成 gem 来使用,方便又愉快。
支援 Preprocessor 预处理
放在app/assets目录下的Assets,Rails支援使用不同的语法,例如使用Sass语法产生CSS、CoffeeScript产生JavaScript,或是用ERb样板也可以。使用的方法是将附档名命名成.sass、.scss、.coffee、.css.erb或.js.erb,Rails就会编译出结果给浏览器。
Sass是一种CSS3语法的扩充,可以使用巢状、变量、混入、选择子继承等等功能,可以更有效率有弹性的撰写Stylesheet。Sass最后会编译出合法的CSS让浏览器使用。使用上它区分成两种形式的语法scss和sass,前者可以与现有的css程式码直接混合在一起,后者则透过缩排来省略了大括号{}
(就像Python用缩排一样)。CoffeeScript也是类似原理,它是一种迷你的程式语言,编译之后会输出可读性高、符合JavaScript Lint规范的JavaScript程式码。
学习这两种新语法需要额外的时间投入,但是对需要常常撰写CSS和JavaScript的设计师来说,应该是很不错的工具,读者可以自行斟酌是否采用。
安装 Bootstrap 样式
Bootstrap 是一个目前非常流行的前端框架,非常适合作为团队开发时,前端、后端开发者当作默认的前端框架。
这里我们使用 boostrap-sass 这个官方的 gem,安装步骤如下:
- 编辑 Gemfile 加上
gem 'bootstrap-sass'
,然后bundle
- 将
app/assets/stylesheets/application.css
改成app/assets/stylesheets/application.scss
,这是因为 Bootstrap 使用 Sass 语法,内容如下
@import "bootstrap-sprockets";
@import "bootstrap";
@import "bootstrap/theme"; // this is optional
这里 @import
的作用等同于本来的 require
,会加载该 css 档案编译成同一份 CSS 档案。注意结尾是没有 .css
副档名的,加了反而之后在 production 上会有问题(注)不会加载。另外,结尾一定要有分号 ;
,不然也会出错。
这是因为 CSS 本身也有
@import
语法,所以加了.css
后,@import
反而就不编译了,而是直接显示出例如@import "bootstrap.css"
;,浏览器看到这个 css 语法会去下载bootstrap.css
这个档案,当然是下载不到因为整个 assets pipepline 最后只会编译出一个.css
档案,就是application-加上一长串digest码.css
。
- 修改
app/assets/javascripts/application.js
,在下方加上
//= require bootstrap-sprockets
这样我们就安装好了。
其他参考资料 https://launchschool.com/blog/integrating-rails-and-bootstrap-part-1
如何处理 image 图档
放在app/assets/images下的图片该怎么使用呢?在实际布署后,Rails会将档案名称加以编码,例如rails.png会变成rails-bd9ad5a560b5a3a7be0808c5cd76a798.png。这么做的原因是当图片有变更的时候,编码就会不同而有不同的档名,这样就可以避免浏览器快取到旧的档案。也因为档案名称会变动,所以放在app/assets/images下的图片,要用的时候就没有办法写死档名。在一般的View中,可以使用imagetag
这个_Helper:
<%= image_tag("rails.png") %>
如果在CSS里的话,有两种办法:一是将档案命名为erb结尾,例如app/assets/stylesheets/main.css.erb,然后使用assetpath
这个_Helper:
h1 {
background-image: url('<%= asset_path("rails.png") %>');
}
另一种方法是使用Sass或SCSS语法。其中SCSS相容于CSS。例如命名为app/assets/stylesheets/main.scss,然后使用image-url
这个Sass提供的方法:
h1 {
background-image: image-url("rails.png")
}
如果是js档案中想要拿图片的位置,就只能用js.erb的格式,然后内嵌asset_path Helper方法了。
其他 Helper
Assets 提供了很多路径 helper 来让你指向你的 assets,可以在 erb 样板中使用:
audio_path("horse.wav") # => /audios/horse.wav
audio_tag("sound") # => <audio src="/audios/sound" />
font_path("font.ttf") # => /fonts/font.ttf
image_path("edit.png") # => "/images/edit.png"
image_tag("icon.png") # => <img src="/images/icon.png" alt="Icon" />
video_path("hd.avi") # => /videos/hd.avi
video_tag("trailer.ogg") # => <video src="/videos/trailer.ogg" />
Sass 还提供了像是 -url
和 -path
这样的 helper 来协助你,因此你可以在 scss
样板中这样使用:
image-url("rails.png") # => url(/assets/rails.png)
image-path("rails.png") # => "/assets/rails.png"
asset-url("rails.png", image) # => url(/assets/rails.png)
asset-path("rails.png", image) # => "/assets/rails.png"
Precompile 编译静态档案
开发的时候,Rails会自动将Asset的压缩结果快取在tmp下,所以开发者不需要特别处理。但是实际正式上线时,最后压缩的档案还是必须放在public目录下由网页服务器直接提供(或是由CDN)效能较好,以下的rake指令可以产生出来:
rake assets:precompile
产生出来的档案在public/assets/下。
rake assets:clobber
这样会删除。
注意,如果在开发模式下执行了rake assets:precompile
,那么因为放在public/assets/下的静态档案会优先丢给浏览器,所以这时候再修改app/assets下的原始码会没有作用。所以,开发时请记得要删除这个目录。
一些实务技巧
前端套件安装
前端套件五花八门,当我们想要安装一个第三方前端套件时,会先优先找找看有没有包成 gem 的合用版本。如果没有 gem 或不合用,再手动下载放到 /vendor/assets/下。
不过如果 CSS 里面有图片的话,则很可能会有路径问题,需要手动修理。如果只是 JavaScript 就比较没有问题。
例如我们想要安装 Autosize 这个 jQuery plugin,但是找到的 autosize-rails gem 年久失修。因此只好自己手动复制它的程式码 autosize.min.js,放到 vendor/assets/javascripts/autosize.min.js
,然后在 app/assets/javascript/application.js
里面去加载它:
//= require autosize.min
$(document).on("turbolinks:load", function(){
autosize($('textarea'));
});
如果该前端套件不需要跟我们的 application.js 压缩在一起,而且它已经是压缩过后的版本,也可以考虑就放到 public
目录下,就完全不经过 Asset Pipeline,你可以在 HTML layout 中直接加载它。
自己的 JavaScript 程式码要写在哪里?
如果只有那一个 HTML template 有用到的 javascript script,我们可以直接塞到该 template 下方:
<script>
//your js code
</script>
当然这样的缺点就是它其实没有经过 Asset Pipeline,当然也就没有压缩或预处理的功能。另外,在新版的 Turbolinks 中,因为 Turbolinks 的快取功能,会让上述放在 body 中的 script 重复执行两遍,我们在 Ajax 一章的 Turbolinks 一节说明过。
如果是每一页都会执行,或是共享的 javascript function,我们放进 assets/javascript/ 下。例如编辑 assets/javascript/application.js
我们多一个 common.js
档案:
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require bootstrap-sprockets
//= require common
新增 assets/javascript/common.js
档案,内容例如:
function your_function() {
// your js code
}
$(document).on("turbolinks:load", function(){
/// you js code
});
另外还有一个小技巧是可以将 js 和 css code 放进 Asset Pipeline 里面,但是只有特定页面才会作用(这一招不管 Turbolinks 有没有移除都适用)。方法是每一页的 body
标籤加上 id
,修改 app/views/layout/application.html.erb
:
<body id="<%= controller.controller_name %>-<%= controller.action_name %>">
这样如果 welcome
controller 的 index
页面的话,就会产生出 <body id="welcome-index">
的标籤。如此这样我们的 css 或 javascript 就可以定位了。例如可以在上述的 assets/javascript/common.js
写:
$(document).on("turbolinks:load", function() {
if ( $("#welcome-index").length > 0 ) {
console.log("this is welcome-index page");
}
})
Rails 产生 controller 档案时,也会产生一个同名的
.coffee
和.scss
档案,这没有什么实际的用途,它并不是说该 controller 的页面才会执行该 js 和 css。建议可以砍掉这个档案,自己安排 js 和 css 的档案即可。
自己的 CSS 程式码要写在哪里?
和上一节类似,我们可以新增一个 app/assets/stylesheets/style.scss
来放我们自己的 CSS,然后编辑 app/assets/stylesheets/application.scss
去加载它:
@import "bootstrap-sprockets";
@import "bootstrap";
@import "bootstrap/theme";
@import "style";
当然,你可以依照自己的需求去分拆 CSS 档案,只要在 app/assets/stylesheets/application.scss
中依序加载即可。
如何新增 manifest 档案 (拆成数个压缩档)?
Rails 默认只会压 application.js和application.css 这两个 JavaScript 和 CSS 档案,如果你的网站不大的话就够用了。不过如果你需要根据不同情境 layout 区分不同的 JavaScript 和 CSS 时,就需要新增 manifest 档案来拆开,例如区分前台、后台等等。
在上述的application.js和application.css中,默认会压缩所有app/assets目录下的档案,要拆开的话,首先需要修改其中的内容把require_tree
那行移除,那么就只会压缩你所指定的目录或档案。
例如,要新增新的Manifest档案的话,假设叫做app/assets/javascripts/widget.js,内容如:
//= require ./foobar
这样就会将assets/javascripts/foobar这个目录下的档案通通压缩成widget.js,而在View中,加上:
<%= javascript_include_tag "widget" %>
就会加载这个 widget.js。注意到如果启用了assets功能,javascriptinclude_tag
也只能接受一个参数,即_Manifest档案的名称。
为了让rake assets:precompile
也能产生新的压缩档案,你还需要编辑config/initializers/assets.rb加入:
Rails.application.config.assets.precompile += %w( widget.js )