相关阅读:webpack5基础配置
案例代码仓库:Github地址
简介
本篇文章我们从提升开发体验、提升webpack打包构建速度、减少代码体积、优化代码运行性能四个方面进行配置。
一、提升开发体验
1、使用Source Map
Source Map 是一种文件格式,用于将转换后的代码映射回原始源代码。它是前端开发中调试工具的一部分,能够帮助开发者在浏览器中更准确地定位到源代码中的错误和警告。通过使用 Source Map,开发者可以在调试过程中准确定位到源代码中的错误,而不是转换后的代码。这对于处理压缩过的 JavaScript、合并后的 CSS 或者预处理器编译后的样式表等场景非常有用,因为它们往往难以直接进行调试。这里建议devtool的值设置为eval-cheap-module-source-map,这样可以在发生错误时定位到对应行,打包运行速度也不会很慢,如果需要其他的,可前往官网->配置->Devtool查阅(注:为了防止代码泄露,因此不建议生产环境配置任何Source Map)
开发环境配置
module.exports = {
// 开启source-map
devtool: 'eval-cheap-module-source-map'
}
二、提升webpack打包构建速度
1、使用oneOf
使用oneOf可以使资源文件一旦被某个loader处理了,就不继续遍历所有rules,加快打包速度,需要注意的是,不能使用两个加载器处理同一种文件,否则会报错
使用方法
module.exports = {
// 模块配置
module: {
// 规则
rules: [
{
// oneOf表示只使用一个加载器, 提高效率, 但是不能有两个加载器处理同一种类型的文件, 否则会报错
oneOf: [
// 处理css,less文件
// TODO
// 处理图片,小于8kb的图片转为base64格式
{
test: /\.(png|jpe?g|gif|webp|svg|ico)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024
}
},
// 生成文件名
generator: {
filename: 'image/[name].[hash:8][ext]'
}
},
// 处理字体文件及其他媒体文件
// TODO
// 配置babel,将ES6+转换为ES5
// TODO
// 处理html中的资源引用
// TODO
]
}
]
}
}
2、使用include/exclude
排除或只检测某些文件,处理文件少,打包速度快,需要注意的是两个只能用一个,不能同时使用,例如对babel-loader的使用时,就需要排除node_modules下的文件
module.exports = {
// 模块配置
module: {
// 规则
rules: [
// 配置babel,将ES6+转换为ES5
{
test: /\.js$/,
// 排除node_modules目录下的文件
exclude: /node_modules/,
use: [
{
// 使用babel-loader
loader: 'babel-loader',
// 配置babel
options: {
// 预设
presets: [
// 预设包含了ES6、7、8的语法转换规则
'@babel/preset-env'
]
}
}
]
}
]
}
}
3、使用cache
使用cache可以对eslint和babel处理的结果进行缓存,下次检查只处理修改的文件,可以让后续打包速度更快。不过对于eslint的缓存可能会出现无法检测新增的文件,因此慎重开启,配置如下:
module.exports = {
// 模块配置
module: {
// 规则
rules: [
// 配置babel,将ES6+转换为ES5
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
// 使用babel-loader
loader: 'babel-loader',
// 配置babel
options: {
// 预设
presets: [
// 预设包含了ES6、7、8的语法转换规则
'@babel/preset-env'
],
// 开启缓存
cacheDirectory: true,
// 关闭缓存压缩
cacheCompression: false
}
}
]
}
]
},
// 配置插件
plugins: [
// eslint插件
new EslintWebpackPlugin({
// eslint检查的文件, 只检查src目录下的文件
context: path.resolve(__dirname, '../src'),
// 开启缓存
cache: true
})
]
}
4、使用thread
使用多进程打包,多进程处理Babel和eslint,速度更快,需要注意的是启动进程需要时间,因此根据项目大小选择性开启,项目较大时才会加快打包速度,项目较小时反而会增加打包时间。配置之前我们首先需要获取当前计算机的总进程数和安装thread-loader,一般配置多进程为总进程数减一。配置如下: 安装thread-loader
npm i thread-loader --save-dev
配置多进程打包
// 引入os模块
const os = require('os')
// 获取当前计算机总进程数
const cpus = os.cpus().length
module.exports = {
// 模块配置
module: {
// 规则
rules: [
// 配置babel,将ES6+转换为ES5
{
test: /\.js$/,
exclude: /node_modules/,
use: [
// 开启多进程打包
{
loader: 'thread-loader',
options: {
// 进程数
workers: cpus - 1
}
},
{
// 使用babel-loader
loader: 'babel-loader',
// 配置babel
options: {
// 预设
presets: [
// 预设包含了ES6、7、8的语法转换规则
'@babel/preset-env'
],
// 开启缓存
cacheDirectory: true,
// 关闭缓存压缩
cacheCompression: false
}
}
]
}
]
},
// 配置插件
plugins: [
// eslint插件
new EslintWebpackPlugin({
// eslint检查的文件, 只检查src目录下的文件
context: path.resolve(__dirname, '../src'),
// 开启缓存
cache: true,
// 开启多进程打包,设置进程数
threads: cpus - 1
})
]
}
三、减少代码体积
1、使用Tree Shaking
剔除了没有使用的多于代码,让代码体积更小,此功能已经在webpack5中内嵌,因此不用多余配置,正常打包即可。
2、使用@babel/plugin-transform-runtime
插件对babel进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小
安装插件
npm i @babel/plugin-transform-runtime --save-dev
配置插件
// 引入os模块
const os = require('os')
// 获取当前计算机总进程数
const cpus = os.cpus().length
module.exports = {
// 模块配置
module: {
// 规则
rules: [
// 配置babel,将ES6+转换为ES5
{
test: /\.js$/,
exclude: /node_modules/,
use: [
// 开启多进程打包
{
loader: 'thread-loader',
options: {
// 进程数
workers: cpus - 1
}
},
{
// 使用babel-loader
loader: 'babel-loader',
// 配置babel
options: {
// 预设
presets: [
// 预设包含了ES6、7、8的语法转换规则
'@babel/preset-env'
],
// 开启缓存
cacheDirectory: true,
// 关闭缓存压缩
cacheCompression: false,
// 配置禁用了 Babel 自动对每个文件的 runtime 注入,
// 而是引入 @babel/plugin-transform-runtime 并且使所有辅助代码从这里引用,来避免重复引入
plugins: ['@babel/plugin-transform-runtime']
}
}
]
}
]
}
}
3、使用压缩插件
压缩项目中的文件,体积更小,请求速度更快。根据官方文档,我们将优化相关插件配置到optimization中。压缩插件一般只在生产环境使用,开发环境无需配置。
(1)使用css压缩插件
安装css-minimizer-webpack-plugin
npm i css-minimizer-webpack-plugin --save-dev
配置css-minimizer-webpack-plugin
// 引入css-minimizer-webpack-plugin插件
const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin')
module.exports = {
// 优化配置
optimization: {
// 配置压缩插件
minimizer: [
// 配置css压缩插件
new CssMinimizerWebpackPlugin()
]
}
}
(2)使用js压缩插件
js压缩插件terser-webpack-plugin已经在webpack5中内置,无需下载,如果不需要配置多进程,在生产模式下该插件默认使用的,下面是单独配置并且开启多进程的例子
// 引入terser-webpack-plugin插件
const TerserWebpackPlugin = require('terser-webpack-plugin')
// 引入os模块
const os = require('os')
// 获取当前计算机总进程数
const cpus = os.cpus().length
module.exports = {
// 优化配置
optimization: {
// 配置压缩插件
minimizer: [
// 配置terser压缩插件, 用于压缩js, 默认是terser-webpack-plugin,
// 但是webpack5中已经内置了, 如果不需要开启多进程打包, 可以不用配置
new TerserWebpackPlugin({
// 开启多进程
parallel: cpus - 1
})
]
}
}
四、优化代码运行性能
1、代码分割(Code Split)
由于打包代码时会将所有的js打包到一个文件中,体积太大,因此我们需要代码分割,生成多个js文件,渲染某个页面时只加载该页面的js文件,速度会更快。代码分割有多种途径,以下是常用的几种
(1)多入口打包
入口配置
module.exports = {
// 多入口入口配置,index和app是自定义的名称
entry: {
index: './src/index.js',
app: './src/app.js'
}
}
出口配置
const path = require('path')
module.exports = {
// 单入口配置
entry: './src/index.js',
// 出口配置
output: {
// 输出路径
path: path.resolve(__dirname, '../dist'),
// 输出文件名
filename: 'js/[name].js',
// 清空输出目录
clean: true
}
}
(2)使用splitChunks
项目中经常有多个模块重复加载一个模块代码的情况,如果不进行代码分割,那每个模块都将引用公共模块代码,使总体体积增大,因此我们配置splitChunks将被多次引用的模块分割出去,单独引用,从而减小打包后的代码体积,该配置有很多自定义配置项,一般我们就用官方配置好的默认项就好,开启方式也很简单。(开发环境生产环境均可配置)如下
module.exports = {
// 优化配置
optimization: {
// 配置代码分割
splitChunks: {
// 配置分割规则
chunks: 'all'
}
}
}
(3)使用runtimeChunk
当将 runtimeChunk 设置为 'single' 时,Webpack 将会将运行时代码提取为一个单独的文件,并将其添加到输出的打包文件中。这样做的好处是,当多个入口文件共享相同的运行时代码时,可以有效地减小打包文件的体积,并避免重复打包相同的运行时代码。(开发环境生产环境均可配置)
module.exports = {
// 优化配置
optimization: {
// 配置将当前模块的记录其他模块的hash单独打包为一个文件
runtimeChunk: 'single'
}
}
(4)按需导入
当我们在文件中,只有进行某些操作才需要加载一个文件中的代码时,我们就可以使用按需导入, 使用/* webpackChunkName: 'add' */ 注释方法,给分离模块命名。并在output中指定chunkFilename: 'js/[name].chunk.js'
document.getElementById("btn").onclick = function () {
import(/* webpackChunkName: 'add' */ "./js/math").then(({ add }) => {
console.log(add(1, 2))
}).catch(() => {
console.log("加载失败")
})
}
配置
const path = require('path')
module.exports = {
// 单入口配置
entry: './src/index.js',
// 出口配置
output: {
// 输出路径
path: path.resolve(__dirname, '../dist'),
// 输出文件名
filename: 'js/[name].js',
// 输出的按需加载的文件的文件名
chunkFilename: 'js/[name].chunk.js',
// 清空输出目录
clean: true
}
}
2、空闲时加载(Preload/Prefetch)
(1)Preload/Prefetch介绍
当浏览器空闲时,加载资源并进行缓存,当真正需要使用资源时,直接使用,可以解决按需加载时如果文件过大,加载卡顿问题。二者区别:
- Preload加载优先级高,Prefetch加载优先级低
- Preload只能加载当前页面使用的资源,Prefetch可以加载当前页面资源,也可以加载下一个页面使用的资源。
缺点:兼容性不好,目前浏览器兼容性Preload:95%,Prefetch:77%,因此需要兼容多种浏览器的慎用。
(2)Preload/Prefetch使用
安装@vue/preload-webpack-plugin
npm i @vue/preload-webpack-plugin --save-dev
配置插件
// 引入preload-webpack-plugin插件
const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin')
module.exports = {
// 配置插件
plugins: [
// 预加载插件
new PreloadWebpackPlugin({
// 预加载的资源
rel: 'preload',
// 仅处理js文件
as: 'script'
}),
]
}
3、core-js
core-js专门用来补充babel-loader局限性问题,由于babel-loader无法转换es6高级语法,因此我们需要下载安装core-js,并引入项目,实现es6高级语法的转换。
安装core-js
npm i core-js --save-dev
使用core-js,全部引入
import 'core-js/actual'
Promise.resolve(42).then(it => console.log(it)) // => 42
Array.from(new Set([1, 2, 3]).union(new Set([3, 4, 5]))) // => [1, 2, 3, 4, 5]
使用core-js,按需引入
import 'core-js/actual/promise'
Promise.resolve(42).then(it => console.log(it)) // => 42
使用babel预设,直接按需加载自动引入,无需手动引入,配置useBuiltIns: 'usage'
module.exports = {
// 模块配置
module: {
// 规则
rules: [
// 配置babel,将ES6+转换为ES5
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
// 使用babel-loader
loader: 'babel-loader',
// 配置babel
options: {
// 预设
presets: [[
// 预设包含了ES6、7、8的语法转换规则
'@babel/preset-env',
// 按需加载,自动引入
{
useBuiltIns: 'usage',
corejs: 3
}
]]
}
}
]
}
]
}
}
4、使用PWA
渐进式 Web 应用程序(Progressive Web Application,PWA)是一种结合了网页和原生应用程序功能的现代 Web 应用程序概念。PWA 的目标是提供类似原生应用程序的用户体验,包括离线访问、推送通知、本地缓存等功能。我们使用PWA可以实现离线访问,优化用户体验。实现该功能需要下载安装workbox-webpack-plugin插件
npm i workbox-webpack-plugin --save-dev
配置插件
// 引入workbox-webpack-plugin插件
const WorkboxWebpackPlugin = require('workbox-webpack-plugin')
module.exports = {
// 配置插件
plugins: [
// workbox插件
new WorkboxWebpackPlugin.GenerateSW({
// 不需要手动配置service-worker.js文件,会自动生成
clientsClaim: true,
skipWaiting: true
})
]
}
注册插件,在入口文件index.js中添加以下代码
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js').then(registration => {
console.log('SW registered: ', registration)
}).catch(registrationError => {
console.log('SW registration failed: ', registrationError)
})
})
}