近期做了一个项目,首页加载比较耗时,所以针对项目环境以及项目中用的第三方依赖进行了一个全面优化,优化后打包体积和首屏加载速度都有了很大的提升。写此文章进行分享,若有错误或不合理之处,欢迎大家指正~
首先介绍一下umi内置插件analyze,包模块结构分析工具,可以看到项目各模块的大小,根据情况进行按需优化。
Umijs官方文档介绍:https://umijs.org/zh-CN/config#analyze
开启analyze,需要在package.json的script中配置: ANALYZE=1 umi build。
配置完成后,运行 npm run analyze 命令后会自动打包并开启一个默认端口号8888的服务,可以查看打包后的产物。如下图所示:
未配置打包优化项时,执行npm run build命令会自动创建dist目录。 目录内容如下:
其中public目录直接拷贝,其他js和css代码都会打包编译到umi.js 和 umi.css文件中。 可以看到打包后的资源文件特别大,网站加载的时候会等待这个文件加载完成再渲染页面,所以就会有可能出现一段时间白屏的情况,很不友好。这也是为什么要做项目包拆分优化的原因。
优化方向:代码格式、代码压缩、第三方js库
具体优化项:
1. dynamicImport 是否启用按需加载,即是否把构建产物进行拆分,在需要的时候下载额外的 JS 再执行。
开启后,打包目录中除了umi.js文件还会默认拆分出来一个vendors.umi.js文件。如下图所示:
这里的 p__brandPositioning_index.js 是路由组件所在路径 src/pages/brandPositioning/index,其中 src 会被忽略,pages 被替换为 p。运行analyze命令查看:如下图所示:
配置按需加载后,umi打包会将第三方组件全都打包到vendors文件中去,页面级别的js也会单独打包到对应的页面级js文件中去,其他js代码仍然打包到umijs文件中。 这样就可以把最初的umijs进行拆分,以减小浏览器一次性加载较大资源文件的压力。
vendors 依赖提取之后,会需要在 umi.js 之前加载 vendors.js,因此需要修改umi配置文件:
2. treeShaking 配置项(umi v2.4.0+):配置是否开启 treeShaking,默认关闭。(也可以通过webpack进行配置)
意思是当引入一个模块的时候,不引入这个模块的所有代码,只引入需要的代码,就需要通过treeShaking进行静态分析。 比如 ant-design-pro 开启 tree-shaking 之后,gzip 后的尺寸能减少 10K。 作用是消除不可能执行的代码,它的工作是使用编辑器判断出某些代码是不可能执行的,然后清除,来减少包的大小,应用于模块之间。
tree-shaking,必须采用es6的模块语法,因为es6的模块采用的是静态分析,也就是从字面量对代码进行 分析。 之前的require是动态分析,必须代码执行到才知道引用的什么模块。(只支持ES Module(import....) 不 支持require....)
treeShaking 开启后,查看analyze,可以看到包体积减少了一部分:
3. uglifyJSOptions:代码压缩。
其中的配置项可以查看UglifyJS文档:https://github.com/mishoo/
项目中常用到的:
配置后查看analyze,可以看到包体积减少了一部分:
4. 结合webpack插件进行文件压缩
文件压缩插件:compression-webpack-plugin,通过设置相关参数,对符合条件的文件进行压缩,生成gzip压缩文件。需服务器端配置开启gzip才能生效。
在umi.js文件中,修改chainWebpack配置项:
再次打包可以看到,对超过10kb的文件都生成了一份gz文件:
基本原理: 1) 浏览器请求资源文件时会自动带一个Accept-Encoding的请求头,告诉服务器支持的压缩编码类型。 2) 服务器配置开启gzip选项: 接收客户端资源文件请求,查看请求头Content-encoding支持的压缩编码格式,如果是包含gzip那么在每次响应资源请求之前进行gzip编码压缩后再响应返回资源文件(在响应头会带上Content-encoding: gzip) 3) 浏览器接收到响应后查看请求头是否带有Content-encoding:gzip,如果有,对返回的资源文件进行解压缩然后再进行解析渲染。
服务器支持gzip的方式可以有两种:
1、浏览器请求xx.js时,服务器对xx.js进行gzip压缩后传输给浏览器,即服务器在线gzip压缩。 服务器端配置项:gzip on;
2、前端打包的时候生成对应的静态的.gz文件,浏览器请求xx.js时,服务器返回对应的xxx.js.gz文件 服务器端配置项:gzip_static on;
如果想要在有静态gz文件的时候直接加载,没有时才执行在线压缩,可修改nginx配置把两个都写上: # 开启gzip gzip on; gzip_static on; 首先,gzip_static的优先级高,会先加载静态gz文件,当同目录下不存在此文件的时候,会执行在线压缩的命令。
如何区分加载的静态文件还是在线压缩的文件呢? 响应头的Content-Edcoding:gzip表示gzip压缩已经生效,而Etag中只有简单字符表示静态资源加载,而前面带 W/ 表示启动了在线压缩。 而且在线压缩的文件加载的时间要比加载静态gz文件消耗的时间长。如下图:
上图为静态gz文件加载:
耗时91ms, 响应H静态gz文件加载:耗时91ms, 响应Header的Etag为简单字符。
服务器在线压缩gz文件加载: 启用在线压缩文件加载:耗时153ms, 响应头中的Etag带W/ 前缀。
5. 第三方库和项目框架优化
代码格式和文件优化完成后,再查看打包分析,看看哪些文件比较大,然后针对性的对其进行优化。第三方库: 比如moment、lodash
优化moment js:umi内置配置项,ignoreMomentLocale 忽略 moment 无用的 locale 文件,用于减少尺寸。
未优化前可以看到moment中有很多国际化语言包:
其中很多我们一般是用不到的,最常用的也就是中英文就够了。
优化配置内容如下:
加上这个配置后查看打包文件:可以看到moment体积减小了特别多,260.74KB -> 57.76KB
lodash优化:3种方式按需加载
1) 单独引入 Lodash 中的每个函数在 NPM 都有一个单独的发布模块,我们可以只引入我们需要的模块。 import isEqual from 'lodash.isequal'; 或 import difference from 'lodash/difference'; 这种方式在模块比较多的时候,这种写法比较麻烦,不推荐。
2) 使用插件优化:lodash-webpack-plugin,在umijs种进行配置:
以上工作完成了,在每个需要使用 lodash 函数的文件中只需要引用一次 lodash,即可调用任意函数而不会造成完全打包。
3) 使用 lodash-es结合tree-shaking 最小化引入
import { isEmpty, isObject, cloneDeep } from 'lodash-es';
antd/icons 优化:
通过analyze可以看到ant-design中的icons占据很大的空间, 如右图所示:
在antd ui框架中, antd V3.x版本中,使用了 import { Icon } from 'antd'; 或者antd组件中的一些组件会默认引进一些icon,比如Select,DatePicker等等组件。只要项目中引入了这些含有内置icon的组件,就会把icons包全部打包进来。
在新版本的antd v4.x版本中引入了按需加载,暂未验证是否会全部打包:
import { HomeOutlined, LoadingOutlined } from '@ant-design/icons';
以下针对针对V3版本进行优化。
1. 通过purched-antd-icons插件优化
目前很多项目中都是使用自定义的iconfont图标库,可以通过purched-antd-icons插件,设置@ant-design/icons的别名以剔除antd中的icon。
修改之后,查看打包分析:已经没有icons/lib 的占比了。 umi.js文件也少了不少,包的整体大小也变成了2.76MB。 查看对比图:
2. 将需要的icon导入进来,在别名中配置@ant-design/icons/lib/dist$指向项目本地自定义的iconfont.js文件,实现按需加载。可通过<Icon type='edit' /> 引用自定义icon图标。
第三方库再次拆分: 例如echatrs、fengmap、antdesign
通过之前的一系列优化,目前来看umijs已经变得相对较小了,但是看vendors.js还是比较大,看看这个文件可以拆分么。 这里面一般都是第三方类库。在示例的项目中,用到了echarts、fengmap、antd、lodash、moment等类库,可以尝试把他们独立拆分出来。
需要利用webpack提供的 splitChunks 方法 来拆分即可。 umi中有个配置是 chainWebpack ,这个是专门用来修改webpack配置的,目的是把符合条件的资源打包到一个group中。
示例代码如图所示:
Js文件提取之后,需要修改umi-plugin-react插件中的chunks配置项,默认只加载[‘umi’];(需要把其他js文件放到umi之前加载) 注意:如果拆分之后没有把拆分的包在chunks中进行配置,会出现白屏的情况,拆分的js没有加载,记得一定要对拆分的包进行上述配置! 其中chunks名称为上图配置的分组名称。
配置修改完成之后,运行analyze命令查看,可以看到上面配置的都已经拆分为独立的js文件。这一步可能不会减少总包的大小,但是浏览器在加载的时候可以同时加载多个较小体积的js文件。
其中cacheGroups的一些参数可以参考webpack的splitChunks配置文档。
相关资料:SplitChunksPlugin配置
https://webpack.docschina.org/plugins/split-chunks-plugin/
https://juejin.cn/post/6844904199394689032