前端工程化基建探索(4)从源码出发探索理解webpack核心特性


theme: smartblue

本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

前言

webpack作为模块打包工具,在前端界可以说是恶棍天使一般的存在,前端人对它大多数对它都是咬牙切齿的,以至于后面出现vite,以及刚刚出炉号称比webpack快700倍的Turbopack,日薄西山的webpacK俨然已经成为过气网红,但是在目前来说,很多公司老项目都还是用webpack体系进行打包,面试的时候,往往也是会提一嘴,你做过什么优化,webpack的一些原理和知识点好像还是必备, 早在两年前我就写过一篇文章《前端开发中常用的webpack优化和相关原理》,那会还是webpack4,后来2020年10月10号左右,webpack5横空出世,截止到今天2022年10月,webpack已经更新到V5.75,再次重新去“顺藤摸瓜”式的去粗读源码,温故知新,当然大家也可以带着下面的问题思考:

  • 1、webpack的构建流程是怎么样的?
  • 2、webpack-cli对于文件打包是必须的吗?
  • 3、webpack中loader先执行还是plugin先执行?
  • 4、webpack中 loader 的链式调用与执行顺序是怎么样的?
  • 5、 module.rules的loader的加载和传递流程是怎么样的呢?
  • 5、 webpack优化新的思考

一、webpack的核心工作过程中的关键环节怎么样的?

1.1 梳理前置知识

从webpack4(2018年2月)开始它的CLI部分单独抽离在 webpack-cli 模块中,接下来我们就带着源码一点点来探索,webpack的核心工作流程。
注:本文下载的

webpack源码版本为"version": "5.75.0"
webpack-cli源码版本为"version": "4.10.0"

通过 上篇文章《# 前端工程化基建探索(3)定制脚手架模板——前端新建项目的“反卷利器”》我们对cli脚手架有个基础知识储备,现在阅读起源码来,也稍微更有方向感,我们从github下载webpack源码到本地,直奔主题。

1.2 启动webpack都做了什么

查看webpack启动文件,我们可以看到它首选做了一些判断是否安装了webpack-cli,如果不安装webpack-cli,程序运行就结束。
image.png
这里我们重点关注 runCli 函数,调用它会执行 webpack-cli 中bin目录下的cli.js 文件,后面的工作就交给webpack-cli 处理了。

接下来我们关注点转移到webpack-cli 源码, webpack-cli的启动文件,打开webpack-cli/bin/cli.js

#!/usr/bin/env node

"use strict";

const importLocal = require("import-local");
const runCLI = require("../lib/bootstrap");

if (!process.env.WEBPACK_CLI_SKIP_IMPORT_LOCAL) {
  // Prefer the local installation of `webpack-cli`
  if (importLocal(__filename)) {
    return;
  }
}

process.title = "webpack";

runCLI(process.argv);

最终它去执行await cli.run(args),用来处理命令行参数

image.png

然后我们去看看 require("./webpack-cli") 里面的 run() 函数,这个run函数,足足有700多行代码(看完烧脑),总的来说是处理命令行,并通过调用webpack的核心函数,构建compiler 对象,最后再执行整个构建流程。

image.png

结论:webpack-cli对于文件打包不是必需的,因为它只是处理命令行参数,我们也可以自行设计cli来处理参数,比如我们常见的 Vue/React框架也是没有使用webpack-cli

启动流程总结

webpack启动流程.png

内部的运行机制

image.png
(图片来源:点击图片即可直达)

1.3 整理一下Webpack 核心工作过程中的关键环节

一、初始化参数

(1)从配置文件解析配置项,开始载入 Webpack 核心模块,传入配置选项,这部分工作由 webpack-cli 处理

二、 开始编译

(1) run():编译的入口方法
(2) run触发compile,接下来就是开始构建options中模块
(3) 构建compilation对象。该对象负责组织整个编译过程,包含了每个构建环节所对应的方法对象内部保留了对compile对象的引用,并且存放所有modules, chunks,生成的assets以及最后用来生成最后JS的template。
(4) compile中触发make事件并调用addEntry
(5) 找到入口js文件,进行下一步的模块绑定

三、 构建模块

(1) 解析入口js文件,通过对应的工厂方法创建模块,保存到compilation对象上(通过单例模式保证同样的模块只有一个实例)
(2) 对module进行build了。包括调用loader处理源文件,使用 acorn生成AST并且遍历AST,遇到requirt等依赖时,创建依赖 Dependency加入依赖数组。
(3) module已经build完毕,此时开始处理依赖的module
异步的对依赖的module进行build,如果依赖中仍有依赖,则循环处理其依赖

四、 完成模块编译

(1) 调用seal方法封装,逐次对每个module和 chunk进行整理,生成编译后的源码,合并,拆分。每一个chunk对应一个入口文件。
(2) 开始处理最后生成的js

五、 输出资源

根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表

(1)所有的module,chunk仍然保存的是通过一个个require()聚合起来的代码,需要通过Template产生最后带有_webpack_require()的格式
(2)MainTemplate:处理入口文件的module, ChunkTemplate:处理非首屏,需异步加载的module
(3)注意这里开始输出生产的 assets,插件有机会最后修改assets

六、 输出完成

在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
(1)不同的dependencyTemplates,如CommonJs,AMD…
(2)生成好的js保存在compilation.assets中
(3)通过emitAssets将最终的is输出到output的path中

二、解析通过源码webpack的中的一些疑问

2.1 webpack中loader先执行还是plugin先执行?

其实这个是一个伪命题,,单从字面上是不能判断执行顺序的,大家都知道plugin 负责 webpack 除了模块化打包外其他多样性的构建任务处理。
image.png
,我们从源码上可以看到,当创建了 Compiler 对象过后,Webpack 就开始注册我们配置中的每一个插件,这样webpack在往后的生命周期里,都可以触发对应的plugin 插件钩子。

loader用于处理不同的文件类型,在编译静态资源个环节。我们继续查阅源码,创建完 Compilation 对象过后,触发了一个叫作 make 的钩子,make的工作就是根据 entry 配置找到入口模块,开始依次递归出所有依赖,形成依赖关系树,buildModule 方法进行模块构建,这里执行具体的 Loader,处理特殊资源加载。

image.png

2.2 webpack中 loader 的链式调用与执行顺序是怎么样的?

在webpack官网上有一句话:

“ loader 总是从右到左被调用。有些情况下,loader 只关心 request 后面的 元数据(metadata),并且忽略前一个 loader 的结果。在实际(从右到左)执行 loader 之前,会先 从左到右 调用 loader 上的 pitch 方法”
对于以下 use 配置:

module.exports = {
  //...
  module: {
    rules: [
      {
        //...
        use: ['a-loader', 'b-loader', 'c-loader'],
      },
    ],
  },
};

将会发生这些步骤:

|- a-loader `pitch`
  |- b-loader `pitch`
    |- c-loader `pitch`
      |- requested module is picked up as a dependency
    |- c-loader normal execution
  |- b-loader normal execution
|- a-loader normal execution

module.rules的loader的加载和传递流程是怎么样的呢?这里我们可以查阅源码lib/rules/RuleSetCompiler.js

这里列举了一两个通过源码查阅,来理解技术的方式,如果你有更多的疑问也可以一一通过源码去查阅理解。

三、简谈项目中webpack优化

webpack优化,一提到这个问题,大家用心玩过webpack的人都能道个一二三,但是怎么样系统的思考优化,或许很少人会去总结。

3.1 怎么去量化优化指标?

一切以结果为导向,优化到底优化了什么,怎么样量化(可视化、数据化),这个作为职场人重要的职业技能,优化,通常来说我们可以从时间空间两个维度去考量:

(1)时间:比如编译和打包过程中的耗时情况,你从3分钟提速到1分钟,这是一个很好的时间参数优化,所以我们可以选择一些基于时间的分析工具或插件,来帮我们统耗时情况,当然你也可以自己卷一个工具,NPM上成熟的工具已经有很多例如: speed-measure-webpack-plugin

(2) 空间:这里通常指的就是输出的代码体积,你从10M压缩到1M,这是一个很巨大体积优化,我们可以通过产物内容分析工具webpack-bundle-analyzer

image.png

可以直观分析打包出的文件包含哪些,大小占比如何,压缩后的大小。找到那些冗余的、可以被优化的依赖项。

3.2 定位优化方向

一、编译提效

我们可以从开发场景,体验了Vite的丝滑后,webpack开发编译等待是个难受的,多的时候得3-5分钟,才能看到编译成功,要提升这一阶段的构建效率,大致可以分为三个方向

  1. 减少执行编译的模块。
  2. 提升单个模块构建的速度。
  3. 并行构建以提升总体效率。

二、打包提效

通常我们发布项目的时候打包也是也超级长的等待时间,我们可以回忆上面webpack的构建流程中的环节,大概可以分为两个方向:

  1. 以提升当前任务工作效率为目标,压缩代码,压缩JS、css、图片等静态资源
  2. 以提升后续环节工作效率为目标,比如分模块打包(Split Chunks),摇树(Tree Shaking)

以上都有很成熟的插件和文章去执行webpack的优化了,这里就不再细做班门弄斧了。

总结

本文主要是简单从几个小角度探索源码出发理解webpack核心特性,和简谈项目中webpack优化,如果你对webpack一些特性有想了解的,也可以通过带着问题和目标,去“顺藤摸瓜”式的去粗读源码,了解更多相关知识点,百尺竿头更进一步!

© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容