前言
紧接上一篇webpack入门篇,继续学习。继续敲!敲!敲!
1.优化构建速度
优化构建速度主要的目的是为了提高开发效率和减少开发的等待时间,从而提高开发者体验
1.1 resolve(解析)
1.1.1 alias
alias用于创建 import 或 require 的别名,来确保模块引入变得更简单。其实配置别名对构建速度的影响微乎其微,主要是避免开发时路径混淆。项目中存在大量的模块引用,并且这些引用使用了长路径或复杂的相对路径时,Webpack在构建过程中需要逐个解析和查找这些模块的路径,这会导致构建过程中的性能下降。
// webpack.common.js
module.exports = {
...
resolve: {
alias: {
'@': path.resolve(__dirname, '../src'),
},
},
};
使用时就可以通过别名来引入外部资源。
// index.js
import '@/css/index.css';
import '@/css/index.less';
import '@/css/index.scss';
import '@/assets/fonts/iconfont.css';
import App from '@/App.vue';
...
1.1.2 extensions
extensions配置项用于配置模块的文件扩展名,指示Webpack在引入模块时可以忽略文件的扩展名。
extensions配置项可能在某些情况下对构建速度产生一些影响。具体而言,当项目中存在大量模块引用时,如果每个引用都需要显式指定扩展名,Webpack在构建过程中需要逐个解析和查找这些模块文件的扩展名。这可能导致构建速度的下降,因为需要进行更多的文件系统操作和查找操作。
// webpack.common.js
module.exports = {
resolve: {
...
extensions: ['.js', '.vue', '.json'],
},
};
在引入资源的时候就可以省略扩展名了。
// index.js
import '@/css/index.css';
import '@/css/index.less';
import '@/css/index.scss';
import '@/assets/fonts/iconfont.css';
import App from '@/App';
1.2 多进程构建
使用时,需将此 loader 放置在其他 loader 之前。放置在此 loader 之后的 loader 会在一个独立的 worker 池中运行。每个 worker 都是一个独立的 node.js 进程,其开销大约为 600ms 左右。
注意:仅在耗时的操作中使用此loader
npm install thread-loader -D
开启多进程之前,打包用时如下:
配置webpack开启多个进程用来处理babel-loader
module.exports = {
...
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'thread-loader',
options: {
worker: 4,// 产生worker的数量
workerParallelJobs: 50,// 一个 worker 进程中并行执行工作的数量,默认20个
},
},
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
],
},
],
},
};
再次打包,发现效果不是很明显。(应该是工程小、依赖少的原因)
1.3 使用缓存
cache在开发模式中被设置成type:'memory',在生产模式中被禁用。缓存有两种缓存:
memory:内存缓存(存储在内存中)filesystem:文件系统缓存(存储在磁盘中)
当开启缓存时,首次打包webpack会根据项目的配置对源代码进行编译、转换和打包,生成最终的输出文件。并且缓存起来。当修改文件后进行第二次打包,webpack会根据先前缓存的结果进行比对,有缓存部分就在缓存中拿,没有缓存部分则进行构建大大提升了构建速度。
// webpack.prod.js
const commonConfig = require('./webpack.common');
const { merge } = require('webpack-merge');
module.exports = merge(commonConfig, {
mode: 'production',
cache: {
type: 'filesystem',
},
});
开启缓存之后,首次打包用时如下:
修改index.js文件,再次打包。
// index.js
import './css/index.css';
import './css/index.less';
import './css/index.scss';
import './assets/fonts/iconfont.css';
import App from './App.vue';
import Vue from 'vue';
console.log('Hello Webpack!');
+ console.log('cache');
console.log([1, 2, 3].map((n) => n + 1));
new Vue({
render: (h) => h(App),
}).$mount('#app');
第二次打包构建的速度明显提升
1.4 externals(外链)
防止将某些 import 的包打包到 bundle 中,而是在运行时再去从外部获取这些扩展依赖。从而减少构建文件体积、加快构建速度。
例如:我们将Vue通过外链的方式在运行时从外部获取。
外链之前打包速度与产物大小如下:
卸载掉vue
npm uninstall vue
在免费的CDN网站上找到对应版本的vue外链,粘贴到我们的HTML模板中。也可以使用html-webpack-plugin插件动态插入外链,这里就不再演示。如果想了解传送门。
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= htmlWebpackPlugin.options.title %></title>
+ <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.min.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
配置webpack
// webpack.common.js
module.exports = {
...
externals: {
vue: 'Vue',
},
};
外链之后打包速度与产物大小如下:
1.5 精确loader应用范围
在webpack配置中,include和exclude是用于指定应该应用或排除Loader的文件或目录的配置选项。这样可以避免对不必要的文件进行处理,提高构建性能和减少不必要的资源消耗。
因为之前的演示中我都是使用了exclude,所以为了做对比构建速度我先进行注释后打包:
配置webpack,指定babel-loader的应用范围
// webpack.common.js
module.exports = {
...
module: {
rules: [
{
test: /\.js$/,
include: path.resolve(__dirname, '../src'),
exclude: /node_modules/,
use: [
{
loader: 'thread-loader',
options: {
worker: 4,
workerParallelJobs: 50,
},
},
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
],
},
],
},
};
1.6 esbuild-loader
esbuild是一个用Go语言编写的快速JavaScript构建工具,具有出色的转译性能,通常比Babel要快一些。并且也能够做JS兼容,那么问题就来了babel-loader与esbuild-loader我该用哪个呢?下面就看看它们各自的优势吧!
esbuild-loader优势:
- 极快的构建速度
- 低资源消耗
- 适合现代语法
babel-loader优势:
- 更广泛的语法支持
- 大型生态系统
- 定制性
所以如果是在开发中我会选择babel-loader。
使用babel-loader构建
npm install esbuild-loader -D
配置webpack
// webpack.common.js
module.exports = {
...
module: {
rules: [
{
test: /\.js$/,
loader: 'esbuild-loader',
include: path.resolve(__dirname, '../src'),
exclude: /node_modules/,
options: {
target: 'es2015',
loader: 'js',
},
// use: [
// {
// loader: 'thread-loader',
// options: {
// worker: 4,
// workerParallelJobs: 50,
// },
// },
// {
// loader: 'babel-loader',
// options: {
// presets: ['@babel/preset-env'],
// },
// },
// ],
},
],
},
};
2.构建产物优化
2.1 压缩CSS
CssMinimizerWebpackPlugin是一个用于压缩和优化CSS代码的Webpack插件。
npm install css-minimizer-webpack-plugin -D
使用插件前
配置webpack
// webpack.prod.js
const { merge } = require('webpack-merge');
const commonConfig = require('./webpack.common');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = merge(commonConfig, {
mode: 'production',
optimization: {
minimize: true,
minimizer: [new CssMinimizerPlugin()],
},
});
2.2 压缩JS
webpack v5 开箱即带有最新版本的 terser-webpack-plugin。这是一个用于压缩和优化JavaScript代码的Webpack插件。它使用Terser进行代码压缩和混淆,旨在减小JavaScript文件的体积,提高加载速度,并优化用户体验。
压缩前:
// webpack.prod.js
const { merge } = require('webpack-merge');
const commonConfig = require('./webpack.common');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = merge(commonConfig, {
mode: 'production',
optimization: {
minimize: true,
minimizer: [new CssMinimizerPlugin(), new TerserPlugin()],
},
});
2.3 压缩CSS&JS
esbuild-loader中的EsbuildPlugin插件不但能够压缩JS还能够压缩CSS
// webpack.prod.js
const { merge } = require('webpack-merge');
const commonConfig = require('./webpack.common');
const { EsbuildPlugin } = require('esbuild-loader');
// const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
// const TerserPlugin = require('terser-webpack-plugin');
module.exports = merge(commonConfig, {
...
optimization: {
minimize: true,
minimizer: [
// new CssMinimizerPlugin(),
// new TerserPlugin(),
new EsbuildPlugin({
target: 'es2015',
css: true, // 压缩CSS
}),
],
},
});
2.4 压缩图片
image-minimizer-webpack-plugin是用于压缩图片的插件,这个插件可以使用2个工具来压缩图像:imagemin与squoosh。
图片还有两种模式优化:有损压缩与无损压缩
推荐imagemin插件进行无损优化
npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D
推荐的有损优化imagemin插件
npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D
因为项目中只有一张jpg格式的图片,所以就只用到imagemin-jpegtran进行处理。
npm install image-minimizer-webpack-plugin imagemin -D
// 使用–ignore-scripts,则会让npm避免执行package.json文件中的scripts脚本
npm install imagemin-jpegtran --ignore-scripts -D
压缩前图片大小如下:
配置webpack
// webpack.prod.js
const { merge } = require('webpack-merge');
const commonConfig = require('./webpack.common');
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
module.exports = merge(commonConfig, {
...
// webpack官网上是将插件放plugin中,插件的github上是放optimization中。亲自试验了一下都可以。
optimization: {
minimize: true,
minimizer: [
new ImageMinimizerPlugin({
minimizer: {
// 官网上是使用,imageminMinify但是试验发现不能生成jpg。使用imageminGenerate可以。
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [['jpegtran', { progressive: true }]],
},
},
}),
],
},
});
运行打包命令时,发现如下报错。说jpegclub.exe不存在。
通过百度后得到了解决方法,只需要去官网上将jpegclub.exe下载下来并拖入vendor目录下即可,下载地址
下载完成后,将其拖入vendor中
然后再次执行打包命令:
2.5 清除无用代码
在项目打包上线的时候,项目中通常会有一些无用的代码。而这些代码会增加打包后的文件体积,不利于浏览器资源的加载。
无用的代码有哪些?
- 调试的代码:console、debugger
- 代码注释:在运行时我们不需要代码注释
- 无用的变量和函数:项目中定义了但是并没有使用到
去除调试代码
const { merge } = require('webpack-merge');
const commonConfig = require('./webpack.common');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = merge(commonConfig, {
...
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,// 去除所有的console代码,需要手动开启
drop_debugger: true,// 去除调试debugger,production模式下自动开启
//pure_funcs: ['console.log'],// 删除指定代码,如果需要打印在控制台可以使用console.info
},
},
}),
],
},
});
webpack5下production模式下默认去除代码注释,默认会通过Tree Shaking去除掉没有被使用的变量和函数,无须进行配置。
2.6 代码分包
splitChunks用于将公共模块(公共依赖)提取出来,以便在多个入口文件之间共享和复用。它可以帮助减少重复的模块加载,提高应用程序的性能和加载速度。
| option | default | description |
|---|---|---|
| chunks | async | 指定需要进行模块拆分的范:async(异步加载模块拆分)、initial(同步模块导入拆分)、all(所有模块拆分) |
| minSize | 20000 | 指定一个模块的最小大小,只有超过这个大小的模块才会被拆分 |
| minChunks | 1 | 指定一个模块在多少个入口文件中被引用时,才会被认为是公共模块 |
| maxAsyncRequests | 30 | 指定异步加载的模块并行请求的最大数量 |
| maxInitialRequests | 30 | 指定初始入口模块并行请求的最大数量 |
| cacheGroups | - | 定义缓存组,允许对不同的模块进行不同的配置 |
配置webpack
// webpack.prod.js
const { merge } = require('webpack-merge');
const commonConfig = require('./webpack.common');
module.exports = merge(commonConfig, {
...
optimization: {
splitChunks: {
chunks: 'all',
minSize: 30000,
minChunks: 1,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: -10,// 优先级
},
common: {
name: 'common',
minChunks: 2,
priority: -20,
// 如果当前chunk包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块。
reuseExistingChunk: true,
},
},
},
},
});