原英文版为: http://guides.rubyonrails.org/asset_pipeline.html
本文: http://blog.csdn.net/aegoose/article/details/70293070. Pipeline的概念:
输入 ===pipeline1处理==> 输出 ===pipeline2处理==> 输出 ... ... ===pipelineN处理==> 输出
1. 什么是Asset Pipeline
Asset pipline提供一个框架,整合并压缩所有的js和css等资源,资源文件可以用coffeeScript,sass或erb扩展编写.
在rails3.1以前,一般要通过第三方库比如jammit和sprockets来实现assets机制,而rails3.1默认的actionpack已支持, 主要通过sprockets插件实现.
开发人员可以只要通过sprokets库便可以对assets资源进行预处理,压缩.这种方式 与DHH在2011的RailsConf所提出的”fastby default”策略相吻合.
Rails3.1中,assetpipeline默认开启, 可以修改 config/application.rb来禁用:config.assets.enabled= false
#也可以在新建工程时禁用sprockets,不推荐.
rails new appname –skip-sprockets
1.1 主要特点
第一个特点是为页面整合所有assets资源.在生产环境此特点极为重要,可以简少浏览器加载页面时对资源的请求次数.
Rails本身也可以通过helper来引用资源文件,如”javascipt_include_tag”或”stylesheet_link_tag”加上”:cache=> true”, 实现资源缓存到浏览器上.不过存在局限性,比如,它不能提前缓存资源,不能隐式包含一些第三方库的资源.(隐式包含是指将第三方库代码如js直接合并到当前引用的js代码中)
Rails3.1默认把所有的js和css文件整合到一个文件中.当然,你也可以按实际需要差分成多个文件.在生产环境,每个生成的资源文件会追加一个MD5的数字摘要串.浏览器会直接缓存这个文件,而当MD5串在后台环境修改了,浏览器会重新请求缓存.第三个特点是用其他语言扩展资源文件内容.可以使用sass编写css,coffeeScript编写js,以及erb编写css或js,这样更好的集成rails的helper.
1.2 Fingerprinting有什么好的?
Fingerprinting是一种文件签名技术.Rails 3.1使用它分析文件内容生成带签名的文件名,标识出文件的最后一次变更,可以确保文件内容与文件名的唯一对应.
由于文件名与文件内容的唯一对应关系,这样浏览器缓存的文件就是某个时期修改了的文件.而当文件内容更新了,fingerprint生成新的文件名,浏览器也会重新缓存新文件.这便是cachebusting技术.global-908e25f4bf641868d8683022a5b62f54.css
Railsasset pipeline 主要使用以上方式.
/stylesheets/global.css?1309495796
这种方式存在局限性:
1)并非所有的缓存机制支持带参的查询串.
SteveSouders曾说到”要避免通过查询串来缓存资源文件”,他研究发现浏览器有5-20%机率不能带参数缓存资源.许多CDN(ContentDelivery Network,内容分发网络)环境不能缓存查询串的资源.
2)针对分布式服务器环境,文件名随时会改变
查询串主要是基于时间撮的,而资源同服务器一同部署,不同的服务器会产生不一样的时间撮,这样就无法保证请求时附加的查询串能对应到产生该串的服务器上了.比如A,B服务器分别产生的查询串为a,b,客户端第一次得到查询串a,第二次用a去请求,该请求可能分配B上,不是B期望的查询串b,这样B会重新传给客户端相同的资源.
另一个问题是当服务器新版本部署后,即使新版本没有改动资源文件,那也会强制客户端重新缓存资源文件.
Fingerprinting根据文件内容生成文件名避开使用查询串的问题,只要内容是相同的,文件名也是相同的.
Fingerprinting在生产环境默认是开启,而其他环境不开启.可以在config/application.rb设置 config.assets.digest 选择开启动.更多参数Optimizecaching
RevvingFilenames: don’t use query string
2. 如何使用assets pipeline
Rails以前的版本,所有的assets资源文件都放在public的子目录中,如imaegs/,javascript/, 及stylesheets.而当使用assetpipeline, 这些资源文件可以放在app/assets目录下.所以文件被srockets插件进行管理.
这并不是说”public”目录不再放资源文件,一些静态的资源文件仍然可以放在public下,而另一些文件可以放在app/assets文件下,以便可以通过预编译处理再被调用到.Rails生产环境默认会预编译这些资源文件到public/assets目录下,这样可以直接在发布到服务器下被使用.
但使用scaffold或者controller生成应用代码,rails会为每个controller生成一个js文件(如果安装了coffeescript插件则会生成对应的js文件)和css文件.
比如,当生成ProjectsController,会同生成app/assets/javascripts/prjects.js.coffee及app/assets/stylesheets/projects.css.scss文件.每一个controller对应一个相同命名的js和css文件.这些文件可以通过如下helper进行自动加载到每个controller的应用:<%=stylesheet_link_tag params[:controller] %>
<%=javascript_include_tag params[:controller]%>
可以使用 ExecJS 支持coffeeScript.更多参考ExecJS相关的文件.
2.1 Asset的组成
Assets可以出现在三处地方,app/assets, lib/assets及vendor/assets.
这三处地方会被加入到sprockets会搜索路径(可以通过Rails.application.config.assets.paths定义Sprockets的搜索路径).当sprockets请求一个资源文件,会检索所有路径直至资源文件匹配,然后将文件编译并应用到服务器上.App/assets存的是当前应用程序的各种资源,如自定义的图片,js和样式;
Lib/assets存的是当前自定义库对应的资源;
vendor/assets则存第三方插件库对应的资源文件(有些插件支持自定义资源)
config.assets.paths<< Rails.root.join("app","assets","flash")
2.2 在程序中引用assets资源
通过helper手工添加资源文件到应用程序中,这点sprockets没有直接支持,
<%=stylesheet_link_tag "application"%>
<%=javascript_include_tag "application"%>
在view中可能添加以下helper引用图片文件:
<%=image_tag "rails.png"%>
假设你已在程序中启动pipeline,那么以上helper会搜索public/assets/rails.png这个图片.
<%=image_tag "icon/rails.png"%>
2.2.1 css整合erb
css可以扩展成css.erb(如application.css.erb),这样可以使用rails的helper,如使用asset_path为css来定义图片资源:
.class{ background-image: url(<%= asset_path 'image.png' %>) }
其中<%%>是ruby语法,调用了asset_path helper,指定对image.png文件的引用,些文件会自动关联到app/assets/image-$('#logo').attr({ src: "<%=asset_path('logo.png')%>" });
这个helper会加载带上data_URI标准的图片路径,详细参考data URI.
2.2.2 css整合sass
css可以扩展成css.sass,sass-rails将通过url或path两种形式重写css的路径,主要在使用在image,font,video, audio, javascript, stylesheet各种具体helper上进行应用.
image-url(“rails.png”)将变成url(/assets/rails.png)
image-path(“rails.png”)将变成“/assets/rails.png”
也可以通用形式的helper,同时指定路径的文件类型:
asset-url(“rails.png”,image) 将变成url(/assets/rails.png)
asset-path(“rails.png”,image)将变成“/assets/rails.png”
2.2.3 js整合ERB
Js文件可以使用js.erb进行扩展(如application.js.erb),这样可以引用rails的helper,用asset_path来定义资源路径:
$('#logo').attr({
src:<%=asset_path('logo.png')%>
});
asset_path将请求的路径引用到对应的assets路径上.
同样的,coffeeScript也可以使用asset_path引用资源.
2.3 manifest文件及其引用规则
Sprockets定义了一些manifest文件集,决策哪些资源被使用.这些文件会使用一些规则来告诉sprockets哪些文件可以被引入,并指导sprockets整合生成单一的js或css文件(当Rails.application.config.assets.compress=true才整合资源).单一资源文件可以减少更多的请求时间.
比如在Rails应用程序中的app/assets/javascripts/application.js文件包含以行内容:
/...
//=require jquery
//=require jquery_ujs
//=require_tree .
引用规则以”//=”作为开始标记.上述实例使用require和require_tree规则.
其中require指加载某些指定文件,上述实例的”require jquery”通知sprockets加载jquery.js文件,这些文件的位置是sprockets path定义的位置.
Rails3.1默认使用jquery-rails插件,jquery.js和jquery_ujs.js都存在插件的assetpipeline位置上,在当前工程的assets上看不到的.
而require_tree指加载所有路径上的资源文件.上述实例的”require_tree .”通知sprockets加载当前路径下的所有js资源.
其他的规则如require_directory则指加载指定路径下的资源文件(js)文件.
引用规则是从上到下顺序解释的,不过require_tree规则没有指定tree目录的翻译顺序,所以tree目录下的js文件最好没有顺序要求.如果是第三方的js插件需要其他js依赖某些js库文件,最好把依赖的js文件放到最上面.注意不要多次引入相用的文件.
同样的,css也会在app/assets/stylesheets/application.css定义引用规则,如下实例:
/*...
*=require_self
*=require_tree .
*/
规则与css的引用规则是相同的.在以上实例中,用到了require_self规则,它会去寻找调用它的页面的路径,并把同名的css包含进来(每个controller会有对应的css和js文件).require_self多次被调用,只有最后一次调用有效.
require_self理解错了,它是把被require进来的css的内容放到最上面而已,比如你的application.js,它除了使用一些=require a.js后,它本身也可以写一些脚本内容(如function b(){}).那么,这些写的脚本内容(b函数的脚本)会放在最前面,而a.js的内容会放在后面. css也是这样的. 如果没有定义,它就会把内容(b函数)放在后面.
可以使用sass的@import规则来引入多个sass文件.
srpockets的引用规则可以定义多个manifest文件,比如定义admin.css和admin.js文件用于包含后台应用需要引用的资源文件,使用规则跟上面所描述的一致.
2.4 资源文件预编译机制
不同资源文件的(如.erb,.sass,.coffee)在生成时将会由不同的解析机制进行预编译(这里的编译是指对文件进行解析和转换,得到新输入格式).
controller和scaffold自动生成时会产生一个coffeescript文件和scss文件,替代传统的js和css文件.
如”projects”controller会产生app/assets/javascripts/projects.js.coffee和app/assets/stylesheets/projects.css.scss文件.当客户请求使用这两个文件时,会分别由coffeescript插件和sass-rails插件编译后再传给客户端.其他形式的文件也是相同的.
文件名按从左到右的格式进行命名,从右到左进行预编译,比如projects.css.scss.erb首先由erb进行编译处理一次,再由scss进行编译处理,最后生成css文件,project.js.coffee.erb则先由erb处理,再由coffeescript处理,最后生成js文件.
3. 开发模式下的设置
开发模式下,manifest文件定义的资源文件会生成独立的文件被网页的头引用:
其中body参数是sprockets定义的.
3.1 禁用debug模式
设置config/environments/development.rb的debug参数:
config.assets.debug= false
一旦debug禁用,sprockets会调用所有编译器解析所有连接所的文件.Manifest文件会生成一个引用文件:
服务器启动后,同时在首次请求资源时,assets资源会被编译并缓存.Sprokets会在http的响应header设置cache-control为must-revalidate,以减少客户端浏览器再次请求资源,下一次请求资源时一般就会返回304响应,表示请求资源无变化.
如果manifest文件变化了,服务器会响应新编译的资源文件.
可以手工设置资源的属性来启动debug模式:
<%=stylesheet_link_tag"application",:debug=>true%>
<%=javascript_include_tag "application",:debug=> true %>
The :debug optionis redundant if debug mode is on.
Youcould potentially also enable compression in development mode as asanity check, and disable it on-demand as required for debugging.
4.生产环境下的设置
生产环境下rails使用figerprinting策略引用资源.资源默认情况是预编译好的,并可以作为静态资源被服务器引用.
而在编译的时候,每个文件都生成一个MD5串,追加到文件名后面,存放在特定位置上.
通过helper来引用文件,helper中文件是普通文件名,而在页面上会是figerprinting文件名.例如:<%=javascript_include_tag"application" %>
<%=stylesheet_link_tag "application" %>
会在页面生成为:
可以通过config.assets.digest 设置是否通过figerprinting编译文件名.
4.1 编译资源
可以使用rails所绑定的rake命令来编译资源文件,主要将manifests及其他文件通过pipeline输出到指定位置.可以设置config.assets.prefix来指定资源输出的路径,默认是public/assets.可以使用以下命令进行编译:
bundle exec rake assets:precompile
可以在config/application.rb设置config.assets.initialize_on_precompile=false, 加速资源文件的编译.不过这样会生成的模板不能辨识出程序的对象及函数.Heroku就要求将些选项设置为false.
Capistrano(v2.8.0以上)已支持自动预编译,只要在Capfile加下以下语句:注意:假如设置nitialize_on_precompile=false,最好在部署前设置一下rakeassets:precompile的结果是否正确. Itmayexpose bugs where your assets reference application objectsormethods, since those are still in scope in developmentmoderegardless of the value of this flag.
load'deploy/assets'
此语句会将config.assets.prefix的路径连接到shared/assets目录.如shared路径已被使用,则要重新设计部署策略.
最好保证shared路径被所有部署工所共享,这样远程服务器已存在的一些缓存资源仍然可以被引用(比如一些文件是客户上传的,可以保证不被新文件覆盖删除掉).
如果是本地编译资源文件,可以使用”bundleinstall–without assets”运行服务器,这样就不同在服务器上加载assets插件了.
默认进行编译的文件主要包括application.js,application.css以及一些满足以下规则的文件集:
[/\w+\.(?!js|css).+/, /application.(css|js)$/ ]
如果需要包含其他manifest文件,或者内部孤css,js文件,可以自定义以下属性进行包含:
config.assets.precompile+= ['admin.js', 'admin.css', 'swfObject.js']
Rake会生成一个manifest.xml文件,包含了所有引用资源及其对应的fingerprint文件名.Rails的helper就是通过查询此关系将资源引用串输出到页面上的.以下是manifest.xml的例子:
---
rails.png:rails-bd9ad5a560b5a3a7be0808c5cd76a798.png
jquery-ui.min.js:jquery-ui-7e33882a28fc84ad0e0e47e46cbf901c.min.js
jquery.min.js:jquery-8a50feed8d29566738ad005e19fe1c2d.min.js
application.js:application-3fdab497b8fb70d20cfc5495239dfc29.js
application.css:application-8af74128f904600e41a6e39241464e03.css
Manifest默认的根目录由config.assets.prefix定义的,默认是”/assets”.
可以通过以下选项的设置改变manifest的根目录:
config.assets.manifest= '/path/to/some/other/location'
注意:在生产环境编译时一旦找不到资源文件会报错:
Sprockets::Helpers::RailsHelper::AssetPaths::AssetNotPrecompiledError
4.1.1 web服务器的资源配置
可以直接通过web服务器定义资源文件,这样这些文件可以由web服务器直接进行响应.
Apache下的定义:
#Some browsers still send conditional-GET requests if there's
#Last-Modified header or an ETag header even if they haven't
#reached the expiry date sent in the Expires header.
Headerunset Last-Modified
Headerunset ETag
FileETagNone
#RFC says only cache for 1 year
ExpiresActiveOn
ExpiresDefault"access plus 1 year"
Nginx下的定义:
location~ ^/(assets)/ {
root/path/to/public;
gzip_staticon;
# to serve pre-gzipped version
expiresmax;
add_headerCache-Control public
;}
当编译资源文件时,sprockets会同时创建一个gz版的assets文件压缩包.Web服务器通常使用的是适中的压缩比压缩文件,而sprockets使用的是高压缩比压缩资源文件,可以减少文件传输带宽.另一个方面,web服务器直接从磁盘加载压缩好的资源文件,而不需要使用deflating机制再次压缩文件(每次请求压缩一次并不实际).
Nginx可以自动启动 gzip_static:location~ ^/(assets)/ {
root/path/to/public;
gzip_staticon; # to serve pre-gzipped version
expiresmax;
add_headerCache-Control public;
}
不过需要在编译web服务器手工加入的编译选项以加载gzip_static模块,Ubuntu及nginx-light默认是加载了此模块,其他要手工.
./configure --with-http_gzip_static_module
If you’re compiling nginx with Phusion Passenger you’ll need to passthat option when prompted.
Unfortunately, a robust configuration for Apache is possible but tricky, please Google around.
4.2 在线编译
某些环境可能需要用在线编译模式(即无预编译过程).所有的资源请求直接由sprockets调用对应的pipeline进行解析编译,可以通过以下选项启动在线编译:
config.assets.compile= true
第一次请求时资源会被编译并缓存,同开发模式相同原理,同时manifest文件的资源会解析成带MD5的资源文件被页面引用.
Sprockets同时在http的header设置了cache-control为max-age=3153600.表示资源文件可以在服务器及客户端缓存1年.这样可以减少从服务器请求相同的资源,客户端直接从本地取得资源,速度更快.
这种模式将会耗费更多的内存资源,性能会比默认方式差.
在生产模式下,如果没有javascript运行时环境,可以用以下作为默认运行时环境:
group :production do
gem'therubyracer'
end
5. 自定义pipeline解释器
5.1 css 压缩器
可以用YUI来压缩css文件.YUI扩展css语法,支持语法缩小.可以设置以下选项提供支持:
config.assets.css_compressor= :yui
必须设置config.assets.compress= true以支持css压缩.
5.2 js压缩器
有三个选项支持js压缩,:closur, :uglifier, :yui, 分别需要closure-compiler,uglifier及yui-compressor插件的支持.
Rails3.1默认使用uglifier,些插件封装了UglifierJS (NodeJS)的ruby实现.压缩主要是去空格及魔术代码串,尽可能取代if和else语法峦三目操作符.可以定义如下选项:
config.assets.js_compressor= :uglifier
必须设置config.assets.compress=true来启动js压缩.
注意:uglifier需要ExecJS运行时环境的支持.如果os环境是Mac或windows,则需要安装js的运行时环境.可以参考ExecJS官方相关的文档.
5.3 自定义压缩类
可以自定义一个类来支持css和js的压缩.此类必须实现compress方法,输入为一个串,输出为压缩后的串:
class Transformer
defcompress(string)
do_something_returning_a_string(string)
end
end
可以在application.rb中启动自定义的压缩类:
config.assets.css_compressor= Transformer.new
5.4 切换资源文件根路径
默认是/assets路径,可以配置换成其他路径:
config.assets.prefix= "/some_other_path"
主要是考虑到rails3.1以前的资源文件使用的路径不是assets,方便扩展.
5.5 X-Sendfile Headers
X-Sendfileheader为web服务定义一些请求参数,以便web服务器忽略应用服务器的响应,而用请求头所指定的文件进行替代.这个选项默认是禁用的,如果web服务器支持,则可以启动些选项.一旦启动,web服务器可以直接从响应头中分析取出输入的资源文件,使响应更快.
apache和nginx运载X-Sendfileheader,可以在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下,用于生产环境下才有效,而开发和测试环境忽略X-Sendfileheader,对开发有利(比如错误提示信息在开发环境可见,在生产环境不可见).
6. How Caching Works
Sprocketsuses the default Rails cachestore to cache assets in development andproduction.
TODO:Addmore about changing the default store.(Sprockets默认在开发和生产环境使用rails缓存机制来缓存assets资源.)
7.为你的Gem加入assets支持
Assets资源可以从gem中获得.
比如jquery-rails插件提供了rails标准js库的支持.插件包含了rails引擎,继承自Rails::Engine,这样它里面的app/assets,lib/assets, vendor/assets目录便可以直接被rails辨识出来,sprockets可以直接读到到相关路径的资源文件.
8. Making Your Library or Gem a Pre-Processor
TODO:Registering gems on Tilt enablingSprockets to find them.
9. 更新到新版本的rails
更新过程需要注意两个问题:
首先要将更变资源文件集的路径.不同类型的文件js,css等移动到不同的位置;其次要重新设置一系列的系统选项,以下是rails3.1.0会添加以下选项:config/application.rb:
#Enable the asset pipeline
config.assets.enabled= true
#Version of your assets, change this if you want to expire all yourassets
config.assets.version= '1.0'
#Change the path that assets are served from
#config.assets.prefix = "/assets"
Config/environments/development.rb:
#Do not compress assets
config.assets.compress= false
#Expands the lines which load the assets
config.assets.debug= true
Config/environments/production.rb:
#Compress JavaScripts and CSS
config.assets.compress= true
#Choose the compressors to use
#config.assets.js_compressor = :uglifier
#config.assets.css_compressor = :yui
#Don't fallback to assets pipeline if a precompiled asset is missed
config.assets.compile= false
#Generate digests for assets URLs.
config.assets.digest= true
#Defaults to Rails.root.join("public/assets")
#config.assets.manifest = YOUR_PATH
#Precompile additional assets (application.js, application.css, andall non-JS/CSS are already added)
#config.assets.precompile += %w( search.js )
config/environment/test.rb:
#test 环境变化不大
config.assets.compile= true
config.assets.compress,config.assets.debug= false
config.assets.digest= false
Gemfile:
#Gems used only for assets and not required
#in production environments by default.
group:assets do
gem'sass-rails', "~> 3.1.0"
gem'coffee-rails', "~> 3.1.0"
gem'uglifier'
end
要求bundler使用assets group, config/application.rb:
if defined?(Bundler)
#If you precompile assets before deploying to production, use thisline
Bundler.require*Rails.groups(:assets=> %w(development test))
#If you want your assets lazily compiled in production, use thisline
#Bundler.require(:default, :assets, Rails.env)
end
rails 3.0则为:
#If you have a Gemfile, require the gems listed there, includingany gems
#you've limited to :test, :development, or :production.
Bundler.require(:default,Rails.env) if defined?(Bundler)
10. Feedback