うなすけとあれこれ

2019年11月29日

Middlemanで生成したHTMLの<img>にloading="lazy"を付与するgemを作りました

performance

ChromeがNative lazy loadingをサポートするようになったので、僕のblogでもこれを使おうと作ってみたgemになります。

Markdownの変換に介入する

Chrome v76から、Native lazy loadingがサポートされるようになりました。

Native lazy-loading for the web

ところで僕のblogは、本文をMarkdownで記述したものをHTMLに変換しています。Markdown記法で画像を挿入した場合に、どのようにloading属性を付与すればいいのでしょうか。Markdown内にHTMLを記述した場合、それはそのまま出力されるので、手でタグを書くことで実現できますが、果たしてそのようなことをしたいでしょうか。面倒です。

なので、Markdownの変換過程に介入することで解決できないだろうかと思い、調べてみました。

僕のblogでは、Markdown engineとしてRedcarpet gemを使用しています。そして、RedcarpetにはCustom Rendererという仕組みがあります。これで、![alt text](src) の変換に介入します。

Railsでカスタムmarkdownを実装する - k0kubun’s blog

class CustomRenderer < ::Redcarpet::Render::HTML
  def image(link, title, alt_text)
    "<img loading=\"lazy\" src=\"#{link}\" alt=\"#{alt_text}\" />"
  end
end

上記のようなコードで、 に loading attrを付与することができます。そして、それをgemにしたのがこれです。

実際には loading 属性には lazyの他にも autoとeagerを指定できるので、gemでもRendererをそれぞれ使い分けることでlazy以外を指定できます。 (なのでgem名にlazyと入れたのはちょっと失敗だったかもしれない)

ここまでが表参道.rb #51 での発表内容になります。

omotesandorb #51

生成されたHTMLを改変する

ところで、この手法は上手くいきませんでした。実際にこのgemを使用してblogをdeployすると、画像が全てリンク切れとなってしまったので、急いでrevertしてdeployをし直しました。

この原因は、Middleman側の設定である asset_hash を有効にしていたためです。この設定によって尊重されるべきMiddleman側のCustom Rendererを自分のgemで上書いてしまっていたために、画像のsrcが正しくないものになってしまっていました。

asset_hash を有効にしたまま loading="lazy" をするには、もう変換後のHTMLを編集するMIddleman pluginを作成するしかなさそうです。

という訳で、after_build のタイミングで以下のようなコードを実行するようなgemを作成しました。

def after_build(builder)
  files = Dir.glob(File.join(app.config[:build_dir], "**", "*.html"))
  files.each do |file|
    contents = File.read(file)
    replaced = contents.gsub(%r[<img], "<img loading=\"#{options[:loading]}\"")
    File.open(file, 'w') do |f|
      f.write replaced
    end
  end
end

このコードは、build directory 以下に存在する .html ファイルについて、全ての <img> タグに対して loading 属性を付けて上書き保存します。

ここまでが表参道.rb #52 での発表内容になります。

omotesandorb #52

改変してはいけない <img> を除外する

この実装では、 pre や code の内部の img にも loading 属性を付与してしまいます。これはどうやって解決するか悩んだのですが、Nokogiriによって親に pre 、 code 、blockquote がある img に関しては属性を編集しない、というように処理を変更しました。

https://github.com/unasuke/middleman-img_loading_attribute/pull/1/files#diff-ea964f9b9ef0f5bc6bdab0998e722d4e

まとめ

これで、ようやく僕のblogでlazy image loadingが実現できました。といってもChrome v76以降のみですが。

ということで、Starやpull reqなど頂けると大変ありがたいです。

2019年11月29日