Webpack相对其他打包器不同的地方
Webpack忽略具体资源类型之间的差异,将所有代码/非代码文件都统一看作Module------模块对象,以相同的加载、解析、依赖管理、优化、合并流程
实现打包,并借助Loader、Plugin两种开放接口将资源差异处理逻辑转交由社区实现,实现统一资源构建模型
优点:
-
所有资源都是Module,所以可以用同一套代码实现诸多特性,包括:代码压缩、Hot Module Replacement、缓存等
-
打包时,资源与资源之间非常容易实现信息互换,例如可以轻易在HTML插入Base64格式的图片
-
借助Loader,Webpack几乎可以用任意方式处理任意类型的资源,例如可以用Less、Stylus、Sass等预编译CSS代码
Webpack的打包过程
-
输入:从文件系统读入代码文件
-
模块递归处理:调用Loader转译Module内容,并将结果转换为AST,从中分析出模块依赖关系,进一步递归调用模块处理过程,直到所有依赖文件都处理完毕
-
后处理:所有模块递归处理完毕后开始执行后处理,包括模块合并、注入运行时、产物优化等,最终输出Chunk集合
-
输出:将Chunk写出到外部文件系统
Webpack配置项分类
- 流程类:作用于打包流程某个或若干个环节,直接影响编译打包效果的配置项
- 工具类:打包主流程之外,提供更多工程化工具的配置项
流程类
-
输入输出:
entry
:用于定义项目入口文件,Webpack会从这些入口文件开始按图索骥找出所有项目文件context
:项目执行上下文路径output
:配置产物输出路径、名称等
-
模块处理:
resolve
:用于配置模块路径解析规则,可用于帮助Webpack更精准、高效地找出指定模块module
:用于配置模块加载规则,例如针对什么类型的资源需要使用哪些Loader进行处理externals
:用于声明外部资源,Webpack会直接忽略这部分资源,跳出这些资源的解析、打包操作
-
后处理
optimization
:用于控制如何优化产物包体积,内置Dead Code Elimination、Scope Hoisting、代码混淆、代码压缩等功能target
:用于配置编译产物的目标运行环境,支持web、node、electron等值,不同值最终产物会有所差异mode
:编译模式短语,支持development
、production
等值,可以理解为一种声明环境的短语
工具类
-
开发效率类:
watch
:用于配置持续监听文件变化,持续构建devtool
:用于配置产物Sourcemap生成规则devServer
:用于配置与HMR强相关的开发服务器功能
-
性能优化类:
cache
:Webpack5之后,该项用于控制如何缓存编译过程信息与编译结果performance
:用于配置当产物大小超过阈值时,如何通知开发者
-
日志类:
state
:用于精准地控制编译过程的日志内容,在做比较细致的性能调试时非常有用infrastructureLogging
:用于控制日志输出方式,例如可以通过该配置将日志输出到磁盘文件
Webpack常用Loader、Plugin
Webpack打包npm包
- 相对于普通项目需要在output#library下加name和type
java
module.exports = {
// ...
output: {
filename: "[name].js",
path: path.join(__dirname, "./dist"),
+ library: {
+ name: "_", // 被调用时的名字
+ type: "umd",
+ },
},
// ...
};
- 排除第三方库 防止打包体积过大 使用
externals
java
// webpack.config.js
module.exports = {
// ...
+ externals: {
+ lodash: {
+ commonjs: "lodash",
+ commonjs2: "lodash",
+ amd: "lodash",
+ root: "_",
+ },
+ },
// ...
};
- 不再打包lodash可以顺手将lodash声明为
peerDependencies
json
{
"name": "6-1_test-lib",
// ...
+ "peerDependencies": {
+ "lodash": "^4.17.21"
+ }
}
- 也可以用
webpack-node-externals
直接排除所有node_modules
模块
ini
// webpack.config.js
const nodeExternals = require('webpack-node-externals');
module.exports = {
// ...
+ externals: [nodeExternals()]
// ...
};
- 抽离CSS代码,通常需要使用
mini-css-extract-plugin
插件将样式抽离成单独文件,由用户自行引入
diff
module.exports = {
// ...
+ module: {
+ rules: [
+ {
+ test: /.css$/,
+ use: [MiniCssExtractPlugin.loader, "css-loader"],
+ },
+ ],
+ },
+ plugins: [new MiniCssExtractPlugin()],
};
- 生成sourcemap
java
// webpack.config.js
module.exports = {
// ...
+ devtool: 'source-map'
};
- 使用
.npmignore
文件忽略不需要发布到npm的文件 - 在
package.json
文件中,使用prepublishOnly
指令,在发布前自动执行编译命令,例如
json
// package.json
{
"name": "test-lib",
// ...
"scripts": {
"prepublishOnly": "webpack --mode=production"
},
// ...
}
- 在
package.json
文件中,使用main
指定项目入口,同时使用modue
指定ES Module模式下的入口,以允许用户直接使用源码版本,例如:
json
{
"name": "6-1_test-lib",
// ...
"main": "dist/main.js",
"module": "src/index.js",
"scripts": {
"prepublishOnly": "webpack --mode=production"
},
// ...
}
Webpack处理图像
导入图像
file-loader
已经内置不需要安装
java
// webpack.config.js
module.exports = {
// ...
module: {
rules: [{
test: /.(png|jpg)$/,
- use: ['file-loader']
+ type: 'asset/resource'
}],
},
};
url-loader
限定文件大小阈值
yaml
module.exports = {
// ...
module: {
rules: [{
test: /.(png|jpg)$/,
- use: [{
- loader: 'url-loader',
- options: {
- limit: 1024
- }
- }]
+ type: "asset",
+ parser: {
+ dataUrlCondition: {
+ maxSize: 1024 // 1kb
+ }
+ }
}],
},
};
raw-loader
bash
module.exports = {
// ...
module: {
rules: [
{
test: /.svg$/i,
- use: ['raw-loader']
+ type: "asset/source"
},
],
},
};
图像优化
压缩
image-webpack-loader
arduino
yarn add -D image-webpack-loader
module.exports = {
// ...
module: {
rules: [{
test: /.(gif|png|jpe?g|svg)$/i,
// type 属性适用于 Webpack5,旧版本可使用 file-loader
type: "asset/resource",
use: [{
loader: 'image-webpack-loader',
options: {
// jpeg 压缩配置
mozjpeg: {
quality: 80
},
disable: process.env.NODE_ENV === 'development' // 非常消耗性能,建议只在生产环境下使用
}
}]
}],
},
};
image-webpack-loader
底层依赖于imagemin
及一系列的图像优化工具:
mozjpeg
:用于压缩JPG(JPEG)图片optipng
:用于压缩PNG图片pngquant
:用于压缩PNG图片svgo
:用于压缩SVG图片gifsicle
:用于压缩Gif图webp
:用于将JPG/PNG图片压缩并转化为WebP图片格式
雪碧图
- 使用
webpack-spritesmith
自动实现雪碧图效果
css
yarn add -D webpack-spritesmith
module.exports = {
// ...
resolve: {
modules: ["node_modules", "assets"]
},
plugins: [
new SpritesmithPlugin({
// 需要
src: {
cwd: path.resolve(__dirname, 'src/icons'),
glob: '*.png'
},
target: {
image: path.resolve(__dirname, 'src/assets/sprite.png'),
css: path.resolve(__dirname, 'src/assets/sprite.less')
}
})
]
};
webpack-spritesmith
会将src.cwd
目录内所有匹配src.glob
规则的图片合并成一张大图并保存到target.image
指定的文件路径
响应式图片
- 使用
responsive-loader
css
yarn add -D responsive-loader sharp
module.exports = {
// ...
module: {
rules: [{
test: /.(png|jpg)$/,
oneOf: [{
type: "javascript/auto",
resourceQuery: /sizes?/,
use: [{
loader: "responsive-loader",
options: {
adapter: require("responsive-loader/sharp"),
},
}],
}, {
type: "asset/resource",
}],
}],
}
};
- 注意,实践中我们通常没必要对项目里所有图片都施加响应式特性,因此这里使用
resourceQuery
过滤出带size/sizes
参数的图片引用,使用方法:
ini
// 引用图片,并设置响应式参数
import responsiveImage from './webpack.jpg?sizes[]=300,sizes[]=600,sizes[]=1024';
const Picture = function () {
return (
<img
srcSet={responsiveImage.srcSet}
src={responsiveImage.src}
sizes="(min-width: 1024px) 1024px, 100vw"
loading="lazy"
/>
);
};
上例的引用参数./webpack.jpg?sizes[]=300,sizes[]=600,sizes[]=1024
最终将生成宽度分别为300、600、1024三张图片,之后设置img
标签的srcset
属性即可实现图片响应式功能
css
.foo {
background: url("./webpack.jpg?size=1024");
}
@media (max-width: 480px) {
.foo {
background: url("./webpack.jpg?size=300");
}
}
Webpack环境治理
- 实现同一份代码打包出多种产物,使用数组方式配置
lua
// webpack.config.js
module.exports = [
{
output: {
filename: './dist-amd.js',
libraryTarget: 'amd',
},
name: 'amd',
entry: './app.js',
mode: 'production',
},
{
output: {
filename: './dist-commonjs.js',
libraryTarget: 'commonjs',
},
name: 'commonjs',
entry: './app.js',
mode: 'production',
},
];
- 可以借助
webpack-merge
合并通用配置
php
const { merge } = require("webpack-merge");
const baseConfig = {
output: {
path: "./dist"
},
name: "amd",
entry: "./app.js",
mode: "production",
};
module.exports = [
merge(baseConfig, {
output: {
filename: "[name]-amd.js",
libraryTarget: "amd",
},
}),
merge(baseConfig, {
output: {
filename: "./[name]-commonjs.js",
libraryTarget: "commonjs",
},
}),
];
- 使用配置函数的方式配置webpack,允许用户根据命令行参数动态创建配置对象,可用于实现简单的多环境治理策略
css
// npx webpack --env app.type=miniapp --mode=production
module.exports = function (env, argv) {
return {
mode: argv.mode ? "production" : "development",
devtool: argv.mode ? "source-map" : "eval",
output: {
path: path.join(__dirname, `./dist/${env.app.type}`,
filename: '[name].js'
},
plugins: [
new TerserPlugin({
terserOptions: {
compress: argv.mode === "production",
},
}),
],
};
};
命令: | env 参数值: |
---|---|
npx webpack --env prod | { prod: true } |
npx webpack --env prod --env min | { prod: true, min: true } |
npx webpack --env platform=app --env production | { platform: "app", production: true } |
npx webpack --env foo=bar=app | { foo: "bar=app"} |
npx webpack --env app.platform="staging" --env app.name="test" | { app: { platform: "staging", name: "test" } |
环境治理策略
需求:
- 开发环境需要使用
webpack-dev-server
实现Hot Module Replacement - 测试环境需要带上完整的Sourcemap内容,以帮助更好地定位问题
- 生产环境需要尽可能打包出更快、更小、更好的应用代码,确保用户体验
方案
- 上面使用到的配置函数配合命令行参数动态计算配置对象
- 业界比较流行的是将不同环境配置分别维护在单独的配置文件中 之后配合
--config
选项指定配置目标
arduino
npx webpack --config webpack.development.js
arduino
.
└── config
├── webpack.common.js
├── webpack.development.js
├── webpack.testing.js
└── webpack.production.js
Webpack入口Entry
使用 entry.dependOn
声明入口依赖:
css
module.exports = {
// ...
entry: {
main: "./src/index.js",
foo: { import: "./src/foo.js", dependOn: "main" },
},
};
foo
入口的dependOn
属性指向main
入口,此时Webpack认为:客户端在加载foo
产物之前必然会加载main
,因此可以将重复的模块代码、运行时代码等都放到main
产物,减少不必要的重复
dependOn适用于哪些有明确入口依赖的场景,例如我们构建了一个主框架Bundle,其中包含了项目基本框架(如React),之后还需要为每个页面单独构建Bundle,这些页面代码也都依赖于主框架代码,此时可用dependOn属性优化产物内容,减少代码重复
使用 entry.runtime
管理运行时代码:
css
const path = require("path");
module.exports = {
mode: "development",
devtool: false,
entry: {
main: { import: "./src/index.js", runtime: "common-runtime" },
foo: { import: "./src/foo.js", runtime: "common-runtime" },
},
output: {
clean: true,
filename: "[name].js",
path: path.resolve(__dirname, "dist"),
},
};
为支持产物代码在各种环境中正常运行,Webpack会在产物文件中注入一系列运行时代码,用以支撑起整个应用框架,运行时代码的多寡取决于我们用到多少特性,例如:
- 需要导入导出文件时,将注入
__webpack_require_.r
等 - 使用异步加载时,将注入
__webpack_require_.l
等
不要小看运行时代码量,极端情况下甚至可能超过业务代码总量,为此,必要时我们可以尝试使用runtime
配置将运行时抽离为单独Bundle,如上:
实例中,
main
与foo
入口均将runtime
声明为common-runtime
,此时Webpack会将两个入口的运行时代码都抽取出来,放在common-runtime
Bundle中
entry.runtime
是一种常用的应用性能优化手段
Webpack 出口 Output
-
target
支持设置构建目标- web
- browserslist
- electron
php
const path = require("path");
const { merge } = require("webpack-merge");
const baseConfig = {
mode: "development",
target: "web",
devtool: false,
entry: {
main: { import: "./src/index.js" },
},
output: {
clean: true,
path: path.resolve(__dirname, "dist"),
},
};
module.exports = [
merge(baseConfig, { target: "web", output: { filename: "web-[name].js" } }),
merge(baseConfig, { target: "node", output: { filename: "node-[name].js" } }),
];
性能优化
Webpack底层工作流程
-
初始化阶段:
- 初始化参数:从配置文件、配置对象、Shell参数中读取,与默认配置结合得出最终的参数
- 创建编译器对象:用上一步的到的参数创建Compiler对象
- 初始化编译环境:包括注入内容插件、注册各种模块工厂、初始化RuleSet集合、加载配置的插件等
- 开始编译:执行comiler对象的run方法,创建Compilation对象
- 确定入口:根据配置中的
entry
找出所有的入口文件,调用compilation.addEntry
将入口文件转换为dependence
对象
-
构建阶段:
- 编译模块:从
entry
文件开始,调用loader
将模块转译为标准JS内容,调用JS解析器将内容转换为AST对象,从中找出该模块依赖的模块,再递归处理这些依赖模块,直到所有入口依赖的文件都经过了本步骤的处理 - 完成模块编译:上一步递归处理所有能触达到的模块后,的到了每个模块被翻译后的内容以及它们之间的依赖关系图
- 编译模块:从
-
封装阶段:
- 合并:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的
Chunk
- 优化:对上述
Chunk
施加一系列优化操作,包括:tree-shaking
、terser
、scope-hoisting
、压缩
、Code Split
等 - 写入文件系统(emitAssets):在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
- 合并:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的
可能造成性能问题的地方
-
构建阶段:
- 首先需要将文件的相对引用路径转换为绝对路径,这个过程可能涉及多次IO操作,执行效率取决于文件层次深度
- 找到具体文件后,需要读入文件内容并调用
loader-runner
遍历Loader数组完成内容转译,这个过程需要执行较密集的CPU操作,执行效率取决于Loader的数量与复杂度 - 需要将模块内容解析为AST结构,并遍历AST找出模块的依赖资源,这个过程同样需要较密集的CPU操作,执行效率取决于代码复杂度
- 递归处理依赖资源,执行效率取决于模块数量
-
封装阶段:
- 根据
splitChunks
配置、entry
配置、动态模块引用语句等,确定模块与Chunk的映射关系,其中splitChunks
相关的分包算法非常复杂,涉及大量CPU计算 - 根据
optimization
配置执行一系列产物优化操作,特别是Terser
插件需要执行大量AST相关的运算,执行效率取决于产物代码量
- 根据
性能分析
Webpack内置了stats接口,专门用于统计模块构建耗时,模块依赖关系等信息,推荐用法
- 添加
profile=true
配置
java
// webpack.config.js
module.exports = {
// ...
profile: true
}
- 运行编译命令,并添加
--json
参数,参数值为最终生成的统计文件名,如:
ini
npx webpack --json=stats.json
- 最后生成
stats.json
如下
json
{
"hash": "2c0b66247db00e494ab8",
"version": "5.36.1",
"time": 81,
"builtAt": 1620401092814,
"publicPath": "",
"outputPath": "/Users/tecvan/learn-webpack/hello-world/dist",
"assetsByChunkName": { "main": ["index.js"] },
"assets": [
// ...
],
"chunks": [
// ...
],
"modules": [
// ...
],
"entrypoints": {
// ...
},
"namedChunkGroups": {
// ...
},
"errors": [
// ...
],
"errorsCount": 0,
"warnings": [
// ...
],
"warningsCount": 0,
"children": [
// ...
]
}
modules
:本次打包处理的所有模块列表,内容包含模块的大小、所属chunk
、构建原因、依赖模块等,特别是modules.profile
属性,包含了构建该模块时,解析路径、编译、打包、子模块打包等各个环节所花费的时间chunks
:构建过程生成的chunks
列表,数组内容包含chunk
名称、大小、包含了哪些模块等assets
:编译后最终输出的产物列表、文件路径、文件大小等entrypoints
:entry列表,包含动态引入所产生的entry项也会包含在这里面children
:子Compiler对象的性能数据,例如extract-css-chunk-plugin
插件内部就会调用compilation.createChildCompiler
函数创建出子Compiler来做CSS抽取的工作
常用性能可视化分析工具
- Webpack Analysis :Webpack 官方提供的,功能比较全面的
stats
可视化工具; - Statoscope:主要侧重于模块与模块、模块与 chunk、chunk 与 chunk 等,实体之间的关系分析;
- Webpack Visualizer:一个简单的模块体积分析工具,真的很简单!
- Webpack Bundle Analyzer:应该是使用率最高的性能分析工具之一,主要实现以 Tree Map 方式展示各个模块的体积占比;
- Webpack Dashboard:能够在编译过程实时展示编译进度、模块分布、产物信息等;
- Unused Webpack Plugin:能够根据
stats
数据反向查找项目中未被使用的文件。
持久化缓存
仅需在Webpack中设置cache.type='filesystem'
java
module.exports = {
//...
cache: {
type: 'filesystem'
},
//...
};
cache
还提供了若干用于配置缓存效果、缓存周期的配置项,包括
type
:缓存类型,支持memory
|filesystem
,需要设置为filesystem
才能开启持久缓存cacheDirectory
:缓存文件路径,默认为node_modules/.cache/webpack
buildDependencies
:额外的依赖文件,当这些文件内容发生变化时,缓存会完全失效而执行完整的编译构建,通常可设置为各种配置文件
css
module.exports = {
cache: {
type: 'filesystem',
buildDependencies: {
config: [
path.join(__dirname, 'webpack.dll_config.js'),
path.join(__dirname, '.babelrc')
],
},
},
};
managedPaths
:受控目录,Webpack构建时会跳过新旧代码哈希值与时间戳的比较,直接使用缓存副本,默认值为['./node_modules']
profile
:是否输出缓存处理过程的详细日志,默认为false
maxAge
:缓存失效时间,默认值为5184000000
并行构建
-
HappyPack:多进程方式运行资源加载(Loader)逻辑;
-
Thread-loader:Webpack 官方出品,同样以多进程方式运行资源加载逻辑;
-
Parallel-Webpack:多进程方式运行多个 Webpack 构建实例;
-
TerserWebpackPlugin:支持多进程方式执行代码压缩、uglify 功能。
Webpack5使用Thread-loader
- 安装依赖:
arduino
yarn add -D thread-loader
- 将 Thread-loader 放在
use
数组首位,确保最先运行,如:
yaml
module.exports = {
module: {
rules: [
{
test: /.js$/,
use: [
{
loader: "thread-loader",
options: {
workers: 2,
workerParallelJobs: 50,
// ...
},
},
"babel-loader",
"eslint-loader",
],
},
],
},
};
-
在Thread-loader中运行的Loader不能调用
emitAsset
等接口,这会导致style-loader
这一类加载器无法正常工作,解决方案是将这类组件放置在thread-loader
之前,如['style-loader','thread-loader','css-loader']
-
Loader中不能活去
compilation、compiler
等实例对象,也无法活去Webpack配置
并行压缩
Webpack5默认提供Terser来进行代码压缩和混淆
ini
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [new TerserPlugin({
parallel: 2 // number | boolean
})],
},
};
上述配置即可设定最大并行进程数为2
注意:
理论上,并行确实能够提升系统运行效率,但 Node 单线程架构下,所谓的并行计算都只能依托与派生子进程执行,而创建进程这个动作本身就有不小的消耗 ------ 大约 600ms,对于小型项目,构建成本可能可能很低,引入多进程技术反而导致整体成本增加,因此建议大家按实际需求斟酌使用上述多进程方案。
按需编译
使用lazyCompilation
,用于实现entry
或异步引用模块的按需编译
java
// webpack.config.js
module.exports = {
// ...
experiments: {
lazyCompilation: true,
},
};
启动lazyCompilation
后,代码中通过异步引用语句如import('./xxx')
导入的模块都不会被立即编译,而是直到页面正式请求该模块资源(例如切换到该路由)时才开始构建,效果与Vite相似,能够极大提升冷启动速度
约束Loader执行范围
使用include
、exclude
等配置项,限定Loader的执行范围------通常可以排除node_modules
文件夹
javascript
// webpack.config.js
module.exports = {
// ...
module: {
rules: [
{
test: /.js$/,
exclude: /node_modules/,
use: ["babel-loader", "eslint-loader"],
},
],
},
};
javascript
const path = require("path");
module.exports = {
// ...
module: {
rules: [{
test: /.js$/,
exclude: {
and: [/node_modules/],
not: [/node_modules/lodash/]
},
use: ["babel-loader", "eslint-loader"]
}],
}
};
// 支持 and/not/or
上述,跳过出了loadash之外的
使用noParse
跳过已经编译好的文件的编译
java
// webpack.config.js
module.exports = {
//...
module: {
noParse: /lodash|react/,
},
};
// 跳过lodash和react的编译
最好不用,因为跳过了npm,可能导致运行时才能发现一些错误
开发模式仅用产物优化
Webpack提供了许多产物优化功能,但是这些优化在开发环境中意义不大,反而会增加构建器的负担,因此建议关闭这一类优化功能:
- 确保
mode='development'
或mode = 'none'
,关闭默认优化策略; optimization.minimize
保持默认值或false
,关闭代码压缩;optimization.concatenateModules
保持默认值或false
,关闭模块合并;optimization.splitChunks
保持默认值或false
,关闭代码分包;optimization.usedExports
保持默认值或false
,关闭 Tree-shaking 功能;
yaml
module.exports = {
// ...
mode: "development",
optimization: {
removeAvailableModules: false,
removeEmptyChunks: false,
splitChunks: false,
minimize: false,
concatenateModules: false,
usedExports: false,
},
};
最小化watch
监控范围
通常情况下,node_modules
不会频繁更新,不需要一直监听
javascript
// webpack.config.js
module.exports = {
//...
watchOptions: {
ignored: /node_modules/
},
};
跳过TS类型检查
yaml
module.exports = {
// ...
module: {
rules: [{
test: /.ts$/,
use: [
{
loader: 'ts-loader',
options: {
// 设置为"仅编译",关闭类型检查
transpileOnly: true
}
}
],
}],
}
};
将TS检查交给其他的:
- 可以借助编辑器的TypeScript插件实现代码检查
- 使用
fork-ts-checker-webpack-plugin
插件将类型检查能力剥离到子进程执行
javascript
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
module.exports = {
// ...
module: {
rules: [{
test: /.ts$/,
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true
}
}
],
}, ],
},
plugins:[
// fork 出子进程,专门用于执行类型检查
new ForkTsCheckerWebpackPlugin()
]
};
优化ESLint性能
使用新版eslint-webpack-plugin
替代旧版eslint-loader
ini
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
// ...
plugins: [new ESLintPlugin(options)],
// ...
};
设置resolve
缩小搜索范围
resolve.extensions
:['.js', '.json', '.wasm'] 可以减少匹配的项
SplitChunksPlugin分包
minChunks
:用于设置引用阈值,被引用次数超过该阈值的 Module 才会进行分包处理;maxInitialRequest/maxAsyncRequests
:用于限制 Initial Chunk(或 Async Chunk) 最大并行请求数,本质上是在限制最终产生的分包数量;minSize
: 超过这个尺寸的 Chunk 才会正式被分包;maxSize
: 超过这个尺寸的 Chunk 会尝试继续做分包;maxAsyncSize
: 与maxSize
功能类似,但只对异步引入的模块生效;maxInitialSize
: 与maxSize
类似,但只对entry
配置的入口模块生效;enforceSizeThreshold
: 超过这个尺寸的 Chunk 会被强制分包,忽略上述其它 size 限制;cacheGroups
:用于设置缓存组规则,为不同类型的资源设置更有针对性的分包策略。
yaml
module.exports = {
//...
optimization: {
splitChunks: {
cacheGroups: {
default: {
idHint: "",
reuseExistingChunk: true,
minChunks: 2,
priority: -20
},
defaultVendors: {
idHint: "vendors",
reuseExistingChunk: true,
test: /[\/]node_modules[\/]/i,
priority: -10
}
},
},
},
};
使用terser-webpack-plugin
压缩JS代码
javascript
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
entry: { foo: "./src/foo.js", bar: "./src/bar.js" },
output: {
filename: "[name].js",
// ...
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
test: /foo.js$/i,
extractComments: "all",
}),
new TerserPlugin({
test: /bar.js/,
extractComments: false,
}),
],
},
};
terser-webpack-plugin 是一个颇为复杂的 Webpack 插件,提供下述 配置项:
test
:只有命中该配置的产物路径才会执行压缩,功能与 module.rules.test 相似;include
:在该范围内的产物才会执行压缩,功能与 module.rules.include 相似;exclude
:与include
相反,不在该范围内的产物才会执行压缩,功能与 module.rules.exclude 相似;parallel
:是否启动并行压缩,默认值为true
,此时会按os.cpus().length - 1
启动若干进程并发执行;minify
:用于配置压缩器,支持传入自定义压缩函数,也支持swc/esbuild/uglifyjs
等值,下面我们再展开讲解;terserOptions
:传入minify
------ "压缩器"函数的配置参数;extractComments
:是否将代码中的备注抽取为单独文件,可配合特殊备注如@license
使用。
使用CssMinimizerWebpackPlugin压缩CSS
javascript
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
//...
module: {
rules: [
{
test: /.css$/,
// 注意,这里用的是 `MiniCssExtractPlugin.loader` 而不是 `style-loader`
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
],
},
optimization: {
minimize: true,
minimizer: [
// Webpack5 之后,约定使用 `'...'` 字面量保留默认 `minimizer` 配置
"...",
new CssMinimizerPlugin(),
],
},
// 需要使用 `mini-css-extract-plugin` 将 CSS 代码抽取为单独文件
// 才能命中 `css-minimizer-webpack-plugin` 默认的 `test` 规则
plugins: [new MiniCssExtractPlugin()],
};
- 使用
mini-css-extract-plugin
将CSS代码抽取为单独的CSS产物文件,这样才能命中css-minimizer-webpack-plugin
默认的test
逻辑 - 使用
css-minimizer-webpack-plugin
压缩CSS代码
使用HtmlMinifierTerser压缩HTML
csharp
yarn add -D html-minimizer-webpack-plugin
xml
const HtmlWebpackPlugin = require("html-webpack-plugin");
const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin");
module.exports = {
// ...
optimization: {
minimize: true,
minimizer: [
// Webpack5 之后,约定使用 `'...'` 字面量保留默认 `minimizer` 配置
"...",
new HtmlMinimizerPlugin({
minimizerOptions: {
// 折叠 Boolean 型属性
collapseBooleanAttributes: true,
// 使用精简 `doctype` 定义
useShortDoctype: true,
// ...
},
}),
],
},
plugins: [
// 简单起见,这里我们使用 `html-webpack-plugin` 自动生成 HTML 演示文件
new HtmlWebpackPlugin({
templateContent: `<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta charset="UTF-8" />
<title>webpack App</title>
</head>
<body>
<input readonly="readonly"/>
<!-- comments -->
<script src="index_bundle.js"></script>
</body>
</html>`,
}),
],
};
零碎的优化方案
动态加载
dart
document.getElementById("someButton").addEventListener("click", async () => {
// 使用 `import("module")` 动态加载模块
const someBigMethod = await import("./someBigMethod");
someBigMethod();
});
HTTP缓存优化 HASH名协商缓存
css
module.exports = {
// ...
entry: { index: "./src/index.js", foo: "./src/foo.js" },
output: {
filename: "[name]-[contenthash].js",
path: path.resolve(__dirname, "dist"),
},
plugins: [new MiniCssExtractPlugin({ filename: "[name]-[contenthash].css" })],
};
使用Tree-Shaking删除多余模块导出
在 Webpack 中,启动 Tree Shaking 功能必须同时满足两个条件:
-
配置
optimization.usedExports
为true
,标记模块导入导出列表; -
启动代码优化功能,可以通过如下方式实现:
- 配置
mode = production
- 配置
optimization.minimize = true
- 提供
optimization.minimizer
数组
- 配置
java
// webpack.config.js
module.exports = {
mode: "production",
optimization: {
usedExports: true,
},
};
使用Scope Hoisting合并模块
Webpack 提供了三种开启 Scope Hoisting 的方法:
- 使用
mode = 'production'
开启生产模式; - 使用
optimization.concatenateModules
配置项; - 直接使用
ModuleConcatenationPlugin
插件。
ruby
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
module.exports = {
// 方法1: 将 `mode` 设置为 production,即可开启
mode: "production",
// 方法2: 将 `optimization.concatenateModules` 设置为 true
optimization: {
concatenateModules: true,
usedExports: true,
providedExports: true,
},
// 方法3: 直接使用 `ModuleConcatenationPlugin` 插件
plugins: [new ModuleConcatenationPlugin()]
};
监控产物体积
javascript
module.exports = {
// ...
performance: {
// 设置所有产物体积阈值
maxAssetSize: 172 * 1024,
// 设置 entry 产物体积阈值
maxEntrypointSize: 244 * 1024,
// 报错方式,支持 `error` | `warning` | false
hints: "error",
// 过滤需要监控的文件类型
assetFilter: function (assetFilename) {
return assetFilename.endsWith(".js");
},
},
};
Loader
Webpack流程
webpack打包阶段
- 初始化阶段:负责设置构建环境,初始化若干工厂类、注入内置插件等
- 构建阶段:读入并分析Entry模块,找到模块依赖,之后递归处理这些依赖、依赖的依赖,直到所有模块都处理完毕,这个过程解决资源输入问题
- 生成阶段:根据Entry配置将模块封装进不同Chunk对象,经过一系列优化后,再将模块代码翻译为产物形态,按Chunk合并成最终产物文件,这个过程解决资源输出的问题