#はじめに:通信量が少ない&&爆速な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)
Googleでの検索結果の例。「光速の体験」ですって。
AMPの制約
AMP対応ページにおける制約のうち、大きなものは以下の通りです。
-
CSSは外部ファイル使用不可、50KBまで
- 高速なページレンダリングのため外部CSSファイルは使えず
<style>
タグにてhtmlページ内に実装する必要があり、50KB以上の大きなCSSも許されされません。
- 高速なページレンダリングのため外部CSSファイルは使えず
-
画像表示に
<img>
タグが使えない -
Javascriptが必須のページは対応できない
- AMPではJavascriptの動作に制限があり、サイト開発者が用意したScriptは動作させることができません。
-
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に認識させます。
Mime::Type.register_alias "text/html", :amp
Step2. .amp
で反応するようにする
AMPに対応させるactionのrespond_toメソッドに修正を加えます。
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?
を実装します。
module ApplicationHelper
def amp_ready?
defined?(@amp_ready) && @amp_ready == true
end
end
続いてapplication.htmlには
(略)
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
を修正します。
- 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
| <!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対象にします。
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をそのまま使えるかもしれません。)
// application.scssをコピーしてきたもの
// @import "news"
@import "blogs" //これだけ使う
// @import "comments"
Step6. image_tagヘルパーのフック
「AMPの制約」に記載したとおり、すべての<img>
タグは<amp-img>
タグに置き換えないといけません。そこでimage_tagをフックし、必要に応じて<amp-img>
が出力されるようにします。
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
ポイントは2点です。
- ampの場合はamp_image_tagメソッドをコールし、ampでない場合は従来のimage_tagメソッドをコールする。(image_tag_with_amp)
- このヘルパーはrequestオブジェクトにアクセスできないので、Thread.current(グローバルオブジェクト)を用いてampかどうかの情報を引き渡します(amp?)。その情報は以下のようにapplication_cotrollerにてセットします。
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を立ち上げると、バリデーション結果が表示されます。
こちらで表示されたエラーを確認しながら、すべて対応していきます。
参考: https://www.ampproject.org/docs/guides/validate
最後に
おつかれさまでした!対応完了までにはちょっと手数が多いですが、画面テンプレートにはほとんど手を加えずに対応できたかと思います。
また、上記対応では触れませんでしたが、
- フォームがあるページには
<amp-form>
- GoogleAnalyticsを仕込んでいるページには
<amp-analytics>
- Adsenseなど広告を入れているページには
<amp-ad>
を使って対応する必要があります。ビジネスユースではこれに加えて効果測定上の工夫も必要となるかと思いますが、個人ユースでは本稿の内容で概ね対応できると思います。
Make our mobile web faster, together!