性能优化篇—Webpack构建速度优化

45次阅读

共计 5875 个字符,预计需要花费 15 分钟才能阅读完成。

如何输出 Webpack 构建分析
输出 Webpack 构建信息的.json 文件:webpack –profile –json > starts.json

–profile: 记录构建中的耗时信息

–json: 以 json 格式输出构建结果,最后只输出一个 json 文件(包含所有的构建信息)

web 可视化查看构建分析:得到了 webpack 构建信息文件 starts.json,如何进行很好的可视化查看?

方案一:通过可视化分析工具 Webpack Analyse,是个在线 Web 应用,上传 starts.json 文件就可以;不过好像需要翻墙;
方案二:安装 webpack-bundle-analyzer 工具 npm i -g webpack-bundle-analyzer, 生成 starts.json 后直接在其文件夹目录执行 webpack-bundle-analyzer 后,浏览器会打开对应网页并展示构建分析文档地址 webpack-bundle-analyzer

webpack-dashboard 是一款统计和优化 webpack 日志的工具,可以以表格形势展示日志信息。其中包括构建过程和状态、日志以及涉及的模块列表
jarvis 是一款基于 webapck-dashboard 的 webpack 性能分析插件,性能分析的结果在浏览器显示,比 webpack-bundler-anazlyer 更美观清晰 GitHub 文档地址

npm i -D webpack-jarvis

webpack.config.js 配置:

const Jarvis = require(“webpack-jarvis”);

plugins: [
new Jarvis({
watchOnly: false,
port: 3001 // optional: set a port
})
];

port: 监听的端口,默认 1337,监听面板将监听这个端口,通常像 http://localhost:port/
host: 域名,默认 localhost, 不限制域名。
watchOnly: 仅仅监听编译阶段。默认为 true, 如果高为 false,jarvis 不仅仅运行在编译阶段,在编译完成后还保持运行状态。
界面:看到构建时间为:Time: 11593ms(作为优化时间对比)

webpack 配置优化

webpack 在启动时会从配置的 Entry 出发,解析出文件中的导入语句,再递归解析。

对于导入语句 Webpack 会做出以下操作:

根据导入语句寻找对应的要导入的文件;
在根据要导入的文件后缀,使用配置中的 Loader 去处理文件(如使用 ES6 需要使用 babel-loader 处理)

针对这两点可以优化查找途径

优化 Loader 配置
Loader 处理文件的转换操作是很耗时的,所以需要让尽可能少的文件被 Loader 处理
{
test: /\.js$/,
use: [
‘babel-loader?cacheDirectory’,// 开启转换结果缓存
],
include: path.resolve(__dirname, ‘src’),// 只对 src 目录中文件采用 babel-loader
exclude: path.resolve(__dirname,’ ./node_modules’),// 排除 node_modules 目录下的文件
},

优化 resolve.modules 配置

resolve.modules 用于配置 webpack 去哪些目录下寻找第三方模块,默认是 [‘node_modules’],但是,它会先去当前目录的./node_modules 查找,没有的话再去../node_modules 最后到根目录;
所以当安装的第三方模块都放在项目根目录时,就没有必要安默认的一层一层的查找,直接指明存放的绝对位置

resolve: {
modules: [path.resolve(__dirname, ‘node_modules’)],
}

优化 resolve.extensions 配置

在导入没带文件后缀的路径时,webpack 会自动带上后缀去尝试询问文件是否存在,而 resolve.extensions 用于配置尝试后缀列表;默认为 extensions:[‘js’,’json’];
及当遇到 require(‘./data’) 时 webpack 会先尝试寻找 data.js,没有再去找 data.json;如果列表越长,或者正确的后缀越往后,尝试的次数就会越多;

所以在配置时为提升构建优化需遵守:

频率出现高的文件后缀优先放在前面;
列表尽可能的小;
书写导入语句时,尽量写上后缀名

因为项目中用的 jsx 较多,所以配置 extensions: [“.jsx”,”.js”],

基本配置后查看构建速度:Time: 10654ms; 配置前为 Time: 11593ms

使用 DllPlugin 优化

在使用 webpack 进行打包时候,对于依赖的第三方库,如 react,react-dom 等这些不会修改的依赖,可以让它和业务代码分开打包;
只要不升级依赖库版本,之后 webpack 就只需要打包项目业务代码,遇到需要导入的模块在某个动态链接库中时,就直接去其中获取;而不用再去编译第三方库,这样第三方库就只需要打包一次。

接入需要完成的事:

将依赖的第三方模块抽离,打包到一个个单独的动态链接库中
当需要导入的模块存在动态链接库中时,让其直接从链接库中获取
项目依赖的所有动态链接库都需要被加载

接入工具 (webpack 已内置)

DllPlugin 插件:用于打包出一个个单独的动态链接库文件;

DllReferencePlugin: 用于在主要的配置文件中引入 DllPlugin 插件打包好的动态链接库文件

配置 webpack_dll.config.js 构建动态链接库

const path = require(‘path’);
const DllPlugin = require(‘webpack/lib/DllPlugin’);

module.exports = {
mode: ‘production’,
entry: {
// 将 React 相关模块放入一个动态链接库
react: [‘react’,’react-dom’,’react-router-dom’,’react-loadable’],
librarys: [‘wangeditor’],
utils: [‘axios’,’js-cookie’]
},
output: {
filename: ‘[name]-dll.js’,
path: path.resolve(__dirname, ‘dll’),
// 存放动态链接库的全局变量名,加上_dll_防止全局变量冲突
library: ‘_dll_[name]’
},
// 动态链接库的全局变量名称,需要可 output.library 中保持一致,也是输出的 manifest.json 文件中 name 的字段值
// 如 react.manifest.json 字段中存在 ”name”:”_dll_react”
plugins: [
new DllPlugin({
name: ‘_dll_[name]’,
path: path.join(__dirname, ‘dll’, ‘[name].manifest.json’)
})
]
}

webpack.pro.config.js 中使用
const DllReferencePlugin = require(‘webpack/lib/DllReferencePlugin’);

plugins: [
// 告诉 webpack 使用了哪些动态链接库
new DllReferencePlugin({
manifest: require(‘./dll/react.manifest.json’)
}),
new DllReferencePlugin({
manifest: require(‘./dll/librarys.manifest.json’)
}),
new DllReferencePlugin({
manifest: require(‘./dll/utils.manifest.json’)
}),
]

注意:在 webpack_dll.config.js 文件中,DllPlugin 中的 name 参数必须和 output.library 中的一致;因为 DllPlugin 的 name 参数影响输出的 manifest.json 的 name;而 webpack.pro.config.js 中的 DllReferencePlugin 会读取 manifest.json 的 name,将值作为从全局变量中获取动态链接库内容时的全局变量名

执行构建

webpack –progress –colors –config ./webpack.dll.config.js
webpack –progress –colors –config ./webpack.prod.js

html 中引入 dll.js 文件

构建时间对比:[“11593ms”,”10654ms”,”8334ms”]

HappyPack 并行构建优化

核心原理:将 webpack 中最耗时的 loader 文件转换操作任务,分解到多个进程中并行处理,从而减少构建时间。
HappyPack

接入 HappyPack

安装:npm i -D happypack

重新配置 rules 部分, 将 loader 交给 happypack 来分配:

const HappyPack = require(‘happypack’);
const happyThreadPool = HappyPack.ThreadPool({size: 5}); // 构建共享进程池,包含 5 个进程

plugins: [
// happypack 并行处理
new HappyPack({
// 用唯一 ID 来代表当前 HappyPack 是用来处理一类特定文件的,与 rules 中的 use 对应
id: ‘babel’,
loaders: [‘babel-loader?cacheDirectory’],// 默认设置 loader 处理
threadPool: happyThreadPool,// 使用共享池处理
}),
new HappyPack({
// 用唯一 ID 来代表当前 HappyPack 是用来处理一类特定文件的,与 rules 中的 use 对应
id: ‘css’,
loaders: [
‘css-loader’,
‘postcss-loader’,
‘sass-loader’],
threadPool: happyThreadPool
})
],
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: [‘happypack/loader?id=babel’],
exclude: path.resolve(__dirname,’ ./node_modules’),
},
{
test: /\.(scss|css)$/,
// 使用的 mini-css-extract-plugin 提取 css 此处,如果放在上面会出错
use: [MiniCssExtractPlugin.loader,’happypack/loader?id=css’],
include:[
path.resolve(__dirname,’src’),
path.join(__dirname, ‘./node_modules/antd’)
]
},
}

参数:

threads:代表开启几个子进程去处理这一类文件,默认是 3 个;

verbose: 是否运行 HappyPack 输出日志,默认 true;
threadPool:代表共享进程池,即多个 HappyPack 示例使用一个共享进程池中的子进程去处理任务,以防资源占有过多

代码压缩用 ParallelUglifyPlugin 代替自带的 UglifyJsPlugin 插件

自带的 JS 压缩插件是单线程执行的,而 webpack-parallel-uglify-plugin 可以并行的执行

配置参数:

uglifyJS: {}:用于压缩 ES5 代码时的配置,Object 类型

test: /.js$/g: 使用正则去匹配哪些文件需要被 ParallelUglifyPlugin 压缩,默认是 /.js$/

include: []: 使用正则去包含被压缩的文件,默认为 [].

exclude: []: 使用正则去包含不被压缩的文件,默认为 []

cacheDir: ”:缓存压缩后的结果,下次遇到一样的输入时直接从缓存中获取压缩后的结果并返回,默认不会缓存,开启缓存设置一个目录路径

workerCount: ”:开启几个子进程去并发的执行压缩。默认是当前运行电脑的 CPU 核数减去 1

sourceMap: false:是否为压缩后的代码生成对应的 Source Map, 默认不生成


minimizer: [
// webpack:production 模式默认有配有 js 压缩,但是如果这里设置了 css 压缩,js 压缩也要重新设置, 因为使用 minimizer 会自动取消 webpack 的默认配置
new optimizeCssPlugin({
assetNameRegExp: /\.css$/g,
cssProcessor: require(‘cssnano’),
cssProcessorOptions: {discardComments: { removeAll: true} },
canPrint: true
}),
new ParallelUglifyPlugin({
cacheDir: ‘.cache/’,
uglifyJS:{
output: {
// 是否输出可读性较强的代码,即会保留空格和制表符,默认为输出,为了达到更好的压缩效果,可以设置为 false
beautify: false,
// 是否保留代码中的注释,默认为保留,为了达到更好的压缩效果,可以设置为 false
comments: false
},
compress: {
// 是否在 UglifyJS 删除没有用到的代码时输出警告信息,默认为输出
warnings: false,
// 是否删除代码中所有的 console 语句,默认为不删除,开启后,会删除所有的 console 语句
drop_console: true,
// 是否内嵌虽然已经定义了,但是只用到一次的变量,比如将 var x = 1; y = x, 转换成 y = 1, 默认为否
collapse_vars: true,
}
}
}),
]

构建结果对比:[“11593ms”,”10654ms”,”8334ms”,”7734ms”]

整体构建速度从 12000ms 降到现在的 8000ms

“积跬步、行千里”—— 持续更新中~,喜欢的话留下个赞和关注哦!

往期经典好文:

你不知道的 CORS 跨域资源共享
Koa 日志中间件封装开发(log4js)
团队合作必备的 Git 操作
使用 pm2 部署 node 生产环境

正文完
 0