核心概念
-
本质:模块打包器
- 将各种资源(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'
核心工作原理
打包流程
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
,按需加载或并行加载,减少初始加载体积。
目的
- 减少初始加载体积:避免用户下载整个应用代码
- 提高加载速度:并行加载多个小文件比单个大文件更快
- 按需加载:只加载当前视图需要的代码
- 缓存优化:第三方库单独打包,充分利用浏览器缓存
实现方式
- 入口点分割 \手动
module.exports = {
entry: {
app: './src/app.js',
vendor: ['react', 'react-dom']
},
output: {
filename: '[name].bundles.js'
}
};
- 动态导入
const loadComponent = () => import('./HeavyComponent');
button.addEventListener('click', () => {
loadComponent().then(module => {
module.default.render();
});
});
- 智能分割
SplitChunksPlugin
optimization: {
splitChunks: {
chunks: 'all',
minSize: 20000,
maxSize: 0,
minChunks: 1,
cachedGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: -10
},
commons:{
name: 'commons',
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
代码分割策略
策略类型 | 适用场景 | 示例 |
---|
入口分割 | 多页面应用 | entry: { home: './home.js', about: './about.js' } |
动态分割 | 路由组件/弹窗 | () => import('./UserProfile') |
第三方库 | 稳定不常更新的库 | cacheGroups.vendors |
运行时代码 | Webpack运行时 | runtimeChunk: 'single' |
Tree Shaking
- 使用ES6模块语法
import
/export
- 生产模式自动启动
- 在
package.json
中添加:
"sideEffects": ["*.css", "*.global.js"]
缓存优化
output: {
filename: '[name].[contenthash].js'
}
懒加载
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
连接
- 文件修改触发重新编译
- 服务端发生更新消息
- 客户端拉取更新模块
- 执行模块替换逻辑