一次vue2项目打包构建优化过程
记一次vue2项目构建优化过程。
项目基本情况
项目是一个vue2+webpack4的管理平台,
接下来看一下项目的打包耗时情况和产物依赖情况
耗时情况
首先使用SpeedMeasurePlugin
来看一下项目的构建过程中各个阶段的耗时情况。
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
module.exports = {
configureWebpack: smp.wrap({
// 在这里配置原本的Webpack配置
// 可以定义entry、output、module等配置
})
};
使用SpeedMeasurePlugin分析打包情况,项目总体耗时43s,其中有一些loader耗时过长,会帮我们标红,尤其是vue-loader
和sass-loader
和postcss-loader
耗时过长,后续可以考虑使用缓存,并行处理使用thread-loader
来优化。
产出依赖
webpack-bundle-analyzer
使用这个插件,可以帮助分析项目的构建结果,以识别过大的模块、重复的依赖和不必要的代码。一旦你生成了可视化分析界面,可以按照以下方式来进行分析:
-
查看模块大小:
- 通过可视化界面,可以看到每个模块的大小。这可以帮助识别哪些模块尺寸过大,从而考虑是否需要对其进行优化,例如拆分代码、按需加载等。
-
检查重复的依赖:
- 可以查看依赖关系图,识别是否有重复的依赖被打包进了多个bundle中。这可能意味着某些依赖被重复引入,可以考虑通过Webpack的代码拆分功能或其他优化策略来避免重复打包。
-
识别不必要的代码:
- 通过分析模块之间的依赖关系,可以发现是否有一些不必要的代码被打包进了bundle中。这可能是因为某些模块被错误引入,或者存在无用的代码。通过分析依赖关系,可以识别并清理这些不必要的代码。
-
查看模块间的依赖关系:
- 了解模块之间的依赖关系可以帮助你优化代码拆分策略,以减少不必要的依赖关系,提高构建性能。
首先安装插件npm install --save-dev webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
configureWebpack: {
plugins: [
new BundleAnalyzerPlugin()
]
}
}
浏览器打开了一个http://127.0.0.1:8888/
的页面,页面上就是我们项目的产物依赖情况,
发现比较大的产物就是echarts、element-ui.common,js和wangeditor,这个后续可以考虑按需加载、使用CDN引入和压缩。
开始优化
升级webpack
为啥把webpack升级放到第一个,个人喜好吧,觉得这个升级可能是性能提升最大的一个。
首先下载一个npm-check,查看当前npm依赖包的情况,有些可能会出现npm证书的情况,比如我的旧项目,做法也比较简单,切换到最新的淘宝镜像源,然后删掉node_modules,重新安装即可。
运行npx npm-check就可以了,或者也可以全局安装。
npm-check是一个用于检查项目中npm依赖包是否有更新版本的工具。通过运行npm-check命令,可以列出当前项目中已安装的npm包,并显示它们是否有可用的更新版本。这样可以帮助开发者及时了解项目依赖包的更新情况,及时更新以确保项目的安全性和稳定性。
会出现这么几种情况,根据具体的保存信息自行调整。 😎 MAJOR UP 有一个或多个主要版本更新可用,建议及时更新以获取最新功能和修复bug。 😕 NOTUSED 表示该依赖包未被项目使用,可以考虑移除以减少项目的依赖项数量。
😟 PKG ERR!表示在检查依赖包时出现错误,可能是由于网络问题或依赖包本身存在问题导致的。 😟 MISSING!表示在项目中缺少某个依赖包,可能会影响项目的正常运行,需要及时安装该依赖包。
接下来使用npm-check-updates 来检查依赖库可更新的版本
运行npx npm-check-updates
即可
npm-check-updates是一个npm包,它可以帮助你检查当前项目中的npm依赖包是否有可用的更新版本。通过运行npm-check-updates命令,你可以快速了解哪些依赖包可以更新到最新版本,以便及时更新你的项目依赖,保持项目的安全性和稳定性。这个工具可以帮助你轻松地管理npm依赖版本,提高项目的维护效率。npm check-updates包含以下常用命令:
npm-check-updates
:检查当前项目中哪些npm包可以更新到最新版本。npm-check-updates -u
:将package.json文件中的依赖版本号更新为最新版本。npm-check-updates -a
:显示所有可用的更新,包括主要版本更新。npm-check-updates -g
:检查全局安装的npm包是否有可用更新。npm-check-updates -f <filter>
:根据提供的过滤器筛选要检查的依赖包。npm-check-updates -x <exclude>
:排除特定的依赖包不进行检查。
npm-check-updates -p <packageManager>
:指定要使用的包管理器,如npm或yarn。
运行npx npm-check-updates -u
在package.json中的依赖便被更新了
因为这里只关注webpack及相关依赖,不涉及vue,于是将vue-router
,vuex
,vue-template-compiler
还原。
运行npm i
再重新安装这些依赖即可。
安装完毕后,运行vue inspect > webpack-config.js
重新查看配置情况
报错
Error: Cannot find module 'webpack/lib/RequestShortener'
于是手动更新webpack,npm install webpack@latest --save-dev
再运行vue inspect > webpack-config.js
ERROR Error: Cannot call .tap() on a plugin that has not yet been defined. Call plugin('preload').use() first. Error: Cannot call .tap() on a plugin that has not yet been defined. Call plugin('preload').use() first.
这个错误通常是由于Webpack插件的使用顺序问题导致的。具体来说,Webpack要求在调用.tap()
方法之前,必须先调用.use()
方法来定义插件。
需要更改下插件的调用方式
比如原先的:
config.plugin("preload").tap(() => [
{
rel: "preload",
// to ignore runtime.js
// https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
fileBlacklist: [/.map$/, /hot-update.js$/, /runtime..*.js$/],
include: "initial",
},
]);
改成:
const PreloadWebpackPlugin = require('preload-webpack-plugin');
config.plugin('preload').use(PreloadWebpackPlugin, [
{
rel: 'preload',
fileBlacklist: [/.map$/, /hot-update.js$/, /runtime..*.js$/],
include: 'initial',
},
]);
接着依次更改下所使用的插件
随后运行vue inspect > webpack-config.js
报错:
Error: Cannot find module 'preload-webpack-plugin'
查找了些资料发现:
在Webpack 5中,一些插件或工具可能需要额外安装,这可能是因为Webpack 5对插件系统或依赖项有所改变,导致某些插件不再默认包含在Webpack中。这可能是为了减少Webpack的体积,提高灵活性,或者是对插件生态系统的调整。
于是安装npm install preload-webpack-plugin
再次运行vue inspect > webpack-config.js
可以看到根目录下出现了一个webpack.config.js配置文件。
开始运行项目npm run serve
问题1:
发现报错 ERROR TypeError: compiler.plugin is not a function TypeError: compiler.plugin is not a function 发现 preload-webpack-plugin和webpack5不兼容,
暂时先干掉
问题2
发现报错 options has an unknown property 'overlay'. These properties are valid:webpack
在将webpack4升级到webpack5时,webpack-dev-server的配置项发生了一些变化。对于overlay属性,webpack5中已经不再支持这个属性,而是使用了新的方式来处理警告和错误。可以将overlay属性替换为client属性,并设置overlay为true来实现类似的功能。
原来的
devServer: {
port: port,
open: true,
overlay: {
warnings: false,
errors: true,
},
...
现在的
devServer: {
port: port,
open: true,
client: {
overlay: {
warnings: false,
errors: true
}
},
问题3:
继续报错
[@vue/compiler-sfc] the >>> and /deep/ combinators have been deprecated. Use :deep() instead. >>>和/deep/这两个组合选择器已经被弃用,取而代之的是使用:deep()伪类选择器来实现相同的功能。 这个需要更改原先的业务代码,而且改动较大,暂时搁置
问题4:
代码有很多不规范的写法导致编译报错
VueCompilerError: <template v-for> key should be placed on the <template> tag
Vue编译器错误,提示你在使用<template v-for>
时应该将key
属性放在<template>
标签上而不是放在内部元素上。
将不规范的地方改过来即可。
问题4:
项目启动之后,获取不到process.env.VUE_APP_BASE_API
,经过各种搜索发现
因为Webpack在构建过程中会将.env
文件中的变量注入到process.env
中,但这些变量在Vue组件中不会直接可见。
为了在Vue组件中访问.env
文件中的变量,可以使用webpack.DefinePlugin
插件将这些变量注入到Vue应用的全局变量中。你可以在vue.config.js
中进行如下配置
chainWebpack: config => {
config.plugin('define').use(require('webpack').DefinePlugin, [
{
'process.env': {
VUE_APP_BASE_API: JSON.stringify(process.env.VUE_APP_BASE_API)
}
}
]);
}
问题5:
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "path": require.resolve("path-browserify") }'
- install 'path-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "path": false }
Webpack 5不再默认包含Node.js核心模块的polyfill,需要手动配置polyfill来解决这个问题。
手动安装npm install path-browserify --save
添加配置
resolve: {
alias: {
...
},
fallback: {
"path": require.resolve("path-browserify")
}
},
问题6:
ERROR in ./node_modules/vue-i18n/dist/vue-i18n.mjs 452:27-35
export 'computed' (imported as 'computed') was not found in 'vue' (possible exports: default)
@ ./src/main.js 36:0-31
这个是版本不兼容导致的,在package.json中对vue-i18n进行还原,然后重新安装
至此我们的项目已经可以跑起来了。
运行打包命令,发现时间比我们没升级前要长,可能是
- 缺少缓存:Webpack 5 默认启用了持久化缓存(persistent caching),但是在升级过程中可能没有正确配置缓存导致重新构建所有模块,从而增加了打包时间。
- 模块解析速度变慢:Webpack 5 在模块解析方面进行了一些改进,但这也可能导致解析速度变慢。
当我们设置缓存和打包环境:
configureWebpack: {
cache: {
type: 'filesystem',
},
mode: 'production',
},
打包时间一下子缩减到20s,优化幅度很大了。后续再从其他方面进一步优化。
缓存
这里主要给babel添加loader缓存
{
loader: 'cache-loader', // 使用缓存
options: {
cacheDirectory: path.resolve(__dirname, '.cache/babel-loader'), // 缓存目录
},
},
{
loader: 'babel-loader',
options: {
cacheDirectory: true, // 同样启用Babel自身的缓存
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-proposal-class-properties'],
},
},
再添加个vue-loader缓存
configureWebpack: config => {
config.module
.rule('vue')
.test(/.vue$/)
.use('cache-loader')
.before('vue-loader')
.loader('cache-loader')
.options({
cacheDirectory: path.resolve(__dirname, '.cache/vue-loader'), // 设置缓存目录
});
},
General output time took 14.6 secs,时间降低到14.6,很快了。
压缩
在webpack5中,当mode设置为production时,默认会压缩代码的,这里实际上不需要额外的设置。然而涉及到图片,还是需要处理下的
{
test: /.(png|jpe?g|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 8192, // 小于8KB的图片将被转换为base64编码
},
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65,
},
optipng: {
enabled: false,
},
pngquant: {
quality: [0.65, 0.9],
speed: 4,
},
gifsicle: {
interlaced: false,
},
},
},
],
},
{
test: /.svg$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192, // 小于8KB的SVG将被转换为base64编码
},
},
{
loader: 'svgo-loader',
options: {
plugins: [
{ removeViewBox: false },
{ removeDimensions: true },
],
},
},
],
},
General output time took 13.47 secs
并行构建
config.module
.rule('js')
.use('thread-loader')
.loader('thread-loader')
.options({
workers: 4 // 指定worker
})
.end();
理想的worker数量通常不应超过你的CPU核心数。如果你有一个4核CPU,通常设置2到4个worker是合理的。设置过多的worker可能会导致上下文切换开销,反而降低效率。最好的办法就是多尝试:比如我的电脑就是8核的,不定的设置workers数,8、6、4、2,发现4效果最好,那就是他了。
General output time took 13.15 secs
最后打包时间为13s左右,发现其实效果没有之前那么显著了,
代码分隔
config.optimization.splitChunks({
chunks: 'all',
minSize: 30000,
minChunks: 1,
cacheGroups: {
vendor: {
test: /[\/]node_modules[\/]/,
name: 'vendors',
priority: -10,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
});
webpack会根据这些规则将公共的依赖模块提取到单独的文件中,避免重复加载。
General output time took 13.57 secs
打包优化时间也是没什么变化。
至于cdn引入,按需加载第三方那个库,这就涉及到业务代码的改动,投入产出不成正比,就不动它了。还有在.gitignore文件中添加*.cache,因为新建了缓存目录,导致会多出很多文件,这个没必要添加到git上去的。
至此构建优化就到此为止了,打包时间从43s优化至13.57s,算是很大的优化了,热更新的时间也是1s左右的。
也想过换成vite试试,查过资料后换vite更是麻烦,一般的流程就是新建一个vite项目,然后将业务代码迁移过去,怕出错,就不试了。
转载自:https://juejin.cn/post/7373659736300584987