核心概念
-
本质:模块打包器
- 将各种资源(JS/CSS/图片等)视为模块
- 通过依赖关系构建依赖图
- 输出静态资源
bundle
-
五大核心概念
- Entry:打包入口起点,
entry: './src/index.js' - Output:输出位置配置,
path: path.resolve(__dirname, 'dist') - Loaders:处理非JS文件,
{ test: /\.css$/, use: ['style-loader', 'css-loader'] } - Plugins:扩展功能,
new HtmlWebpackPlugin() - Mode:环境模式,
mode:'development'
- Entry:打包入口起点,
核心工作原理
打包流程
graph LR
A[入口文件 Entry] --> B[解析模块依赖]
B --> C[构建依赖图]
C --> D[应用 Loaders 转换]
D --> E[打包模块资源]
E --> F[执行插件优化]
F --> G[输出 Bundles]
G --> H[完成打包]
classDef default fill:#f9f,stroke:#333,stroke-width:2px;
classDef process fill:#6cf,stroke:#333,stroke-width:2px;
classDef output fill:#2ecc71,stroke:#333,stroke-width:2px;
class A,H process;
class B,C,D,E,F process;
class G output;
关键过程
- 入口文件:
- 从配置的入口文件开始打包过程
- 通常是
index.js/main.js
- 解析模块依赖:
- 分析文件中的
import/require语句 - 递归查找所有依赖模块
- 分析文件中的
- 构建依赖图:
- 创建模块依赖关系图谱
- 确定模块加载顺序
- 应用Loaders转换:
- 使用配置的Loaders处理各类资源
- 如:JS转译、CSS预处理、图片优化
- 打包模块资源:
- 将所有模块合并为代码块
chunk - 应用代码分割规则
- 将所有模块合并为代码块
- 执行插件优化:
- 运行插件进行额外处理
- 如:代码压缩、资源注入、生成html
- 输出bundles
- 将最终结果写入文件系统
- 生成
JS/CSS/资源文件
- 完成打包
- 输出构建统计信息
- 触发回调通知
配置webpack.config.js
基础结构
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader',
},
],
},
plugins: [
new HtmlWebpackPlugin({ template: './src/index.html' }),
],
mode:'production',
};
Loaders
| 文件类型 | Loader配置 |
|---|---|
| Javascript | babel-loader, @babel/preset-env |
| CSS | ['style-loader', 'css-loader', 'postcss-loader'] |
| SASS | ['sass-loader'] [需配合css-loader] |
| 图片/字体 | type: 'asset/resource' [webpack5内置] |
| SVG | @svg/webpack |
Plugins
| 插件 | 用途 |
|---|---|
HtmlWebpackPlugin | 生成html文件 |
MiniCssExtractPlugin | 提取CSS到单独文件 |
DefinePlugin | 定义环境变量 |
CleanWebpackPlugin | 清理构建目录 |
BundleAnalyzerPlugin | 包体积分析 |
高级优化策略
代码分割Code Splitting
将应用代码拆分为多个独立报chunk,按需加载或并行加载,减少初始加载体积。
目的
- 减少初始加载体积:避免用户下载整个应用代码
- 提高加载速度:并行加载多个小文件比单个大文件更快
- 按需加载:只加载当前视图需要的代码
- 缓存优化:第三方库单独打包,充分利用浏览器缓存
实现方式
- 入口点分割 \手动
// webpack.config.js
module.exports = {
entry: {
app: './src/app.js',
vendor: ['react', 'react-dom']
},
output: {
filename: '[name].bundles.js'
}
};
- 动态导入
// 使用import语法
const loadComponent = () => import('./HeavyComponent');
button.addEventListener('click', () => {
loadComponent().then(module => {
module.default.render();
});
});
- 智能分割
SplitChunksPlugin
optimization: {
splitChunks: {
chunks: 'all',
minSize: 20000, // 将包拆分成不小于minSize的包
maxSize: 0, // 当一个包大于指定的大小时,才进行拆包
minChunks: 1,
// 将需要进行拆包的内容进行分包
cachedGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: -10
},
commons:{
name: 'commons',
minChunks: 2, // 被2个以上chunk引用
priority: -20,
reuseExistingChunk: true
}
}
}
}
代码分割策略
| 策略类型 | 适用场景 | 示例 |
|---|---|---|
| 入口分割 | 多页面应用 | entry: { home: './home.js', about: './about.js' } |
| 动态分割 | 路由组件/弹窗 | () => import('./UserProfile') |
| 第三方库 | 稳定不常更新的库 | cacheGroups.vendors |
| 运行时代码 | Webpack运行时 | runtimeChunk: 'single' |
Tree Shaking
移除JavaScript上下文中未使用的代码dead code
工作原理
- ES6模块静态分析:
import/export是静态声明(编译时确定)CommonJS是动态的(运行时确定)
- 标记未使用代码
// math.js
export function square(x) { return x * x; } // 被使用 ✅
export function cube(x) { return x * x * x; } // 未被使用❌
// app.js
import { square } from './math.js';
- 压缩阶段移除
- 通过
TerserPlugin等工具删除标记代码
- 通过
启动Tree Shaking
- 基本配置
// webpack.config.js
module.exports = {
mode: 'production',
optimization: {
usedExports: true, // 标记未使用
minimize: true // 移除未使用
}
}
- package.json配置
{
"name": "my-demo",
"sideEffects": [
"*.css",
"*.scss",
"@babel/polyfill"
]
}
sideEffects: false:所有文件无副作用sideEffects: [...]:列出有副作用的文件
常见问题及解决方案
- Babel转换导致失效
// .babelrc
{
"presets": [
["@babel/preset-env", { "modules": false }] // 关键:不转换ES6模块
]
}
- 第三方库不支持
// 使用es模块版本
import { debounce } from 'lodash-es';
// 或使用插件
const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
- CSS Tree Shaking[待深入]
// webpack.config.js ?
// 使用PurgeCSS
const PurgeCSSPlugin = require('purgecss-webpack-plugin');
{
...,
plugins: [
new PurgeCSSPlugin({
paths: glob.sync(`${PATH.src}/**/*`, { nodir: true }),
})
]
}
Code Splitting VS Tree Shaking
| 特性 | Code Splitting | Tree Shaking |
|---|---|---|
| 目的 | 拆分代码为多个包 | 移除未使用代码 |
| 优化方向 | 加载性能 | 包体积优化 |
| 触发时机 | 构建时+运行时 | 仅构建时 |
| 主要技术 | import() + SplitChunks | ES6模块 + Terser |
| 影响范围 | 模块/文件级别 | 函数/变量级别 |
| 依赖语法 | 动态import() | ES6 import/export |
最佳实践组合
- 优化流程
graph LR
A[原始代码] --> B[Tree Shaking]
B --> C[移除未使用代码]
C --> D[代码分割]
D --> E[按需加载]
- 配置示例
// webpack.config.js
module.exports = {
mode:'production',
optimization: {
usedExports: true,
splitChunks: {
chunks: 'all',
cachedGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors'
}
}
},
runtimeChunk: 'single'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [['@babel/preset-env', { modules:false }]]
}
}
}
]
}
}
-
代码分割是横向切割(将代码拆分成多个文件)
- 适用:路由分割、第三方库分离、动态加载
- 核心:
import()+splitChunks
-
Tree Shaking是纵向修剪(删除文件内未使用的代码)
- 适用:工具函数库、组件类、工具类
- 核心:ES6模块 +
sideEffects标记
-
最佳实践:两者结合使用,先通过Tree Shaking删除无用代码,再通过代码分割拆分剩余代码,可达到最佳优化效果。
缓存优化
output: {
filename: '[name].[contenthash].js'
}
懒加载
// dynamic import
const loadModule = () => import('./heavyModule.js');
自定义扩展
自定义loader
module.exports = function(source) {
return source.replace(/console\.log\(.*\);?/g, '');
};
自定义plugin
class myPlugin {
apply(complier) {
commplier.hooks.done.tap('MyPlugin', stats => {
console.log('编译完成!');
});
}
}
性能优化实践
构建速度优化
| 方法 | 实现 |
|---|---|
缓存loaders | use: ['cache-loader', 'babel-loader'] |
| 多进程处理 | thread-loader |
| DLL预构建 | DllPlugin + DllReferencePlugin |
| 缩小搜索范围 | resolve: { modules: [path.resolve('node_modules')] } |
输出优化
| 方法 | 实现 |
|---|---|
| CDN加速 | output.publicPath: 'https://cdn.example.com/' |
| Gzip压缩 | compression-webpack-plugin |
| 图片优化 | image-webpack-loader |
原理深入
Tapable事件流机制
compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
// 处理逻辑
callback();
});
核心对象关系
classDiagram
Compiler *-- Compilation
Compilation *-- Module
Module *-- Dependency
Compiler : hooks
Compilation : modules
Compilation : chunks
HMR热更新原理
- 建立
WebSocket连接 - 文件修改触发重新编译
- 服务端发生更新消息
- 客户端拉取更新模块
- 执行模块替换逻辑