Assets Pipeline 是 Rails 3.1 一個重要的功能,一直並沒有很去了解其特性,但因為最近都在寫前端的東西在 assets pipeline 的東西上跌跌撞撞了不少次(尤其在 deploy 上 production 後常爆炸,爆到我無處自容),這篇就是好好研究後的心得以及筆記。

Assets Pipeline 有什麼好處,不用會怎樣?

不用當然不會怎樣,你可以在 confing/application.rb 中把他關掉:

1
config.assets.enabled = false

但是 Assets Pipeline 有著許多優良的好處,幫助你處理的過去一些需要由第三方元件來處理的事情,像是:

  • 將所有的 js 或是 css 壓縮打包成單一檔案,減少 http request 的大小與數量,增加你網站的效能及速度。
  • 支援像是 SCSS 及 CoffeeScript 這樣的 high-lever 語言,你可以用更簡單更棒的方式來寫 css 及 js。
  • 取代原先不可靠的 query string 改用 MD5 的 fingerprint,query string 的用意在於當檔案內容更動的時候也會一併更動檔案的 query string,這樣可以分辨檔案是否有更動過,因此客戶端可以保留快取並比對自己擁有的版本以及伺服器上的版本是否一致,減少每次的 request,無奈 query string 的作法還是有些問題,像是在部分 CDN 上根本不會快取、在多伺服器的環境中檔案名稱可能會變動、以及許多無效的 cache 問題,因此在 Rails 3.1 改使用 MD5 的 Fingerprinting 來解決了這個問題。

Assets Pipeline 的功能主要由兩個重要的元件提供:Sprockets 以及 Tilt。Sprockets 用來從你的 assets 路徑中打包壓縮你所有的 assets 後包裝成一個檔案,然後放到你目的地路徑(public/assets),而 Tilt 主要是一個樣板引擎,用來讓 Sprockets 可以去解析像是 SCSS、CoffeeScript 或是 ERB 等各種樣板,你可以參考 Tilt 的 Readme 來了解支援哪些樣板。

Assets 的結構

首先必須了解 Assets 的結構,在 Rails 的目錄結構中有三個地方:

  • app/assets(通常放置我們自己為了自己的程式所寫的 js、css 或是 images)
  • lib/assets(通常是我們所使用的套件中去用到的 assets)
  • 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 來使用,方便又愉快。

Assets 的載入

再來是 assets 目錄下的檔案 import 方式,以 app/asset/javascripts/application.js 這支檔案為例,這是一支 manifest 檔案,主要用來告訴 Sprockets 說哪些檔案是要被載入最後要被包起來壓縮的,最後這支檔案裡面所有的東西就會被包成 application.js 這支檔案,也是我們 layout/application.html.erb 中的 javascript_include_tag 'application' 中的檔案,打開這支檔案除了上面的說明外只有這三行:

1
2
3
//= require jquery
//= require jquery_ujs
//= require_tree .

上面兩行很明顯的就是要載入 jquery 以及 jquery_ujs 這兩支檔案,這兩支檔案剛剛有提到他其實是被包含在我們所使用的 Gem 中,而下面那行 require_tree . 表示是把三個 assets/javascript 目錄下的檔案或是子目錄內的檔案全部都包進來,這時候你一定會想問如果有些 js 或是 css 我只想在某些特定頁面中使用的話該怎麼辦,例如說假設我們今天有個 admin_functions.js 的檔案只想在我們的後台使用,有兩種方法可以使用:

  1. 你可以將 require_tree 的目錄改成其他目錄,例如在 app/assets/javascript 目錄下建個 common 資料夾,把 require_tree . 改成 require_tree ./common,這樣子所產生的 application.js 這支檔案就不會用到 admin_functions.js 這支檔案。
  2. 你可以建立一個新的資料夾來放你不想要被 application.js 載入的檔案,例如我們在 app/assets/javascript 下建立一個 admin 資料夾把剛剛的 admin_functions.js 檔案放進去,然後把原先 application.js 中的 require_tree . 改為 require_directory . 這樣子只會抓與 apllication.js 檔案同目錄底下的所有檔案而不會去載入子目錄中的檔案。

最後再建立另外一支 manifest 用來 import 那些我們要獨立出來的 assets,例如我們建立一支 admin.js 的檔案用來載入其他功能,一樣使用 require_tree 或是 require_directory 的方式來載入,然後在你需要用到的頁面中使用 javascript_include_tag 'admin' 來存取。

千千萬萬要記得,當你使用 application.js 以外的 manifest 檔案時,一定要在你環境設定檔中將這支檔案加入 precompile 的清單,否則上了 staging 或是 production 時你就會收到一堆 500 Error XXXX isn’t precompiled,加入的位置在環境設定檔像是 production.rb 中的 config.assets.precompile += %w( search.js ) 中。

如果有很多文件需要加入清单,或者随着项目变化需要加入文件,可以采用下述表达式:

config.assets.precompile += [Proc.new {|path| File.basename(path) =~ /^[^_].*\.\w+$/}]

除了 require_treerequire_directory 之外,還有其他的用法,你都可以使用絕對或是相對路徑來指定檔案位置,副檔名可有可無:

  • require [路徑] 載入某支特定檔案,如果這支檔案被載入多次,Sprockets 也會很聰明的只幫你載入一次。
  • include [路徑] 與 require 一樣,差別在即使是被載入過的檔案也會再被載入。
  • require_directory [路徑] 將路徑下不包含子目錄的檔案按照字母順序依次載入。
  • require_tree [路徑] 會將路徑下包含子目錄的檔案全部載入。
  • require_self [路徑] 告訴 Sprockets 再載入其他的檔案前,先將自己的內容插入。
  • depend_on [路徑] 宣告依賴於某支 js,在需要通知某支快取的 assets 過期時非常實用。
  • stub [路徑] 將路徑中的 assets 加入黑名單,所有其他的 require 都不會將他載入。

你可以看 Sprockets 的 Readme 來獲得更多的資訊。

Preprocessing

另外就是 Sprockets 在 Tilt 的協助下有 preprocessing 的功能,例如你可以使用像是 something.js.coffee.erb 這樣的檔名,Sprockets 會從檔名的最後面一直解析回去成最後的檔案,因此你可以在 js 中使用 CoffeeScript 的寫法來寫 js,並在裡面寫 ruby code 來產生你想要的東西,例如:

1
2
jQuery ->
number = <%= 1 + 1 %>

不用我說我想你也知道會有什麼結果。

Helper

Assets 提供了很多路徑 helper 來讓你指向你的 assets:

1
2
3
4
5
6
7
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 來協助你,因此你也可以這樣使用:

1
2
3
4
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"

Production

在還不熟悉的情況下,很容易上了 production 環境後發現原本在本機好好的東西全部炸掉了,因此我們必須了解一下在 production 運作時的情形,如果你直接在 console 中打 rails s -e production 來啟動 production 環境時,你會發現馬上就噴錯誤 application.css isn't precompiled,這是因為在 production 的環境下我們的 assets 是必須被 compile 過後存在 public/assets 底下的,你可以在 console 中打 rake assets:precompile,Rails 會幫你把所有的 assets 檔案依照你 manifests 以及環境設定打包壓縮成單一的檔案後放在 public/assets 目錄底下,所有的檔案名稱並會加入 MD5 的 fingerprinting 用來表示其內容供快取,這些 assets 會被 Rack Cache middleware 自動的被快取,如果你想要使用自己的 server 來取代 middleware 的功能你也可以自己 precompile 後上傳。

在你 precompile 後你再打開 localhost:3000 會發現這時已經沒有錯誤,但是網站看起來就是沒有 css 的感覺,這時候查看 log 你會看到 Rails 找不到 assets 檔案的錯誤,這是因為在 production 環境中是不處理靜態檔案的,因此你必須先在 production.rb 中將 config.serve_static_assets = false 改成 true,這時候重開一次就會看到一切正常,我們已經順利在本機上跑起 production 環境了,你就可以在本機上測試你的 assets pipeline 是否正常,如果你去檢視 precompile 後的檔案你會發現它們都加上了我們之前提到的 MD5 digest,用來辨認其檔案內容是否有所更動,因此你可以將你的 Server 設定中的 expires 時間調到最長,因為在檔案沒有更動的情況下他都會保持相同的檔名,讓使用者可以達到最快的效能。

Deploy 的小技巧

在本機 precompile 來節省在 server 上 precompile 的記憶體使用量

一般我們都會使用 Capistrano 來 Deploy,記得要將 Capfile 中的 load 'deploy/assets' 的註解取消,Deploy 的過程中會使用 production server 來 compile 你的 assets,如果你很在意 production server 的效能,你可以在本機先 compile 後再上傳到 Server,我們只需要覆寫原本 Capistrano 所提供的 assets:precompile 功能,在你的 deploy.rb 中加入下面的 Code:

1
2
3
4
5
6
7
8
9
10
11
namespace :deploy do
namespace :assets do
desc "Precompile assets on local machine and upload them to the server."
task :precompile, :roles => web, :except => {:no_release => true} do
run_locally "bundle exec rake assets:precompile"
find_servers_for_task(current_task).each do |server|
run_locally "rsync -vr --exclude='.DS_Store' public/assets #{user}@#{server.host}:#{shared_path}/"
end
end
end
end

這樣會在本機 precompile 後使用 rsync 將檔案上傳上去,如果你有使用 Git 的話別忘了把 public/assets 加到 .gitignore 中。

如果 assets 沒有更新時,就不要跑 precompile

precompile 應該是整個 deploy 過程中最漫長的一段時間,即使你沒有更新 assets 時也免不了給你跑一下,你可以一樣覆寫 assets:precompile 來判斷是否有更新 assets,如果沒有才執行 precompile:

1
2
3
4
5
6
7
8
9
10
11
12
namespace :deploy do
namespace :assets do
task :precompile, :roles => :web, :except => { :no_release => true } do
from = source.next_revision(current_revision)
if capture("cd #{latest_release} && #{source.local.log(from)} vendor/assets/ app/assets/ | wc -l").to_i > 0
run %Q{cd #{latest_release} && #{rake} RAILS_ENV=#{rails_env} #{asset_env} assets:precompile}
else
logger.info "Skipping asset pre-compilation because there were no asset changes"
end
end
end
end

當然你可以將上面兩段 Code 合而為一,有更新 assets 的情況就在本機 precompile 後才上傳到伺服器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace :deploy do
namespace :assets do
task :precompile, :roles => :web, :except => { :no_release => true } do
from = source.next_revision(current_revision)
if capture("cd #{latest_release} && #{source.local.log(from)} vendor/assets/ app/assets/ | wc -l").to_i > 0
run_locally "bundle exec rake assets:precompile"
find_servers_for_task(current_task).each do |server|
run_locally "rsync -vr --exclude='.DS_Store' public/assets #{user}@#{server.host}:#{shared_path}/"
end
else
logger.info "Skipping asset pre-compilation because there were no asset changes"
end
end
end
end

參考資料:

一次搞懂 Assets Pipeline 转载自http://gogojimmy.net/2012/07/03/understand-assets-pipline/的更多相关文章

  1. 完全搞懂傅里叶变换和小波(2)——三个中值定理<转载>

    书接上文,本文章是该系列的第二篇,按照总纲中给出的框架,本节介绍三个中值定理,包括它们的证明及几何意义.这三个中值定理是高等数学中非常基础的部分,如果读者对于高数的内容已经非常了解,大可跳过此部分.当 ...

  2. 完全搞懂傅里叶变换和小波(1)——总纲<转载>

    无论是学习信号处理,还是做图像.音视频处理方面的研究,你永远避不开的一个内容,就是傅里叶变换和小波.但是这两个东西其实并不容易弄懂,或者说其实是非常抽象和晦涩的! 完全搞懂傅里叶变换和小波,你至少需要 ...

  3. 彻底搞懂Javascript的“==”

    本文转载自:@manxisuo的<通过一张简单的图,让你彻底地.永久地搞懂JS的==运算>. 大家知道,==是JavaScript中比较复杂的一个运算符.它的运算规则奇怪,容让人犯错,从而 ...

  4. java线程间通信:一个小Demo完全搞懂

    版权声明:本文出自汪磊的博客,转载请务必注明出处. Java线程系列文章只是自己知识的总结梳理,都是最基础的玩意,已经掌握熟练的可以绕过. 一.从一个小Demo说起 上篇我们聊到了Java多线程的同步 ...

  5. (转)从一道面试题彻底搞懂hashCode与equals的作用与区别及应当注意的细节

    背景:学习java的基础知识,每次回顾,总会有不同的认识.该文系转载 最近去面试了几家公司,被问到hashCode的作用,虽然回答出来了,但是自己还是对hashCode和equals的作用一知半解的, ...

  6. 机器学习:让我们彻底搞懂CNN【转】

    本文转载自:http://115.com/182920/T1266078.html 机器学习:让我们彻底搞懂CNN 上世纪科学家们发现了几个视觉神经特点,视神经具有局部感受眼,一整张图的识别由多个局部 ...

  7. 搞懂分布式技术21:浅谈分布式消息技术 Kafka

    搞懂分布式技术21:浅谈分布式消息技术 Kafka 浅谈分布式消息技术 Kafka 本文主要介绍了这几部分内容: 1基本介绍和架构概览 2kafka事务传输的特点 3kafka的消息存储格式:topi ...

  8. ERP 到底是什么? 一则故事搞懂ERP

    你知道什么是ERP? ERP是什么? 你知道什么是ERP吗? (通俗易懂版) 一个故事搞懂“ERP” 一天中午,丈夫在外给家里打电话:“亲爱的老婆,晚上我想带几个同事回家吃饭可以吗?”(订货意向) 妻 ...

  9. 一文搞懂vim复制粘贴

    转载自本人独立博客https://liushiming.cn/2020/01/18/copy-and-paste-in-vim/ 概述 复制粘贴是文本编辑最常用的功能,但是在vim中复制粘贴还是有点麻 ...

随机推荐

  1. How Many Equations Can You Find(dfs)

    How Many Equations Can You Find Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 ...

  2. 2014年辛星解读Javascript之DOM之冒泡和捕获

    上篇博客提到了Javascript事件绑定函数的三个參数.第一个是一个event.第二个是一个function.第三个是一个布尔变量.它用于指定事件传递的顺序,分为冒泡和捕获两种方式,接下来我们将揭开 ...

  3. 提高你的Java代码质量吧:使用构造函数协助描述枚举项

    一.分析 一般来说,我们经常使用的枚举项只有一个属性,即排序号,其默认值是从0.1.2... ....但是除了排序号外,枚举还有一个(或多个)属性. 二.场景 比如,可以通过枚举构造函数声明业务值,定 ...

  4. 1.Solution的Build、Rebuild和Clean

    大家好,我是原文,这篇随笔是对原文的翻译以及自己的体会. 做程序员没追求的话是永远找不到女朋友的,当然有追求也找不到,这个先不提,好在有追求的时候我是充实而且开心的.现在我们的问题是,每天调试项目,在 ...

  5. 如何利用Github Pages展示自己写的项目

    接触github很久了,自己搭建过hexo博客,但是对于web项目托管github pages感觉很懵,所以在此总结分享给有需要的亲们. 教程开始: 1.创建一个新库 2.给库命名 3.创建新库后点击 ...

  6. 转场动画2-Pop动画

    上一篇试讲push动画,这篇分解pop动画 里面关于矩阵有不懂得,参考CATransform3D 特效详解 上图(虚拟机下,图是渣渣 ) 代码直接上 // // PopTransition.h // ...

  7. Web颜色搭配 - 收集

    颜色1  颜色一   背景 字 RGB 43,41,46 92,187,207 HEX #2B292E #5CBBCF HSB 264,11,18 190,56,81 CMYK 7,11,0,82 5 ...

  8. javascript --推箱子

    <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" ...

  9. justAP1.3.0版发布了

    justAP是一个简单的.易于使用的php运行环境,适合php开发人员使用.与wamp.xampp等不同,它仅仅包含Apache httpd和Php,这也是它名字的来由(justAP=just Apa ...

  10. 树的判断(poj nyoj hduoj)

    题目: http://ac.jobdu.com/problem.php?pid=1481 http://acm.nyist.net/JudgeOnline/problem.php?pid=129 ht ...