3ファイルでできるサイト全体の自動的なWebP対応

Apacheで運営しているサイトをお手軽にWebP対応する方法を紹介します。
※ nginxも対応可能です。記事の末尾に参考情報を記載しました。

Googleが推している新フォーマットなので、[PageSpeed Insights](https://developers.google.com/speed/pagespeed/insights/?hl=ja)や、[Lighthouse](https://developers.google.com/web/tools/lighthouse/?hl=ja)のスコアがわずかですがほぼ確実に上がります(笑)

さて、方法は次の3ファイルをWebサーバーに設置して、cronによる定期処理を登録するだけです。

* WebPへの変換を行う`cwebp`コマンドファイル
* ディレクトリ内の画像に`cwebp`コマンドを実行するシェルスクリプトファイル
* Apacheに振り分けを指示する`.htaccess`ファイル

非常にシンプルで本番環境では厳しい部分も多々ありますが、小規模なサイトはこれで十分という場合もあると思います。

[弊社のWebサイト](https://www.ideamans.com/)や[ブログ](https://blog.ideamans.com/)など実際、小規模なので十分だったりします。**…ということで実際に導入しました。**

ChromeやSafariでアクセスしてください。デベロッパーコンソールで画像がWebPになっていることを確認できます。
https://www.ideamans.com/

成果などはまた別の記事で報告します。

<img width="558" alt="アイデアマンズ株式会社.png" src="https://qiita-image-store.s3.amazonaws.com/0/35178/1bd4a887-d327-4095-ddad-67cacf7443aa.png">

WebP非対応のFirefoxではPNGやJpegが表示されます。

<img width="772" alt="アイデアマンズ株式会社.png" src="https://qiita-image-store.s3.amazonaws.com/0/35178/861c405a-730f-2728-d8b5-c22f771d7353.png">

# 手順

では実際のWebサイトへの設定手順を説明します。前提は以下の通りです。

* SSHなどでシェルにログインできること
* Bashが使えること
* cronに定期実行コマンドを追加できること

## 1. cwebpコマンドをインストール

`cwebp`コマンドはJpegやPNGファイルをWebP形式に変換するコマンドです。バイナリ形式でも配布されているので、ほとんどの環境でビルド不要のアップロードのみで利用できるはずです。

※ すでに`cwebp`コマンドがインストールされていればこの手順は不要です。

こちらからお使いの環境に合わせた[コンパイル済みのWebPコマンド群](https://developers.google.com/speed/webp/docs/precompiled)をダウンロードして、`bin/cwebp`ファイルを任意のパスに配置します(例: `/usr/local/bin/cwebp`)。

実際のダウンロードはこちらです。
https://storage.googleapis.com/downloads.webmproject.org/releases/webp/index.html

> 2018年10月13日現在、最新1.0.0のLinux版がなぜかありませんでした。私は`rc3`を選択しました。

とりあえずバージョンを表示して実行できるか確認します。

```bash:バージョン表示で動作確認
$ /usr/local/bin/cwebp -version
1.0.0
```

## 2. 一括でWebPファイルを生成するスクリプト

次のBash向けのスクリプトを任意のパスに保存します(例: `/home/me/webp.sh`)。

ディレクトリを再帰的にスキャンして、JpegとPNGにそれぞれ異なるオプションで`cwebp`を実行、ただしWebPファイルが生成済みで元のファイルより新しい場合はスキップ、それだけのシンプルなスクリプトです。

ファイル名に半角スペースがある場合も考慮しています。

**上部の変数宣言(特に`DIR`)は、環境に合わせて書き換えてください。**

※ findの記述を修正しました(10/16)

```bash:webp.sh
#!/bin/bash

DIR="./images" # 対象ディレクトリパス(要変更)
JPEG_CWEBP_OPTS="-q 75 -m 4" # Jpeg向け非可逆cwebpオプション
PNG_CWEBP_OPTS="-lossless" # PNG向け可逆cwebpオプション
CWEBP="/usr/local/bin/cwebp" # cwebpコマンドの場所

cd $(dirname $0)
shopt -s nocasematch

find $DIR -type f -regextype posix-extended -iregex ".*\.(jpe?g|png)$" -print0 | \
while IFS= read -r -d '' SRC; do
  WEBP="$SRC.webp"
  if [[ ! -e $WEBP || $SRC -nt $WEBP ]]; then
    if [[ $SRC =~ \.jpe?g$ ]]; then
      echo "Convert to lossy WebP: $SRC"
      "$CWEBP" $JPEG_CWEBP_OPTS "$SRC" -o "$WEBP"
    elif [[ $SRC =~ \.png$ ]]; then
      echo "Convert to lossless WebP: $SRC"
      "$CWEBP" $PNG_CWEBP_OPTS "$SRC" -o "$WEBP"
    fi
  fi
done
```

このスクリプトに実行権限を付与します。

```bash
$ chmod a+x /home/me/webp.sh
```

## 3. デバイスに応じてWebP画像を返す.htaccessファイル

画像専用ディレクトリのトップか、Apacheのドキュメントルートに以下の`.htaccess`ファイルを設置します。
nginxでは使用できないので次節をご覧ください。

このファイルについては先日、詳しく記事を書きました。内容については、[.htaccessによるWebPの選択的レスポンスとその問題点と改善案](https://qiita.com/miyanaga/items/6570c3c9ae8e15dbb57c)をご覧ください。

```apache:.htaccess
<IfModule mod_setenvif.c>
  SetEnvIf Request_URI "\.(jpe?g|png)$" _image_request
</IfModule>

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{HTTP_ACCEPT} image/webp
  RewriteCond %{SCRIPT_FILENAME}.webp -f
  RewriteRule .(jpe?g|png)$ %{SCRIPT_FILENAME}.webp [T=image/webp]
</IfModule>

<IfModule mod_headers.c>
  Header append Vary Accept env=_image_request
</IfModule>

<IfModule mod_mime.c>
  AddType image/webp .webp
</IfModule>
```

すでに`.htaccess`ファイルがある場合は上書きに気をつけてマージしてください。

## 4. cronに変換スクリプトを登録

定期的に変換スクリプトを実行します。例えば30分に1回実行する場合は次のように登録します。

```cron
*/30 * * * * /home/me/webp.sh
```

私は次のようにログファイルに出力するように登録しました。`cwebp`の出力が標準エラーに対して行われるので、`2>&1`もあった方がよいです。

```cron
*/30 * * * * /usr/local/bin/webp.sh >> /path/to/webp.log 2>&1
```

画像が追加・更新されてから実際にWebP画像が配信されるまで最長で30分のタイムラグがありますが、それまでは元のJpegまたはPNGが表示されます。

# nginxの場合について

`/etc/nginx/nginx.conf`などのシステム設定ファイルを編集する必要があります。

[こちらの記事](https://qiita.com/miyanaga/items/94447efae0bf767b9f2b)にnginxにおける方法をまとめたのでご参考ください。

# IISの場合について

※ 10/15追記

IISもrewrite moduleという外部モジュールでURLの書き換えが可能なようです。
以下のページにXMLファイルの例がありました。

https://www.keycdn.com/blog/convert-to-webp-the-successor-of-jpeg

# 課題

大規模なサイトでの本番運用には以下のようにいろいろ課題がありますが、最低限のWebP対応方法として参考になれば幸いです。

* もしWebPの方がサイズが大きくなるなら変換しない方がいい
* 何%削減できたか成果がわからない
* エラーが発生してもわからないし、毎回それを繰り返す
* 30分で処理が終わらなかった場合の対処
* GIF対応(GIFをWebPに変換するコマンドもあり)

# inotifyによるファイル変更検知の例

※ 10/15追記

Linuxにはinotifyと呼ばれるファイル変更検知の仕組みがあります。
その機能を使って、JpegファイルとPNGファイルが追加・変更されたら瞬時にWebPを作成する例がありました。

https://www.digitalocean.com/community/tutorials/how-to-create-and-serve-webp-images-to-speed-up-your-website

この方法であれば、WebP配信までのタイムラグがほとんどありません。