前后端分离做成 SPA 后,首页文件常常超过 1MB,首屏就会慢。常见思路有:UI 组件按需加载、路由懒加载、处理组件重复打包、以及 gzip。前两者要看项目条件,UI 按需和 gzip 则几乎总能做。
本文先聊 ElementUI 按需引入和 gzip。按需能拆掉巨大的 element-ui.commons.js;gzip 无论把前端拷进 Spring Boot,还是用 Nginx 分开部署,都能立刻把传输体积压下去。
| 手段 |
做什么 |
效果 |
| ElementUI 按需 |
babel-plugin-component + 按组件 import |
vendor 里那块 commons 消失,大约少 150KB |
| 后端 gzip |
Spring Boot application.yml 打开压缩 |
响应带 Content-Encoding: gzip |
| Nginx gzip |
gzip on 并配 types / 阈值 |
大于 1K 的 js/css 等被压缩 |
一、ElementUI 按需加载
问题复现
不做优化时,一般在 main.js 整包引入:
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI,{size:'small'});
CSS 必须引入。但这种写法会把接近 60 个组件全部打进项目,用不到的就浪费。用下面命令生成 report.html 分析包内容:
vue-cli-service build --report
关键日志类似:
warning
entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
app (1.17 MiB)
css/chunk-vendors.9948ce82.css
js/chunk-vendors.11959501.js
css/app.4e8a7623.css
js/app.ce6f575c.js
File Size Gzipped
dist/js/chunk-vendors.11959501.js 840.77 KiB 227.94 KiB
dist/js/app.ce6f575c.js 99.08 KiB 30.95 KiB
dist/js/chunk-64435448.d0a0516e.js 26.52 KiB 5.87 KiB
dist/js/chunk-0c17a57a.d553638c.js 23.79 KiB 5.49 KiB
dist/js/chunk-a3fdbb9c.ddc4c008.js 13.30 KiB 3.45 KiB
dist/js/chunk-54277bc7.2882c4cd.js 10.40 KiB 2.95 KiB
dist/js/chunk-4e552d82.c64f4d10.js 1.78 KiB 0.63 KiB
dist/js/chunk-18458ebc.32fb57c9.js 1.54 KiB 0.62 KiB
dist/js/chunk-2d0d03c8.3a093d55.js 0.52 KiB 0.36 KiB
dist/js/chunk-2d237c54.0b312051.js 0.43 KiB 0.33 KiB
dist/css/chunk-vendors.9948ce82.css 258.19 KiB 41.36 KiB
dist/css/app.4e8a7623.css 3.46 KiB 1.09 KiB
dist/css/chunk-0c17a57a.9fe19f94.css 0.86 KiB 0.35 KiB
dist/css/chunk-64435448.3755e146.css 0.30 KiB 0.15 KiB
入口已经超出官方建议的 244KB。把打包结果拷到 Spring Boot 的 resources/static,浏览器里最大的 chunk-vendors.*.js 加载用了 369ms:

dist 里的 report.html 能看出是谁把 JS 撑大的:chunk-vendors 大,主要是因为里面的 element-ui.commons.js 大。每个模块都能优化,但先打这个出头鸟。

按需引入
官网的办法:先装 babel-plugin-component:
npm install babel-plugin-component -D
改 babel.config.js:
module.exports = {
presets: [
'@vue/app',
['@babel/preset-env', {
modules: false
}]
],
plugins: [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
再改 main.js,用到的组件一个一个引入:
import {
Button,
Input,
Table,
TableColumn,
Dialog,
Card,
Container,
Icon,
Select,
Form,
Tag,
Tree,
Pagination,
Badge,
Loading,
Message,
MessageBox,
Menu,
Tabs,
TabPane,
Breadcrumb,
BreadcrumbItem,
Dropdown,
Steps,
Tooltip,
Popover,
Collapse,
FormItem,
Checkbox,
Header,
DropdownMenu,
DropdownItem,
Aside,
Main,
MenuItem,
Submenu,
Option,
Col,
Row,
Upload,
Radio,
DatePicker,
RadioGroup,
CollapseItem,
Switch
} from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.prototype.$ELEMENT = {size: 'small', zIndex: 3000};
Vue.use(Switch);
Vue.use(CollapseItem);
Vue.use(Radio);
Vue.use(RadioGroup);
Vue.use(DatePicker);
Vue.use(Upload);
Vue.use(Row);
Vue.use(Col);
Vue.use(Option);
Vue.use(Submenu);
Vue.use(MenuItem);
Vue.use(Header);
Vue.use(DropdownMenu);
Vue.use(DropdownItem);
Vue.use(Aside);
Vue.use(Main);
Vue.use(Checkbox);
Vue.use(FormItem);
Vue.use(Collapse);
Vue.use(Popover);
Vue.use(Menu);
Vue.use(Tabs);
Vue.use(TabPane);
Vue.use(Breadcrumb);
Vue.use(BreadcrumbItem);
Vue.use(Dropdown);
Vue.use(Steps);
Vue.use(Tooltip);
Vue.use(Tree);
Vue.use(Pagination);
Vue.use(Badge);
Vue.use(Loading);
Vue.use(Button);
Vue.use(Input);
Vue.use(Table);
Vue.use(TableColumn);
Vue.use(Dialog);
Vue.use(Card);
Vue.use(Container);
Vue.use(Icon);
Vue.use(Select);
Vue.use(Form);
Vue.use(Tag);
Vue.prototype.$alert = MessageBox.alert
Vue.prototype.$confirm = MessageBox.confirm
两处要注意:MessageBox 的引入方式和其他组件不同;给组件统一定制 size 和 zIndex 的方式也变了。然后再 build –report:
entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
app (1.03 MiB)
css/chunk-vendors.26d2c5b9.css
js/chunk-vendors.e2a11728.js
css/app.4e8a7623.css
js/app.c5dd78e5.js
File Size Gzipped
dist/js/chunk-vendors.e2a11728.js 683.05 KiB 177.91 KiB
dist/js/app.c5dd78e5.js 101.70 KiB 31.90 KiB
dist/js/chunk-64435448.d0a0516e.js 26.52 KiB 5.87 KiB
dist/js/chunk-0c17a57a.d553638c.js 23.79 KiB 5.49 KiB
dist/js/chunk-33b8cd94.7bbae1a0.js 13.30 KiB 3.46 KiB
dist/js/chunk-df7e035a.414b548f.js 10.40 KiB 2.95 KiB
dist/js/chunk-4e552d82.c64f4d10.js 1.78 KiB 0.63 KiB
dist/js/chunk-18458ebc.32fb57c9.js 1.54 KiB 0.62 KiB
dist/js/chunk-2d0d03c8.3a093d55.js 0.52 KiB 0.36 KiB
dist/js/chunk-2d237c54.0b312051.js 0.43 KiB 0.33 KiB
dist/css/chunk-vendors.26d2c5b9.css 262.71 KiB 42.11 KiB
dist/css/app.4e8a7623.css 3.46 KiB 1.09 KiB
dist/css/chunk-0c17a57a.9fe19f94.css 0.86 KiB 0.35 KiB
dist/css/chunk-64435448.3755e146.css 0.30 KiB 0.15 KiB
和前面比,按需引入有效果,但不算惊人。再打开 report.html:庞大的 element-ui.commons.js 没了,换成一堆小模块,相关文件大约少了 150KB。感觉还不够,继续上 gzip。

二、gzip:两种部署
Spring Boot 打开压缩
第一种方式前端不用额外处理。后端 application.yml:
server:
compression:
enabled: true
重启后首页请求基本都被压缩:

点开一条请求可以看到 gzip 已生效,效果立竿见影:

Nginx 部署
打开配置:vim /usr/local/nginx/conf/nginx.conf,找到如下一段修改:
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
#gzip_http_version 1.0;
gzip_comp_level 2;
gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_vary off;
gzip_disable "MSIE [1-6]\.";
含义简要如下:
-
-
-
-
第4行:反向代理末端若是 HTTP/1.0 再考虑;默认 HTTP/1.1,有这句就注释掉
-
-
第6行:压缩的文件类型;JavaScript 有两种写法最好都写上,否则有人会抱怨 js 没压到
-
第7行:和 Squid 等缓存有关,on 会加 “Vary: Accept-Encoding”
-
保存后 nginx -s reload。用 curl 测是否开启:
curl -I -H "Accept-Encoding: gzip, deflate" "http://www.slyar.com/blog/"
HTTP/1.1 200 OK
Server: nginx/1.0.15
Date: Sun, 26 Aug 2012 18:13:09 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
X-Powered-By: PHP/5.2.17p1
X-Pingback: http://www.slyar.com/blog/xmlrpc.php
Content-Encoding: gzip
页面成功压缩。再测 css:
curl -I -H "Accept-Encoding: gzip, deflate" "http://www.slyar.com/blog/wp-content/plugins/photonic/include/css/photonic.css"
HTTP/1.1 200 OK
Server: nginx/1.0.15
Date: Sun, 26 Aug 2012 18:21:25 GMT
Content-Type: text/css
Last-Modified: Sun, 26 Aug 2012 15:17:07 GMT
Connection: keep-alive
Expires: Mon, 27 Aug 2012 06:21:25 GMT
Cache-Control: max-age=43200
Content-Encoding: gzip
css 成功压缩。再测 js:
curl -I -H "Accept-Encoding: gzip, deflate" "http://www.slyar.com/blog/wp-includes/js/jquery/jquery.js"
HTTP/1.1 200 OK
Server: nginx/1.0.15
Date: Sun, 26 Aug 2012 18:21:38 GMT
Content-Type: application/x-javascript
Last-Modified: Thu, 12 Jul 2012 17:42:45 GMT
Connection: keep-alive
Expires: Mon, 27 Aug 2012 06:21:38 GMT
Cache-Control: max-age=43200
Content-Encoding: gzip
js 成功压缩。图片也可以压:
curl -I -H "Accept-Encoding: gzip, deflate" "http://www.slyar.com/blog/wp-content/uploads/2012/08/2012-08-23_203542.png"
HTTP/1.1 200 OK
Server: nginx/1.0.15
Date: Sun, 26 Aug 2012 18:22:45 GMT
Content-Type: image/png
Last-Modified: Thu, 23 Aug 2012 13:50:53 GMT
Connection: keep-alive
Expires: Tue, 25 Sep 2012 18:22:45 GMT
Cache-Control: max-age=2592000
Content-Encoding: gzip
最后来个不到 1K 的文件,阈值是 1K,所以没压缩:
curl -I -H "Accept-Encoding: gzip, deflate" "http://www.slyar.com/blog/wp-content/plugins/wp-multicollinks/wp-multicollinks.css"
HTTP/1.1 200 OK
Server: nginx/1.0.15
Date: Sun, 26 Aug 2012 18:23:27 GMT
Content-Type: text/css
Content-Length: 180
Last-Modified: Sat, 02 May 2009 08:46:15 GMT
Connection: keep-alive
Expires: Mon, 27 Aug 2012 06:23:27 GMT
Cache-Control: max-age=43200
Accept-Ranges: bytes
一句话总结:SPA 首页先把 UI 库改成按需引入,再在 Spring Boot 或 Nginx 上打开 gzip,体积和耗时都会立刻下来。