3ファイルでできるサイト全体の自動的なWebP対応の記事を参考に作成しました。
参考記事によってwebpには対応できたのですが、処理のついでに
jpg,pngのファイルサイズを最適化した画像も生成したかったのでこの記事を書きました。
参考記事とは異なりluaを使用します。
WSL上のDebian 10(buster)で動作確認を行っています。
この記事に書いている方法ではなく、webp-heroというpolyfillを使う方法もあります。
準備
2019年11月時点の情報になります。
libwebp(cwebp)
自分でビルドする方法と、ビルド済みのパッケージを下記リンクから落としてくる方法があります。
https://storage.googleapis.com/downloads.webmproject.org/releases/webp/index.html
fd(find代替)
sudo apt install fd-find
sudo ln -s /usr/bin/fdfind /usr/local/bin/fd
大体のものはパッケージ名fd
またはfd-find
でインストール可能です。
fd-find
でインストール可能なものはfd
だと別のパッケージがインストールされるので注意してください。
mozjpeg
別のcjpeg(libjpeg-turboなど)が入っていないことを確認してから実行してください。
autotools(autoreconf)でインストールする記事も見つかりますが、執筆時のバージョン(v3.3.1)ではcmakeを使う必要があります。
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のインストールを試してみてください。
終わったらインストールに使用したファイルは削除しても問題ありません。
cd ../../
sudo rm -fr mozjpeg
pngquant
sudo apt install pngquant
シェルスクリプト
#!/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などから実行します。
/path/convert.sh /path/img_dir
利用方法
nginx+lua(openresty)サーバーでの利用
まずはopenrestyをインストールします。
map $http_accept $haswebp {
default false;
"~*image/webp" true;
}
server {
listen 80;
location / {
...
}
include "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行を削除して下の行を有効化します。
実行します。
sudo openresty
または再読込。
sudo openresty -s reload
ドキュメントルートにHTMLと画像を配置してhttp://localhost/を開きます。
Chromeの場合は検証のNetworkを開いてtypeがwebpになっていたら成功です。
CloudFlare(CDN)での利用
CloudFlareなどのCDNでは出し分けが終わったあとの画像がキャッシュされてしまい
最初に表示すた環境がwebp対応だった場合は次以降に表示した環境がwebp未対応であってもwebpで表示されます。
逆に最初に表示した環境が非対応だった場合はwebp対応環境でもwebpで表示されません。
対応方法は複数あります。
1、画像をバイパスする
この方法が最も簡単ですがデメリットもあります。
Page Rulesを開いて画像のバイパス設定をします。
*.example.com/*.jpg
*.example.com/*.png
「Cache Level」で「Bypass」を設定
jpegも追加すると3つになります。
デメリット
- 画像がCDNにキャッシュされない。
- フリー版だと3つまでしか設定できないページルール設定を2~3個使用してしまう。
有料版なら設定次第でwebpを自動的に用意してくれます(cwebpなども不要)
課金前提ならimgixを使用するのも良いと思います。
2、Javascriptで検出し置き換える
Javascriptの知識と手間が必要になりますが、こちらの方法はnginxやApacheなどの設定は不要です。
- 自分で検出するスクリプトを書く
- Modernizrを使用してwebpサポートを検出する
- ServiceWorkerを使用してwebpサポートを検出する
生成済みのwebpがサーバー上に存在するかチェックする必要も出てきます。
lazyloadに対応させる場合はさらに複雑になります。
img要素をpicture要素でラップするjsを書いてみるのもいいかもしれません。
その場合は元からpicture要素だったものへの対応も必要になります。
lazyloadへの対応はさらに難しくなります。