サイト全体をWebPに自動対応させ、未対応環境には自動圧縮した画像を提供する

[3ファイルでできるサイト全体の自動的なWebP対応](https://qiita.com/miyanaga/items/94b5bb42501e2292fb67)の記事を参考に作成しました。

参考記事によってwebpには対応できたのですが、処理のついでに
jpg,pngのファイルサイズを最適化した画像も生成したかったのでこの記事を書きました。
参考記事とは異なりluaを使用します。

WSL上のDebian 10(buster)で動作確認を行っています。

この記事に書いている方法ではなく、[webp-hero](https://www.npmjs.com/package/webp-hero)というpolyfillを使う方法もあります。


## 準備

2019年11月時点の情報になります。

### [libwebp(cwebp)](https://developers.google.com/speed/webp/docs/using)

自分でビルドする方法と、ビルド済みのパッケージを下記リンクから落としてくる方法があります。
https://storage.googleapis.com/downloads.webmproject.org/releases/webp/index.html

### [fd(find代替)](https://github.com/sharkdp/fd)

```bash:Ubuntu、Debianの場合
sudo apt install fd-find
sudo ln -s /usr/bin/fdfind /usr/local/bin/fd
```

大体のものはパッケージ名`fd`または`fd-find`でインストール可能です。
`fd-find`でインストール可能なものは`fd`だと別のパッケージがインストールされるので注意してください。

### [mozjpeg](https://github.com/mozilla/mozjpeg/blob/master/BUILDING.md)

別のcjpeg(libjpeg-turboなど)が入っていないことを確認してから実行してください。
autotools(autoreconf)でインストールする記事も見つかりますが、執筆時のバージョン(v3.3.1)ではcmakeを使う必要があります。

```bash
sudo apt install cmake autoconf automake libtool nasm make pkg-config git
git clone https://github.com/mozilla/mozjpeg.git

cd mozjpeg
mkdir build && cd build
sudo cmake -G"Unix Makefiles" ../
sudo make install

# シンボリックリンクを張る
sudo ln -s /opt/mozjpeg/bin/cjpeg /usr/local/bin/cjpeg
sudo ln -s /opt/mozjpeg/bin/jpegtran /usr/local/bin/jpegtran
```

エラーが出る場合はlibpng-devのインストールを試してみてください。
終わったらインストールに使用したファイルは削除しても問題ありません。

```bash
cd ../../
sudo rm -fr mozjpeg
```

### [pngquant](https://pngquant.org/)

```bash
sudo apt install pngquant
```

## シェルスクリプト

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

if [ $# -ne 1 ]; then
	echo "対象ディレクトリへのパスを指定してください" 1>&2
	echo "run '$0 /path/img_dir'" 1>&2
	exit 1
fi

DIR="$1" # 対象ディレクトリパス
JPEG_CWEBP_OPTS="-q 80 -m 4" # Jpeg向け非可逆cwebpオプション
PNG_CWEBP_OPTS="-lossless" # PNG向け可逆cwebpオプション

cd $(dirname $0)
shopt -s nocasematch

fd -ip0 ".*\.(jpe?g|png)$" "$DIR" --exclude "*.min.{jpg,jpeg,png}" | \
while IFS= read -r -d '' SRC; do
	#WEBP="$SRC.webp"
	WEBP=`echo $SRC | sed -r 's@/([^/]*)\.(jpe?g|png)$@/\1.webp@i'`
	if [[ ! -e $WEBP || $SRC -nt $WEBP ]]; then
		if [[ $SRC =~ \.jpe?g$ ]]; then
			cwebp $JPEG_CWEBP_OPTS "$SRC" -o "$WEBP" -quiet
			chmod 2664 "$WEBP"
		elif [[ $SRC =~ \.png$ ]]; then
			cwebp $PNG_CWEBP_OPTS "$SRC" -o "$WEBP" -quiet
			chmod 2664 "$WEBP"
		fi
	fi

	# 画像最適化
	MIN=`echo $SRC | sed -r 's@/([^/]*)\.(jpe?g|png)$@/\1.min.\2@i'`
	if [[ ! -e $MIN || $SRC -nt $MIN ]]; then
		if [[ $SRC =~ \.jpe?g$ ]]; then
			cjpeg -optimize "$SRC" > "$MIN"
			chmod 2664 "$MIN"
		elif [[ $SRC =~ \.png$ ]]; then
			pngquant "$SRC" -o "$MIN" --strip
			chmod 2664 "$MIN"
		fi
	fi
done
```
- コマンドの引数で対象ディレクトリを指定する方式に変更。
- minファイルを除外するためfindからfdに変更。
  - fdのほうが速いのでwebpのみの変換の場合にもおすすめです。
- webpの方にquietオプションを追加。
- luaを使用するので`.png.webp` `.jpg.webp`と元の拡張子がついていたものを`.webp`に変更。
  - 元の拡張子がついている状態にする場合は`#WEBP="$SRC.webp"`のコメントを外して、その下の行を削除してください。


以下のコマンドのパスを正しいものに変更して、コマンドラインやcronなどから実行します。

```bash
/path/convert.sh /path/img_dir
```


# 利用方法

## nginx+lua(openresty)サーバーでの利用
まずは[openrestyをインストール](https://openresty.org/en/linux-packages.html)します。

```nginx:nginx.conf
map $http_accept $haswebp {
	default   false;
	"~*image/webp"  true;
}

server {
	listen 80;

	location / {
		...
	}

	include "content_negotiation.conf";
}
```

```nginx:content_negotiation.conf
location ~* \.(png|jpe?g)$ {
	add_header Vary Accept;

	set_by_lua_block $image {
		local haswebp = ngx.var.haswebp
		local image = ngx.var.uri
		local pattern = [[([^/]*)\.(jpe?g|png)$]]

		if (haswebp == "false") then
			-- # xx.png -> xx.min.png
			local repl    = "$1.min.$2"
			image = ngx.re.sub(ngx.var.uri, pattern, repl, "i")
		else
			-- # xx.png -> xx.webp
			local repl    = "$1.webp"
			image = ngx.re.sub(ngx.var.uri, pattern, repl, "i")

			-- # xx.png -> xx.png.webp
			-- #image = ngx.var.uri .. ".webp"
		end

		ngx.log(ngx.DEBUG, image)
		return image
	}

	try_files $image $uri =404;
}
```

この処理でxx.pngへのリクエストを未対応ブラウザはxx.min.pngに、対応ブラウザはxx.webpに置き換えます。
xx.png.webpに置き換えたい場合はelse内の上4行を削除して下の行を有効化します。


実行します。

```bash
sudo openresty
```

または再読込。

```bash
sudo openresty -s reload
```

ドキュメントルートにHTMLと画像を配置して[http://localhost/](http://localhost/)を開きます。
Chromeの場合は検証のNetworkを開いてtypeがwebpになっていたら成功です。

## CloudFlare(CDN)での利用

CloudFlareなどのCDNでは出し分けが終わったあとの画像がキャッシュされてしまい
最初に表示すた環境がwebp対応だった場合は次以降に表示した環境がwebp未対応であってもwebpで表示されます。
逆に最初に表示した環境が非対応だった場合はwebp対応環境でもwebpで表示されません。

対応方法は複数あります。

### 1、画像をバイパスする
この方法が最も簡単ですがデメリットもあります。
Page Rulesを開いて画像のバイパス設定をします。

```:rule
*.example.com/*.jpg
*.example.com/*.png
```

「Cache Level」で「Bypass」を設定

jpegも追加すると3つになります。

#### デメリット
- 画像がCDNにキャッシュされない。
- フリー版だと3つまでしか設定できないページルール設定を2～3個使用してしまう。

有料版なら設定次第でwebpを自動的に用意してくれます（cwebpなども不要）
課金前提ならimgixを使用するのも良いと思います。

### 2、Javascriptで検出し置き換える
Javascriptの知識と手間が必要になりますが、こちらの方法はnginxやApacheなどの設定は不要です。

- [自分で検出するスクリプトを書く](https://stackoverflow.com/questions/5573096/detecting-webp-support)
- Modernizrを使用してwebpサポートを検出する
- [ServiceWorkerを使用してwebpサポートを検出する](https://davidwalsh.name/detect-webp)

生成済みのwebpがサーバー上に存在するかチェックする必要も出てきます。
lazyloadに対応させる場合はさらに複雑になります。

img要素をpicture要素でラップするjsを書いてみるのもいいかもしれません。
その場合は元からpicture要素だったものへの対応も必要になります。
lazyloadへの対応はさらに難しくなります。