面试必问的 webpack 原理 (1)-环境准备

一、为什么要写这个系列?

谨以此专栏向我的老板以及像他一样致力于开源繁荣的大佬致敬!

首先排除摸鱼,因为最近忙死,几乎无鱼可摸。主要两点原因吧,说来有趣:

1. 致敬我的老板

如果你有个框架作者的老板是种什么感觉?是的,我的老板就是框架作者!要说周会唯一开心的事儿莫过于他第一个汇报工作,他的汇报总是带着技术分享的,他总是说就是随便讲讲,你会发现他讲的都是思考,都是原理,都是源码,甚至会分析这么干的优劣,还会带着更好的实现思路。

每次请教问题,他都能非常流利的回答,甚至一些官网文档都没提及的特性,它都能如数家珍般的娓娓道来。佩服之余,我觉得我也应该再进一步!

来了我司之后是我的老板让我看到确实有一大帮人在看源码、写源码,并且将技术付诸于业务实践,并非无效卷!g

2. 总结+鞭策

我在动笔之前,webpack v4 的前半部分源码我看了大概有5/6遍了,持续时长超过2年,中间因为种种原因被打断过好几次,看了忘,忘了看,反反复复的。此外,在 buildModule 递归收集依赖的地方总是会卡住,上周末我看了一个下午,终于递归收集依赖的部分攻克了。

看了很多很多文章,好多文章把关键点都省略掉了,这部分关键点省略之后是看不懂的,同时为了给自己定个目标也是为了好好的梳理,我决定动笔写个专栏,秉持着写只要看就能懂的源码分享文章,把自己遇到的问题平铺直叙的写出来。

立个 FLAG,2024年内完成这个专栏,我预计应该会在 100 篇左右,预祝各位看官老爷早日成为尊贵的奥迪车主!!!

二、Demo 准备

本专栏讲述的是 webpack v5.x 的源码,至于为啥不讲 webpack v4,答案很简单:我的 webpack 4 没看完😂

2.1 demo 仓库地址

github 示例代码仓库

2.2 demo 依赖信息

上 package.json 文件:

json 复制代码
{
  "name": "w5-itm",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "build": "webpack",
    "watch": "webpack --watch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.20.2",
    "@babel/plugin-transform-runtime": "^7.19.6",
    "@babel/preset-env": "^7.20.2",
    "babel-loader": "^9.1.0",
    "clean-webpack-plugin": "^4.0.0",
    "html-webpack-plugin": "^5.5.0",
    "webpack": "^5.75.0",
    "webpack-cli": "^4.10.0",
    "webpack-virtual-modules": "^0.5.0"
  }
}

2.2 webpack.config.js 配置

这里提一点,我们的配置是个动态的过程,我们以打包一个 bundle 为例子开始,主要分析 webpack 如何从模块变成 bundle 的过程。

在这个过程中,后面这个配置文件是会成长的,最初的配置就是下面这个样子,其中 loader 部分我们只配置了 babel-loader 和 一个我们自己写的测试 loader!

js 复制代码
module.exports = {
  entry: {
    bundle1: './src/index.js',
    // bundle2: './src/a/d.js'
  },
  cache: {
    type: 'filesystem',
    cacheDirectory: path.join(__dirname, './.cache'),
    buildDependencies: {
      config: [__filename]
    }
  },
 
  // mode: 'development',
  mode: 'production', 
  devtool: 'source-map',
  output: {
    filename: 'bundle.[chunkhash:4].js',
    path: path.resolve(__dirname, './dist')
  },
  recordsPath: path.join(__dirname, 'records.json'),
  module: {
    rules: [
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env'],
              plugins: [
                [ // babel transform-runtime 暂时屏蔽,会额外多处很多模块,不利于看到现状
                 '@babel/plugin-transform-runtime',
                //   {
                //     corejs: 3
                //   }
                // ]
              ]
            }
          }
        ]
      }
    ]
  },
  resolve: {
    alias: {
      Src: path.resolve(__dirname, './src')
    },
    extensions: ['.js', '.jsx', '.json'],
    fallback: {
      assert: require.resolve('assert'),
      buffer: require.resolve('buffer')
    },
 
  },
  plugins: [
    new CleanWebpackPlugin()
  ]
}

2.3 src 目录结构

src 下的目录结构如下,index.js 是整个构建的主入口!

text 复制代码
.
├── index.css
├── index.html
├── index.js
├── ok.js
└── some-dir
    ├── a.js
    ├── b.js
    ├── c.js
    ├── d.js
    ├── e.js
    └── f.js

三、调试环境准备

调试环境依旧使用我的 Webstorm 调试环境,不需要配置,开箱即用,如果你 vsc 用户,自己搜一下 vscode 配置 node.js 断点调试(我一直用 webstorm,因为真的太好用了)

3.1 在 Webstorm 中加入断点

你只需要在要调试的代码行号处点击一下即可,点击后行号处出现一个红点,表示断点已经生成:

3.2 启动调试

webpack 调试不能通过右键 debugger 调试某个文件,因为这个文件不是入口,我们需要调试整个构建流程,因此需要以调试方式运行构建命令,你需要在 package.json 中加入 webpack 命令:

json 复制代码
"scripts": {
  "test": "echo "Error: no test specified" && exit 1",
  "build": "webpack",
  "watch": "webpack --watch"
},

我们接下来要调试 build 这个命令,你会发现 webstorm 在 build 行号处有个绿色小三角,点击这个三角,在弹出菜单中选择 Debug 'build'

当代码运行到断点处就会停住,如下图:

四、webpack 基础知识

在开启正式的代码阅读之前,我们需要你有以下基础知识,相信这一波就够劝退一大票人的:

4.1 Tapable 和 Hook

Tapable个发布订阅的事件库。与 event-emitter 不同的是,这个库提供了一种钩子机制(Hook),相当于是个事件名。这些 hook 有的是同步的(SyncHook),有的是异步的(SsyncHook),有的是串行的(SeriesHook),有的是并行(ParallelHook)的,有的有返回(WaterfallHook)值,有的是有熔断(BailHook)的....

关于 Tapable 已经有很多文章再说了,虽然这里不再赘述,但是我们希望你有这部分基础!

4.1 webpack 中的常用名词及含义

  1. webpack 生命周期webpack 基于 Tapable 库实现,webpack 中设计了很多 hook,这些钩子可以被任意订阅,而 webpack 的核心工作流也是通过指定的节点去触发指定 hook 来推进的。这些 hook 被称为 webpack 生命周期 hook。

  2. plugin:插件,上面介绍 hook 的时候说了,webpack 内部注册了很多 hook 供订阅,那么订阅这些钩子的一方实现某些固定的功能的就是一个 webpack 插件了。订阅某个流程节点钩子,webpack 当执行流推进到这个节点时,就会触发这个钩子并且传入一些重要的对象如 Compiler/Compilation/NormalModuleFactory/Parser...

  3. Compiler: 编译器,由 webpack 创建的编译器对象,继承自 Tapable,整个构建声明周期之后一份,负责调度 webpack 顶层的生命周期 hook:beforeRun,run,compilation,thisCompilation,compile,make,emit....(这些钩子有个印象就行,后面会细讲)

  4. Compilation: 由 Compiler 创建,同样继承自 Tapable,负责具体的构建工作,比如创建 Module,生成 Module Graph,Seal,Render 这些具体的构建工作细节。

  5. Resolver:资源(loader、代码模块)路径解析器,继承自 Tapable,负责解析被引用到代码模块、loader 模块的资源路径,webpack 的 Resolver 基于 enhanced-resolve 这个库创建

  6. ModduleFactory:模块工厂类,继承自 Tapable,用于创建模块对象,我们手写的 js 模块是文本文件,webpack 通过 ModuleFactory 提供的工厂函数将我们写的 js 代码变成 Module 对象,期间会调用对应的 loader 进行预处理。这里我们主要讨论 NormalModuleFactory(NMF);

  7. Parser:代码转 AST 的解析器,webpack 使用的是 acorn,Parser 同样继承自 Tapable。解析的重要意义在于通过分析 AST 找到这个模块依赖的其他模块,进行依赖收集。当然,不同的 AST 解析目的不一,这里强调的是 webpack 的 AST 解析,如果是 Babel 的 AST 解析自然是为了转义!

4.2 webpack 构建流程

  1. 首先 webpack 通过 webpack-cli 启动,期间会整合命令行参数;
  2. 然后通过 webpack 创建 Compiler 对象(这个过程伴随着 Compiler 的顶层生命周期 hook 的注册);
  3. Compiler 创建 Compilation 对象(这个过程伴随着 Compilation 生命周期 hook 的注册);
  4. 接着通过 Compilation 的生命周期开启构建流程,直到最后生成 bundle 文件!

五、总结

本文详述了后续代码的 demo 和 webpack 源码阅读的基础:

  1. Tapable 库及 hook 机制;
  2. webpack 基础概念及作用;
  3. webpack 整个运行流程;
相关推荐
程序猴老王25 分钟前
el-select 和el-tree二次封装
前端·vue.js·elementui
blzlh37 分钟前
手把手教你做网易云H5页面,进大厂后干的第一件事
前端·javascript·css
贩卖纯净水.1 小时前
网站部署及CSS剩余模块
前端·css
Justinc.1 小时前
CSS3_BFC(十二)
前端·css·css3
刺客-Andy1 小时前
React第四节 组件的三大属性之state
前端·javascript·react.js
黄毛火烧雪下1 小时前
React 表单Form 中的 useWatch
前端·javascript·react.js
爱健身的小刘同学2 小时前
钉钉免登录接口
前端·javascript·钉钉
啵咿傲2 小时前
跨域相关的一些问题 ✅
前端
命运之光2 小时前
生日主题的烟花特效HTML,CSS,JS
前端·javascript·css
Cshaosun2 小时前
js版本之ES5特性简述【String、Function、JSON、其他】(二)
前端·javascript·es