摘要:本篇文章要讲的主要内容是如何让我们可以使用 capistrano 和 rails assets pipeline 部署 Rails + Angular2 项目。
本篇文章要讲的主要内容是如何让我们可以使用 capistrano 和 rails assets pipeline 部署 Rails + Angular2 项目。
在这之前我们先来了解几个概念:
1. Angular2 组件中使用 templateUrl
我们现在把 hello_world.js 重构成下面这样:
app/assets/angular2/components/hello_world.js
import {Component} from 'angular2/core';
@Component({
selector: 'helloWorld',
templateUrl: 'assets/components/hello_world.html',
styleUrls: ['assets/components/hello_world.css']
})
export class HelloWorldComponent {
constructor() {
}
}
app/assets/angular2/components/hello_world.html
Hello World with templateUrl !
app/assets/angular2/components/hello_world.css
h1 {
color: red;
}
刷新页面我们看到的应该是这样的内容:

2. 关于 Assets Pipeline 的 Fingerprinting 技术及其用途
简单来说 Fingerprinting 是一项根据文件内容来生成文件名的技术。当文件内容发生改变时文件名也会随之改变。
那它到底有什么用处呢? 当文件名是唯一并且依赖于文件内容时,我们可以通过设置HTTP headers 将文件保存到 CDNs,ISPs, 网络设备 或者 浏览器进行缓存。如果下次部署的时候某个文件内容发生了改变,那它对应的 fingerprint 也会改变,这样客户端就会重新请求该文件,这个过程通常又叫做:cache busting
Rails Assets Pipeline insert a hash of the content into the name, usually at the end.
例如我们有一个 css 文件叫 application.css
application-908e25f4bf641868d8683022a5b62f54.css
注:具体请参考 Assets Pipeline Fingerprinting
3. 如何利用Assets Pipeline 的 Fingerprinting 解决 Angular2 中 templateUrl 和 styleUrls 的缓存问题。
我们现在要考虑的问题有两个:
- 开发环境下 template(html) 和 style(css)文件是不需要通过 Assets Pipeline 进行编译的,因为会影响开发速度,项目很大的时候对通过 Assets Pipeline 编译太多文件会明显影响速冻。
- 生产环境下我们需要将上面代码中的 templateUrl 和 styleUrls 替换成 Assets Pipeline 添加过 Fingerprinting 的文件名。
配置 Assets Pipeline precompile Angular2 组件内所有的 html 和 css 文件
将 config/initializers/assets.rb 改成下面这样:
Rails.application.config.assets.version = '1.0'
Rails.application.config.assets.paths << Rails.root.join("app", "assets", "angular2")
# make assets pipeline precompile all html(template) and css(style) file for angular2 components
Rails.application.config.assets.precompile << Proc.new { |path|
if path =~ /\.(css|html)\z/
full_path = Rails.application.assets.resolve(path)
app_assets_path = Rails.root.join('app', 'assets').to_path
full_path.starts_with?(app_assets_path + '/angular2/components')
else
false
end
}
执行 precompile:
✗ bundle exec rake assets:precompile
在文件 app/view/home/index.html.erb 末尾添加下面这行代码:
assets_manifest.assets %>
重启 Rails Server 刷新页面你会看到页面上多出的内容是一个 Hash :
{
"components/hello_world.css" =>
"components/hello_world-7c13caf4ede58c40f375232f9e1aa57f44370d81e7f5f302968ee98139437581.css",
"components/hello_world.html" =>
"components/hello_world-2fc4fdd447b9722d97e198f16056e5bf4f01705d453e9f9ea69e6bf759842855.html",
"application.js" =>
"application-2f4c216d83942d7817a6a5510231868e91eab74e7ab25c651dae90f27abcc647.js",
"application.css" =>
"application-f1a3ad384315c3290f52141a03b396b98a93f743dbc123decff7be0c6785679d.css"
}
从上面 Hash 的内容可以看出 assets_manifest.assets 返回的是 assets precompile 前后 文件的对应关系。
我们可以通过在 config/application.rb 中配置 config.assets.manifest 的方式来指定 Assets precompile 上面所述文件对应关系的存储路径:
config.assets.manifest = "#{Rails.root}/config/manifest.json"
执行:
✗ bundle exec rake assets:clean
✗ bundle exec rake assets:precompile
✗ more config/manifest.json
查看 config/manifest.json 的内容会发现里面存储的就是上文所属的precompile 前后文件名的对应关系。
所以 assets_manifest.assets 刚好就是我们所需要的。
自定义:Helper method embed_js
app/helpers/application_helper.rb
def embed_js
data = {
env: Rails.env,
assetsManifest: assets_manifest.assets,
assetHost: Rails.application.config.action_controller.asset_host
}
window.Rails = #{data.to_json}
window._assetPath = function(path) {
if (window.Rails.env == 'development') {
return "/assets/" + path
} else {
path = "/assets/" + Rails.assetsManifest[path]
if (window.Rails.assetHost) {
return window.Rails.assetHost + path
} else {
return path
}
}
}
window._jsUrl = function(js) {
return window._assetPath(js+'.js')
}
window._templateUrl = function(html) {
return window._assetPath('components/' + html+'.html')
}
window._styleUrl = function(css) {
return window._assetPath(css+'.css')
}
EOS
end
然后打开 app/views/layouts/application.html.erb 改成如下代码:
DevOps
csrf_meta_tags %>
action_cable_meta_tag %>
embed_js %>
stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => 'reload' %>
javascript_include_tag 'application', 'data-turbolinks-track' => 'reload' %>
yield %>
也就是我们在 application.html.erb 里面加了一行代码: <%= embed_js="" %="">
上面的代码中 embed_js 中我们通过 Ruby 代码成了一段 JS 代码,然后将 JS 代码加载到了页面的 Header 中。
这段 JS 代码中定义了 一个全局变量 Rails ,Rails 的值是一个 Object,三个 key 分别是当前 Rails 的环境,assetsManifest, 以及 assetsHost。
然后定义了四个全局函数,其中 window._assetPath 中先判断当前环境是否为 development, 如果是,则直接返回想对应的 js/html/css 文件,若果不是 development 环境,则 通过 assetsManifest 中的对应关系找出 precompile 之后的文件路径,加载到页面中。
有了这四个函数,我们就可以重构上面的 Hello World 了。
import {Component} from 'angular2/core';
@Component({
selector: 'helloWorld',
templateUrl: _templateUrl('hello_world'),
styleUrls: [_styleUrl('hello_world')]
})
export class HelloWorldComponent {
constructor() {
}
}
这样在开发环境下 Angular2 组件直接加载 template 和 style 文件,而通过 capistrano 发布项目时只要执行 Assets Precompile 就会生成 manifest,从而使我们的组件能就加载带 fingerprint 的 html, css 和 js。
至此为止,我们开发和部署 Rails + Angular2 项目的所有配置就完成了。