webpack的基本原理

前言

本文是对知乎博主范文杰《[万字总结] 一文吃透 Webpack 核心原理》的沉淀化总结,原文写的可谓是鞭辟入里 精彩绝伦,推荐大家去看原文

学习webpack也是同理,我们得先知道

  1. 为什么要学习webpack,他到底解决了哪些问题
  2. 其次我们得知道webpack大体包含了哪些内容,这样学起来才能做到心中有数

我们都知道,js本身就是一个不完善的脚本语言,并没有实现模块化,早年也只是在浏览中写写脚本操作DOM,但是随着时间的发展web应用变得越来越复杂,模块化开发变得刻不容缓,webpack其实就是充当了这个角色,只不过他所指代的模块更加宽泛,不仅仅指的js模块,甚至css图片字体都能当做模块来处理,最终打包出能在浏览器运行的js文件。 总的来说就是内容转换+资源合并,具体包含以下三个阶段

  1. 初始化阶段

    1. 初始化参数: 从配置文件,配置对象,shell参数中读取,与默认配置结合得出最终的参数
    2. 创建编译器对象: 用上一步得到的参数创建compiler对象
    3. 初始化编译环境: 包括注入内置插件,注册各种模块工厂,加载配置插件等

开始编译: 执行compiler对象的run方法

  1. 确定入口: 根据配置中的entry找到所有入口文件,调用compilation.addEntry将入口文件转换为dependence对象

  2. 构建阶段

    1. 编译模块(make) : 根据entry对应的dependence创建module对象,调用loader将模块转译为标准JS内容,调用JS解释器将内容转换为AST对象,从中找到该模块依赖的模块,通过递归本步骤直到所有入口依赖的文件都经过本步骤的处理
    2. 完成模块编译: 上一步递归处理所有能触达到的模块后,得到每个模块被翻译后的内容以及他们之间的依赖关系图
  3. 生成阶段

    1. 输出资源(seal) : 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的chunk,再把每个chunk转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
    2. 写入文件系统(emitAssets) : 在确定好输出内容后,根据配置确定输出的路径和文件名,将文件内容写入到文件系统

一些技术名称的介绍

  • entry: 编译入口,webpack编译的起点
  • compiler: 编译管理器,webpack启动后会创建compiler对象,该对象一直存活直到结束退出
  • compilation: 单次编译过程中管理器,比如watch=true时,运行过程中只有一个compiler,但每次文件变更触发重新编译时,都会创建一个新的compilation对象
  • dependence: 依赖对象,webpack基于该类型记录模块间依赖关系
  • module: webpack内部所有资源都会以module对象形式存在,所有关于资源的操作,转译,合并都是以module为基本单位进行的
  • chunk: 编译完成准备输出时,webpack会将module按照特定的规则组织成一个个的chunk,这些chunk某种程度上和最终输出一一对应
  • loader: 资源内容转换器,其实就是实现将内容A转换成内容B
  • plugin: webpack构建过程中,会在特定的实际广播对应的时间,插件监听这些时间,在特定的时间点接入编译过程

核心流程

初始化阶段

大概流程如下

  1. 将process.args + webpack.config.js合并成用户配置

  2. 调用validateSchema校验配置

  3. 调用getNormalizeWebpackOptions + applyWebpackOptionsBaseDefaults合并出最终配置

  4. 创建compiler对象

  5. 遍历用户定义的plugins集合,执行插件的apply方法

  6. 调用new webpackOptionsApply.process方法,加载各种内置插件,这些插件不需要我们手动配置而是会根据配置内容动态注入,比如

    1. 注入EntryOptionPlugin插件,处理entry配置
    2. 根据devtool值判断后续用哪个插件处理sourcemap
    3. 注入RuntimePlugin,用于根据代码内容动态注入webpack运行时
    4. ...

构建阶段

构建阶段从入口文件开始

  1. 调用handleModuleCreate,根据文件类型创建module子类

  2. 调用loader转译module内容,通常是从各种资源类型转译为js文本

  3. 调用acorn将js文本解析成AST

  4. 遍历AST,触发各种钩子

    1. 在HarmonyExportDependencyParsePlugin插件监听exportImportSpecifier钩子,解读js文本对应的资源依赖
    2. 调用module对象的addDenpendency将依赖对象加入到module依赖列表中
  5. AST遍历完成后,调用module.handleParseResult处理模块依赖,对于新增的依赖,则调用handleModuleCreate递归处理

  6. 所有依赖解析完毕 则构建结束

在整个数据流中module => ast => dependences => modules,先转AST再从AST中寻找依赖,举个例子,入口文件为index.js 最终生成结果为

生成阶段

经历过构建阶段,webpack已经掌握了所有module信息以及他们之间的依赖关系,此时通过调用compilation.seal方法将module转换成chunk 大体流程如下

  1. 构建本次编译的ChunkGraph对象
  2. 遍历compilation.modules集合,将module按照entry/动态引入的规则分配给不同的chunk对象
  3. 得到完成chunk集合后,调用createXxxAssets方法将信息记录到compilation.assets对象中
  4. 触发seal回调,控制流回到compiler对象,通过调用compiler.emitAssets将assets集合写入文件系统

这里有个关键点就是我们都知道module可以有很多个,但是最终打包出来的chunk文件是很少的,其中的封装规则是咋样的?其实默认规则很简单

  • entry和entry触达的模块,组合成一个chunk
  • 动态引入的模块,各自组合成一个chunk

这里可以查看一个例子,index-a.js和index-b.js都是入口文件 最终生成的chunk结构如下 而且细心的观众可以可以发现c.js被打包进了两个chunk中,造成了重复,其实这种情况webpack也考虑到了,可以通过CommonChunkPlugin,SplitChunkPlugin插件来进行优化 那SplitChunkPlugin是如何优化chunk的呢,其实我们在创建完chunk对象后会触发各种优化钩子,SplitChunkPlugin利用钩子获取到chunk对象进行分析并增加一些通用的chunk,例如分析出多个chunk中都存在c模块从而将c模块独立成一个chunk。这其中也体现了webpack架构其实非常具有扩展性。

资源流转形态

为了更加完整了解入口文件最终是如何变成打包文件的,可以查看这张图

  • compilation.make阶段

    • entry文件以dependence对象形式加入compilation的依赖列表,dependence对象记录有entry的类型,路径等信息
    • 根据dependence调用对应的工厂函数创建module对象,之后读入module对应的文件内容,调用loader进行内容转化,转换结果如果有其他依赖则继续读入依赖资源,重复此过程直到所有依赖都转换成module
  • compilation.seal阶段

    • 遍历module集合,根据entry配置和引入资源的方式,将module分配到不同的chunk
    • 遍历chunk集合,调用compilation.emitAssets方法标记chunk的输出规则,也就是转换成assets集合
  • compiler.emitAssets阶段

    • 将assets写入文件系统

loader

loader其实就是实现将资源处理成标准的js语法,比如图片处理成 export defult "http://xxx.png",也比如将ts处理成js,后续webpack才能基于转义出来的js解析成AST并进行解析依赖等操作。 由于内容比较独立,这里也不做展开。

插件

webpack的插件架构有点类似订阅发布模型,但是又有不同之处,区别在于一般来说订阅发布仅仅是订阅了某种消息后得到通知,但是webpack触发的回调钩子中会携带上下文,用户可以通过修改上下文的数据结构或者调用其接口来产生side effect,从而达到影响编译状态和后续流程。 举个例子,正如前面所提到了的SplitChunkPlugin通过监听compilation.hooks.optimizeChunks钩子实现chunk的拆分 由于内容比较独立,这里也不做展开。

相关推荐
学习使我快乐012 小时前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio19952 小时前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
黄尚圈圈3 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水4 小时前
简洁之道 - React Hook Form
前端
正小安6 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch8 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光8 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   8 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   8 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web8 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery