Webpack模块打包原理
约 1621 字大约 5 分钟
webpackbundling
2025-07-28
概述
Webpack 是目前最流行的 JavaScript 模块打包工具,它将项目中的所有资源(JavaScript、CSS、图片等)视为模块,构建依赖图(Dependency Graph),然后打包成浏览器可执行的静态资源。理解 Webpack 的内部工作原理,有助于优化构建配置和排查打包问题。
Webpack 工作流程总览
1. 依赖图构建
Webpack 从 entry 出发,递归解析所有依赖,构建完整的模块依赖图。
// webpack.config.js
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};模块解析过程
// 模块解析配置
module.exports = {
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx'], // 自动补全扩展名
alias: {
'@': path.resolve(__dirname, 'src'), // 路径别名
'vue$': 'vue/dist/vue.esm.js' // 精确匹配
},
modules: ['node_modules', 'src'], // 模块查找目录
mainFields: ['browser', 'module', 'main'] // package.json 入口字段
}
};2. Loader 机制
Loader 负责将非 JavaScript 文件转换为 Webpack 可处理的模块。Loader 本质上是一个转换函数。
Loader 的执行顺序
module.exports = {
module: {
rules: [
{
test: /\.scss$/,
// Loader 从右到左(从下到上)执行
use: [
'style-loader', // 3. 将 CSS 注入 <style> 标签
'css-loader', // 2. 解析 CSS 中的 @import 和 url()
'sass-loader' // 1. 将 SCSS 编译为 CSS
]
},
{
test: /\.tsx?$/,
use: [
{
loader: 'ts-loader',
options: { transpileOnly: true } // Loader 配置选项
}
]
}
]
}
};自定义 Loader
// my-loader.js
module.exports = function(source) {
// this: Loader Context
// source: 文件原始内容
// 同步 Loader
const result = source.replace(/console\.log\(.*?\);?/g, '');
return result;
};
// 异步 Loader
module.exports = function(source) {
const callback = this.async(); // 声明异步
someAsyncOperation(source)
.then(result => callback(null, result))
.catch(err => callback(err));
};
// Pitch Loader(从左到右执行,可提前终止链)
module.exports.pitch = function(remainingRequest) {
// 如果返回值,则跳过后续 Loader
if (someCondition) {
return 'module.exports = "cached result"';
}
};3. Plugin 机制与 Tapable
Plugin 基于 Tapable 事件系统,在 Webpack 构建生命周期的各个钩子中执行自定义逻辑。
自定义 Plugin
class MyPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
// compiler 钩子:整个构建过程
compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
// compilation 钩子:单次编译过程
compilation.hooks.optimizeChunks.tap('MyPlugin', (chunks) => {
// 在 chunk 优化阶段执行逻辑
for (const chunk of chunks) {
console.log(`Chunk: ${chunk.name}, Modules: ${chunk.getNumberOfModules()}`);
}
});
});
// 异步钩子
compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
// 在输出文件前执行异步操作
const content = JSON.stringify(Object.keys(compilation.assets));
compilation.assets['filelist.json'] = {
source: () => content,
size: () => content.length
};
callback();
});
}
}
module.exports = MyPlugin;Tapable 钩子类型
const { SyncHook, AsyncSeriesHook, AsyncParallelHook } = require('tapable');
class Car {
constructor() {
this.hooks = {
start: new SyncHook(['speed']), // 同步执行
brake: new AsyncSeriesHook(['force']), // 异步串行
boost: new AsyncParallelHook([]), // 异步并行
};
}
}
const car = new Car();
// 注册
car.hooks.start.tap('Logger', (speed) => {
console.log(`Starting at ${speed}`);
});
car.hooks.brake.tapAsync('Safety', (force, cb) => {
setTimeout(() => {
console.log(`Braking with force ${force}`);
cb();
}, 1000);
});
// 触发
car.hooks.start.call(100);
car.hooks.brake.callAsync(5, () => console.log('Brake complete'));4. Code Splitting(代码分割)
module.exports = {
// 多入口
entry: {
main: './src/main.js',
admin: './src/admin.js'
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: -10
},
common: {
minChunks: 2, // 被至少 2 个 chunk 引用
name: 'common',
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
// 动态导入(按需加载)
const LazyComponent = () => import(
/* webpackChunkName: "lazy-component" */
/* webpackPreload: true */
'./LazyComponent'
);5. Tree Shaking
Tree Shaking 利用 ES Module 的静态结构,在打包时移除未使用的导出。
// utils.js
export function used() {
return 'I am used';
}
export function unused() {
return 'I am not used'; // 会被 tree shaking 移除
}
// index.js
import { used } from './utils';
console.log(used());// webpack.config.js
module.exports = {
mode: 'production', // 生产模式自动启用 tree shaking
optimization: {
usedExports: true, // 标记未使用的导出
minimize: true, // 启用代码压缩(由 Terser 删除标记的代码)
sideEffects: true // 启用 sideEffects 优化
}
};
// package.json 中标记无副作用
{
"sideEffects": false, // 所有文件无副作用
// 或指定有副作用的文件
"sideEffects": ["*.css", "*.scss", "./src/polyfills.js"]
}6. Hot Module Replacement(HMR)
// HMR API 使用
if (module.hot) {
module.hot.accept('./App', () => {
// 模块更新时的处理逻辑
const NextApp = require('./App').default;
render(NextApp);
});
module.hot.dispose((data) => {
// 模块被替换前的清理逻辑
data.savedState = getCurrentState();
});
}7. 打包产物分析
Webpack 最终输出的 bundle 结构:
// bundle.js 简化结构
(function(modules) {
// 模块缓存
var installedModules = {};
// webpack 的 require 实现
function __webpack_require__(moduleId) {
// 检查缓存
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 创建新模块
var module = installedModules[moduleId] = {
id: moduleId,
loaded: false,
exports: {}
};
// 执行模块函数
modules[moduleId].call(
module.exports,
module,
module.exports,
__webpack_require__
);
module.loaded = true;
return module.exports;
}
// 入口模块
return __webpack_require__(0);
})([
/* 0 - index.js */
function(module, exports, __webpack_require__) {
var utils = __webpack_require__(1);
console.log(utils.greet('World'));
},
/* 1 - utils.js */
function(module, exports) {
exports.greet = function(name) {
return 'Hello, ' + name;
};
}
]);8. 常用优化配置
const TerserPlugin = require('terser-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: true, // 多进程压缩
terserOptions: {
compress: { drop_console: true } // 移除 console
}
})
],
moduleIds: 'deterministic', // 稳定的模块 ID(利于缓存)
runtimeChunk: 'single', // 提取 runtime 代码
},
cache: {
type: 'filesystem', // 文件系统缓存(加速二次构建)
buildDependencies: {
config: [__filename] // 配置文件变化时使缓存失效
}
},
plugins: [
new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' }),
new BundleAnalyzerPlugin() // 可视化分析 bundle 大小
]
};总结
Webpack 的核心工作流程是:从 Entry 出发构建依赖图,通过 Loader 转换各类资源,基于 Tapable 的 Plugin 系统在生命周期各阶段执行优化,最终输出打包后的静态资源。Code Splitting 实现按需加载,Tree Shaking 移除未使用代码,HMR 提供开发时的热更新体验。深入理解这些机制,能帮助开发者编写高效的构建配置,解决复杂的打包优化问题。
贡献者
更新日志
2026/3/14 13:09
查看所有更新日志
9f6c2-feat: organize wiki content and refresh site setup于