使用 Nginx 和 mod_pagespeed 自动将图片转换为 WebP 并输出
关于 WebP 格式,Google 是这样介绍的:
WebP is a modern image format that provides superior lossless and lossy compression for images on the web. Using WebP, webmasters and web developers can create smaller, richer images that make the web faster.
WebP lossless images are 26% smaller in size compared to PNGs. WebP lossy images are 25-34% smaller than comparable JPEG images at equivalent SSIM quality index.
Lossless WebP supports transparency (also known as alpha channel) at a cost of just 22% additional bytes. For cases when lossy RGB compression is acceptable, lossy WebP also supports transparency, typically providing 3× smaller file sizes compared to PNG.
这样来看对比目前互联网上常见图片格式——PNG 和 JPG 来说,优势就很明显了,在 Google 的 PageSpeed Insights 中,对于网站的优化许多时候也会有这样一条优化建议——将图片使用 WebP 输出。
以《那些年我开过的车(们)》中长安逸动的照片为例,以下是 eado-pov.jpg
➜ du -h eado-pov.jpg
1.4M eado-pov.jpg
使用 cwebp
进行转换之后大小变为了:
➜ du -h eado-pov.webp
292K eado-pov.webp
其中转换过程如下:
Saving file 'eado-pov.webp'
File: eado-pov.jpg
Dimension: 4109 x 2229
Output: 297652 bytes Y-U-V-All-PSNR 40.17 47.73 46.92 41.53 dB
(0.26 bpp)
block count: intra4: 17345 (48.21%)
intra16: 18635 (51.79%)
skipped: 3906 (10.86%)
bytes used: header: 322 (0.1%)
mode-partition: 66470 (22.3%)
Residuals bytes |segment 1|segment 2|segment 3|segment 4| total
macroblocks: | 1%| 7%| 22%| 70%| 35980
quantizer: | 45 | 45 | 38 | 30 |
filter level: | 14 | 18 | 56 | 45 |
图片如下(以下是 eado-pov.webp
):
至少作为网页输出来说,我肉眼没有看到什么差距,而且目前 WebP 对于主流浏览器是全部兼容的:
Amongst web browsers, Google Chrome, Firefox, Opera, GNOME Web, Midori, Falkon, Pale Moon, and Waterfox natively support WebP. Microsoft Edge supports WebP through a platform extension (installed by default). Microsoft Edge doesn’t support platform extensions, including the WebP image format extension, when running in the security hardened “Application Guard” mode.
嗯,我们离一个更快的互联网又近了一步!
为了满足这样的需求,我们有一些解决方案,比如:
- 同时准备原图(PNG/JPG)和 WebP 格式图片,让 Nginx 按照请求输出对应的文件
- 不改变原有文件结构,让 Nginx 可以 On-the-fly 地将原图转换输出 WebP
注意哈,这里 On-the-fly 的意思,不是访问一个
/xxx.jpg
就会自动变成 WebP 格式的/xxx.jpg
。(对于这个需求我们还需要一些别的方法)而是让 Nginx 检查页面中的元素,发现
/xxx.jpg
,会自动将其转换为 WebP(并存放在定义好的缓存文件夹中),并在渲染页面的时候自动把/xxx.jpg
替换成xxx.jpg.pagespeed.ic.pWglov2dVZ.webp
由于图片转换这个操作对于服务来说一般没有什么压力,且「方案一」需要写一堆的 map 和 JS 比较 dirty,这里决定采用「方案二」,让服务器直接转换并输出 WebP 格式图片。而要达成方案二也有两种方式:
- 用 Openresty 并自己搓 Lua 脚本
- 使用 Pagespeed 插件
再一次,我选择使用「方案二」。
编译 ngx_pagespeed
首先确保 Nginx 有 --with-compat
编译参数,这样我们就不需要按照一些奇怪的教程让大家从头开始编译 Nginx,使用 nginx -V
确认,比如我的 Nginx 输出如下:
nginx version: nginx/1.17.4
...
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx ... --with-compat ...
如果大家使用的 Ubuntu 系统的话,系统自带的源会比较老并且没有 --with-compat
,所以这里建议参考官方方式使用官方源:
如果已经安装过 Nginx 了请备份好
nginx.conf
之后apt remove nginx nginx-common nginx-full nginx-core
echo "deb http://nginx.org/packages/mainline/ubuntu `lsb_release -cs` nginx" \
| sudo tee /etc/apt/sources.list.d/nginx.list
curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo apt-key add -
sudo apt update
sudo apt install nginx
以 Ubuntu 18.04 LTS 为例,以上安装方式会安装 Nginx 1.17.4。
接下来开始编译 pagespeed:
首先安装必备环境:
sudo apt install build-essential zlib1g-dev libpcre3 libpcre3-dev unzip uuid-dev
下载 Nginx 1.17.4 源码并解压:
wget https://nginx.org/download/nginx-1.17.4.tar.gz tar xvf nginx-1.17.4.tar.gz
找到 incubator:
git clone https://github.com/apache/incubator-pagespeed-ngx.git
在 incubator-pagespeed-ngx 目录下下载 PageSpeed Optimization Libraries 并解压:
wget https://dl.google.com/dl/page-speed/psol/1.13.35.2-x64.tar.gz tar xvf 1.13.35.2-x64.tar.gz
切换到 nginx 源代码目录下开始配置编译环境:
./configure --with-compat --add-dynamic-module=../incubator-pagespeed-ngx
编译 modules:
make modules
将对应编译好的 module 放到 nginx 目录下:
sudo cp objs/ngx_pagespeed.so /etc/nginx/modules/
启用 ngx_pagespeed
在 Nginx 主配置文件(nginx.conf
)顶部加上:
load_module modules/ngx_pagespeed.so;
并且创建好缓存文件夹以便存放自动转换的图片:
sudo mkdir -p /var/ngx_pagespeed_cache
sudo chown -R www-data:www-data /var/ngx_pagespeed_cache
如果希望所有的站点都开启 PageSpeed ,可以直接在 nginx.conf
中加入以下:
# enable pagespeed module on this server block
pagespeed on;
# Needs to exist and be writable by nginx. Use tmpfs for best performance.
pagespeed FileCachePath /var/ngx_pagespeed_cache;
# Ensure requests for pagespeed optimized resources go to the pagespeed handler
# and no extraneous headers get set.
location ~ "\.pagespeed\.([a-z]\.)?[a-z]{2}\.[^.]{10}\.[^.]+" {
add_header "" "";
}
location ~ "^/pagespeed_static/" { }
location ~ "^/ngx_pagespeed_beacon$" { }
pagespeed RewriteLevel CoreFilters;
其中最后一个部分(pagespeed RewriteLevel CoreFilters;
)表示启用的优化方式,其中包括了一些基础的优化,比如:
add_head
combine_css
combine_javascript
convert_meta_tags
extend_cache
fallback_rewrite_css_urls
flatten_css_imports
inline_css
inline_import_to_link
inline_javascript
rewrite_css
rewrite_images
rewrite_javascript
rewrite_style_attributes_with_url
如果需要加入别的 Filter ,可以类似这样写:
pagespeed EnableFilters combine_css,extend_cache,rewrite_images;
所有的 Filters 列表可以参考:Configuring PageSpeed Filters,对于我们图片的转换的话,由于 PageSpeed 会自动判断是否需要转换,对于我们需要彻底转换 WebP 的需求,还需要加上几个 filter:
pagespeed EnableFilters convert_png_to_jpeg,convert_jpeg_to_webp;
重启 Nginx 之后打开页面应该就可以发现图片的 URL 已经被替换成 WebP 格式的了:
一些小问题
如果发现你的图片并没有自动被转换成 WebP 格式的话,可以在你的 URL 后面加上 ?PageSpeedFilters=+debug
,然后查看源代码,并注意源代码中图片后面的部分,在我配置的过程中遇到过以下问题:
<!--4xx status code, preventing rewriting of xxx
由于手上 Wordpress 的机器都是放在 Docker 中,前置了 Cloudflare,所以默认的回源方式会出错,这个时候需要这样配置一下,其中localhost:2404
是本地 Docker 监听地址:pagespeed MapOriginDomain "http://localhost:2404/" "https://nova.moe/";
<!--deadline_exceeded for filter CacheExtender--><!--deadline_exceeded for filter ImageRewrite-->
这个表示 PageSpeed 正在生成对应的缓存图片。
<!--The preceding resource was not rewritten because its domain (nova.moe) is not authorized-->
由于有反向代理,SSL 在 Nginx 上就已经结束,需要配置一下代理中的:proxy_set_header X-Forwarded-Proto $scheme;
并加上:
pagespeed RespectXForwardedProto on;
优化前后对比 && 总结
优化前:
注意那个 Serve Images in next-gen formats 的优化建议。
优化后:
总结
虽然这样做可以在不(手动)改变页面元素的情况下将 JPG 和 PNG 自动转换为 WebP 输出,但是在以上实验中,需要做到站点和媒体文件使用的一个路径,换句话说,就是需要使用 Wordpress 媒体库,而 Wordpress 媒体库于我而言并不好用,无论是为了以后备份还是迁移,所以对于我的博客,所有的图片都被放在了 /
下,在这样一个情况下就没法做到用 PageSpeed 自动 WebP 转换了,也是接下来需要优化的一个重点(图片虽然不多,但是有些图片 1.4M 实在是有点太大了)。