JekyllでHTMLをminifyする
タグ: jekyll / 初版公開: 2018-07-17

サマリ

Jekyllが生成するHTMLを圧縮(minify)したいことがあります。 そのような場合はjekyll-compress-htmlを試してみる価値があるかも知れません。

前提バージョン

ソフトウェアバージョン備考
jekyll3.8.3-
jekyll-compress-html3.0.4-

jekyll-compress-html

jekyll-compress-htmlはpure Liquidで書かれた圧縮したHTMLのjekyllのレイアウトです。 Gemやプラグインではなく、2つほどファイルを配置するだけでjekyllの出力をminifyできます。

以下ではjekyll-compress-htmlの使い方を説明します。

compress layoutの追加

compress.htmlをjekyllの_layouts以下に配置します。 compress.htmlはjekyll-compress-htmlの最新のリリースから取得して下さい。

https://raw.githubusercontent.com/penibelst/jekyll-compress-html/master/site/_layouts/compress.html

jekyll-compress-htmlの3.0.4時点のcompress.htmlは以下のとおりです。 見ての通り改行や不要な要素を取り除く複雑なLiquidであることがわかります。 なんだこのLiquidはっ! と思いますが、こういうものなのでこのまま利用させてもらいましょう。

---
---

{% capture _LINE_FEED %}
{% endcapture %}{% if site.compress_html.ignore.envs contains jekyll.environment %}{{ content }}{% else %}{% capture _content %}{{ content }}{% endcapture %}{% assign _profile = site.compress_html.profile %}{% if site.compress_html.endings == "all" %}{% assign _endings = "html head body li dt dd p rt rp optgroup option colgroup caption thead tbody tfoot tr td th" | split: " " %}{% else %}{% assign _endings = site.compress_html.endings %}{% endif %}{% for _element in _endings %}{% capture _end %}</{{ _element }}>{% endcapture %}{% assign _content = _content | remove: _end %}{% endfor %}{% if _profile and _endings %}{% assign _profile_endings = _content | size | plus: 1 %}{% endif %}{% for _element in site.compress_html.startings %}{% capture _start %}<{{ _element }}>{% endcapture %}{% assign _content = _content | remove: _start %}{% endfor %}{% if _profile and site.compress_html.startings %}{% assign _profile_startings = _content | size | plus: 1 %}{% endif %}{% if site.compress_html.comments == "all" %}{% assign _comments = "<!-- -->" | split: " " %}{% else %}{% assign _comments = site.compress_html.comments %}{% endif %}{% if _comments.size == 2 %}{% capture _comment_befores %}.{{ _content }}{% endcapture %}{% assign _comment_befores = _comment_befores | split: _comments.first %}{% for _comment_before in _comment_befores %}{% if forloop.first %}{% continue %}{% endif %}{% capture _comment_outside %}{% if _carry %}{{ _comments.first }}{% endif %}{{ _comment_before }}{% endcapture %}{% capture _comment %}{% unless _carry %}{{ _comments.first }}{% endunless %}{{ _comment_outside | split: _comments.last | first }}{% if _comment_outside contains _comments.last %}{{ _comments.last }}{% assign _carry = false %}{% else %}{% assign _carry = true %}{% endif %}{% endcapture %}{% assign _content = _content | remove_first: _comment %}{% endfor %}{% if _profile %}{% assign _profile_comments = _content | size | plus: 1 %}{% endif %}{% endif %}{% assign _pre_befores = _content | split: "<pre" %}{% assign _content = "" %}{% for _pre_before in _pre_befores %}{% assign _pres = _pre_before | split: "</pre>" %}{% assign _pres_after = "" %}{% if _pres.size != 0 %}{% if site.compress_html.blanklines %}{% assign _lines = _pres.last | split: _LINE_FEED %}{% assign _lastchar = _pres.last | split: "" | last %}{% assign _outerloop = forloop %}{% capture _pres_after %}{% for _line in _lines %}{% assign _trimmed = _line | split: " " | join: " " %}{% if forloop.last and _lastchar == _LINE_FEED %}{% unless _outerloop.last %}{{ _LINE_FEED }}{% endunless %}{% continue %}{% endif %}{% if _trimmed != empty or forloop.last %}{% unless forloop.first %}{{ _LINE_FEED }}{% endunless %}{{ _line }}{% endif %}{% endfor %}{% endcapture %}{% else %}{% assign _pres_after = _pres.last | split: " " | join: " " %}{% endif %}{% endif %}{% capture _content %}{{ _content }}{% if _pre_before contains "</pre>" %}<pre{{ _pres.first }}</pre>{% endif %}{% unless _pre_before contains "</pre>" and _pres.size == 1 %}{{ _pres_after }}{% endunless %}{% endcapture %}{% endfor %}{% if _profile %}{% assign _profile_collapse = _content | size | plus: 1 %}{% endif %}{% if site.compress_html.clippings == "all" %}{% assign _clippings = "html head title base link meta style body article section nav aside h1 h2 h3 h4 h5 h6 hgroup header footer address p hr blockquote ol ul li dl dt dd figure figcaption main div table caption colgroup col tbody thead tfoot tr td th" | split: " " %}{% else %}{% assign _clippings = site.compress_html.clippings %}{% endif %}{% for _element in _clippings %}{% assign _edges = " <e;<e; </e>;</e>;</e> ;</e>" | replace: "e", _element | split: ";" %}{% assign _content = _content | replace: _edges[0], _edges[1] | replace: _edges[2], _edges[3] | replace: _edges[4], _edges[5] %}{% endfor %}{% if _profile and _clippings %}{% assign _profile_clippings = _content | size | plus: 1 %}{% endif %}{{ _content }}{% if _profile %} <table id="compress_html_profile_{{ site.time | date: "%Y%m%d" }}" class="compress_html_profile"> <thead> <tr> <td>Step <td>Bytes <tbody> <tr> <td>raw <td>{{ content | size }}{% if _profile_endings %} <tr> <td>endings <td>{{ _profile_endings }}{% endif %}{% if _profile_startings %} <tr> <td>startings <td>{{ _profile_startings }}{% endif %}{% if _profile_comments %} <tr> <td>comments <td>{{ _profile_comments }}{% endif %}{% if _profile_collapse %} <tr> <td>collapse <td>{{ _profile_collapse }}{% endif %}{% if _profile_clippings %} <tr> <td>clippings <td>{{ _profile_clippings }}{% endif %} </table>{% endif %}{% endif %}

compressレイアウトを指定したレイアウトの定義

続いてcompressを指定したレイアウトを定義します。

レイアウトを指定していないすべてのページでcompressを適用したいなら_layouts/default.htmlとして以下の内容を書きます。 他のレイアウトでも構いませんが、その場合はcompressを適用したいページのlayout:で、明示的にcompressを適用しているレイアウトを指定する必要があります。

このレイアウトではlayout: compressがポイントです! これでこのレイアウトが適用されたページは先程配置したcompress.htmlで出力前にレイアウトされ、HTMLでは不要な空白などが除去されるようになります。 この例では<html>要素内に{{ content }}を配置していますが、layout: compressさえ指定していれば内容は任意です。

---
layout: compress
---

<html>
{{ content }}
</html>

jekyll-compress-htmlの設定

_config.ymlcompress.htmlの挙動を変更することができます。

改めて元のLiquidテンプレートを良く見ると、所々に分岐が書かれていることがわかります。 これが_config.ymlで挙動を切り替える処理です。

compress_html以下に以下の値を設定できます。

compress_html:
  clippings: []
  comments: []
  endings: []
  ignore:
    envs: []
  blanklines: false
  profile: false
  startings: []

詳細な解説は公式の解説をご覧下さい。

私が試行錯誤した限り、おそらく大抵の用途を満たしつつ問題を起こさずにHTMLを圧縮する設定は以下のとおりです。

compress_html:
  clippings: all
  comments: all
  endings: all
  ignore:
    envs: []
  blanklines: true
  profile: false
  startings: []

この設定は以下の挙動になります。

  • 空白を除去しても安全なすべての要素に囲まれた空白を除去する
  • すべてのコメントを除去する
  • 任意の終了タグをすべて除去する
  • 環境変数によってcompressレイアウトを無効化(無視)しない
  • 空白行を除去する
  • プロファイルモードを無効化
  • 任意の開始タグを除去しない

制限事項

jekyll-compress-htmlには制限事項がドキュメントに明記されています。 ここだけはきちんと翻訳しておきます。

  • Whitespaces inside of the textarea element are squeezed. Please don’t use the layout on pages with non-empty textarea.
  • Inline JS can become broken where // comments used. Please remove the comments or change to /**/ style.
  • Invalid markup can lead to unexpected results. Please make sure your markup is valid before.
  • textarea要素の内部の空白が除去されます。textareaが空でないページでcompressレイアウトを使用しないでください。
  • インラインJavaScriptは//のコメントが使用されている箇所で破壊されることがあります。コメントを削除するか/ ** /スタイルのコメントに変更してください。
  • 不正なマークアップは予期しない結果を招く恐れがあります。マークアップが正しいことを確認してください。

まとめ

jekyll-compress-htmlはLiquidだけでminifyを実現していて、なかなか興味深いレイアウトです。 Gemも必要ありませんので、必要に応じてレイアウトをコピペするだけで、Jekyllで生成されるHTMLをminifyできます。 defaultのレイアウトにしてしまえば勝手にminifyされますので、1バイトでもHTMLの容量を縮めたいときには重宝します。

なおこのブログはjekyll-compress-htmlでminifyしてあります。