Webpack详细打包流程解析

📦 详细打包流程解析

本文详细解析一下Webpack详细打包流程,包含与常用loader和Plugin的交互。

直接上图

分阶段解析

第1阶段:初始化(Initialization)

触发调用 :当执行 webpack 命令或调用 webpack(config) API 时启动。

参数交互

  • 读取 webpack.config.js 配置对象,与命令行参数合并
  • 接收参数:config(配置对象)、callback(可选回调函数)
  • 输出对象:创建 Compiler 实例并返回

关键过程

  1. 配置合并:将用户配置、CLI参数和Webpack默认配置深度合并,形成最终配置对象
  2. Compiler创建:实例化Compiler类,这是Webpack的核心控制类,继承自Tapable,提供钩子系统
  3. Plugin实例化 :遍历 config.plugins 数组,对每个插件调用 new Plugin() 创建实例
  4. Plugin注册 :对每个插件实例调用 apply(compiler) 方法,插件通过此方法订阅生命周期钩子
  5. DefinePlugin工作 :在 compiler.options 中注入全局常量,如 process.env.NODE_ENV = 'production'
  6. 环境准备 :根据 mode 配置,Webpack内部加载对应环境的内置插件

涉及PluginDefinePlugin 在此阶段通过修改 compiler.options 定义全局常量。

第2阶段:编译准备(Compilation Preparation)

触发调用 :调用 compiler.run()compiler.watch() 启动构建过程。

钩子触发顺序

  1. beforeRun:清理缓存等准备工作
  2. run:开始执行编译
  3. compile:创建Compilation对象前
  4. thisCompilation:初始化Compilation对象时
  5. compilation:Compilation对象创建完成
  6. make:开始从入口构建依赖图

关键过程

  1. 钩子触发:Compiler按顺序触发各生命周期钩子,插件可以监听这些钩子执行自定义逻辑
  2. Compilation创建 :在 compile 钩子后创建Compilation实例,它代表一次独立的构建过程
  3. 入口解析 :EntryPlugin监听 make 钩子,根据 entry 配置解析入口文件路径
  4. 模块工厂创建:为不同类型的模块(NormalModule, ContextModule等)创建对应的ModuleFactory

参数传递compilation 对象被传递给所有监听 compilation 钩子的插件,包含本次构建的所有上下文信息。

第3阶段:模块处理(Module Processing)

触发调用:由NormalModuleFactory在解析模块时触发。

Loader执行机制

  • 匹配规则 :根据 module.rules 配置的 testincludeexclude 匹配文件

  • 执行顺序:对匹配的规则,Loader从右到左、从下到上执行(pitch阶段从左到右)

  • 参数传递:每个Loader接收上一个Loader处理后的内容或原始文件内容

    解释一下这个执行顺序:
    loader分两个阶段:Pitch函数(预处理)、Normal函数(主逻辑)
    假如配置:
    {
    test: /test.js$/,
    use: [
    './loaders/loader1.js',
    './loaders/loader2.js',
    './loaders/loader3.js'
    }
    执行链路为:loader1(pitch) -> loader2(pitch) -> loader3(pitch) -> loader3(normal) -> loader2(normal) -> loader1(normal)。
    Pitch阶段: 各loader传递一个"如何找到并处理这个文件"的计划描述:字符串路径和请求描述
    Normal阶段:最后一个loader(最右边)接收原始文件内容,第一个loader(最左边)输出最终结果
    这种设计让loader可以:

    • 在pitch阶段进行前置检查或优化
    • 通过熔断机制跳过不必要的处理
    • 通过共享data对象传递元信息
    • 形成清晰的处理管道,每个loader职责单一
      实际开发中,style-loader就利用了这个机制:它的pitch函数直接返回一段JS代码,将CSS注入到页面,从而避免CSS文件再经过其他loader处理。

    记忆技巧:
    Pitch是"从左到右"的准备阶段,可以做检查、熔断、设置数据
    Normal是"从右到左"的处理阶段,实际转换文件内容
    Pitch像"请求",Normal像"响应",形成完整的处理闭环

不同类型模块的处理流程

Vue文件(vue-loader)

  1. 接收Vue SFC(单文件组件)原始内容
  2. 解析为 descriptor 对象,包含 template、script、styles 块
  3. 对每个块生成虚拟模块,如 App.vue?vue&type=template&id=xxxx
  4. 各虚拟模块重新进入Loader匹配流程,分别处理

SCSS文件处理链

javascript 复制代码
// Loader链:从右到左执行
[
  'style-loader',           // 将CSS注入DOM
  'css-loader',            // 解析@import和url()
  'postcss-loader',        // 添加浏览器前缀等
  'sass-loader'            // 将SCSS编译为CSS
]
// 或者使用 MiniCssExtractPlugin.loader 替代 style-loader 提取CSS

JS/JSX/TS文件(babel-loader)

  1. 调用 @babel/coretransformSynctransformAsync 方法
  2. 读取 .babelrcbabel.config.js 或 package.json 中的babel配置
  3. 应用presets(如@babel/preset-env、@babel/preset-react)和plugins
  4. 生成转译后的ES5代码和source map

资源文件(file-loader/url-loader)

  • url-loader:比较文件大小与 limit 配置
    • 小于limit:转换为Base64 Data URL,格式:data:image/png;base64,...
    • 大于limit:回退到 file-loader
  • file-loader:复制文件到输出目录,返回新的公共URL路径

模块对象创建:每个模块处理后,创建NormalModule实例,包含:

  • resource:模块绝对路径
  • loaders:应用的Loader数组
  • rawRequest:原始请求字符串
  • dependencies:依赖数组

第4阶段:依赖分析(Dependency Analysis)

触发调用:在Module构建完成后,由ModuleGraph模块触发依赖分析。

AST解析过程

  1. 解析器调用 :使用 @babel/parser(原 babylon)或 acorn 解析JavaScript代码
  2. AST遍历 :使用 @babel/traverse 或自定义遍历器访问AST节点
  3. 依赖识别
    • 识别 import 语句(ES6模块)
    • 识别 require 调用(CommonJS)
    • 识别 require.resolveimport() 动态导入
    • 识别CSS中的 @importurl()(由css-loader处理)
  4. 依赖收集:将识别到的依赖路径转换为绝对路径,添加到模块的dependencies数组

递归处理

  1. 创建依赖队列,初始包含入口模块
  2. 循环处理队列中的每个模块
  3. 对每个模块进行第3阶段的Loader处理
  4. 解析处理后的代码,提取新依赖加入队列
  5. 重复直到队列为空

依赖关系图:最终构建出完整的ModuleGraph,包含:

  • 所有模块的元数据
  • 模块间的依赖关系(父子、兄弟关系)
  • 模块的导出和引用信息

第5阶段:封装优化(Sealing and Optimization)

触发调用 :当所有模块构建完成后,Compilation调用 seal() 方法。

钩子触发顺序

  1. finishModules:所有模块构建完成
  2. seal:开始封装过程
  3. optimizeDependencies:依赖优化
  4. afterOptimizeDependencies:依赖优化后
  5. beforeChunks / afterChunks:Chunk生成前后
  6. optimize:优化阶段开始
  7. optimizeModules / afterOptimizeModules:模块优化
  8. optimizeChunks / afterOptimizeChunks:Chunk优化
  9. optimizeTree / afterOptimizeTree:依赖树优化
  10. optimizeChunkAssets / afterOptimizeChunkAssets:资源优化

关键过程

Chunk生成

  1. 根据入口模块创建初始Chunk
  2. 识别动态导入(import())创建异步Chunk
  3. 构建ChunkGraph,表示Chunk间的依赖关系

SplitChunksPlugin工作

  1. 分析所有Chunk中的模块
  2. 根据 optimization.splitChunks 配置:
    • chunks: 'all' 分析所有Chunk
    • minSize: 20000 最小20KB才提取
    • cacheGroups 配置缓存组,如将node_modules提取到vendors
  3. 提取符合条件的公共模块到独立Chunk

Tree Shaking

  1. 标记阶段:遍历所有模块,标记未使用的导出(在production模式下自动启用)
  2. 摇摆阶段:分析副作用,package.json 中的 sideEffects 字段影响此过程
  3. 清除阶段:在代码压缩时移除未使用的代码

代码压缩

  • TerserPlugin :调用terser库压缩JavaScript,接收参数:
    • parallel: true 启用多进程
    • terserOptions 传递压缩选项
  • CssMinimizerWebpackPlugin:使用cssnano压缩CSS

资产生成:将每个Chunk转换为最终资源(Asset),包含:

  • 生成文件名(应用 [contenthash] 等占位符)
  • 应用模板生成最终代码(如将模块包装到webpack bootstrap代码中)

第6阶段:输出文件(Output)

触发调用 :在 seal() 完成后,Compiler调用 emitAssets()

钩子触发顺序

  1. afterCompile:编译完成
  2. shouldEmit / emit:准备输出资产
  3. afterEmit:资产输出后
  4. done / failed:构建完成或失败

关键过程

CleanWebpackPlugin工作

  1. 监听 emitbeforeRun 钩子
  2. 读取 output.path 配置
  3. 删除目录中的所有文件(或匹配模式的文件)
  4. 可配置 dry: false(实际删除)、verbose: true(显示删除日志)

资产写入

  1. 遍历 compilation.assets 对象
  2. 对每个资产,调用 fs.writeFile 写入文件系统
  3. 应用 output.filenameoutput.chunkFilename 的命名规则
  4. 生成source map文件(如果配置了 devtool

HtmlWebpackPlugin工作

  1. 监听 emit 钩子,在资产写入前执行
  2. 读取模板文件(默认或自定义)
  3. 遍历 compilation.chunks,收集所有JS和CSS资产
  4. 使用 html-webpack-plugin 的模板引擎将资产注入HTML
  5. 将生成的HTML添加到 compilation.assets
  6. 支持多页面应用(多次实例化插件)

路径注入示例

html 复制代码
<!-- 输入模板 -->
<!DOCTYPE html>
<html>
<head>
  <%= htmlWebpackPlugin.options.title %>
</head>
<body>
  <div id="root"></div>
</body>
</html>

<!-- 输出HTML -->
<!DOCTYPE html>
<html>
<head>
  <title>My App</title>
  <link href="main.abc123.css" rel="stylesheet">
</head>
<body>
  <div id="root"></div>
  <script src="runtime.def456.js"></script>
  <script src="vendors.ghi789.js"></script>
  <script src="main.abc123.js"></script>
</body>
</html>

第7阶段:完成与分析(Completion and Analysis)

触发调用:资产写入文件系统后,Compiler触发完成钩子。

钩子参数

  • done 钩子接收 stats 对象,包含构建统计信息
  • failed 钩子接收 error 对象

webpack-bundle-analyzer工作

  1. 监听 done 钩子,接收 stats 对象
  2. stats 中提取bundle信息
  3. 启动本地服务器(默认端口8888)
  4. 生成可视化图表,展示:
    • 每个bundle的体积
    • 模块组成结构
    • 模块大小分布
    • 重复模块检测
  5. 支持交互:缩放、搜索、筛选

开发模式特殊处理

  • HotModuleReplacementPlugin
    • compiler.hooks 上注册多个钩子监听器
    • 建立WebSocket连接,监听文件变化
    • 实现增量更新机制
  • webpack-dev-server
    • 提供开发服务器
    • 实现内存文件系统(不写入磁盘)
    • 支持代理、historyApiFallback等特性

Stats对象:包含完整的构建信息,可用于:

  • 控制台输出格式化
  • 生成JSON报告
  • 自定义分析工具
  • 性能监控

整个Webpack构建流程通过这些阶段的精密配合,将源代码转换为可部署的静态资源,同时提供了丰富的扩展点供Loader和Plugin介入,形成了强大的前端构建生态系统。

相关推荐
明朝百晓生2 小时前
强化学习[page14]【chapter7】Temporal-Difference Learning (TD learning)
前端·html
我只会写Bug啊2 小时前
B站/爱奇艺防录屏防截屏原理及Vue3实战实现
前端·软件开发
蜗牛攻城狮2 小时前
前端构建工具详解:Vite 与 Webpack 深度对比与实战指南
前端·webpack·vite·构建工具
Q_Q19632884752 小时前
python+django/flask+vue爬虫的网络小说热度分析系统
spring boot·python·django·flask·node.js·php
IT_陈寒2 小时前
Redis 性能翻倍的 5 个冷门技巧,90%开发者都不知道的底层优化!
前端·人工智能·后端
青山的青衫2 小时前
【优先级队列(堆)+排序】LeetCode hot100+面试高频
算法·leetcode·面试
a程序小傲2 小时前
百度Java面试被问:HTTPS解决了HTTP什么问题?
java·后端·http·百度·面试