在《使用Webpack等搭建一个适用于React项目的脚手架(3 - Eslint、Jest)》中记录了在项目中使用Eslint检查代码风格以及使用Jest等进行单元测试。这篇文章主要记录了一点简单的优化。
打包结果优化
在生产环境,需要代码体积尽量小,这样能减少请求资源的时间。其实配置Webpack的mode为production,Webpack会自行针对生产环境做一些优化,比如代码最小化(minimize)。在《使用Webpack等搭建一个适用于React项目的脚手架(2 - React Router、Redux、Sass)》中也使用了React的代码分割,实现在需要的时候再请求相关的文件。
分析
Webpack官网--打包分析中提供了一些打包分析工具地址,本文使用webpack-bundle-analyzer。
安装依赖:
npm i --save-dev webpack-bundle-analyzer
webpack-bundle-analyzer可以在Webpack配置中以插件的形式使用,也能以命令行工具的方式使用。本文选择在命令行中使用,在package.json中添加:
"scripts": {
...
"analysis": "cross-env NODE_ENV=production webpack --config ./config/webpack.prod.js --profile --json > stats.json && webpack-bundle-analyzer stats.json dist/",
...
},
analysis代表的执行指令的意思:
cross-env NODE_ENV=production
:设置环境变量NODE_ENV为production。
webpack --config ./config/webpack.prod.js
:执行webpack命令,并指定Webpack配置文件。
webpack --profile --json > stats.json
:生成一个包含模块统计信息的JSON文件,stats.json中包含依赖关系图以及其他的构建信息。更多内容查看Stats Data。
webpack-bundle-analyzer stats.json dist/
:使用webpack-bundle-analyzer,dist/是设置的bundleDir,即放置打包后文件的文件夹。
在命令行执行npm run analysis
,会分析数据后在浏览器中打开http://127.0.0.1:8888/
,在浏览器中能看到以下这种分析界面:

目前的项目代码还很少,所以分析结果是:vendors.5369f8222d4e012157e9.js的体积最大,vendors.5369f8222d4e012157e9.js是安装的依赖(node_modules中的模块)打包出的文件,包括react-dom、redux等。
优化
(1)分离依赖文件
参考split your chunks with webpack将从主包中分离出来的vendors.hash.js文件进一步拆分(这里的hash指代生成的hash字符串)。这避免了任何一个模块更新,整个vendors.hash.js文件就需要重新请求的情况。例如,客户端中已有缓存,React被打包到了A块中,Axios被打包到了B块中,那么React更新时,只需要重新请求A文件就可以了,B文件可以直接使用缓存,不用重新请求。
修改webpack.common.js中内容:
optimization: {
runtimeChunk: 'single',
splitChunks: {
maxInitialRequests: 6,
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name(module) {
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
return `npm.${packageName.replace('@', '')}`;
},
chunks: 'all'
},
},
},
},
runtimeChunk: 'single',
表示生成一个单独的运行时文件,这个文件会被所有的块(chunks)共享。运行时文件包含在交互时连接模块所需的加载和解析逻辑,简言之是用来在浏览器中处理模块交互的。更多信息查看Webpack官网--表现。
maxInitialRequests: 6,
设置入口文件的最大并行请求数量为6。
name方法设置模块的名字。
(2)gzip压缩
使用CompressionWebpackPlugin压缩代码,如分析图中所示,压缩后代码体积会减小很多。虽然代码体积减少了,但是解压缩也需要时间,应该根据具体情况选择是否使用gzip。
安装依赖:
npm i --save-dev compression-webpack-plugin
修改webpack.prod.js代码:
...
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = merge(common, {
...
plugins: [
new CompressionPlugin({
cache: true,
algorithm: 'gzip',
test: /\.(js|css)$/,
}),
],
});
使用gzip压缩js和css文件,允许缓存文件,压缩后的文件名在原本文件名后面加上了.gz后缀。需要在服务端配置优先使用gz文件,这样当服务器收到一个请求比如a.js的时候,会先去找a.js.gz文件,如果找不到a.js.gz再去找未压缩的文件a.js。
(3)隐藏不需要的打包信息
在执行npm start
和npm run build
的时候,在命令行界面会打印出一长串这种信息:
./src/redux/actionTypes/novel.ts 105 bytes {0} [built]
可以配置Webpack的stats隐藏这些打印的信息。
webpack.prod.js:
...
module.exports = smp.wrap(merge(common, {
mode: 'production',
...
stats: 'errors-warnings',
}));
webpack.dev.js
...
module.exports = merge(common, {
devServer: {
...
stats: 'errors-warnings',
},
});
stats: 'errors-warnings'
设置只打印错误信息和警告信息。
打包速度优化
参考玩转 webpack,使你的打包速度提升 90%,使用speed-measure-webpack-plugin分析打包速度,使用thread-loader提高打包速度。
分析
安装依赖:
npm i --save-dev speed-measure-webpack-plugin
在webpack.prod.js文件中添加内容:
...
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
module.exports = smp.wrap(merge(common, {
...
}));
执行npm run build
,打印出以下内容:
SMP ⏱
General output time took 7.12 secs
...
SMP ⏱ Loaders
babel-loader, and
eslint-loader took 3.89 secs
...
为了比较优化前后的打包时间,在执行npm run build
前,先使用rm -rf node_modules/.cache
删掉缓存内容,因为之前配置了loader: 'babel-loader?cacheDirectory=true',
,这会缓存babel-loader打包结果,构建过一次之后,后续的Webpack构建会尝试从缓存中拿数据,打包速度会比第一次构建快。
优化
可以使用thread-loader将thread-loader之后的loader放在worker池里面执行,减少打包时间。每个worker是一个单独的node.js进程。注意每个进程会有大概600ms的消耗,进程之间的通信也会有消耗,所以只在高消耗的操作中使用这个loader。
npm i --save-dev thread-loader
将thread-loader放到babel-loader前面,使babel-loader在一个单独的进程中执行:
{
test: /(\.js(x?))|(\.ts(x?))$/,
exclude: /[\\/]node_modules[\\/]/,
use: [
'thread-loader',
'babel-loader?cacheDirectory=true',
],
},
执行npm run build
之后得到的分析结果:
SMP ⏱
General output time took 8.004 secs
...
SMP ⏱ Loaders
thread-loader, and
babel-loader, and
eslint-loader took 4.72 secs
...
使用thread-loader反而比不使用thread-loader的打包时间多了0.884秒,猜想是项目太小了,worker消耗的时间大于它节省的时间,所以对于小项目还是不要使用thread-loader了。先把thread-loader注释:
use: [
// 'thread-loader',
'babel-loader?cacheDirectory=true',
],
最后稍微修改下.gitignore文件和README.md文件的内容:
.gitignore新增stats.json忽略文件。
README.md中新增:
# npm run analysis
bundle analysis.