サイズの大きいhtmlファイルをminifyするときに便利なSedコマンドの使い道


※ 当ページには【広告/PR】を含む場合があります。
2020/04/15
2022/09/30
Sedコマンドで改行も含めた正規表現パターンをキャプチャする方法の考察




静的なウェブページを配信する際にプリレンダリングしたhtmlファイルをより効果的にminifyしたい場合があります。
そんなときhtml-minifierなどを利用してもどうしても手のかゆいところまで届かない箇所の修正をsedで行う場合のお役立ち手法をまとめます。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】シェルスクリプトをこれから学びたい人のためのオススメ書籍&教材特集

sedコマンドの基本的なオプション



Linuxなら標準装備しているコマンドベースのストリームエディタです。
Windowsユーザーの方でも、Busyboxを利用すると
sed がもれなく使えます。

以前の記事でwindows10でawkをBusyboxを使って導入する ような内容をまとめましたので、 Busybox に関してはそちらを参考ください。

sedのマニュアル--help などでオプションを今回の記事の範囲内で利用するものに絞って、ざっとおさらいすると以下のようになります。

            -e [script] / --expression=[script]:
    実行するコマンドとして script を追加する

-f [script-file] / --file=[script-file]:
    実行するコマンドとして script-file の内容を追加する

-i[SUFFIX] / --in-place[=SUFFIX]:
    ファイルをインプレース処理で編集する (SUFFIX 指定時はバックアップを取る)

        

以上、押さえておくべきオプションは主に3つです。
sedコマンドを利用するには特に
-e オプションを理解することが大事なのです。
中身で記述する正規表現の扱い方がもっとも難しい...のであって、コマンドの用法自体は簡単です。
今回はhtmlファイルに、どうやってsedコマンドを使って軽量化するのかのおおまかな手順を説明しますので、正規表現の具体的な中身にはスポットを当てません。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】シェルスクリプトをこれから学びたい人のためのオススメ書籍&教材特集

minify後のhtmlファイルでもsedを使うというモチベーション



ずばり、
外部のリソースをindex.htmlに埋め込んでコードにインライン化することで、ウェブページの読み込みオーバーヘッド時間を軽減させる 、ということです。
通常はindex.htmlが読み込まれた後に、外部のjsファイルやcssファイルを呼び込んでいる時間が勿体無いでの、あらかじめindex.htmlにリソースを貼り付けていたらかなりのウェブページのレスポンスを高速化できる可能性があります。
本記事では、
Angular/Vuejs/Reactjsなどでビルドしてindex.html生成 --> minifyでhtmlを最適化・軽量化 --> sedで必要なリソースをインライン化 の手順を想定して話を進めていきます。

ビルド後のindex.htmlをコマンドでminifyしてくれるライブラリ ~ html-minifier



著者が仕事用に使っているnodejs向けのhtml用のminifierは、
html-minifier です。
言わずもがなで、webpackやbrowserifyのようなパッケージバンドラ向けのプラグインでminifyする方が今は主流の気がします。
やり方や考え方は人それぞれで、個人的には細かくオプション指定できて、「自分でminifyしてる感」があるので、コマンドラインベースで好んでよく利用しております。
html-minifierの作成者の方が、おまけ(?)で公開している
プレイグラウンドサイト があります。
これをnodejsにパッケージをインストールしていなくても雰囲気を味わえます。
論より証拠で一例だけ試して見ましょう。

ここのサイトに公開されている 以下のような最小限の index.html のブランクのテンプレート例をベースにminifyしてみます。

            <!DOCTYPE html>
<!-- 互換性とアクセシビリティのために<html>タグの「lang=""」属性をセット。
     HTMLタグのほかの属性については下記ページを参考に。
     参考: http://www.w3.org/TR/html-markup/global-attributes.html -->
<html>

<head>

<!-- IE8+に対して「IE=edge」と指定することで、
     利用できる最も互換性の高い最新のエンジンを使用するよう指示できます。
     参考: https://www.modern.ie/en-us/performance/how-to-use-x-ua-compatible -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">

<!-- パフォーマンスのために使用する文字のエンコーディングを記述。
     最初のオプションとしてHTTPヘッダで指定し、
     HTTPヘッダとmetaタグでの指定が同じであることを確認。
     参考: https://developers.google.com/speed/docs/best-practices/rendering#SpecifyCharsetEarly -->
<meta charset="utf-8">

<!-- ページのタイトルを記述 -->
<title></title>

<!-- Scontent属性にページの紹介文を記述 -->
<meta name="description" content="">

<!-- content属性にページの著者情報を記述 -->
<meta name="author" content="">

<!-- モバイル端末への対応、
     ページをビューポートの幅に合わせてレンダリング(Android, iOS6以降)
     ズームを許可しない設定「user-scalable=no」は加えない -->
<meta name="viewport" content="width=device-width, initial-scale=1">

<!-- スタイルシートはできるだけ早くレンダリングされるため、
     HTMLドキュメントの上の方に記述
     href属性にスタイルシートファイルのURIを記述 -->
<link rel="stylesheet" href="">

<!-- IE8以下用に2つのスクリプトを記述
     html5shiv.js: IE8以下にHTML5の要素を認識するようにさせる
     respond.js: IE8以下にMedia Queriesの代替え機能を提供 -->
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->

<!-- href属性にファビコンファイルのURIを記述 -->
<link rel="shortcut icon" href="">

<!-- コメントアウトしてあるコードは、iOS/Android用のアイコン指定 -->
<!--
<meta name="mobile-web-app-capable" content="yes">
<link rel="icon" sizes="196x196" href="">
<link rel="apple-touch-icon" sizes="152x152" href="">
-->

<!-- スクリプトでブロッキングを起こさないものはここに記述
     可能であれば「async(文書の読み込みが完了した時点でスクリプトを実行)」を使用
     例: <script src="" async></script> -->
</head>
<body>

<!-- コンテンツを配置 -->

<!-- スクリプトでブロッキングを起こすものはここに記述
     ブロッキングを起こす原因としては、
     CSSのセレクタ操作(IE)、負荷の高いDOM操作、多数のスクリプトなど -->
<!-- SCRIPTS -->
<!-- 例: <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> -->
</body>
</html>

        

これを標準のオプションで、プレイグラウンドでminifyしたのが以下です。

            <!doctypehtml><meta content="IE=edge"http-equiv=X-UA-Compatible><meta charset=utf-8><title></title><meta content=""name=description><meta content=""name=author><meta content="width=device-width,initial-scale=1"name=viewport><link href=""rel=stylesheet><!--[if lt IE 9]><script src=//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.2/html5shiv.min.js></script><script src=//cdnjs.cloudflare.com/ajax/libs/respond.js/1.4.2/respond.min.js></script><![endif]--><link href=""rel="shortcut icon">

        

軽量化された結果も以下のように表示されています。

            Original size: 2,084. Minified size: 487. Savings: 1,597 (76.63%).

        

これだけでも十分素晴らしい軽量化が実現しているのですが、js系のフレームワークでビルド後に出力されるindex.htmlで生成される要素、例えば
<link rel="stylesheet" href="/styles.css"> のように、外部ファイル(ここでは styles.css )に依存する要素を「コード埋め込み・インライン化」するところまではやってもらえません。
そのような要求の細かい作業を行うには、他のライブラリ任せにせず、
sed を利用することで地道にやっていくほうがベターなやり方です。
ということで、以降でsedでソースコードの中身をhtmlに埋め込む方法を解説します。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】シェルスクリプトをこれから学びたい人のためのオススメ書籍&教材特集

sedコマンドを利用したリソースファイルのインライン化

htmlファイルをsedで書き換える際の注意点



通常はsedの
-e オプションで与えるスクリプトの正規表現は'/'(スラッシュ)で挟んで利用するやり方が紹介されています。
利用可能な文字はスラッシュに限定されるわけでもなく、`{任意文字}**
{任意文字} **{任意文字}`の作法さえ揃えたら、実はどんな文字で囲んでもアリ、というルールです。
例えば、

            #tacosをtomatoに置換
$ echo 'tacos tacos tacos' | sed -e 's/tacos/tomato/g'
tomato tomato tomato
$ echo 'tacos tacos tacos' | sed -e 's+tacos+tomato+g'
tomato tomato tomato
$ echo 'tacos tacos tacos' | sed -e 'sxtacosxtomatoxg'
tomato tomato tomato
$ echo 'tacos tacos tacos' | sed -e 's%tacos%tomato%g'
tomato tomato tomato

        

で上手く行きます。
ここで注意が必要なのが、囲み文字に利用した文字が、置換前後の文字パターンに含まれているとそのままではエラーが発生します。
例えば、

            #tacosをスラッシュ文字を含むtomato/で置換
$ echo 'tacos tacos tacos' | sed -e 's/tacos/tomato//g'
sed: -e expression #1, char 16: `s' に対するオプションが不明です

#スラッシュ文字を含むta/cosを置換させている
$ echo 'tacos tacos tacos' | sed -e 's/ta/cos/tomato/g'
sed: -e expression #1, char 10: `s' に対するオプションが不明です

#置換パターンはバックラッシュ(\)を利用すると通常の文字と認識してくれる
$ echo 'tacos tacos tacos' | sed -e 's/tacos/tomato\//g'
tomato/ tomato/ tomato/

        

という感じです。
htmlをcssやjsのようなリソースファイルで置き換えを考えた場合、スラッシュ文字(
/ )が頻発するので、これをそのままsedの囲い文字で利用した場合、意図せずエラーが起こることがあります。
今日日、使わない文字というのもあまり思いつかないですが、個人的には
~ を利用するのがいい感じです。

            $ echo 'tacos tacos tacos' | sed -e 's~tacos~tomato/~g'
tomato/ tomato/ tomato/

        

ただ、その際にはcssで
~ セレクターを使わないマイルール...を定めておくといいかもしれません。

インライン化の実践例



では最後に一例だけ、sedコマンドでhtmlインライン化するスクリプトの一例を考えてみようかとおもいます。
プロジェクトのファイル構造として、
index.html の同階層に、外部ファイルの styles.css が存在しているような場合を考えます。

            .
├── index.html
└── styles.css

        

通常は
index.html が読み込まれて、外部リソースである styles.css が読み込まれる順番になるのですが、sedで styles.css の中身を index.html に埋め込むことで、index.htmlだけの読み込みで完結させるのが狙いです。

index.html は先程minifyさせたhtmlを少し変えて使います。

            <!doctypehtml><meta content="IE=edge"http-equiv=X-UA-Compatible><meta charset=utf-8><title></title><meta content=""name=description><meta content=""name=author><meta content="width=device-width,initial-scale=1"name=viewport><link href="/styles.css"rel=stylesheet><link href=""rel="shortcut icon">

        

埋め込み側の
styles.css ファイルの中身は、

            body,html{width:100%;height:100%;margin:0;padding:0;overflow:auto}body{font-size:62.5%}body .wrapper{min-height:100vh;position:relative;padding-top:60px;padding-bottom:270px;box-sizing:border-box}

        

のようになっているとしましょう。
次に埋め込み処理のスクリプトを用意します。
以下のように
postbuild.sh というファイルを index.html と同階層のフォルダに新規作成し、以下の内容で編集・保存します。

            #!/bin/bash

#Read inline Css
css_inline_text=$(cat ./styles.css)
css_inline_text=$(echo "<style>${css_inline_text}</style>")

#Replace a link element into an inline css text
sed -i -e "s~<link href=\"/styles.css\"rel=stylesheet>~$css_inline_text~" ./index.html

        

それでは、カレントディレクトリ上でこのスクリプトを実行します。

            $ chmod +x postbuild.sh && ./postbuild.sh
#OR
$ bash postbuild.sh

        

実行後に、

            <!doctypehtml><meta content="IE=edge"http-equiv=X-UA-Compatible><meta charset=utf-8><title></title><meta content=""name=description><meta content=""name=author><meta content="width=device-width,initial-scale=1"name=viewport><style>body,html{width:100%;height:100%;margin:0;padding:0;overflow:auto}body{font-size:62.5%}body .wrapper{min-height:100vh;position:relative;padding-top:60px;padding-bottom:270px;box-sizing:border-box}</style><link href=""rel="shortcut icon">

        

でスタイル部分が置き換わっていたらインライン化成功です。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】シェルスクリプトをこれから学びたい人のためのオススメ書籍&教材特集

まとめ



以上、cssファイルだけの埋め込みでhtmlのインライン化をやってみましたが、もちろんやろうと思えば、jQueryの用なCDNライブラリーもindex.htmlに組み込むことが可能です。
今回の記事は、静的なウェブページを作成されている方で、少しでも読み込みを高速化して、Google Lighthouseのパフォーマンスインデックスを値を向上したい方にオススメです。

参照サイト

sedコマンドの主要な用法と実例が紹介されているサイトです| hydroculのメモ > コマンドの使い方(Linux) > sed コマンド