Ruby
Rails
AMP
Rails5

Railsの既存コードをほぼ変更せずにAMP対応する8ステップ

More than 1 year has passed since last update.


はじめに:通信量が少ない&&爆速なAMP

AMP(Accelerated Mobile Pages)はサイト表示を高速化する技術です。特に帯域制限されたスマホでも素早く快適に画面が表示されるので、(私もそうなのですが)スマホの通信料金を節約したい!帯域細い!というユーザーには待望の技術ではないでしょうか。

しかしその高速さの一方で、AMP開発チームがその技術を


User Experience > Developer Experience > Ease of Implementation


と言い切っている通り、「UX(スピード)最優先、作りやすさは二の次!」で扱いにくい側面もあります。

参考: https://www.ampproject.org/learn/design-principles/

そんなAMPを、なるべく既存のコードを修正せず(特に、画面テンプレートにはほとんど手を入れないで)AMP対応したいと思います。(開発環境: Rails5.0.1)

スクリーンショット 2016-12-04 22.10.58.png

Googleでの検索結果の例。「光速の体験」ですって。


AMPの制約

AMP対応ページにおける制約のうち、大きなものは以下の通りです。



  1. CSSは外部ファイル使用不可、50KBまで


    • 高速なページレンダリングのため外部CSSファイルは使えず<style>タグにてhtmlページ内に実装する必要があり、50KB以上の大きなCSSも許されされません。




  2. 画像表示に<img>タグが使えない


    • AMPではロードに時間がかかるものは非同期処理されます。そのためにタグは使えず、専用の<amp-img>タグを使います。またレンダリング速度のためにwidth属性とheight属性を設定しておく必要があります。




  3. Javascriptが必須のページは対応できない


    • AMPではJavascriptの動作に制限があり、サイト開発者が用意したScriptは動作させることができません。




  4. Cookieが実質使用不可


    • Google検索結果からAMPページに遷移すると、

      https://www.google.co.jp/amp/s/自サイトのドメイン/パス

      というGoogleドメイン下のキャッシュページが表示されます。このページは自サイトのドメインではないため、自サイトのCookie情報が使われません。つまり会員向け機能などCookieを活用した機能は提供できません。



特に3と4の制約からインタラクティブなページのAMP対応は難しく、今回は「見るだけで完結するページ」をAMP対応させることとします。


対応方針

各Railsのアクションに.amp拡張子をつけることで対応する方針とします。例えば

/domain/controller/action

というページがあった場合には

/domain/controller/action.amp

としてamp対応ページを返却することとします。


Step1. .amp を認識させる

まず、.ampをMimetypeとしてRailsに認識させます。


config/initializers/mime_types.rb

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



Step2. .ampで反応するようにする

AMPに対応させるactionのrespond_toメソッドに修正を加えます。


app/controllers/blogs_controller.rb

def some_action

# :
# 必要な処理
# :

respond_to do |format|
format.html
@amp_ready = true
format.amp do
lookup_context.formats = [:amp, :html] # .htmlのテンプレートも検索する
render
end
end
end


ここでのポイントは2つです。


rel="amphtml"のための@amp_ready変数

AMPに対応したページのオリジナルhtmlページには、「このページはAMPに対応しているよ!」と知らしめるために@amp_ready = true をセットします。詳しくは後述します。


.htmlテンプレートを使うためのlookup_context

RailsのActionViewは、拡張子に応じてテンプレートを探します。ですので.amp拡張子の場合は「.amp」がついているテンプレートしか探しません。このままでは各アクションごとに'action.amp.erb'を用意しなくてはならずツライので、.ampテンプレートがなければ.htmlテンプレートを探すように修正します。

lookup_context.formats = [:amp, :html]

これにより.htmlテンプレートの流用が可能になります(やった!)。


Step3. 従来のhtmlページにlink rel="amphtml"を付与する

さきほど@amp_ready変数を設定しましたが、この変数を利用して元のページに

<link rel="amphtml" href="https://domain/controller/action.amp"> というlinkタグを付与します。まずamp対応ページかどうかを判定するamp_ready?を実装します。


app/helpers/application_helper.rb

module ApplicationHelper

def amp_ready?
defined?(@amp_ready) && @amp_ready == true
end
end

続いてapplication.htmlには


app/views/layouts/application.html.slim

(略)

html
head
(略)
- if amp_ready?
- amp_uri = URI.parse(request.url)
- amp_uri.path = "#{amp_uri.path}.amp"
- amp_uri.query = h(amp_uri.query) if amp_uri.query.present?
link rel="amphtml" href = amp_uri.to_s.html_safe


のように、amp_readyの場合には<link rel="amphtml">を出力します。

細かい工夫ですが、URIオブジェクトを使うことで拡張子のあとにパラメータが付いていても正しく変換できます。

blogs/action?foo=bar => blogs/action.amp?foo=bar


Step4. AMP用レイアウトを用意する

AMPでは、<amp-*>タグ用のスクリプトなどをロードするボイラープレート(模範表現)が提供されています。これを読み込めるようにapplication.htmlを修正します。


app/views/layouts/application.html.slim

- if request.format ==  :amp

= render 'layouts/application' #application.amp.slimを読み込む
- else
doctype html
html
(以下従来のHTMLテンプレート)

読み込まれるapplication.ampは、ampproject.orgで提供されているボイラープレートをベースに改変します。

https://www.ampproject.org/docs/get_started/create/basic_markup


app/views/layouts/application.amp.slim

| <!doctype html>

html amp=""
head
meta[charset="utf-8"]
meta[name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1"]
link rel='canonical' href = request.url.gsub('.amp', '')
style[amp-boilerplate]
| body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}
noscript
style[amp-boilerplate]
| body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}
style[amp-custom]
- controller = params[:controller]
- if Rails.application.assets && Rails.application.assets["amp/amp_#{controller}"]
// development
= Rails.application.assets["amp/amp_#{controller}"].to_s.html_safe
- else
// production
= File.read "#{Rails.root}/public#{stylesheet_path("amp/amp_#{controller}", host: nil)}"
script[async src="https://cdn.ampproject.org/v0.js"]
= render 'header'
= yield
= render "footer"


Step5. CSSの対応

前述のapplication.ampで、styleタグの部分にRails.application.assetsの処理が入っていたのに気づきましたか?

「AMPの制約」に記載したとおり、CSSは外部ファイルを利用できず、また容量制限があります。そこで以下のような方針で対応します。


  • asset pipelineで作られるCSSをstyleタグで読み込む

  • コントローラごとにAMP用CSSを作成することで容量を削減する

先のコードをもう一度確認します。

style[amp-custom]

  - controller = params[:controller]
  - if Rails.application.assets && Rails.application.assets["amp/amp_#{controller}"]
   // development
   = Rails.application.assets["amp/amp_#{controller}"].to_s.html_safe
  - else
   // production
   = File.read "#{Rails.root}/public#{stylesheet_path("amp/amp_#{controller}", host: nil)}"

Rails.application.assetsに注目してください。

開発環境ではRails.application.assetsにコンパイルされたCSSが格納されています。残念ながら本番環境ではこの変数はnilになっていますが、代わりにasset precompileしたファイルをstylesheet_pathヘルパによって取得することが可能です。そのファイルをFile.readで読み込みます。

読み込まれるAMP用CSS自体はconfig/applicatio.rbにてasset precompile対象に設定しておきます。AMP対応するファイルが増えるたびapplication.rbをメンテするのは面倒なので、app/assets/stylesheets/amp以下のCSSファイルはすべてasset precompile対象にします。


config/application.rb

 amp_css_paths = Dir.entries("#{config.root}/app/assets/stylesheets/amp").select { |name| name =~ /css$/ }.map { |name| "amp/#{name}" }

config.assets.precompile += amp_css_paths

参考: https://coderwall.com/p/wpyasq/how-to-create-an-amp-page-for-your-dynamic-content-in-rails

読み込むCSSファイルは"amp/amp_#{controller_name}"とし、オリジナルのapplication css の中から必要なものを取捨選択します。(50KBを超えなければよいので、小規模サイトならオリジナルのコンパイル済みCSSをそのまま使えるかもしれません。)


app/assets/stylesheets/amp/amp_blogs.scss

// application.scssをコピーしてきたもの

// @import "news"
@import "blogs" //これだけ使う
// @import "comments"



Step6. image_tagヘルパーのフック

「AMPの制約」に記載したとおり、すべての<img>タグは<amp-img>タグに置き換えないといけません。そこでimage_tagをフックし、必要に応じて<amp-img>が出力されるようにします。


config/initializers/image_tag_helper.rb

module ActionView

module Helpers
module AssetTagHelper
def amp_image_tag(source, options = {})
src = options[:src] = ActionController::Base.helpers.path_to_image(source, skip_pipeline: options.delete(:skip_pipeline))

unless src.start_with?('cid:', 'data:') || src.blank?
options[:alt] = options.fetch(:alt) { ActionController::Base.helpers.image_alt(src) }
end
options[:layout] ||= 'fixed'
ActionController::Base.helpers.tag('amp-img', options)
end

# amp-imgかimgかをrequest.formatによって使い分ける。
def image_tag_with_amp(source, options = {})
if amp?
amp_image_tag(source, options)
else
image_tag_without_amp(source, options)
end
end

def amp?
Thread.current[:format] == 'amp'
end
alias_method_chain :image_tag, :amp
end
end
end


参考: https://github.com/rails/rails/blob/5-0-1/actionview/lib/action_view/helpers/asset_tag_helper.rb#L210

ポイントは2点です。


  • ampの場合はamp_image_tagメソッドをコールし、ampでない場合は従来のimage_tagメソッドをコールする。(image_tag_with_amp)

  • このヘルパーはrequestオブジェクトにアクセスできないので、Thread.current(グローバルオブジェクト)を用いてampかどうかの情報を引き渡します(amp?)。その情報は以下のようにapplication_cotrollerにてセットします。


app/controllers/application_controller.rb

class ApplicationController < ActionController::Base

before_action :set_format

def set_format
Thread.current[:format] = request[:format]
end
end


以上により、ampのときだけが使われるimage_tagメソッドができました。

この変更を加えた上でimage_tag使用箇所全てにwidht, height属性を設定します。


Step7. JSON-LDへの対応(optional)

AMPではJSON-LDと呼ばれる構造化データを利用します。構造化データはタイトルや画像、作成日などのページのメタ情報をmachine-readableにするものです。サイトの性質によって異なりますので一概には言えませんが、ブログ、コラム、ニュースの類であればArticleというフォーマットを利用します。

参考: https://developers.google.com/search/docs/data-types/articles#amp-sd

参考: http://schema.org/NewsArticle


Step8. デバッグ

AMPのバリデータはchromeに標準搭載されています。

URLに#development=1をつけてページを開き、

Chrome DevTool Consoleを立ち上げると、バリデーション結果が表示されます。

image

こちらで表示されたエラーを確認しながら、すべて対応していきます。

参考: https://www.ampproject.org/docs/guides/validate


最後に

おつかれさまでした!対応完了までにはちょっと手数が多いですが、画面テンプレートにはほとんど手を加えずに対応できたかと思います。

また、上記対応では触れませんでしたが、


  • フォームがあるページには<amp-form>

  • GoogleAnalyticsを仕込んでいるページには<amp-analytics>

  • Adsenseなど広告を入れているページには<amp-ad>

を使って対応する必要があります。ビジネスユースではこれに加えて効果測定上の工夫も必要となるかと思いますが、個人ユースでは本稿の内容で概ね対応できると思います。

Make our mobile web faster, together!