gzip压缩分为服务器压缩和前端文件压缩。应该二者结合,综合使用。

一、问题描述

有一个VUE项目,开发环境下,使用npm运行,一切正常,发布部署到nginx上一片空白。用谷歌浏览器进入开发者工具,提示什么“Uncaught SyntaxError: Unexpected token ‘<’ ”

vue项目发布采用gzip压缩文件部署到nginx无法运行问题_vue压缩


为什么会报这个错误呢?这与nginx的设置有关。

location / {
root E:/订餐谁拿饭抓阄评估规划执行监控审计管理系统/code/web/dist;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}

最后这一句,当出错的时候会强制跳转到 index.html 页面,而js文件不能识别html,因此报Uncaught SyntaxError: Unexpected token < 错误。

更确切地说,是浏览器向服务器请求js文件,服务器报错了,依照nginx的设定,转跳到了index.html,即nginx向客户端返回了index.html的内容。而浏览器接收到这部分"javascript"内容后,无法解释,于是就报了“Uncaught SyntaxError: Unexpected token <”的错。

为什么向服务器请求js文件会报错呢?原因是,在我们的发布包里,没有这些js文件,只有js文件的压缩包:

vue项目发布采用gzip压缩文件部署到nginx无法运行问题_nginx支持gzip_02


nginx应该是可以识别这些压缩包,能处理gzip,所以没有报404错误(如果将发布包部署到IIS,会报404错误),但不知道什么原因,nginx没有正确返回js内容,而是触发了异常。

二、vue的文件压缩处理

之所以我这个vue项目的发布文件,有*.gz文件,是因为配置文件vue.config.js中指定使用了压缩插件:

switch (process.env.NODE_ENV) {
case "development":
...
break;
case "production"://vue3默认情况下,npm run build时,process.env.NODE_ENV==="production"
...
WEBPACK_PLUGINS.push(
new CompressionWebpackPlugin({
filename: "[path].gz[query]",
algorithm: "gzip",
test: new RegExp("\\.(" + productionGzipExtensions.join("|") + ")$"),
threshold: 10240,
minRatio: 0.8,
deleteOriginalAssets: true,//删除压缩后的原文件
})
);
break;
}

三、nginx的gzip设置

事实上,对于服务器来说,有2种应用gzip的途径。一是我们发布文件的时候,不做任何处理,由nginx在处理客户端请求时,将内容压缩返回;二就是我们发布压缩文件,nginx直接读取并返回给客户端。

两种方案,nginx的设置有所不同。

1、nginx压缩

http {
include mime.types;
default_type application/octet-stream;
sendfile on;

server {
listen 8001;
server_name localhost;

gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
#gzip_http_version 1.0;
gzip_comp_level 8;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_proxied any;
gzip_vary off;
gzip_disable "MSIE [1-6]\.";

location / {
root E:/订餐谁拿饭抓阄评估规划执行监控审计管理系统/code/web/dist;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}
}

使用浏览器观察,可以看到

vue项目发布采用gzip压缩文件部署到nginx无法运行问题_nginx_03

2、发布文件压缩

http {
include mime.types;
default_type application/octet-stream;
sendfile on;

server {
listen 8001;
server_name localhost;

#会优先查找静态gzip资源
gzip_static on;

location / {
root E:/订餐谁拿饭抓阄评估规划执行监控审计管理系统/code/web/dist;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}
}

我的情况,就属于第二种方案,然而却出现了问题。出现问题的原因,是应该保留压缩前的原文件,而不是删掉。即部署时,被压缩文件既有压缩包,又有原文件。nginx会有限查找静态gzip资源。要保留原文件的原因,是nginx用来索引和对照的?

vue项目发布采用gzip压缩文件部署到nginx无法运行问题_vue压缩_04


使用浏览器观察,

vue项目发布采用gzip压缩文件部署到nginx无法运行问题_vue发布压缩_05


注意方案1的ETag存在"W/"开头,而方案2的ETag则没有。‘W/’(区分大小写)表示使用弱验证器 。弱 etags 很容易生成,但在比较时用处不大。强验证器是比较的理想选择,但很难有效地生成。ETag弱相同,表示两个资源可能在语义上是等效的,但不是逐字节相同的。这意味着当使用字节范围请求时,弱 ETag 会阻止缓存,但强 ETag 意味着范围请求仍然可以被缓存。

3、两种压缩方案的对比
对比第一种和第二种方式,发现请求ETag有区别,第二种应该比较好。避免前端没有做压缩处理,服务端最好兼容两种都配置,存在静态gzip则用静态资源,不存在则服务端处理。

四、解决方案

综上,解决方案很简单,就是我们在发布的时候,既要压缩,又要保留压缩前的文件,即将deleteOriginalAssets的值设为false。

vue.config.js

WEBPACK_PLUGINS.push(
new CompressionWebpackPlugin({
filename: "[path].gz[query]",
algorithm: "gzip",
test: new RegExp("\\.(" + productionGzipExtensions.join("|") + ")$"),
threshold: 10240,
minRatio: 0.8,
deleteOriginalAssets: false,//删除压缩后的原文件
})
);

nginx采用方案2或者结合起来使用均可。

五、小结

这样子发布出来的包,比之前更大了。但是,可以带来性能的提升,值得去做,这点存储空间不算什么。

为什么系统会提供一个删掉压缩包原文件的选项呢?事实已经证明,这种模式在nginx上不被支持。我猜测,这可能是用于运行在node服务器上的。

参考文章:
​​​Vue 项目性能优化之gzip​