记录一下对webpack打包,工程的优化
最近在整理之前自己做过的一个项目,项目存在几个问题
- 路径问题,没有设置baseName,在Nginx上部署不好根据项目来区分
- 项目体积过大(4M),首屏加载时间过长
- 构建速度慢,接近90s
- 每次构建完之后,都需要手动将内容copy到服务器
- 页面的访问量,访问次数,异常情况
webpack打包体积优化
在做第二次优化之前,已经对项目体积进行过优化,具体可以参考 链接 的后半部分,大体上从这几方面入手。
- 使用 uglifyJs 来压缩代码体积(在webpack4中,只要指定了mode为production,就会自动压缩代码了)。
- 使用 mini-css-extract-plugin 插件将css单独提取出来
- 使用 optimize-css-assets-webpack-plugin 插件来压缩css文件
- 使用 splitChunks 来提取公共代码
- 使用 compression-webpack-plugin 来将文件压缩成gzip格式
引入webpack-bundle-analyzer插件分析体积
引入 webpack-bundle-analyzer 插件
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
...
plugins: [
new BundleAnalyzerPlugin(),
],
运行打包命令后,就会自动打开该页面
这是目前优化后的代码,可以看到压缩后任然有3.2M,直接部署到服务器后,首屏加载时间过长,达到了26s,估计按照这速度,用户早就等的不耐烦了。
优化手段:
将体积较大的第三方文件采用cdn的方式引入
具体解决办法,可以参考webpack提供的 externals 功能
- 在index.html中引入js文件
<script src="https://gw.alipayobjects.com/os/lib/antv/g2/3.5.16/dist/g2.min.js"></script>
<script src="https://unpkg.com/@antv/data-set"></script>
- webpack.config.js
module.exports = {
//...
externals: {
g2: 'G2',
dataSet: 'DataSet',
THREE: 'THREE',
},
};
- 将项目中引用g2、dataSet、THREE的模块替换
- import { Chart } from '@antv/g2';
+ import { Chart } from 'g2';
...
- import { View } from '@antv/data-set';
+ import { View } from 'dataSet';
可以看到,压缩后的体积已经减少到1.3M,页面的请求时间也减少了9s
提取公共模块,分包策略
对于webpack4来说,他有自己的默认的分包策略,也会帮你自动Scope hoisting 和 Tree-shaking。它内置的代码分割策略是这样的:
- 新的 chunk 是否被共享或者是来自 node_modules 的模块
- 新的 chunk 体积在压缩之前是否大于 30kb
- 按需加载 chunk 的并发请求数量小于等于 5 个
- 页面初始加载时的并发请求数量小于等于 3 个
Nginx服务器上开启Gzip
server{
...
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 application/css text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_vary off;
gzip_disable "MSIE [1-6]\.";
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qSvzpzm6-1621488348810)(/Users/wenqiongweng/Library/Application Support/typora-user-images/image-20210519223119306.png)]
开启 gzip 后,我们发现时间缩短到了4s,大大减少了网络请求的时间。同时我们也看到一种 br 的压缩方式,听说比gzip更高效。
小结
经过优化,加载时间从原来的90s减少到了4s,体积从4M减少到了1M,将较大的第三方资源库使用cdn加速(使用第三方的,有失效风险)。后期需要继续探索的点:
- webpack4中不自定义 splitChunks 时,默认的分包策略是怎么样的?会存在什么样的问题?
- Nginx服务器中已存在.gz,开启gzip_on后,是直接使用.gz还是会自动再压缩一遍,是否还能优化?
- cdn加速的原理?了解一下服务商的收费,调高的效率有多少?
- 了解下http2,根据http协议的版本来定分包的策略?
webpack打包时间优化
加入 SpeedMeasurePlugin 插件对各个阶段的时间进行分析
我们看到 eslint-loader 和 uglifyJsPlugin 占用了大部分的时间。eslint 只是为了开发环境中使用,为什么生产环境下还有 eslint 的存在?? 估计是当初配置的时候 忘记区分环境了,干脆在 production 环境下将 eslint-loader 删除。
可以看到,为我们减少了 20s 的构建时间。
使用HappyPack提升构建速度
由于 loader 是针对不同类型文件做处理的,所以这个过程实际上可以同步进行。虽然js是单线程的,但是 HappyPack能够使得Webpack 同一时刻处理多个任务,发挥多核 CPU 电脑的威力,以提升构建速度。(plugin肯定是不行的,因为plugin是本质上是有先后顺序的,本次要处理的内容有可能依赖于上一次的处理结果,当然也有一些异步的插件只需要等某个过程处理完成就可以开始工作)
可以看到,使用 happypack 我们打包的时间又减少了5s
对UglifyJsPlugin进行优化
其实,UglifyJsPlugin 算起来才是最耗时的。UglifyJsPlugin 需要对代码进行压缩,混淆,需要耗费大量时间。
-
首先开启多进程并行来提高构建速度,时间从55.8s降到了 46.7s减少了9s。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gnDsFpEF-1621488348813)(/Users/wenqiongweng/Library/Application Support/typora-user-images/image-20210517230657657.png)]
- 可以指定压缩/不压缩的文件,减少压缩时间
预编译资源模块
在使用webpack进行打包的时候,对于依赖的第三方库,比如vue,vuex等这些不会修改的依赖,我们可以让它和自己项目中的业务代码分开打包,这样一来,我们修改本地文件的时候,webpack就只需要编译我们项目本身的代码,而不需要去编译第三方库。
新建 webpack.dll.js
/* eslint-disable import/no-extraneous-dependencies */
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode: 'production',
entry: {
// TODO
// three: ['src/container/three-school/js/three.module',
// 'src/container/three-school/js/GLTFLoader',
// 'src/container/three-school/js/OrbitControls'],
react: ['react', 'react-dom'],
g2: ['@antv/g2', '@antv/data-set'],
moment: ['moment'],
},
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, './dll'),
library: '[name]',
},
plugins: [
new webpack.DllPlugin({
name: '[name]',
path: path.resolve(__dirname, './dll/[name].manifest.json'),
}),
],
};
在webpack.common.js中加入DllReferencePlugin
const files = fs.readdirSync(path.resolve(__dirname, './dll'));
files.forEach((file) => {
if (/.*\.dll.js/.test(file)) {
baseConfig.plugins.push(new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, './dll', file),
}));
}
if (/.*\.manifest.json/.test(file)) {
baseConfig.plugins.push(new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, './dll', file),
}));
}
});
修改 package.json 中的配置,新增build:dll命令
"build:dll": "webpack --config ./config/webpack.dll.js",
运行命令后,会在config/dll 目录下生成已经编译好的react,g2,moment文件,以及map文件
之后,我们再跑一次 yarn build 发现打包时间大大的减少了
小结
经过一顿分析,将构建时间从90s,优化到了23s。但是也存在一些不足
- dll 是不是可以对node_modules中所有的第三方库都做预编译(yarn link会失效)
- 缓存