一、Webpack 打包流程详解
Webpack 本质上是一个静态模块打包工具 ,它的核心流程可以概括为三个阶段:初始化 → 编译 → 输出。
1.1 整体流程概览

1.2 详细阶段拆解
阶段一:初始化
- 读取配置文件
webpack.config.js - 创建
Compiler对象,它是整个编译过程的"指挥官" - 注册所有内置插件和用户配置的插件
- 调用
compiler.run()启动编译
阶段二:编译构建
- 确定入口 :从
entry配置开始,读取入口文件内容 - 模块解析:
-
- 将文件内容解析为 AST(抽象语法树)
- 找出文件中所有的
import/require语句 - 递归解析所有依赖,构建依赖关系图
- Loader 处理:在解析过程中,匹配到的 Loader 会对模块源码进行转换
- 生成 Chunk:根据依赖关系图,将模块分组为不同的 Chunk
阶段三:输出文件
- 根据
output配置,将每个 Chunk 渲染成一个独立的 bundle 文件 - 执行 Plugin 的
emit钩子,允许修改最终输出的资源 - 写入磁盘
1.3 核心数据结构
| 概念 | 说明 | 示例 |
|---|---|---|
| Module | 一个文件就是一个 Module | .js, .css, .png |
| Chunk | 代码块,由多个 Module 组成 | 入口 chunk、异步 chunk |
| Bundle | 最终输出的文件 | main.js, vendors.js |
二、Loader 深入理解
2.1 Loader 是什么?
Loader 是一个导出为函数的 Node 模块,用于对模块源码进行转换。因为 Webpack 原生只认识 JavaScript 和 JSON,Loader 负责将其他类型的文件(CSS、图片、TypeScript 等)转换为 Webpack 能够处理的模块。
2.2 Loader 的执行顺序
java
javascript
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /.less$/,
use: [
'style-loader', // ③ 最后执行
'css-loader', // ② 中间执行
'less-loader' // ① 最先执行
]
}
]
}
}
执行顺序规律 :从右到左,从下到上(类似于函数组合 compose)
2.3 Loader 的分类
| 类型 | 特点 | 示例 |
|---|---|---|
| 同步 Loader | 直接返回转换结果 | babel-loader |
| 异步 Loader | 通过 this.async() 回调 | file-loader |
| Raw Loader | 接收 Buffer 而非字符串 | 处理图片、字体 |
| Pitch Loader | 在正常执行之前运行 | style-loader 的 pitch |
三、Plugin 深入理解
3.1 Plugin 是什么?
Plugin 是一个具有 apply 方法的 JavaScript 类 。它通过监听 Webpack 生命周期中的钩子(Hook) ,在特定时机介入编译过程,从而实现自动化操作。
3.2 Plugin 与 Loader 的区别
| 维度 | Loader | Plugin |
|---|---|---|
| 作用 | 转换模块内容 | 扩展功能、干预流程 |
| 运行时机 | 模块解析阶段 | 整个编译生命周期 |
| 本质 | 函数 | 类(含 apply 方法) |
| 能力 | 有限,只能处理文件内容 | 强大,可访问 Compiler 和 Compilation |
四、自定义 Loader 实战
4.1 自定义同步 Loader:移除 console.log
js
// loaders/strip-console-loader.js
/**
* 功能:移除代码中的所有 console.log 语句
* 用法:{
* test: /.js$/,
* use: './loaders/strip-console-loader.js'
* }
*/
module.exports = function(source) {
// source: 源文件内容(字符串)
// this: Webpack 提供的 Loader API
// 使用正则替换所有 console.log
const result = source.replace(/console.log([^)]*);?/g, '');
// 同步 Loader 必须返回处理后的内容
return result;
};
4.2 自定义异步 Loader:添加版权注释
js
// loaders/copyright-loader.js
const path = require('path');
module.exports = function(source) {
// 标记为异步 Loader
const callback = this.async();
// 模拟异步操作(比如读取版权文件)
setTimeout(() => {
const copyright = `
/**
* Copyright © ${new Date().getFullYear()} 技术分享会
* Author: Webpack 实战
*/
`;
// 在文件头部插入版权注释
const result = copyright + source;
// 异步 Loader 必须调用 callback
callback(null, result);
}, 100);
};
4.3 使用自定义 Loader
js
// webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /.js$/,
use: [
'./loaders/copyright-loader.js',
'./loaders/strip-console-loader.js'
]
}
]
}
};
五、自定义 Plugin 实战
5.1.1 自定义 Plugin:生成构建报告
js
// plugins/build-report-plugin.js
class BuildReportPlugin {
constructor(options) {
// 接收用户配置
this.options = options || {};
this.filename = options.filename || 'build-report.json';
}
// apply 方法是 Plugin 的入口,接收 compiler 参数
apply(compiler) {
// 监听 compilation 钩子,获取编译对象
compiler.hooks.compilation.tap('BuildReportPlugin', (compilation) => {
// 监听 processAssets 钩子,在优化 chunk 资源时执行
compilation.hooks.processAssets.tap(
{
name: 'BuildReportPlugin',
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
},
(assets) => {
// 收集构建信息
const report = {
timestamp: new Date().toISOString(),
totalModules: compilation.modules.size,
chunks: [],
assets: []
};
// 遍历所有 chunk
for (const chunk of compilation.chunks) {
report.chunks.push({
id: chunk.id,
name: chunk.name,
files: Array.from(chunk.files),
size: chunk.size()
});
}
// 遍历所有资源
for (const [filename, source] of Object.entries(assets)) {
report.assets.push({
name: filename,
size: source.size()
});
}
// 将报告作为额外资源添加到输出
const reportContent = JSON.stringify(report, null, 2);
compilation.emitAsset(
this.filename,
new compiler.webpack.sources.RawSource(reportContent)
);
}
);
});
// 监听 done 钩子,在构建完成后打印信息
compiler.hooks.done.tap('BuildReportPlugin', (stats) => {
console.log(`\n📊 构建报告已生成:${this.filename}`);
console.log(`⏱ 耗时:${stats.endTime - stats.startTime}ms`);
console.log(`📦 输出文件数:${Object.keys(stats.compilation.assets).length}\n`);
});
}
}
module.exports = BuildReportPlugin;
5.1.2 自定义 Plugin:自动上传到 CDN
js
// plugins/upload-to-cdn-plugin.js
const path = require('path');
const fs = require('fs');
class UploadToCDNPlugin {
constructor(options) {
this.options = options || {};
this.cdnUrl = options.cdnUrl || 'https://cdn.example.com';
this.bucket = options.bucket || 'default';
}
apply(compiler) {
// 监听 afterEmit 钩子,在文件写入磁盘后执行
compiler.hooks.afterEmit.tapAsync('UploadToCDNPlugin', (compilation, callback) => {
const outputPath = compilation.outputOptions.path;
// 遍历所有输出的资源
const assets = compilation.getAssets();
const uploadPromises = assets.map(async (asset) => {
const filePath = path.join(outputPath, asset.name);
// 模拟上传到 CDN
console.log(`☁️ 正在上传 ${asset.name} 到 CDN...`);
// 这里可以接入真实的 CDN SDK,比如阿里云 OSS、AWS S3
// await s3.putObject({ Bucket: this.bucket, Key: asset.name, Body: fs.createReadStream(filePath) });
// 模拟上传延迟
await new Promise(resolve => setTimeout(resolve, 200));
console.log(`✅ ${asset.name} 上传完成`);
console.log(`🔗 CDN URL: ${this.cdnUrl}/${asset.name}`);
});
Promise.all(uploadPromises).then(() => {
console.log('\n🎉 所有资源已上传到 CDN!');
callback();
}).catch((err) => {
console.error('❌ 上传失败:', err);
callback(err);
});
});
}
}
module.exports = UploadToCDNPlugin;
5.2 使用自定义 Plugin
js
// webpack.config.js
const BuildReportPlugin = require('./plugins/build-report-plugin');
const UploadToCDNPlugin = require('./plugins/upload-to-cdn-plugin');
module.exports = {
// ... 其他配置
plugins: [
new BuildReportPlugin({
filename: 'report.json'
}),
new UploadToCDNPlugin({
cdnUrl: 'https://my-cdn.example.com',
bucket: 'production'
})
]
};
六、Loader vs Plugin 对比总结
| 对比维度 | Loader | Plugin |
|---|---|---|
| 本质 | 函数(Function) | 类(Class) |
| 核心方法 | module.exports = function(source) {} | class X { apply(compiler) {} } |
| 作用对象 | 单个文件(Module) | 整个构建过程(Compilation) |
| 触发时机 | 模块解析和转换时 | 构建生命周期的各个阶段 |
| 能力范围 | 文件内容转换 | 资源管理、环境变量、优化、输出等 |
| 配置方式 | module.rules数组 | plugins数组 |
| API 来源 | this上下文(Loader Context) | compiler和 compilation对象 |