Webpack基本概念及核心流程

什么是Webpack

Webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 Webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

在下图中可以看出,Webpack 将左侧错综复杂的各自不同类型文件的模板依赖关系,包括 .js、.hbs、.cjs、.sass、.jpg、.png 等类型文件,打包成 .js、.css、.jpg、.png 4 种类型的静态资源。

简单来说,Webpack 就是一个静态资源打包工具,负责将项目中依赖的各个模块,打包成一个或多个文件。

在这里插入图片描述

它的主要功能包括:

1)模块打包

将项目中的所有模块(JavaScript、CSS、图片等)当作一个整体,通过依赖关系将它们打包成一个或多个静态资源文件

2)依赖管理

Webpack 可以分析模块之间的依赖关系,根据配置的入口文件找出所有依赖的模块,并将其整合到打包结果中

3)文件转换

Webpack 本身只能处理 JavaScript 模块,但通过加载器(Loader)的使用,可以将其他类型的文件(如CSS、LESS、图片等)转换为有效的模块,使其能够被打包到最终的结果中

4)代码拆分

Webpack 支持将代码拆分成多个模块,按需加载,实现按需加载和提升应用性能

5)插件系统

Webpack 提供了丰富的插件系统,可以通过插件实现各种功能的扩展,例如压缩代码、自动生成 HTML 文件等

基本概念

dependency graph(依赖图)

依赖图指的就是各个模块之间的依赖关系,例如模块 A 导入了模块 B,则在依赖图中,模块 A 会指向模块 B,如上文引入过的图:

在这里插入图片描述

构建依赖图的主要步骤如下:

1)解析入口文件

从配置文件中指定的入口文件(如index.js)开始解析,入口文件是打包的起点

2)查找依赖

Webpack 会解析入口文件中的代码,查找所有通过importrequire或其他方式引入的模块,例如以下代码,Webpack 会找到vue./styles.css这两个依赖

import Vue from 'vue';
import './styles.css';

3)递归解析

对于每个找到的依赖,Webpack 会继续递归解析其内部的依赖。例如./styles.css文件中可能又引入了其他 CSS 文件或图片资源,Webpack 会继续解析这些资源

4)构建图结构

在解析过程中,Webpack 会将每个文件作为图中的一个节点,并根据依赖关系建立边,最终形成一个完整的依赖图

entry(入口)

入口是指依赖关系图的开始,从入口开始寻找依赖,打包构建。Webpack 允许一个或多个入口配置,配置示例如下:

module.exports = {
  entry: 'index.js',
};

在这里插入图片描述

output(输出)

输出则是用于配置 Webpack 构建打包的出口,如打包的位置、文件名等,配置示例如下:

module.exports = {
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-first-webpack.bundle.js',
  },
};

在这里插入图片描述

loader(加载器)

Webpack 自带 JavaScript 和 JSON 文件的打包构建能力,无需格外配置。而其他类型的文件,如 CSS、TypeScript,则需要安装 loader 来进行处理,它让 Webpack 能够去处理其他类型的文件,并将它们转换为有效模块。配置示例如下:

module.exports = {
  module: {
    rules: [{ test: /.txt$/, use: 'raw-loader' }],
  },
};

在这里插入图片描述

plugin(插件)

插件则是用于扩展 Webpack 的能力,常见的插件有:

mode(模式)

Webpack5 提供了模式选择,包括开发模式、生产模式、空模式,并对不同模式做了对应的内置优化,可通过配置模式让项目性能更优,配置示例如下:

module.exports = {
  mode: 'development',
};

resolve(解析)

resolve 用于控制模块的解析规则,通过配置 resolve,Webpack 可以更高效地解析模块路径,找到正确的文件,并处理不同类型的模块。常见的配置项如下:

  • alias:为模块路径设置别名,简化模块引入
  • extensions:用于指定在解析模块时自动尝试的文件扩展名,在引入模块时可不带后缀
  • modules:指定 Webpack 在解析模块时应该查找的目录,默认情况下会查找 node_modules 目录
  • symlinks:控制是否解析符号链接,禁用可提升编译速度

配置示例如下:

module.exports = {
    resolve: {
        extensions: ['.js', '.jsx', '.ts', '.tsx', '.json', '.d.ts'],
        alias: {
            '@': path.resolve(__dirname, 'src'),
            'components': path.resolve(__dirname, 'src/components')
        }
        modules: [path.resolve(__dirname, 'src'), 'node_modules'], // 先查找 src 目录,再查找 node_modules
        symlinks: false,
      }
}

optimization(优化)

optimization 用于自定义 Webpack 的内置优化配置,一般用于生产模式提升性能,常用配置项如下:

  • minimize:是否需要压缩 bundle
  • minimizer:配置压缩工具,如 TerserPlugin、OptimizeCSSAssetsPlugin
  • splitChunks:拆分 bundle
  • runtimeChunk:是否需要将所有生成 chunk 之间共享的运行时文件拆分出来

配置示例如下:

module.exports = {
  optimization: {
    minimizer: [
      // 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
      // `...`,
      new CssMinimizerPlugin(),
    ],
    splitChunks: {
      // include all types of chunks
      chunks: 'all',
      // 重复打包问题
      cacheGroups:{
        vendors:{ //node_modules里的代码
          test: /[\/]node_modules[\/]/,
          chunks: "all",
          name: 'vendors', //chunks name
          priority: 10, //优先级
          enforce: true 
        }
      }
    },
  },
}

核心流程

Webpack 核心功能:将各种类型的资源,包括图片、css、js 等,转译、组合、拼接、生成 JS 格式的 bundler 文件,这个过程核心完成了内容转换 + 资源合并两种功能,实现上包含三个阶段:初始化阶段、构建阶段、生成阶段,单次构建过程自上而下按顺序执行。

初始化阶段

1)初始化参数:从配置文件、 配置对象、Shell 参数中读取,与默认配置结合得出最终的参数

2)创建编译器对象:用上一步得到的参数创建 Compiler 对象

3)初始化编译环境:包括注入内置插件、注册各种模块工厂、初始化 RuleSet 集合、加载配置的插件等

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

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

构建阶段

构建阶段从entry开始递归解析资源与资源的依赖,在compilation对象内逐步构建出module集合以及module之间的依赖关系,核心流程:

在这里插入图片描述

从上图看出,构建阶段从入口文件开始:

  • 调用handleModuleCreate ,根据文件类型构建module子类

  • 调用 loader-runner 仓库的runLoaders转译module内容,通常是从各类资源类型转译为 JS 文本

  • 调用 acorn 将 JS 文本解析为 AST

  • 遍历 AST,触发各种钩子

    • 监听exportImportSpecifier钩子,解读 JS 文本对应的资源依赖
    • 调用module对象的addDependency将依赖对象加入到 module 依赖列表中
  • AST 遍历完毕后,调用module.handleParseResult处理模块依赖

  • 对于module新增的依赖,调用handleModuleCreate,控制流回到第一步

  • 所有依赖都解析完毕后,构建阶段结束

这个过程中数据流:module => ast => dependences => module ,先转 AST 再从 AST 找依赖,这就要求loaders处理完的最后结果必须是可以被 acorn 处理的标准 JavaScript 语法。比如说对于图片,需要从图像二进制转换成 base64 格式或 url 格式。

compilation按这个流程递归处理,逐步解析出每个模块的内容以及module依赖关系,后续就可以根据这些内容打包输出。

生成阶段

基础流程

构建阶段围绕module展开,生成阶段则围绕chunks展开。经过构建阶段之后,Webpack 得到足够的模块内容与模块关系信息,接下来开始生成最终资源了。代码层面就是开始执行compilation.seal函数:

// 取自 webpack/lib/compiler.js 
compile(callback) {
    const params = this.newCompilationParams();
    this.hooks.beforeCompile.callAsync(params, err => {
      // ...
      const compilation = this.newCompilation(params);
      this.hooks.make.callAsync(compilation, err => {
        // ...
        this.hooks.finishMake.callAsync(compilation, err => {
          // ...
          process.nextTick(() => {
            compilation.finish(err => {
              **compilation.seal**(err => {...});
            });
          });
        });
      });
    });
  }

seal函数主要完成 module chunks 的转化,核心流程:

在这里插入图片描述

简单梳理一下:

  • 构建本次编译的ChunkGraph对象
  • 遍历compilation.modules集合, module entry/动态引入 的规则分配给不同的 Chunk 对象
  • compilation.modules集合遍历完毕后,得到完整的chunks集合对象,调用createXxxAssets方法
  • createXxxAssets遍历module/chunk ,调用compilation.emitAssets方法将assets信息记录到compilation.assets对象中
  • 触发seal回调,控制流回到compiler对象

这一步的关键逻辑是将module按规则组织成chunks ,Webpack 内置的 chunk 封装规则比较简单:

  • entry entry 触达到的模块,组合成一个chunk
  • 使用动态引入语句引入的模块,各自组合成一个chunk

chunk是输出的基本单位,默认情况下这些chunks与最终输出的资源一一对应,那按上面的规则大致上可以推导出一个entry会对应打包出一个资源,而通过动态引入语句引入的模块,也对应会打包出相应的资源

SplitChunksPlugin的作用

上面提到 Webpack 主流程里面是按entry / 动态引入两种情况组织chunks的,这必然会引发一些不必要的重复打包,而SplitChunksPlugin能将多个模块中重复的代码提取到一个单独的chunk中,这样可以避免在多个文件中重复加载相同的代码,从而减少最终打包文件的体积。

SplitChunksPlugin的拆分过程分为以下几个步骤:

1)模块依赖分析:通过 Webpack 的模块依赖图,识别出被多个chunk共同引用的模块

2)应用拆分规则:根据配置的规则(如minSizeminChunkschunks等),筛选出符合条件的模块

3)创建新的chunk:将符合条件的模块移动到一个新的 chunk ,并更新模块依赖关系

4)生成最终的打包文件:根据更新后的依赖关系,生成最终的打包文件

资源形态流转

上面已经把逻辑层面的构造主流程梳理完了,这里结合资源形态流转的角度重新考察整个过程:

在这里插入图片描述

compiler.make阶段:

  • entry文件以dependence对象形式加入compilation的依赖列表,dependence对象记录有entry的类型、路径等信息
  • 根据dependence调用对应的工厂函数创建module对象,之后读入module对应的文件内容,调用loader-runner对内容做转化,转化结果若有其它依赖则继续读入依赖资源,重复此过程直到所有依赖均被转化为 module

compilation.seal阶段:

  • 遍历module集合,根据entry配置及引入资源的方式, module 分配到不同的 chunk
  • 遍历chunk集合,调用compilation.emitAsset方法标记chunk的输出规则,即转化为 assets 集合

compiler.emitAssets阶段:

  • assets 写入文件系统
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值