Webpack源码浅析
webpack启动方式
webpack有两种启动方式:
- 通过
webpack-cli
脚手架来启动,即可以在Terminal
终端直接运行;webpack ./debug/index.js --config ./debug/webpack.config.js
- 通过
require('webpack')
引入包的方式执行;其实第一种方式最终还是会用require
的方式来启动webpack,可以查看./bin/webpack.js
文件
webpack编译的起点
从const compiler = webpack(config)
开始
webpack函数源码(./lib/webpack.js
):
const webpack = (options, callback) => {let compiler = createCompiler(options)// 如果传入callback函数,则自启动if(callback){compiler.run((err, states) => {compiler.close((err2)=>{callbacl(err || err2, states)})})}return compiler
}
webpack函数执行后返回compiler
对象,在webpack中存在两个非常重要的核心对象,分别为compiler
和compilation
,它们在整个编译过程中被广泛使用。
- Compiler类(
./lib/Compiler.js
):webpack的主要引擎,在compiler对象记录了完整的webpack环境信息,在webpack从启动到结束,compiler
只会生成一次。你可以在compiler
对象上读取到webpack config
信息,outputPath
等; - Compilation类(
./lib/Compilation.js
):代表了一次单一的版本构建和生成资源。compilation
编译作业可以多次执行,比如webpack工作在watch
模式下,每次监测到源文件发生变化时,都会重新实例化一个compilation
对象。一个compilation
对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。
两者的区别?
compiler代表的是不变的webpack环境; compilation代表的是一次编译作业,每一次的编译都可能不同;
举个例子:
compiler就像一条手机生产流水线,通上电后它就可以开始工作,等待生产手机的指令; compliation就像是生产一部手机,生产的过程基本一致,但生产的手机可能是小米手机也可能是魅族手机。物料不同,产出也不同。
Compiler
类在函数createCompiler
中实例化(./lib/index.js
):
const createCompiler = options => {const compiler = new Compiler(options.context)// 注册所有的自定义插件if(Array.isArray(options.plugins)){for(const plugin of options.plugins){if(typeof plugin === 'function'){plugin.call(compiler, compiler)}else{plugin.apply(compiler)}}}compiler.hooks.environment.call()compiler.hooks.afterEnvironment.call()compiler.options = new WebpackOptionsApply().process(options, compiler) // process中注册所有webpack内置的插件return compiler
}
Compiler
类实例化后,如果webpack函数接收了回调callback
,则直接执行compiler.run()
方法,那么webpack自动开启编译之旅。如果未指定callback
回调,需要用户自己调用run
方法来启动编译。
process(options, compiler)
WebpackOptionsApply
类的工作就是对webpack options
进行初始化。 打开源码文件lib/WebpackOptionsApply.js
,你会发现前五十行都是各种webpack内置的Plugin
的引入,那么可以猜想process
方法应该是各种各样的new SomePlugin().apply()
的操作,事实就是如此。
compiler.run()
先贴上源码吧(./lib/Compiler.js
):
class Compiler {constructor(context){// 所有钩子都是由`Tapable`提供的,不同钩子类型在触发时,调用时序也不同this.hooks = {beforeRun: new AsyncSeriesHook(["compiler"]),run: new AsyncSeriesHook(["compiler"]),done: new AsyncSeriesHook(["stats"]),// ...}}// ...run(callback){const onCompiled = (err, compilation) => {if(err) returnconst stats = new Stats(compilation);this.hooks.done.callAsync(stats, err => {if(err) returncallback(err, stats)this.hooks.afterDone.call(stats)})}this.hooks.beforeRun.callAsync(this, err => {if(err) returnthis.hooks.run.callAsync(this, err => {if(err) returnthis.compile(onCompiled)})})}
}
通读一遍run
函数过程,你会发现它钩住了编译过程的一些阶段,并在相应阶段去调用已经提前注册好的钩子函数(this.hooks.xxxx.call(this)
),效果与React中生命周期函数是一样的。在run
函数中出现的钩子有:beforeRun --> run --> done --> afterDone
。第三方插件可以钩住不同的生命周期,接收compiler
对象,处理不同逻辑。
run
函数钩住了webpack编译的前期和后期的阶段,那么中期最为关键的代码编译过程就交给了this.compile()
来完成了。在this.comille()
中,另一个主角compilation粉墨登场了。
compiler.compile()
compile(callback){const params = this.newCompilationParams() // 初始化模块工厂对象this.hooks.beforeCompile.callAsync(params, err => {this.hooks.compile.call(params)// compilation记录本次编译作业的环境信息 const compilation = new Compilation(this)this.hooks.make.callAsync(compilation, err => {compilation.finish(err => {compilation.seal(err=>{this.hooks.afterCompile.callAsync(compilation, err => {return callback(null, compilation)})})})})})
}
compile
函数和run
一样,触发了一系列的钩子函数,在compile
函数中出现的钩子有:beforeCompile --> compile --> make --> afterCompile
。
其中make
就是我们关心的编译过程。但在这里它仅是一个钩子触发,显然真正的编译执行是注册在这个钩子的回调上面。
webpack因为有Tapable
的加持,代码编写非常灵活,node中流行的callback回调机制(说的就是回调地狱),webpack使用的炉火纯青。
this.parser
其实就是JavascriptParser
的实例对象,最终JavascriptParser
会调用第三方包acorn
提供的parse
方法对JS源代码进行语法解析。
const result = this.parser.parse(source)parse(code, options){// 调用第三方插件`acorn`解析JS模块let ast = acorn.parse(code)// 省略部分代码if (this.hooks.program.call(ast, comments) === undefined) {this.detectStrictMode(ast.body)this.prewalkStatements(ast.body)this.blockPrewalkStatements(ast.body)// 这里webpack会遍历一次ast.body,其中会收集这个模块的所有依赖项,最后写入到`module.dependencies`中this.walkStatements(ast.body)}
}
有个线上小工具 AST explorer 可以在线将JS代码转换为语法树AST,将解析器选择为acorn
即可。
通常我们会使用一些类似于babel-loader
等 loader 预处理源文件,那么webpack 在这里的parse
具体作用是什么呢?parse
的最大作用就是收集模块依赖关系,比如调试代码中出现的import {is} from 'object-is'
或const xxx = require('XXX')
的模块引入语句,webpack会记录下这些依赖项,记录在module.dependencies
数组中。
compilation.seal()
至此,从入口文件开始,webpack收集完整了该模块的信息和依赖项,接下来就是如何进一步打包封装模块了。
compiler.hooks.emit.callAsync()
在seal执行后,关于模块所有信息以及打包后源码信息都存在内存中,是时候将它们输出为文件了。接下来就是一连串的callback回调,最后我们到达了compiler.emitAssets
方法体中。在compiler.emitAssets
中会先调用this.hooks.emit
生命周期,之后根据webpack config文件的output配置的path属性,将文件输出到指定的文件夹。至此,你就可以在./debug/dist
中查看到调试代码打包后的文件了。
this.hooks.emit.callAsync(compilation, () => {outputPath = compilation.getPath(this.outputPath, {})mkdirp(this.outputFileSystem, outputPath, emitFiles)})
简单总结一下 webpack 编译模块的基本流程:
- 调用
webpack
函数接收config
配置信息,并初始化compiler
,在此期间会apply
所有 webpack 内置的插件; - 调用
compiler.run
进入模块编译阶段; - 每一次新的编译都会实例化一个
compilation
对象,记录本次编译的基本信息; - 进入
make
阶段,即触发compilation.hooks.make
钩子,从entry
为入口: a. 调用合适的loader
对模块源码预处理,转换为标准的JS模块; b. 调用第三方插件acorn
对标准JS模块进行分析,收集模块依赖项。同时也会继续递归每个依赖项,收集依赖项的依赖项信息,不断递归下去;最终会得到一颗依赖树🌲; - 最后调用
compilation.seal
render 模块,整合各个依赖项,最后输出一个或多个chunk
以下为时序图:
相关文章:

Webpack源码浅析
webpack启动方式 webpack有两种启动方式: 通过webpack-cli脚手架来启动,即可以在Terminal终端直接运行; webpack ./debug/index.js --config ./debug/webpack.config.js通过require(webpack)引入包的方式执行;其实第一种方式最终…...

Hadoop:HDFS学习巩固——基础习题及编程实战
一 HDFS 选择题 1.对HDFS通信协议的理解错误的是? A.客户端与数据节点的交互是通过RPC(Remote Procedure Call)来实现的 B.HDFS通信协议都是构建在IoT协议基础之上的 C.名称节点和数据节点之间则使用数据节点协议进行交互 D.客户端通过一…...
SASS 官方文档速通
前言:参考 Sass 中文网。 一. 特色功能 Sass 是一款强化 CSS 的辅助工具,在 CSS 语法的基础上增加了变量、嵌套、混合、导入等高级功能。有助于组织管理样式文件,更高效地开发项目。 二. 语法格式 .scss 拓展名:在 CSS3 语法的基…...

《动手学深度学习(PyTorch版)》笔记7.4
注:书中对代码的讲解并不详细,本文对很多细节做了详细注释。另外,书上的源代码是在Jupyter Notebook上运行的,较为分散,本文将代码集中起来,并加以完善,全部用vscode在python 3.9.18下测试通过&…...
关于自动驾驶概念的学习和一些理解
文章目录 对于自动驾驶的认识自动驾驶技术的优势自动驾驶的技术要求自动驾驶技术的挑战自动驾驶技术的潜在影响总结 对于自动驾驶的认识 自动驾驶是指车辆在没有人类驾驶员控制的情况下进行行驶的技术。随着人工智能的快速发展,自动驾驶技术已经成为将来交通行业的…...
C++ dfs搜索枚举(四十八)【第八篇】
曾经我们讲过枚举算法,那假设我们把枚举算法应用到搜索里呢? 1.搜索枚举 以前我们在进行枚举的时候是用了多层循环嵌套,但是当枚举的变量过多或者是输入的数量的时候就很难利用循环完成枚举了,不过我们可以尝试利用搜索进行枚举。…...

【优先级队列(大顶堆 小顶堆)】【遍历哈希表键值对】Leetcode 347 前K个高频元素
【优先级队列(大顶堆 小顶堆)】【排序】Leetcode 347 前K个高频元素 1.不同排序法归纳2.大顶堆和小顶堆3.PriorityQueue操作4.PriorityQueue的升序(默认)与降序5.问题解决:找前K个最大的元素 :踢走最小的&…...

Java设计模式-模板方法模式(14)
行为型模式 行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对…...
【C++ 二维前缀和】约会
题目描述 从前,小兔发现了一个神秘的花园。 花园是一个 n 行 m 列的矩阵,第 i 行 j 列的花的美丽度为 ai,j,一个合法的约会场所为任意一个正方形子矩阵,定义子矩阵的浪漫度为这个子矩阵的两条对角线上的花的美丽度之和。 现在小兔…...

基于Springboot的社区疫情防控平台
末尾获取源码作者介绍:大家好,我是墨韵,本人4年开发经验,专注定制项目开发 更多项目:CSDN主页YAML墨韵 学如逆水行舟,不进则退。学习如赶路,不能慢一步。 一、项目简介 以往的社区疫情防控管理…...
JAVA中的类方法
一、定义 1.类方法也叫静态方法 格式 访问修饰符 static 数据返回类型 方法名(){} 2.类方法的调用 前提:满足访问修饰符的访问权限 使用方式:类名.类方法名或者对象名.类方法名 二、注意事项 1.类方法中没有this的参数 class D{private int n1 …...
rust嵌入式开发之RTICvsEmbassy
RTIC和Embassy是目前rust嵌入式开发中比较热门的两个框架。本来呢,针对RTIC的移植已经完成了一小半,但在移植过程中感受到了RTIC的不足,正好跳出来全面考察下embassy,本文就是根据目前的尝试结果做个对比总结。 RTIC和Embassy是两…...
Bug地狱 #1 突然宕机,企业级应用到底怎么了
Bug地狱 #1 突然宕机,企业级应用到底怎么了 背景 目前就职的企业经营是一家服务小微门店Saas企业,以进销存管理和客户营销为主体提供订阅服务。项目正式上线可以说是从13年,基础架构是Web和后端使用C# .net,数据库使用SQL Serve…...

使用 Python、Elasticsearch 和 Kibana 分析波士顿凯尔特人队
作者:来自 Jessica Garson 大约一年前,我经历了一段压力很大的时期,最后参加了一场篮球比赛。 在整个过程中,我可以以一种我以前无法做到的方式断开连接并找到焦点。 我加入的第一支球队是波士顿凯尔特人队。 波士顿凯尔特人队是…...

探索C语言结构体:编程中的利器与艺术
✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:C语言学习 贝蒂的主页:Betty‘s blog 1. 常量与变量 1. 什么是结构体 在C语言中本身就自带了一些数据类型&#x…...

Git介绍与常用命令总结
Git介绍与其常用命令总结 1、Git介绍2、Git的使用3、Git常用命令3.1 初始化仓库3.2 克隆仓库3.3 配置用户信息3.4 提交代码(Commit)3.5 推送代码(Push)3.6 拉取代码(Pull)3.7 分支(Branch)3.8 远程仓库(Remote)3.9 撤销回退本地改动3.10 更新本地仓库与远程仓库 1、Git介绍 Gi…...

机器学习 | 探索朴素贝叶斯算法的应用
朴素贝叶斯算法是一种基于贝叶斯定理和特征条件独立假设的分类算法。它被广泛应用于文本分类、垃圾邮件过滤、情感分析等领域,并且在实际应用中表现出色。 朴素贝叶斯法是基于贝叶斯定理与特征条件独立假设的分类方法: 1)对于给定的待分类项r…...

【无刷电机学习】电流采样电路硬件方案
【仅作自学记录,不出于任何商业目的】 目录 AD8210 INA282 INA240 INA199 AD8210 【AD8210数据手册】 在典型应用中,AD8210放大由负载电流通过分流电阻产生的小差分输入电压。AD8210抑制高共模电压(高达65V),并提供接地参考缓冲输出&…...
对于协同过滤算法我自己的一些总结和看法
文章目录 协同过滤算法的基本原理协同过滤算法的分类用户相似度计算UserCF && ItemCF应用场景 协同过滤算法的优缺点优点缺点 协同过滤算法的总结与展望Q&A 协同过滤算法的基本原理 关于协同过滤算法,我看过很多老师写的博客以及一些简单的教程&#x…...

数据库管理phpmyadmin
子任务1-PHPmyadmin软件的使用 本子任务讲解phpmyadmin的介绍和使用操作。 训练目标 1、掌握PHPmyadmin软件的使用方法。 步骤1 phpMyAdmin 介绍 phpmyadmin是一个用PHP编写的软件工具,可以通过web方式控制和操作MySQL数据库。通过phpMyAdmin可以完全对数据库进行…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...

蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...
MySQL JOIN 表过多的优化思路
当 MySQL 查询涉及大量表 JOIN 时,性能会显著下降。以下是优化思路和简易实现方法: 一、核心优化思路 减少 JOIN 数量 数据冗余:添加必要的冗余字段(如订单表直接存储用户名)合并表:将频繁关联的小表合并成…...

免费数学几何作图web平台
光锐软件免费数学工具,maths,数学制图,数学作图,几何作图,几何,AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...

高考志愿填报管理系统---开发介绍
高考志愿填报管理系统是一款专为教育机构、学校和教师设计的学生信息管理和志愿填报辅助平台。系统基于Django框架开发,采用现代化的Web技术,为教育工作者提供高效、安全、便捷的学生管理解决方案。 ## 📋 系统概述 ### 🎯 系统定…...