前端性能优化与上线
4000字长文,多图预警!!!流量慎入!!
性能优化 - 屌丝前端性能优化、上线一条龙
大家好我又来了,本章给大家带来的内容是:上线和上线后的性能优化
项目地址
本章你会了解
- 前端需要了解的 docker 基础知识
- 部署前端项目到本地/外网服务
- 前端项目的 gZip 优化
- 了解 CDN 的重要性
- webpack 按需加载
- 图片的相关优化
- 如何分析项目依赖,方便针对性处理
- 如何减小 webpack 打包大小/速度
上线
我们通常在本地开发,本地环境和线上也并非完全一样,很多项目第一次上线几乎都会遇到本地开发无法复现的问题,可能是字体、样式的问题,也可能是webpack 编译的问题、甚至可能是本地的奇葩环境。所以 本地完美运行 ≠ 线上完美运行,我们需要 build 项目,模拟线上测试一下,看看是否可以完美运行,有问题可以方便及时作出调整。
准备
为了避免本教程污染大家本地环境,推荐大家安装一个docker,后期运维也会根据 docker 展开。
看到这个 Title :《准备docker》,没接触过的前端不要怂,装一个,勇于跨出第一步,不学习就是等死「点击这里了解 docker」
虽然 tomcat nginx apache jboss jetty
等等等等都可以作为 http 服务,本章以最常见的 nginx 展开讲述:
大白话介绍下 docker
docker 就是用更优雅的方法,做到了虚拟机的事情,并且做的更好,可编程管理集群。docker 启动容器,在容器内部运行你的环境,默认各个容器是互相隔离的,当然你可以通过 link network 关联容器,或者直接使用 docker-compose 编排,启动容器的前提是镜像,也类似与虚拟机的镜像,想跑容器,先得下载「pull」镜像。
使用 docker
也许很多人没用过,没用过也不讲怎么安装了,自己去看官网吧中文官网、社区版下载、中国镜像加速,windows 的话可能要开启虚拟化,linux 推荐 ubuntu, 为了性能请不要在ventos中运行 Docker ,证据在这里 - 查看翻译点这里),几年前的文章了,现在怎么样有待考究。
看下 images 状态
docker images
可以看到我已经有一些镜像了「我已经删除了nginx」
dockerHub 拉 Nginx 镜像
docker pull registry.docker-cn.com/library/nginx:latest
正常 docker pull nginx 即可,中间那段是中国镜像源
ok,我们成功 pull 下来了 Nginx 的镜像。默认存储的镜像名为: registry.docker-cn.com/library/nginx
打包
进入我们上一章源码的目录,build 一下进行发布。
上一章源码在这里
npm run build
启动 docker 容器
docker run --name nginx -d -p 8888:80 -v /new-bee/dist:/usr/share/nginx/html registry.docker-cn.com/library/nginx
- 上调命令一些解释「不多讲,避免消化不良,自己探究」
CMD | 解释 |
---|---|
-d | 守护进程运行 |
-p | 端口映射 8888 :80 docker80端口映射到本机「宿主机」 |
-v | 挂载宿主机的一个目录 本机「宿主机」: docker容器 |
—name | 为容器命名 |
测试一下
http://localhost:8888/#/
当然初次尝试 docker 你可能会有更多的疑问:
- 你怎么知道需要将主目录挂载到: /usr/share/nginx/html ?
- 能否/怎样 查看 Nginx 日志 ?
- 容器内的 nginx 能否自定义配置 ?
- ......
这些小白问题本章简单讲讲,后面做自动运维的时候单独展开讲,可以关注我的博客
gZip
我们可以通过 webpack 压缩脚本文件,上传到 http 服务器,浏览器浏览的时候,经过压缩的HTTP应答报文是由浏览器解压的,比起压缩,解压的速度是非常快的(只要数据正常,可以解压的话),所以不用担心浏览器用于解压的时间会降低用户体验。事实上,浏览器解压消耗的这点时间比起数据包因为网络拥堵而耽误的时间要少的多也可控的多。
在浏览器发给服务器的HTTP请求报文中,使用Accept-Encoding字段标明自己支持的压缩格式,即自己可以解压哪几种压缩报文(gzip、zlib库提供的deflate)。服务器回复客户端的HTTP应答报文中,使用Content-Encoding字段标明该应答报文使用哪种压缩方式。
gZip 攻破 webpack、nginx
像我这样屌丝的服务器一般都买 1M 的,大的资源文件 hold 不住,一个动辄 400K 的 vendar 文件这很蛋疼,不上 gZIp 很难受。
打开 network 观察一下:
它有 144K 这么大
我们就以 webpack 打包的核心 vendor 为例,我们发现,客户端向服务端请求了 gZIp 资源 Accept-Encoding: gzip, deflate
,但可惜服务端并没有给我们理想中的 response - Content-Encoding: gzip
的响应, 我们需要排查一下原因。
- 首先看看 webpack 到底打没打出来打出来 gZip 呢?看看他的目录有没有 js 的 .gz 文件。
很遗憾没有,只有一些压缩文件和用于定位的 map 文件,看来首先我们的打包就出现了问题。
大家还记得当初构建项目我发的这张图吗?
- package.json 项目描述文件
打开看看 build 命令执行了哪个脚本?
打开 build.js 看看执行了哪些内容,难道是 vue-cli 没有为我们配置好webpack gZip 相关的配置吗?
我们发现没什么特别的,发现一个const webpackConfig = require('./webpack.prod.conf')
的依赖,大概就是字面意思(webpack生产配置)进去看看。
哦,我们看到了,webpack 确实为我们配置了 gZip 相关配置。
可是发现这个配置被这个判断包裹住了:
if (config.build.productionGzip) { }
追踪下去
// Gzip off by default as many popular static hosts such as // Surge or Netlify already gzip all static assets for you. // Before setting to `true`, make sure to: // npm install --save-dev compression-webpack-plugin productionGzip: false,
我们的全部疑惑都被揭开了,开发者通过注释这样告诉我们他的理由,我简单翻译一下:
首先下载一下依赖:
vim package.json "devDependencies": { "compression-webpack-plugin": "^1.1.12", }
然后 productionGzip 改成 true
- 废话不多说打个包试试:
npm run build
成功了,出现了 .zg 文件压缩包,但是 gZip 是需要服务端的支持的,服务器通过客户端请求的 Accept-Encoding
首部开判断返回哪种格式的脚本文件,然后由浏览器解压,我们拉下来的 nginx 镜像,nginx 是不会为我们默认配置 gZIp 服务端压缩的,我们去查看一下吧。
进入 docker 主机
docker exec -it nginx /bin/bash
或者
docker exec -it nginx "bash"
CMD | 解释 |
---|---|
exec | 进入docker容器 |
-i | -i: 以交互模式运行容器,通常与 -t 同时使用; |
-t | -t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用; |
-it | -it = -i -t |
“bash” 或 /bin/bash | /bin/bash的作用是因为docker后台必须运行一个进程,否则容器就会退出 |
进入 nginx 主机的第一件事
nginx 在哪???
Linux whereis 命令用于查找文件。
该指令会在特定目录中查找符合条件的文件。这些文件应属于原始代码、二进制文件,或是帮助文件。
该指令只能用于查找二进制文件、源代码文件和man手册页,一般文件的定位需使用locate命令。
语法
whereis [-bfmsu][-B <目录>...][-M <目录>...][-S <目录>...][文件...]
- 查看 nginx 位置
root@e0017cab245f:/# whereis nginx nginx: /usr/sbin/nginx /usr/lib/nginx /etc/nginx /usr/share/nginx
- 我们在 /usr/share/nginx 找到了根目录 html/
- 我们在 /etc/nginx/ 找到了 nginx 配置文件
ps 中间件这么多,谁记得住呢,记不住自己看看就行了不是吗?
- 查看一下 nginx 的配置
确实 gZip 真的没开启,被注掉了。
我们打开 gZip 的注释,并且防止服务端对 css 偷懒,我们一步到位加上几行经典配置。
gzip on; gzip_min_length 1k; gzip_buffers 4 16k; gzip_comp_level 6; gzip_types application/javascript text/plain application/x-javascript text/css application/xml text/javascript application/json; gzip_vary on;
nginx配置 代码在这里
如何修改 docker 内部的配置
就目前来看,你有两种方法可以选择:
- 直接 exec 容器进行修改,增加上述 gZip 代码段。缺点:若想重新基于镜像构建容器容器内的配置会丢失。(除非你 commit 镜像)
- 独立出配置文件,一劳永逸。
我们基于第二点在 new-bee/ 目录 同级 创建了一个目录 nginx/ 创建一个同名的 nginx.conf 文件。
nginx配置 代码在这里
- 先停止 nginx 容器
docker stop nginx
- 删除 nginx 容器
docker rm nginx
- 重新构建 nginx 容器
docker run --name nginx -d -p 8888:80 -v /new-bee/dist:/usr/share/nginx/html -v /nginx/nginx.conf:/etc/nginx/nginx.conf:ro registry.docker-cn.com/library/nginx
- 看看效果
http://localhost:8888/#/
为了避免浏览器加载刚才的 304 缓存,清除下浏览器缓存或进行隐身模式
已经奏效了。
- 看看大小压缩到多少
只有 50K 左右,压缩了 2/3 的大小,这对于大型项目来说,节省的不只是 100K ,甚至是更多,webpack 或者说 gz 等压缩算法,会将所有的大量重复的片段单独标记,所以重复的越多,压缩的越多,这对于现在带宽比金子贵的云服务来说是十分重要的。
CDN
大家注意到,有些能用 CDN 的我选择使用了 CDN,那么 CDN 对于线上服务来说到底有多重要呢?
原理
请求速度
废话先不说给大家上个对比图 测试地址
- 这是我的 论坛
可以看到仅有几个地方还算不错,其余地方都是一塌糊涂
- 这是淘宝
不用说了吧?不过还好,这部分我们资金不足败了也很正常,但大家可能也大概知道 CDN 的意义了,主要意义不是节省开源项目服务器带宽,而是全国各个节点的访问速度问题,也就解释了:我部署的项目访问速度还不错,你这里怎么这么慢,你网不好吧?CDN 来告诉你答案。
cookie
我们还是拿实战的 bbs论坛 举例子吧,查看网络状态:
使用 CDN 的几点优势
- 访问快
- 服务端压力小
- webpack打包小且快(下面讲)
客户端的 cookie 是绑定服务端 域名 的, 看上图,我们需要 XHR 请求携带 cookie 访问服务端获取对应权限,但试想一下:每一个 js、img、甚至是css 都携带垃圾的 cookie ,在大用户量下,服务端承受着不应该属于他的痛苦,这样的消耗是特别应该避免的,我们可以随便翻一翻任何一个成熟的网站,都会发现存在自己的 CDN 服务,这样既优化了中国不同地区的访问速度,同时也大大减小了服务端的开销。
节省 webpack 打包大小/速度
很长时间前经历过公司前端 webpack 编译特别慢的问题,dev 模式下我们可以注掉开发范围外的 路由,但是 build 发布的时候似乎没法解决,使用了 Happypack 多线程打包还是不如人意,查阅资料读到了 这篇文章
我们可以把能够 externals 调的排除掉,然后使用 webpack 的 webpack.DllPlugin 生成依赖库(这点很重要),大大减少便以速度,DllPlugin 本质上的做法和我们手动分离这些第三方库是一样的,但是对于包极多的应用来说,自动化明显加快了生产效率。
如何分析项目依赖
webpack-bundle-analyzer
其实很多人都知道,可能刚入坑的同学不太了解,不管是 npm maven 都有自己一套以来分析工具,当然也都来源于第三方,这里为大家介绍 npm 的以来分析工具: webpack-bundle-analyzer ,他会在浏览器生成一个报表,直观的展示哪里大,哪里需要优化,以及预测 gZip 的大小,还是以 实战项目为例:
按照官方指引的配置,下载依赖,package.json 文件指定下 build 的脚本:
"analyz": "NODE_ENV=production npm_config_report=true npm run buildProd",
运行一下:
npm run analyz
效果:
分析:
发现了问题,static/
静态文件下 hightlight 文件比较大,有钱可以考虑下 CDN,node_modules/
下 element-ui 饿了么组件比较大,(我比较懒,全局导入的,可以用哪个引入哪个避免全局打包问题)可以优化,然后无聊的同学没事儿点点玩玩吧。
webpack 按需加载 : 一切皆模块
当打包构建应用时,Javascript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。 Webpack 的代码分割功能, 实现路由组件的懒加载.
官方说的挺详细了,这里就偷个懒不上代码了,给大家提供一种经典处理方式,我们不放在组件上,直接对路由进行拆分,具体可以看 实战项目路由 的路由拆分
会发现很多这种注释:
const Blog = () => import(/* webpackChunkName: "blog" */ '@/container/blog/Blog')
那么类似:
/* webpackChunkName: "blog" */
不是白写的,他是配合 webpack 对项目各路由拆分的,我们可以看看 实际项目加载情况 :
这个 blog.hash.js
不是我们写的,是 webpack 进行分割的,这样类似 vue 这样的单页面架构,不会加载某模块总是加载全部脚本,大大提升加载速度。
图片处理
本来不想讲的,简单说说吧,常用的也就那几种 svg 、base64、 或使用fastdfs组件类似 CDN 的服务。
base64
简单来讲 base64 会减少你的 http 请求数量,要知道 XHR 可不是省油的灯,他会带来额外的处理请求和处理响应损耗,以表情为例,动辄几十个表情 http 请求似乎太智障了一些,通常采用 base64 处理,减少了 http 请求数量,但是增大了图片本身的体积,如果你用了webpack 且你的表情在本地,那么 webpack 可以帮你自动进行 base64 编码哦。
压缩图片
用户上传的图片可以通过压缩图片大小或质量减少带宽哦,通常使用 GM 对用户上传的有必要大锁的图片 压缩成不同大小的,根据业务加载,比如头像,默认肯定不会请求原始图片,今日头条的正文,使用流量的情况下也会默认加载小图,这些都不是客户端能做到的,需要服务端压缩。
结语
当然这些知识万里长征的第一步,以后的优化之路茫茫多,能大概想起来的比如 :Lazy-Load(优化首屏体验)、PWA(构建web APP)、服务端渲染(为了SEO)、骨架屏(提升用户体验),后端和服务端文章还没写,没时间了 1.0 版本就放这些吧,回头可以补个第二版。
SO - 努力吧!
ps:mac下求推荐个懒人图床,七牛开始收费了,mweb 不能直接发布到七牛了,一张一张上传,我也很无奈啊。