Asset Pipeline
Assets指的是JavaScript、Stylesheets和图档等静态档案,这些档案并不会随Requests不同而有所不同。而在Rails目录中,只有public这个目录是公开读取的,所以通常我们会将静态档案都放在public这个目录下,好让浏览器可以直接读取。但是随着JavaScript和Stylesheet档案越来越多时,如何管理这些档案变为一项议题,为了加快浏览器的下载速度,我们会合并JavaScript和Stylesheet档案,来减少浏览器Request下载次数。更进一步的还会压缩这些档案来加速下载时间。像是Yahoo!和Google都有各自开源出自己的压缩工具YUI Compressor和。
而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
其中的和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
*/
让我们看看View,在Layout档案中:
<%= stylesheet_link_tag "application" %>
<%= javascript_include_tag "application" %>
因为最后输出都压缩成一个档案了,所以这里只需要加载application.css和application.js。
除了require_tree
之外,还有其他的用法,你都可以使用绝对或是相对路径来指定档案位置,副档名可有可无:
require_tree
[路径] : 会将路径下包含子目录的档案全部加载。require_self
[路径] : 告诉 Sprockets 再加载其他的档案前,先将自己的内容插入。
你可以看 Sprockets 的官方说明 来获得更多的资讯。
另外,在Rails中的assets目录其实有三个:
app/assets
(放置我们自己为了自己的程式所写的 js、css 或是 images)lib/assets
(可以放我们自己写的 js 和 css library)vendor/assets
(放一些我们从别的地方借用的 assets,例如说一些 jQuery 的套件)
这三个目录,在默认情况下这三个资料夹的东西是共通的(因为都会被打包成一个档案),你可以把你的 rails app 跑起来后在 中看到你所有的 js 都在这支档案中,css 同理亦然,你可以在 terminal 中输入
Rails.application.config.assets.paths
来查看所有的 assets 路径。你可以发现,除了原本我们刚刚说的三个 assets 目录之外,还出现了包含在我们 GemFile 中的 jquery,这代表你的 assets 现在也可以包成 gem 来用,如果你有很多个 projects 常重复使用一些共通的 assets,不妨考虑包成 gem 来使用,方便又愉快。
放在app/assets目录下的Assets,Rails支援使用不同的语法,例如使用Sass语法产生CSS、产生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的设计师来说,应该是很不错的工具,读者可以自行斟酌是否采用。
这里我们使用 这个官方的 gem,安装步骤如下:
- 编辑 Gemfile 加上
gem 'bootstrap-sass'
,然后bundle
- 将
app/assets/stylesheets/application.css
改成app/assets/stylesheets/application.scss
,这是因为 Bootstrap 使用 Sass 语法,内容如下
@import "bootstrap-sprockets";
@import "bootstrap";
这里 @import
的作用等同于本来的 require
,会加载该 css 档案编译成同一份 CSS 档案。注意结尾是没有 .css
副档名的,加了反而之后在 production 上会有问题(注)不会加载。另外,结尾一定要有分号 ;
,不然也会出错。
- 修改
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提供的方法:
如果是js档案中想要拿图片的位置,就只能用js.erb的格式,然后内嵌asset_path 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"
开发的时候,Rails会自动将Asset的压缩结果快取在tmp下,所以开发者不需要特别处理。但是实际正式上线时,最后压缩的档案还是必须放在public目录下由网页服务器直接提供(或是由CDN)效能较好,以下的rake指令可以产生出来:
rake assets:precompile
产生出来的档案在public/assets/下。
这样会删除。
注意,如果在开发模式下执行了rake assets:precompile
,那么因为放在public/assets/下的静态档案会优先丢给浏览器,所以这时候再修改app/assets下的原始码会没有作用。所以,开发时请记得要删除这个目录。
一些实务技巧
前端套件五花八门,当我们想要安装一个第三方前端套件时,会先优先找找看有没有包成 gem 的合用版本。如果没有 gem 或不合用,再手动下载放到 /vendor/assets/下。
例如我们想要安装 这个 jQuery plugin,但是找到的 autosize-rails gem 年久失修。因此只好自己手动复制它的程式码 ,放到 vendor/assets/javascripts/autosize.min.js
,然后在 app/assets/javascript/application.js
里面去加载它:
//= require autosize.min
autosize($('textarea'));
});
如果该前端套件不需要跟我们的 application.js 压缩在一起,而且它已经是压缩过后的版本,也可以考虑就放到 public
目录下,就完全不经过 Asset Pipeline,你可以在 HTML layout 中直接加载它。
如果只有那一个 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
档案:
新增 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");
}
})
和上一节类似,我们可以新增一个 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
中依序加载即可。
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功能,也只能接受一个参数,即_Manifest档案的名称。