【sedコマンド】 htmlファイルをシェルスクリプトでminifyする人向けのsedテクニック講座


2020/04/15

静的なウェブページを配信する際にプリレンダリングした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 コマンド

記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。