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.sealrender 模块,整合各个依赖项,最后输出一个或多个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可以完全对数据库进行…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...
【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
解析两阶段提交与三阶段提交的核心差异及MySQL实现方案
引言 在分布式系统的事务处理中,如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议(2PC)通过准备阶段与提交阶段的协调机制,以同步决策模式确保事务原子性。其改进版本三阶段提交协议(3PC…...
Vue 3 + WebSocket 实战:公司通知实时推送功能详解
📢 Vue 3 WebSocket 实战:公司通知实时推送功能详解 📌 收藏 点赞 关注,项目中要用到推送功能时就不怕找不到了! 实时通知是企业系统中常见的功能,比如:管理员发布通知后,所有用户…...
无需布线的革命:电力载波技术赋能楼宇自控系统-亚川科技
无需布线的革命:电力载波技术赋能楼宇自控系统 在楼宇自动化领域,传统控制系统依赖复杂的专用通信线路,不仅施工成本高昂,后期维护和扩展也极为不便。电力载波技术(PLC)的突破性应用,彻底改变了…...
Linux 内存管理调试分析:ftrace、perf、crash 的系统化使用
Linux 内存管理调试分析:ftrace、perf、crash 的系统化使用 Linux 内核内存管理是构成整个内核性能和系统稳定性的基础,但这一子系统结构复杂,常常有设置失败、性能展示不良、OOM 杀进程等问题。要分析这些问题,需要一套工具化、…...
