一、webpack如何多入口打包文件
// entry: string | array | object
module.exports = {
entry: {
main: './src/app.js',
vendor: './src/vendor.js',
},
output: {
filename: '[name].[contenthash].bundle.js',
},
};
二、多入口打包优势
(1)依赖关系优化
通过分离入口点,webpack可以更智能地处理共享依赖,使得公共模块单独打包,入口点只包含专属逻辑,避免重复依赖
// 场景:app.js 和 vendor.js 都使用 lodash
optimization: {
splitChunks: {
chunks: 'all' // 自动提取公共依赖
}
}
(2)缓存策略优化
多入口文件支持更精细化的缓存策略,比如vendor.js这种低频修改的文件可以长期缓存,app.js这种高频修改的文件可以短周期缓存,使得热更新更快
- 按需加载的基础,多入口架构为后续的代码拆分和动态导入奠定了基础
// 后续可轻松改为动态导入
import(/* webpackChunkName: "vendor" */ './vendor.js')
(3)按需加载和动态导入的基础
// 后续可轻松改为动态导入
import(/* webpackChunkName: "vendor" */ './vendor.js')
三、多入口打包使用场景
(1) 主应用+第三方库分离
项目结构:
src/
├── app.js // 业务逻辑
├── vendor.js // 第三方库
└── utils.js // 共享工具
打包策略:
- 将稳定依赖(如React、Lodash)放入vendor
- 高频变动的业务逻辑放入app
- 共享工具自动提取
打包结果特征:
- vendor包变化频率低 → 适合持久缓存
- app包体积精简 → 加快热更新速度
(2) 多页面应用(MPA)架构
对于传统多页面项目:
entry: {
home: './src/home/index.js',
about: './src/about/index.js',
contact: './src/contact/index.js'
}
配合 html-webpack-plugin 自动生成各页面的HTML:
plugins: [
new HtmlWebpackPlugin({
template: './home.html',
chunks: ['home'] // 只注入home相关的JS
})
]
(3)微前端架构适配
在多团队协作的微前端场景中:
entry: {
main: './src/main.js',
'team-a': './src/team-a/bootstrap.js',
'team-b': './src/team-b/bootstrap.js'
}
优势体现:
- 独立团队拥有自己的入口
- 共享依赖自动优化
- 独立部署能力
四、通过插件分析入口依赖
性能监控集成
结合webpack-bundle-analyzer分析各入口:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static'
})
]
};
五、webpack打包的完整过程
核心打包流程
输入 (源代码) -> 依赖解析与模块转换 -> 构建依赖图 -> 封装与代码生成 -> 输出 (Bundle)
下面我们详细拆解每一步。
1. 初始化:读取配置与创建 Compiler
- webpack 启动后,首先会读取你的
webpack.config.js和命令行参数,合并得到最终的配置。 - 然后,初始化 Compiler 对象。Compiler 是 webpack 的编译管理器,是贯穿整个打包周期的核心对象。它包含了完整的配置信息,并暴露了生命周期钩子。
javascript
// 伪代码,示意过程
const webpack = (options, callback) => {
// 1. 创建 compiler 对象
const compiler = new Compiler(options.context);
compiler.options = options;
// 2. 注册所有配置的插件
if (options.plugins && Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
// 调用插件的 apply 方法,将 compiler 实例传入
plugin.apply(compiler);
}
}
// 3. 触发 environment, initialize 等生命周期钩子
// ...
return compiler;
};
2. 编译与构建模块图
这是最复杂也是最核心的一步。Compiler 会找到一个入口文件(如 ./src/index.js),开始编译。
a. 模块解析
- 读取入口文件内容。
- 调用 AST(抽象语法树)解析器(默认是
acorn)将源代码转换成 AST,便于分析。
b. 依赖收集
- 遍历 AST,找到所有的
import,require,define等模块导入语句。 - 根据导入语句的路径,使用 enhanced-resolve 库解析出模块的绝对路径。这就是为什么 webpack 能处理各种模块规范(ES6, CommonJS, AMD)和文件类型。
c. 模块转换
-
对于每一个解析出来的模块,根据配置中的
module.rules(loader 规则)进行处理。 -
Loader 的本质是一个函数,它接收源文件内容,返回转换后的内容。这个过程是链式的。
-
例如,对于
./style.css:css-loader处理@import和url(),将其转换为 JS 可执行的模块。style-loader接收 css-loader 返回的 JS 字符串,生成一段 JS 代码:通过DOM API将<style>标签插入到 HTML 中。
d. 构建依赖图
- 递归地重复 a, b, c 步骤,处理入口文件的所有依赖,以及依赖的依赖,直到项目中所有用到的模块都被处理完毕。
- 最终形成一个依赖图,其中节点是模块,边是模块间的依赖关系。
text
// 依赖图示例
Entry: ./src/index.js
|-- ./src/utils/math.js
|-- ./src/components/Button.jsx
|-- ./src/styles/button.css
|-- ./node_modules/react/index.js
3. 封装与代码生成:生成 Chunk
在得到完整的依赖图后,Compiler 进入 seal 阶段。
- 根据配置的 入口点和代码分割规则(如
SplitChunksPlugin),将依赖图中的模块分到不同的 Chunk 中。 - 一个 Chunk 对应一个或多个输出文件(如
.js,.css)。 - 然后,webpack 开始为每个 Chunk 生成最终的代码。它使用自己的模板系统来生成运行时代码。
关键:webpack 如何让模块在浏览器中运行?
webpack 实现了一个模块打包器 runtime。它不会直接执行 import/require,而是将所有模块封装在一个函数作用域内,并自己实现了一套模块加载和执行逻辑。
生成的 Bundle 代码结构大致如下:
javascript
// 一个简化版的 bundle.js
(function(modules) { // webpackBootstrap 自执行函数
// 1. 模块缓存
var installedModules = {};
// 2. webpack 自己实现的 require 函数 (__webpack_require__)
function __webpack_require__(moduleId) {
// 检查缓存
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 创建新模块并缓存
var module = installedModules[moduleId] = {
i: moduleId,
l: false, // loaded?
exports: {}
};
// 执行模块函数
// 这里的 modules[moduleId] 就是下面传入的模块定义
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
module.l = true; // 标记为已加载
return module.exports;
}
// 3. 从入口模块开始执行
return __webpack_require__(__webpack_require__.s = "./src/index.js");
})({
// 4. 传入的 modules 对象:一个包含所有模块定义的 Map
// Key: 模块ID (通常是路径)
// Value: 一个被包装的函数,包含了模块的源代码
"./src/index.js": (function(module, exports, __webpack_require__) {
// 这是由 index.js 转换而来的代码
// 原来的 `import utils from './utils/math.js'` 被替换了
const utils = __webpack_require__("./src/utils/math.js");
console.log('Hello Webpack');
}),
"./src/utils/math.js": (function(module, exports) {
// 模块自己的代码
module.exports = {
add: (a, b) => a + b
};
})
});
4. 输出 Assets
- 在确定好输出内容后,根据配置的
output路径和文件名,将 Chunk 转换成独立的文件。 - 在这个过程中,还会进行文件内容优化(如代码压缩、Tree Shaking 等)。
- 最后,通过 Node.js 的 fs 模块将文件写入到指定的输出目录。
核心原理总结
| 步骤 | 核心工作 | 关键工具/概念 |
|---|---|---|
| 1. 初始化 | 合并配置,创建 Compiler,注册插件 | Compiler, 生命周期钩子 |
| 2. 编译 | 从入口开始,递归解析依赖,构建模块图 | AST (acorn), enhanced-resolve, Loader |
| 3. 生成 | 根据依赖图和规则,生成 Chunk 和最终代码 | Chunk, Template (运行时代码) |
| 4. 输出 | 将 Chunk 写入文件系统,生成 Bundle | fs 模块 |
一句话总结:Webpack 通过 AST 静态分析代码中的模块依赖关系,将所有模块包装成一个或多个自执行函数,并通过自己实现的 __webpack_require__ 方法在浏览器环境中模拟 CommonJS 模块系统,最终将所有模块组织成一个或多个 Bundle 文件。