PWA实践
引言
最近在搞基础框架,使用Docker部署到我1M带宽,1核CPU的阿里云上,在演示或者被别人打开的时候老是被吐槽加载太慢了,那么问题来了,怎么优化哪?
思考
- 硬件优化
我的阿里云硬件已经限制了我的带宽,扩容对我来说没必要,毕竟只是有人需要看得时候才访问,再者又不是要用流量挣钱,所以升级硬件不可能。
- Docker 优化
我的阿里云本来就是个喳喳机器,上面还跑着别的几个服务,每个有1-2各实例,再跑两个实例,估计连SSH都用不成了。下面是我docker-compose的配置。还有打包的镜像我用的是caddy,而且caddy也作了gzip压缩, 以下两段代码是我 docker-compose 和 caddy 的配置文件
docker-compose.yml
services:
summit:
image: gu****ab
ports:
- "80:80"
deploy:
mode: replicated
replicas: 1
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
update_config:
parallelism: 1
delay: 10s
placement:
constraints:
- node.role == manager
Caddyfile
0.0.0.0:80
root /www
gzip
log stdout
errors stdout
/apiServer {
gzip
proxy / http://api.server.com/api {
proxy_header Host {host}
proxy_header X-Real-IP {remote}
proxy_header X-Forwarded-Proto {scheme}
}
}
- Webpack优化
在Webpack的基础上优化已经达到极限了;各种文件已经经过webpack的Tree Shaking 和 Code Splitting了,所以期待更强大的插件,同时也希望大佬推荐。
...
entry: {
app: ['@babel/polyfill', './src/index.tsx']
},
output: {
filename: 'static/js/[name]_bundle.js',
chunkFilename: 'static/js/[name]_bundle.js',
path: path.resolve(__dirname, './build/'),
publicPath: publicUrl
},
...
optimization: {
splitChunks: {
chunks: 'all',
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
name: true,
cacheGroups: {
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
name: 'default'
},
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
name: 'vendors'
}
}
},
runtimeChunk: {
name: 'runtime'
}
},
...
new WebpackParallelUglifyPlugin({
uglifyES: {
mangle: false,
output: {
beautify: false,
comments: false
},
compress: {
warnings: false,
drop_console: true,
collapse_vars: true,
reduce_vars: true
}
}
}),
- PWA--渐进式应用程序
年前的时候写过一篇PWA的文章,有兴趣的可以移步PWA基础知识整理及实践,在此抄一遍PWA的特性
PWA 的主要特点包括下面三点:
可靠 - 即使在不稳定的网络环境下,也能瞬间加载并展现
体验 - 快速响应,并且有平滑的动画响应用户的操作
粘性 - 像设备上的原生应用,具有沉浸式的用户体验,用户可以添加到桌面
有了PWA,能快速加载,还能离线使用,还有什么问题呢?想到了么,首屏加载!
的确,首屏加载依然是个问题,看了一下打包出来的文件的大小,是不是吓坏了? 但是不要怕,经过gzip压缩,到前端就不到1.5M了,那么接下来的问题就是第一次浏览比较慢,只能流失一部分用户了。那就开始实践吧。
分析
我们用的式React 和 TypeScript, 而且我们用的是webpack, webpack打包出来的结果因为带了hash,所以每次结果都不一样,再者Service Worker的缓存列表是不支持正则表达式的,只能每个文件都写具体的文件名, 如果我们自己写一个service-worker.js,那我Server Worker的缓存列表是不可控的,所以只能借助第三方工具包,在npmjs.com 上搜了一下service worker,主流有两种,而sw-precache-webpack-plugin是第一个,按照我搜索第一的惯例,我肯定用sw-precache-webpack-plugin, 这个插件用起来还可以,配置也不是很多,在webpack里引入,然后加入plugin中,可以不写配置,最后在template.html中对service worker进行注册就可以。然而看到打包出来的结果我接受了
["/dist/style/4.css", "566bec76673f96860938dc6c4558d01a"],
["/dist/style/app.css", "f82983901e4266328c1af75d12ae1d88"],
["/dist/style/vendors.css", "dc3df81b3bb041761595338061eb28aa"],
["/home/c4/Desktop/Project/xxxxx/build/config.js", "b0ebcf61d580987a8b3b075bd6aeff6a"],
["/home/c4/Desktop/Project/xxxxx/build/index.html", "ffb440e6c16a845e9d017141cfd96970"]
],
cacheName = "sw-precache-v3-sw-precache-webpack-plugin-" + (self.registration ? self.registration.scope : ""),
ignoreUrlParametersMatching = [/^utm_/],
addDirectoryIndex = function (e, a) {
有问题那就解决问题。我想了各种方法,各种谷歌,然而没有一个是成功的,最后无奈只能在这个库中开了一个issuse。最后说明一下, 这个问题是webpack中用了html-webpack-plugin 和 copy-webpack-plugin 造成的,如果你用了这两个库还要用sw-precache-webpack-plugin, 那就要小心了。
接下来就是第二个库workbox-webpack-plugin, 出自谷歌大厂,应该不会有问题,那就动手呗。
动手
官方文档写的太详细了,以至于我真的读不下去了。
- 安装
$ yarn add -D workbox-webpack-plugin
- webpack配置
当然这些配置实在生产环境中了哦。
...
const {GenerateSW} = require('workbox-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
....
module.exports = {
...
plugins: [
...
new CopyWebpackPlugin([{
from: './public/config.js',
to: path.resolve(__dirname, 'build'),
toType: 'dir'
}, {
from: './public/manifest.json',
to: path.resolve(__dirname, 'build'),
toType: 'dir'
}, {
from: './public/icon.png',
to: path.resolve(__dirname, 'build'),
toType: 'dir'
}, {
from: './public/favicon.ico',
to: path.resolve(__dirname, 'build'),
toType: 'dir'
}]),
new GenerateSW({
importWorkboxFrom: 'local', // 一定要用local,除非你的用户可以FQ
skipWaiting: true,
clientsClaim: true
})
]
};
看结果
因为是内部项目就不录视频了,下面这张截图是我在浏览器中offline后,重新打开页面的效果。个人体验到,第二次之后打开,速度确实是不一般的快。
还有一张实在不想拿出来的图, lightHouse评测的结果。
总结
还是那句老话,前端坑太深了。回到正题,性能优化还有其他各种方案,以我现在的经历,只能想到这些解决方案。
路漫漫其修远兮 吾将上下而求索