Reading / Coding / Hacking
Rails 开发者从 3.0 升级到 3.1 时往往都会被 asset pipeline 的配置问题卡住,我一直等到 3.1 正式发布才敢升级(10月中旬),像 Compass 兼容这些问题那个时候已经被解决了,也有很多人写了详细的升级流程,比如我参考的就是 Upgrade your projects to Rails 3.1 这一系列文章,少走了很多弯路,整个过程中只遇到以下几个(那个时候 Google 不到答案的)“新”问题。
官方推荐的 asset pipeline 用法是在 application.css
和 application.js
里包含整个项目的所有 CSS / JavaScript 文件的内容,在执行 rake assets:precompile
的时候只有这两个文件和其他非 CSS / JavaScript 会被处理。按照 Ruby on Rails Guides 所说的,如果有其他文件需要被处理,就要像这样设置:
config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js']
由于我这个项目的 CSS / JavaScript 文件比较分散,我期望的效果是默认包含 assets 目录中所有非 Compass partial 的文件(文件名是下划线开头的,扩展名是 sass),逐个文件指定不太现实,就自己摸索着写了一个很复杂的正则去匹配——然后失败了,总是会漏掉一些文件,觉得不对劲,去翻 Sprockets (准确来说是 ActionPack 对 Sprockets 的扩展)的源代码,在 static_compiler.rb 里找到了 compile_path?
这个关键方法,也明白了为什么自己的正则匹配会不成功:这里传入的参数不是文件名,而是一组完整的路径,几乎不可能写出“完美”的正则去匹配……
解决方案很简单,从 compile_path?
的源码里可以看出,config.assets.precompile
可接受的参数值除了 String 和 Regexp 之外,还可以是 Proc,所以最省事的写法应该是:
# 写在 config/environments/production.rb 里:
ASSET_PRECOMPILE_PROC = Proc.new do |path|
if File.basename(path) =~ /^[^_].*\.\w+$/
puts "Compiling: #{path}"
true
else
puts "Ignoring: #{path}"
false
end
end
MyApplication::Application.configure do
#...
config.assets.precompile = [ASSET_PRECOMPILE_PROC]
#...
end
之前遇到的一个比较诡异的 bug 是执行 AJAX 操作的时候总是会重复提交,在 Chrome 的 Inspector 里可以看到一次 AJAX 请求是 application.js
发出的,另一次则是 jquery_ujs.js
发出的,原因是在开发环境里 config.assets.debug
被设为了 true
,因此 application.js
里 require
的文件会被额外单独载入一次,导致 jquery_ujs.js
里的 AJAX 提交事件被绑定了两次……
解法是把 config.assets.debug
设为 false
(不是很明白这个设置有什么用 = =),如果真要用可以在 URL 里加入 ?debug_assets=true
参数,效果是一样的。
如果用的是 Nginx,记得在 config/environments/production.rb 里加入 config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
这行。Nginx 服务器配置中静态文件部分也要更新:
# Rails 3.0 的写法
location ~ ^/(images|javascripts|stylesheets)/ {
root /path/to/project/public;
gzip_static on;
expires 30d;
}
# Rails 3.1 的写法
location ~ ^/assets/ {
root /path/to/project/public;
gzip_static on;
expires max;
add_header Cache-Control public;
# Some browsers still send conditional-GET requests if there's a
# Last-Modified header or an ETag header even if they haven't
# reached the expiry date sent in the Expires header.
add_header Last-Modified "";
add_header ETag "";
break;
}
执行 rake assets:precompile
过程中如果报错说找不到 JS Runtime 什么的,就在 Gemfile
的 assets
组加入 gem 'execjs'
和 gem 'therubyracer'
这两行,然后 bundle install
一下。貌似安装一个 NodeJS 也可以,我没有试验过。