在你的 Rails App 實作 AMP

#AMP #ruby #Rails
Irene Chang
技術文章
在你的 Rails App 實作 AMP

AMP 是 Google 在 2016 年 2 月所推出的加速行動網頁(Accelerate Mobile Pages)開源專案,能讓靜態內容網頁快速呈現。

根據 What is AMP,AMP 在運作上由三個部分組成:

  • AMP HTML:基本 HTML 配合 AMP 的規則與屬性,以符合效能的需求。
  • AMP JS:使用 AMP JS 函式庫來確保 AMP HTML 頁面可以快速呈現。
  • AMP Cache:Google AMP Cache 用來提供快取好的 AMP HTML 網頁。

HTML 和 JS?這不是網頁初學者也能上手的東西嗎?!可是進一步參考 AMP HTML Specification 會發現所謂「受限的基本 HTML」必須滿足以下條件:

  • 只接受 inlined style,不接受透過 <link rel=”stylesheet”> 形式讀取的 CSS 檔案
  • 不能使用自定義或第三方的 Javascript
  • img 標籤必須替換成 amp-img 並預先定義圖片的 widthheight
  • 部分標籤如 iframe, embedobject 必須替換成 AMP 定義的格式或被禁止使用
  • ......(更多請參考 AMP 官方文件)

因此,導入 AMP 的大多是以內容為主的新聞或部落格網站,減少對 UI 的要求以提升使用者的閱讀體驗。儘管我們可以建立一個全面支援 AMP 的網站,但這可能會大大限制 UI 設計的發展且必須耗費更多時間來開發。

假設你有個 Rails 專案,除了主要頁面之外,也有定期會發表文章的內容頁面,我們可以選擇性地為你的專案快速導入 AMP。

AMP HTML

註:本篇 HTML 範例以 Slim 語法為主

建立 AMP Templete

這邊的做法是保有原本的頁面,並另外設計該頁面的 AMP 版本。

首先,我們需要建立符合 AMP 規範的 HTML:

  1. 複製 app/views/layouts/application.html.slim 並重新命名為 application.amp.slim
  2. 調整 application.amp.slim 以符合 AMP HTML Specification #Required markup
  3. 同樣複製你想要導入 AMP 的頁面(e.g. app/views/blogs/show.amp.erb)並改為 .amp.erb

為了讓 Google Search 在找到非 AMP 頁面時能知道該頁面有 AMP 版本,我們需要特別設定 link 標籤:

  • app/views/layouts/application.html.slimhead 中加上
link href="#{url_for}" rel="amphtml" /
  • app/views/layouts/application.amp.slimhead 中加上
link href="#{url_for}" rel="canonical" /

設定 Controller

在導入 AMP 頁面所屬的 controller 底下加上:

respond_to do |format|
  format.html
  format.amp
end

設定 mime_types.rb

config/initializers/mime_types.rb 中加上

Mime::Type.register_alias "text/html", :amp

這時候開啟 Server 並在 URL 後面加上 .amp 就能瀏覽該頁面 AMP 的版本

AMP 驗證

在 URL 後面加上 #development=1 就能在 Console 中驗證你的 AMP 頁面。

看到 Powered by AMP ⚡ HTML ...... 表示你的頁面已經順利導入 AMP,但別高興得太早,直到修正所有錯誤並印出 AMP validation successful. 才真的成功 ⚡️ 在那之前都只是 Another Miserable Problem.

關於 AMP errors 可以參考 Resolving validation errors – AMP 來 debuuuuuug 🐞

Inlined CSS 塞好塞滿

這時通常會看到這樣的錯誤訊息...

The attribute 'href' in tag 'link rel=stylesheet for fonts' is set to the invalid value 'base.css'.

你可能會 OS:『我有成千上萬行的 CSS 而且還是用 Sass 編譯過來的,誰在那邊跟你一行一行 inlined CSS!』作為設計師,驚恐指數再乘上 8.7 😱

由於在 AMP 裡 link rel=stylesheet 只接受如 Google Fonts 或 Font Awesome 等 custom fonts。當我們需要處理獨立的 CSS 檔案時,可以參考 Supported CSS – AMP 使用 {% include "/assets/css/main.min.css" %} 或以下方法:

  • application_helper.rb 新增一個 method:
def render_css(path)
    raw Sass::Engine.for_file("#{Rails.root}/app/assets/stylesheets/#{path}", {
      load_paths: ["#{Rails.root}/app/assets/stylesheets"],
      style: :compressed
    }).to_css
end
  • 接著在 application.amp.slimhead 中加上:
style[amp-custom]
  = render_css('amp.sass')

如此一來就能輕鬆引入編譯好的 CSS 作為 inlined style。但 AMP 限制使用 CSS 的目地正是為了縮短載入速度,因此也限制了 inlined style 的大小不得超過 50 KB。建議另開檔案 import AMP 需要的 style 即可。

排除第三方 JavaScript

好不容易解決 CSS,可是瓶頸之後還有瓶塞 👻

The tag 'script' is disallowed except in specific forms.

這時你可能又要尖叫加三級。重看不重用的特效先拔掉、社群分享的按鈕也拔掉、連留言功能都要拔掉 ⋯⋯ 什麼?!連 hamburger button 也要拔掉。就說太快就會很空虛啊 Orz

雖然 AMP 限制 JavaScript 的使用,但也提供許多 AMP Components 作為替代方案。你可以在 Learn AMP by Example 尋找合適的替代方法,不僅有詳細的說明也有完整的 sample code 或 live demo。

舉例來說,AMP 有自己的 lightbox 套件 amp-lightbox 可以做替換,或使用 amp-sidebar 代替 responsive navbar。比較複雜的互動如 Scroll to top 則結合 amp-position-observeramp-animation 而成。

善用 AMP Components 已足夠解決大部分的問題、讓你的 AMP 頁面幾乎和原本的頁面一樣。如果你熟練 JavaScript 也可以嘗試撰寫符合 AMP 條件的方法來完善你的專案。

沒圖沒真相:amp-image

amp-img 標籤必須定義圖片的 widthheight 來提升載入速度。這時可能會碰到一個問題:你的產品提供後台讓使用者自由編輯圖文,那要怎麼知道使用者上傳圖片的大小呢?!

FastImage 在這裡提供一個很有效的解法,它能幫助我們輕鬆抓取圖片的大小:

  1. 安裝 gem 'fastimage'
  2. app/helpers/application_helper.rb 中新增一個 method:

    def get_img_size(src)
        FastImage.size(Rails.root.to_s + "/public" + src) # path_to_image
    end
    

    get_img_size(src) 會回傳一個陣列(e.g [900, 675])正是我們需要的寬度與高度。

  3. 如果使用者在後台編輯完、儲存在欄位裡的資料是一段完整的 HTML code,我們可以再新增一個 method:

    def content_with_amp_img(content)
        markup = Nokogiri::HTML.fragment(content)
        markup.css('img').each do |img|
          img_w, img_h = get_img_size(img['src'])
          img.name = 'amp-img'
          img.set_attribute('width', img_w)
          img.set_attribute('height', img_h)
          img.set_attribute('layout', "responsive")
        end
    
        markup.to_html
    end
    

    Nokogiri::HTML.fragment(content) 會把 HTML 依照標籤切割成獨立的物件,加上 .css('img') 就能取出所有 img 標籤。先把 img 改成 amp-img 再透過 get_img_size(img['src']) 分別加上 widthheightlayout 可以參考 AMP 官方文件設定需要的屬性。最後,使用 .to_html 就可以把全部的物件再組回一般的 HTML 格式。

  4. 在你的 show.amp.slim 就可以呼叫 content_with_amp_img(content) 來取得包涵 amp-img 的內容了

小結

儘管本篇看似勸退文無誤(X)但很多雷如果事先知道的話都能直接避開,因此也可以將本文章定位成「導入 AMP 要先知道的幾件事。」請繫緊安全帶,加速愉快 😉


👩‍🏫 課務小幫手:

✨ 想掌握 Ruby on Rails 觀念和原理嗎?

我們有開設 🏓 Ruby on Rails 實戰課程 課程唷 ❤️️