通用前置操作
vue-cli5 和 vue-cli4 区别:
- 都是使用的 vue2.6+
- vue-cli4 使用的是 webpack4,vue-cli5 使用的是 webpack5
对于优化主要是两个方面
- 构建速度
- 打包体积
所以不管是分析问题还是解决问题有围绕这连个方面进行处理。
Vue-Cli 自带
- cache-loader 会默认为
Vue/Babel/TypeScript
编译开启。文件会缓存在node_modules/.cache
中。 如果你遇到了编译方面的问题,记得先清缓存目录之后再试试看。 - thread-loader 会在多核 CPU 的机器上为
Babel/TypeScript
转译开启。
查看 Vue-Cli 默认配置
vue inspect --mode production > output-prod.js
vue inspect --mode development > output-dev.js
然后通过 chatgpt 还原成 webpack 的配置:
下面是通过 vue-cli 搭建的前端项目,通过 vue inspect 输出的被序列化的格式,请将其还原成原本的 webpack 配置文件,我的目的是了解 vue-cli 默认对 webpack 做了什么配置:
{
mode: 'production',
context: 'D:\\code\\hello-world-v4',
devtool: 'source-map',
node: {
...
}
}
分析构建时间
安装:
npm install --save-dev speed-measure-webpack-plugin
配置:
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
module.exports = {
chainWebpack: (config) => {
config.plugin('speed-measure-webpack-plugin').use(SpeedMeasurePlugin).end();
},
};
查看打包大小
vue-cli 内置工具
vue-cli-service build --report
成功后就会在项目目录下找到/dist/report.html
和之前的 webpack-bundle-analyzer 效果一样,就没必要使用了。
vue-cli4
下面是还原的 production 下的 webpack 配置:webpack-prod.js
提升构建速度
如果分析构建时间短,则不需要优化。
构建速度优化
1、并行构建:已默认配置(是否为 Babel 或 TypeScript 使用 thread-loader
)
2、并行压缩:从转换的代码可以看到 TerserPlugin 已默认配置 parallel
3、开启缓存: - TerserPlugin 已默认配置 cache - loader 缓存:已经默认开启 vue-loader,babel-loader, eslint-loader,ts-loader 等缓存 - 使用 hard-source-webpack-plugin 配置持久化缓存功能 安装:npm install hard-source-webpack-plugin -D
配置:
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
module.exports = {
configureWebpack: (config) => {
/**
* 开启 HardSourceWebpackPlugin 构建缓存
* HardSourceWebpackPlugin 的缓存机制依赖于文件系统中的缓存数据。
* 如果在开发环境中生成的缓存数据由于某些原因被损坏或者不完整,可能会导致生产环境中的构建出现问题。所以在生产环境中不建议使用 HardSourceWebpackPlugin。
*
*/
if (process.env. NODE_ENV !== 'production') {
config.plugins.push(new HardSourceWebpackPlugin());
}
},
};
```
缓存的默认路径为:`node_modules/.cache/hard-source`
开启前后使用npm run dev对比,速度明显减小(工程为vue-cli4默认构建的hello-word项目)
| 项目/次数 | 1 | 2 | 3 | 4 |
| ------------------------- | ----- | ----- | ----- | ----- |
| 不加HardSourceWebpackPlugin | 3.49s | 1.12s | 1.08s | 1.13s |
| 加HardSourceWebpackPlugin | 2.46s | 0.27s | 0.29s | 0.77s |
> vue-cli4默认没用生成vue.config.js,需要自己手动添加:
模板:
```js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
// options...
})
会报错,因为defineConfig
is used by Vue CLI 5 not by Vue CLI 4, the right syntax for vue cli 4:
// vue.config.js
module.exports = {
// options...
};
[!warning] 关于
DllPlugin
,DllPlugin 是通过预编译特定的模块(通常是第三方库),将这些模块打包成独立的动态链接库 (DLL),在主构建中只需要引用这些预编译的模块,从而大幅减少构建时间。但是,如果已经使用 HardSourceWebpackPlugin 并且不考虑缓存失效的情况下,会将第三方依赖(如 React、Lodash 等)缓存到文件系统中。这样在后续的构建中,这些依赖的编译结果会直接从缓存中读取,因此无需重新编译这些依赖。在这种情况下,
DllPlugin
的作用会有所减弱,因为HardSourceWebpackPlugin
已经提供了类似的缓存功能。确实不再需要 DllPlugin。而且,vue-cli 引入 webpack4 之后,移除了该包,"因为 Webpack 4 的打包性能足够好的,dll 没有在 Vue ClI 里继续维护的必要了。"
关于 thread-loader,并不是所有的 loader 都要使用:
仅在耗时的操作中使用 thread-loader,否则使用 thread-loader 会后可能会导致项目构建时间变得更长,因为每个 worker 都是一个独立的 node.js 进程,其开销大约为 600ms 左右,同时还会限制跨进程的数据交换等。所以一般只在 babel-loader 中使用。
减小打包体积
1、css tree shaking
可以使用 purgecss 插件,但区别于 webpack 的配置,可以使用vue add 的方式进行添加:
安装:
vue add @fullhuman/purgecss@^3
[!warning] 注意 这里要添加版本 3,否则会报错:
Error: PostCSS plugin postcss-purgecss requires PostCSS 8.
然后会自动生成并配置好 postcss.config.js,无需其他额外配置。
2、gzip 压缩
安装:
npm install compression-webpack-plugin@^6 --save-dev
配置: 我 bulid 的时候报了Cannot read property 'tapPromise' of undefined
的错,其实就是版本和 vue-cli 的某些包不兼容,把 compression-webpack-plugin 的版本降低到6
就可以了。
const CompressionWebpackPlugin = require('compression-webpack-plugin');
module.exports = {
configureWebpack: (config) => {
if (process.env. NODE_ENV === 'production') {
// 在生产环境中使用 gzip 压缩
config.plugins.push(
new CompressionWebpackPlugin({
filename: '[path][base].gz', // 压缩后的文件名,默认值是 [path][base].gz
algorithm: 'gzip',
test: /\.(js|json|css|html|svg)$/,
threshold: 10240, // 对超过10k的数据压缩
minRatio: 0.8, // 压缩比
deleteOriginalAssets: false, // 是否删除原始文件
})
);
}
},
};
nginx 中配置:
server {
location ~ .*\.(js|json|css)$ {
gzip on;
gzip_static on; # 启用 gzip_static 选项
gzip_min_length 1k;
gzip_http_version 1.1;
gzip_comp_level 9;
gzip_types text/css application/javascript application/json;
root /home/tsgz/dist_hlj/; # 修改为项目前端目录
}
}
查看效果:
3、图片压缩
安装:
cnpm install image-webpack-loader -D
image-webpack-loader
是需要配合file-loader
来使用的。不过,Vue Cli 搭建的项目中已经内置了file-loader
就不需要我们进行额外安装配置了!
配置:
chainWebpack: (config) => {
// 找到图片加载器并对其进行配置
config.module
.rule('images')
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({
mozjpeg: { progressive: true, quality: 65 },
optipng: { enabled: true },
pngquant: { quality: [0.65, 0.9], speed: 4 },
gifsicle: { interlaced: false },
webp: { quality: 75 },
});
},
如果打包报错:Syntax Error: Error: Cannot find module 'imagemin-gifsicle' 则使用 cnpm 安装。
[!danger] 目前只能压缩 jpg,如果引入了 png 则打包出错,原因未知。。。
4、动态 polyfill
vue-cli 已经基于你的浏览器目标自动决定要运用的语法转换和 polyfill,无需我们配置: https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-babel-preset-app/README.md
5、production 环境不生成 SourceMap
module.exports = {
productionSourceMap: false,
};
6、分离基础库使用 cdn
慎用,除非公司有稳定的 cdn 服务。
不需要安装 html-webpack-externals-plugin,Vue CLI 自带的 html-webpack-plugin
已经足够用于我们的需求。
配置:
module.exports = {
configureWebpack: (config) => {
config.externals = {
vue: 'Vue',
'vue-router': 'VueRouter',
axios: 'axios',
};
},
chainWebpack: (config) => {
config.plugin('html').tap((args) => {
args[0].cdn = {
js: [
'https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js',
'https://cdn.jsdelivr.net/npm/vue-router@3.5.1/dist/vue-router.min.js',
'https://cdn.jsdelivr.net/npm/axios@0.21.1/dist/axios.min.js',
],
css: [],
};
return args;
});
},
};
修改 public/index.html,以便在生成的 HTML 文件中自动插入 CDN 链接:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title><%= htmlWebpackPlugin.options.title %></title>
<% if (htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %> <% for (var css of htmlWebpackPlugin.options.cdn.css) {
%>
<link rel="stylesheet" href="<%= css %>" />
<% } %> <% } %>
</head>
<body>
<noscript>
<strong
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to
continue.</strong
>
</noscript>
<div id="app"></div>
<% if (htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %> <% for (var js of htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%= js %>"></script>
<% } %> <% } %>
</body>
</html>
7、删除 console.log
安装:
npm install terser-webpack-plugin@^4 --save-dev
配置:
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
configureWebpack: (config) => {
if (process.env. NODE_ENV === 'production') {
config.optimization.minimizer = config.optimization.minimizer || [];
config.optimization.minimizer.push(
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true, // 移除生产环境的 console.log
drop_debugger: true, // 移除生产环境的 debugger
},
output: {
comments: false, // 删除所有注释
},
},
})
);
}
},
};
其他友好配置
1、chunk-vendors 分包
配置:
chainWebpack: config => {
if (process.env. NODE_ENV === 'production') {
config.optimization.splitChunks({
chunks: 'all', // 表明选择哪些 chunk 进行优化。通用设置,可选值:all/async/initial。设置为 all 意味着 chunk 可以在异步和非异步 chunk 之间共享。
minSize: 20000, // 允许新拆出 chunk 的最小体积
maxAsyncRequests: 10, // 每个异步加载模块最多能被拆分的数量
maxInitialRequests: 10, // 每个入口和它的同步依赖最多能被拆分的数量
enforceSizeThreshold: 50000, // 强制执行拆分的体积阈值并忽略其他限制
cacheGroups: {
libs: { // 第三方库
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/, // 请注意'[\\/]'的用法,是具有跨平台兼容性的路径分隔符
priority: 10 // 优先级,执行顺序就是权重从高到低
chunks: 'initial' // 只打包最初依赖的第三方
},
elementUI: { // 把 elementUI 单独分包
name: 'chunk-elementUI',
test: /[\\/]node_modules[\\/]element-ui[\\/]/,
priority: 20 // 权重必须比 libs 大,不然会被打包进 libs 里
},
commons: {
name: 'chunk-commons',
minChunks: 2, // 拆分前,这个模块至少被不同 chunk 引用的次数
priority: 0,
reuseExistingChunk: true
},
svgIcon: {
name: 'chunk-svgIcon',
// 函数匹配示例,把 svg 单独拆出来
test(module) {
// `module.resource` 是文件的绝对路径
// 用`path.sep` 代替 / or \,以便跨平台兼容
// const path = require('path') // path 一般会在配置文件引入,此处只是说明 path 的来源,实际并不用加上
return (
module.resource &&
module.resource.endsWith('.svg') &&
module.resource.includes(`${path.sep}icons${path.sep}`)
)
},
priority: 30
}
}
})
}
}
2、IgnorePlugin:忽略指定的模块或文件,通常用于如果引入了 moment.js,则忽略其他的语言包,另外我通常会在项目中新建 demo 模板,也可以使用IgnorePlugin
忽略测试文件。
const webpack = require('webpack');
const path = require('path');
module.exports = {
configureWebpack: (config) => {
config.plugins.push(
new webpack.IgnorePlugin({
resourceRegExp: /src\/views\/demo/, // 匹配要忽略的资源路径
})
);
},
};
3、resolve.alias resolve.alias 是用于创建 import 或 require 的别名,来确保模块引入变得更简单:
chainWebpack: (config) => {
// 配置别名
config.resolve.alias
.set('@build', pathResolve('../build')) // 构建目录
.set('@', pathResolve('../src'))
.set('@api', pathResolve('../src/api'))
.set('@utils', pathResolve('../src/utils'))
.set('@views', pathResolve('../src/views'));
};
4、去掉 preload 和 prefetch
preload:在页面加载时立即下载,优先级高。 prefetch:在浏览器空闲时下载,优先级低。
但是如果引入的资源过多,会占用大量带宽,导致其他关键资源(如 CSS 和主 JavaScript 文件)的下载速度变慢,prefetch 虽然在空闲时间下载,但是依然会有影响。
所以,尽管 preload
在某些情况下可以显著提升页面性能,但如果没有精细管理和选择性使用,全部加入 preload
可能会适得其反。因此,综合考虑性能和简化配置,全部去掉 preload
会是更安全和更通用的选择。
module.exports = {
chainWebpack: (config) => {
config.plugins.delete('prefetch');
config.plugins.delete('preload');
},
};
全量配置
参考:https://github.com/staven630/vue-cli4-config
vue-cli5
在处理之前,先比较下,vue-cli4 生成的 webpack 配置和 vue-cli5 生成的 webpack 配置的区别:
在提升打包速度和减小打包体积上,Vue CLI 4 和 Vue CLI 5 的配置文件有一些明显的区别。以下是详细的列举:
一、构建速度方面
- TerserPlugin 的配置差异:
- Vue CLI 4:javascript
new TerserPlugin({ terserOptions: { compress: { /* many options */ }, mangle: { safari10: true, }, }, sourceMap: true, cache: true, parallel: true, extractComments: false, });
- Vue CLI 5:javascriptVue CLI 5 移除了
new TerserPlugin({ terserOptions: { compress: { /* many options */ }, mangle: { safari10: true, }, }, parallel: true, extractComments: false, });
sourceMap
和cache
配置,这可以在某些情况下提升打包速度,因为生成 source map 和缓存处理可能会增加额外的时间。
- Vue CLI 4:
在 Webpack 4 中,由于没有内置的高级缓存机制,使用 TerserPlugin 的 cache
选项可以显著提升压缩过程中的性能。而在 Webpack 5 中,由于有了内置的持久化缓存机制,就不再需要单独在 TerserPlugin 中配置 cache
选项。
在 vue-cli 5 中,cache
会在开发
模式被设置成 type: 'memory'
而且在 生产
模式 中被禁用。
memory 意味着缓存数据会存储在内存中,重启构建工具后缓存会丢失。
- 插件变化:
- Vue CLI 4 使用了
OptimizeCssnanoPlugin
来压缩 CSS,且未提及并行处理。 - Vue CLI 5 使用了
CssMinimizerPlugin
,并启用了并行处理:javascript启用并行处理可以显著提升 CSS 压缩的速度。new CssMinimizerPlugin({ parallel: true, minimizerOptions: { preset: [ 'default', { mergeLonghand: false, cssDeclarationSorter: false, }, ], }, });
- Vue CLI 4 使用了
主要区别:
- 并行处理:
CssMinimizerPlugin
支持并行处理,而OptimizeCssnanoPlugin
不支持。这使得CssMinimizerPlugin
在处理大规模 CSS 文件时具有更好的性能。 - 集成度:
CssMinimizerPlugin
专为 Webpack 5 设计,能更好地利用 Webpack 5 的新特性和优化机制。而OptimizeCssnanoPlugin
更适合于 Webpack 4 及之前的版本。 - 配置简洁性:
CssMinimizerPlugin
的配置更简洁,并且内置了许多优化,减少了手动配置的复杂度。 - 维护与更新:
CssMinimizerPlugin
是 Webpack 官方推荐的插件,更新和维护更加及时,适配新版本的 Webpack 更加迅速。
- hashFunction 的使用:
- Vue CLI 5 在
output
中使用了更高效的哈希函数xxhash64
,这有助于提升生成哈希值的速度。javascriptoutput: { hashFunction: 'xxhash64', // other options }
- Vue CLI 5 在
二、打包体积方面
CSS 压缩插件的差异:
- Vue CLI 4 使用
OptimizeCssnanoPlugin
来压缩 CSS。 - Vue CLI 5 使用
CssMinimizerPlugin
,默认的配置同样通过cssnano
进行压缩,但配置了mergeLonghand: false
和cssDeclarationSorter: false
来避免某些长声明合并和 CSS 声明排序的问题,这些设置可以确保在最小化体积的同时,不影响 CSS 的兼容性和性能。
- Vue CLI 4 使用
splitChunks 配置的改进:
- 两者在
splitChunks
配置上大致相似,但 Vue CLI 5 默认禁用了realContentHash
:javascript这可以减少生成文件的体积,因为optimization: { realContentHash: false, splitChunks: { cacheGroups: { // configurations } }, // other options }
realContentHash
的启用会增加额外的哈希计算,从而增大文件体积。
- 两者在
移除了未使用的插件:
- Vue CLI 5 移除了
HashedModuleIdsPlugin
,这一插件用于生成稳定的模块 ID,但也会增加额外的哈希计算和模块大小。
- Vue CLI 5 移除了
构建速度优化
terser-webpack-plugin 在 v5 弃用了 cache 选项。而且在 Webpack 5 中,一般不再需要使用 hard-source-webpack-plugin
,因为 Webpack 5 本身已经内置了强大的cache
缓存功能,能够提供与 hard-source-webpack-plugin
类似的缓存效果。
所以,构建速度优化方面无需任何额外配置。
减小打包体积
1、css tree shaking:依然需要
2、gzip 压缩:依然需要
3、图片压缩:依然需要(在 webpack4 中使用的是image-webpack-loader
,但是一直报错,也没有其他的选择,但是 webpack5 中可以使用官方的ImageMinimizerWebpackPlugin
)
安装:
`npm install image-minimizer-webpack-plugin imagemin imagemin-mozjpeg imagemin-pngquant imagemin-svgo --save-dev`
imagemin-mozjpeg 安装失败,可以使用 cnpm 安装。
[!bug] 自己尝试后,依然无法安装。。。
算了,不如找个在线压缩网站,每过一段时间进行压缩处理:https://imagecompressor.com/zh/
总结
如果是 vue-cli4/5 搭建的项目,需要配置:
- 构建缓存(只有 4 需要,且热更新耗时才需要):hard-source-webpack-plugin
- css treeshaking:
@fullhuman/purgecss@^3
- 打包压缩:
compression-webpack-plugin@^6
- 图片压缩
- 生产环境关闭 sourcemap
- 删除 console.log
- 去掉 preload 和 prefetch