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可以完全对数据库进行…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...
Windows安装Miniconda
一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...
[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...
libfmt: 现代C++的格式化工具库介绍与酷炫功能
libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库,提供了高效、安全的文本格式化功能,是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全:…...
用递归算法解锁「子集」问题 —— LeetCode 78题解析
文章目录 一、题目介绍二、递归思路详解:从决策树开始理解三、解法一:二叉决策树 DFS四、解法二:组合式回溯写法(推荐)五、解法对比 递归算法是编程中一种非常强大且常见的思想,它能够优雅地解决很多复杂的…...
