Ruby middleman AMP

テックブログを AMP 対応しました

Ruby middleman AMP

執行役員 CPO (Chief Productivity Officer)の @hsbt です。

しばらく前になりますが、このテックブログをAMP(Accelerated Mobile Pages)対応したので、どのように進めたのかをご紹介します。

AMP 対応したページの作成方法の調査

このテックブログは Ruby の静的ホームページジェネレータの middleman で作成されています。最初は middleman のプラグインなどで、Gemfile に追加するだけで AMP に対応したページを生成できるのではと思い、探してみましたがこれだというプラグインを見つけることはできませんでした。

この時点ではそもそも AMP とはどのような HTML を生成すれば良いのか、ということ自体をわたし自身が正しく理解していませんでした。そこで、 middleman と AMP というキーワードで見つけた以下の二つのページから AMP を理解しつつ、やるべき事を洗い出して行くことにしました。

これらのエントリを改めて読むことで middleman における AMP 対応は以下のように行えば良いのだという事を理解しました(勝間さんのブログに書かれている内容も含みます)

  • middleman は静的ジェネレータのため、AMP に対応した HTML は別の url として作成する
  • AMP 対応した HTML を示す url を head の link タグに rel=amphtml として入れる
  • AMP 対応した HTML を通常の middleman build と同時に生成するようにタスクにフックを入れる

次の章では上記の 3 つの対応について順番に紹介します。

middleman の AMP 対応

middleman が生成した HTML を nokogiri で書き換える

middleman 本体やテンプレートに手を入れることで middleman を用いて AMP に対応した HTML を生成することは可能ですが、今回は Ruby の XML パーサーである nokogiri を用いて、一旦生成済みの HTML をインプットとして、AMP の仕様に準拠するように DOM を全て書き換えた上で AMP 用の HTML を出力し保存することにしました。以下が、実際に AMP 対応をするためのコードの一部です。

# html_path は書き換える html が存在するファイルパス、html は html 本体
def ampnize(html_path, html)
  # CSS は外部参照ではなく HTML 本体に埋め込みます
  stylesheet_link_regex = /<link href="([..\/]*?stylesheets\/[\w\-]*\.css)" rel="stylesheet" \/>/
  inline_stylesheet = File.read('build' + html.match(stylesheet_link_regex)[1])
  html.gsub!(stylesheet_link_regex, '<style amp-custom>'+ inline_stylesheet + '</style>')

  html = Nokogiri::HTML(html)

  # amphtml を canonical に書き換えます
  canonical = html.css('link[rel=amphtml]')[0]
  canonical['rel'] = "canonical"
  canonical['href'] = canonical['href'].gsub(/amp\//, '')

  # AMP ページではソーシャルボタンは AMP 仕様に沿ったものしか使えないので全部取り除きます
  html.search('.hatena-bookmark').remove
  html.search('.fb-share-button').remove
  html.search('.pocket').remove

  # inline な style は AMP では使えないので消します
  html.search('.//@style').remove
  # br に clear 属性は許されていないそうです
  html.search('.//br').each{|e| e.attributes['clear'].remove if e['clear'] }
  # target=blank は AMP では使えないので _blank に置き換えます
  html.search('.//@target').each{|e| e.value = "_blank" if e.value == "blank" }
  ...(省略)
  html.to_s

上記のようなコードを用いて middleman で生成された HTML ファイルを読み込み、新しく生成した HTML が The AMP Validator で valid と判定されるまで html の書き換えを繰り返しました。上記に示した以外の書き換え対応としては img タグの置き換えや Google Analytics のコードの置き換え、AMP 対応したソーシャルウィジェットの配置などを行っています。

middleman が生成する HTML に amphtml 属性を付与する

続いて、AMP ページの URL を通常の HTML に埋め込みます。これは middleman が提供している layout ファイルを変更し、rel 属性の値として amphtml という値を持つタグを埋め込みました。

doctype html
html
  head
    meta charset="utf-8"
    meta http-equiv="X-UA-Compatible" content="IE=edge;chrome=1"
    meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,user-scalable=no"
    link rel='amphtml' href="https://tech.pepabo.com/amp/#{current_page.path.sub(/blog\//, '').sub(/index.html/, '')}"
    ...(省略)

この "rel=amphtml" のタグは最初に紹介した ampnize メソッドによって、AMP 対応の HTML では "rel=canonical" になるように書き換えを行っています。

AMP 対応 HTML の生成

最後に AMP 対応の HTML を通常の middleman build コマンドの実行時に生成するようにフックを入れます。まず、AMP 化する対象のページとして middleman-blog の記事である Middleman::Blog::BlogArticle のみを対象とするように、amp-mmのコードを変更した上で、対象ページを after_build フックを用いて AMP 化しています。

# For AMP: https://github.com/adekbadek/amp-mm/
PAGES_TO_AMP = []
#
# https://middlemanapp.com/advanced/sitemap/#using-the-sitemap-in-config-rb
ready do
  sitemap.resources.each do |page|
    # XXX https://github.com/middleman/middleman-blog/blob/master/lib/middleman-blog/blog_article.rb#L344
    next unless page.inspect =~ /Middleman::Blog::BlogArticle/
    amp_page_path = page.path.gsub(/^blog\//, '')
    proxy "/amp/#{amp_page_path}", page.path
    PAGES_TO_AMP.push(amp_page_path)
  end
end

after_build do
  PAGES_TO_AMP.each do |name|
    amp_page = "build/amp/#{name}"

    html = File.read("build/#{name}")
    amp_html = ampnize(amp_page, html)
    File.write(amp_page, amp_html)
  end
end

ここまでの対応を全て終えると tech.pepabo.com/amp/... 配下に AMP 対応された HTML が出力されます。

継続的な AMP 対応の検証

ひとまずファーストステップとして AMP 対応は終えましたが、将来にわたって作成されるエントリ全てが AMP として準拠している事を保証するために amphtml-validator という npm モジュールを CI に入れ、middleman build によって生成される AMP 対応 HTML が AMP として valid であるかを継続的に確認するようにしました。以下はコマンドの例です。

- npm -g install amphtml-validator
- amphtml-validator build/amp/*/*/*/*/*.html

まとめ

Ruby の静的ホームページジェネレータの middleman で生成されているサイトを AMP 対応させる方法についてご紹介しました。今回は記事のみの対応でしたが、今後はトップページやカテゴリページなどサイト全ての AMP 対応や nokogiri を用いた書き換えではない middleman のプラグインとして生成する仕組みなどについて取り組んでいきます。