• Webpack完整打包流程分析


    前言

    webpack 在前端工程领域起到了中流砥柱的作用,理解它的内部实现机制会对你的工程建设提供很大的帮助(不论是定制功能还是优化打包)。

    下面我们基于 webpack5 源码结构,对整个打包流程进行简单梳理并进行实现,便与思考和理解每个阶段所做的事情,为今后扩展和定制工程化能力打下基础。

    一、准备工作

    在流程分析过程中我们会简单实现 webpack 的一些功能,部分功能的实现会借助第三方工具:

    • tapable 提供 Hooks 机制来接入插件进行工作;
    • babel 相关依赖可用于将源代码解析为 AST,进行模块依赖收集和代码改写。
    // 创建仓库
    mkdir webpack-demo && cd webpack-demo && npm init -y
    
    // 安装 babel 相关依赖
    npm install @babel/parser @babel/traverse @babel/types @babel/generator -D
    
    // 安装 tapable(注册/触发事件流)和 fs-extra 文件操作依赖
    npm install tapable fs-extra -D
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    接下来我们在 src 目录下新建两个入口文件和一个公共模块文件:

    mkdir src && cd src && touch entry1.js && touch entry2.js && touch module.js
    
    • 1

    并分别为文件添加一些内容:

    // src/entry1.js
    const module = require('./module');
    const start = () => 'start';
    start();
    console.log('entry1 module: ', module);
    
    // src/entry2.js
    const module = require('./module');
    const end = () => 'end';
    end();
    console.log('entry2 module: ', module);
    
    // src/module.js
    const name = 'cegz';
    module.exports = {
       
      name,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    有了打包入口,我们再来创建一个 webpack.config.js 配置文件做一些基础配置:

    // ./webpack.config.js
    const path = require('path');
    const CustomWebpackPlugin = require('./plugins/custom-webpack-plugin.js');
    
    module.exports = {
       
      entry: {
       
        entry1: path.resolve(__dirname, './src/entry1.js'),
        entry2: path.resolve(__dirname, './src/entry2.js'),
      },
      context: process.cwd(),
      output: {
       
        path: path.resolve(__dirname, './build'),
        filename: '[name].js',
      },
      plugins: [new CustomWebpackPlugin()],
      resolve: {
       
        extensions: ['.js', '.ts'],
      },
      module: {
       
        rules: [
          {
       
            test: /\.js/,
            use: [
              path.resolve(__dirname, './loaders/transformArrowFnLoader.js'), // 转换箭头函数
            ],
          },
        ],
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    参考webpack视频讲解:进入学习

    以上配置,指定了两个入口文件,以及一个 output.build 输出目录,同时还指定了一个 plugin 和一个 loader

    接下来我们编写 webpack 的核心入口文件,来实现打包逻辑。这里我们创建 webpack 核心实现所需的文件:

    // cd webpack-demo
    mkdir lib && cd lib
    touch webpack.js // webpack 入口文件
    touch compiler.js // webpack 核心编译器
    touch compilation.js // webpack 核心编译对象
    touch utils.js // 工具函数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这里我们创建了两个比较相似的文件:compilercompilation,在这里做下简要说明:

    • compiler:webpack 的编译器,它提供的 run 方法可用于创建 compilation 编译对象来处理代码构建工作;
    • compilation:由 compiler.run 创建生成,打包编译的工作都由它来完成,并将打包产物移交给 compiler 做输出写入操作。

    对于入口文件 lib/webpack.js,你会看到大致如下结构:

    // lib/webpack.js
    function webpack(options) {
       
      ...
    }
    
    module.exports = webpack;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    对于执行入口文件的测试用例,代码如下:

    // 测试用例 webpack-demo/build.js
    const webpack = require('./lib/webpack');
    const config = require('./webpack.config');
    
    const compiler = webpack(config);
    
    // 调用run方法进行打包
    compiler.run((err, stats) => {
       
      if (err) {
       
        console.log(err, 'err');
      }
      // console.log('构建完成!', stats.toJSON());
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    接下来,我们从 lib/webpack.js 入口文件,按照以下步骤开始分析打包流程。

    1、初始化阶段 - webpack

    • 合并配置项
    • 创建 compiler
    • 注册插件

    2、编译阶段 - build

    • 读取入口文件
    • 从入口文件开始进行编译
    • 调用 loader 对源代码进行转换
    • 借助 babel 解析为 AST 收集依赖模块
    • 递归对依赖模块进行编译操作

    3、生成阶段 - seal

    • 创建 chunk 对象
    • 生成 assets 对象

    4、写入阶段 - emit

    二、初始化阶段

    初始化阶段的逻辑集中在调用 webpack(config) 时候,下面我们来看看 webpack() 函数体内做了哪些事项。

    2.1、读取与合并配置信息

    通常,在我们的工程的根目录下,会有一个 webpack.config.js 作为 webpack 的配置来源;

    除此之外,还有一种是通过 webpak bin cli 命令进行打包时,命令行上携带的参数也会作为 webpack 的配置。

    在配置文件中包含了我们要让 webpack 打包处理的入口模块、输出位置、以及各种 loader、plugin 等;

    在命令行上也同样可以指定相关的配置,且权重高于配置文件。(下面将模拟 webpack cli 参数合并处理)

    所以,我们在 webpack 入口文件这里将先做一件事情:合并配置文件与命令行的配置。

    // lib/webpack.js
    function webpack(options) {
       
      // 1、合并配置项
      const mergeOptions = _mergeOptions(options);
      ...
    }
    
    function _mergeOptions(options) {
       
      const shellOptions = process.argv.slice(2).reduce((option, argv) => {
       
        // argv -> --mode=production
        const [key, value] = argv.split('=');
        if (key && value) {
       
          const parseKey = key.slice(2);
          option[parseKey] = value;
        }
        return option;
      }, {
       });
      return {
        ...options, ...shellOptions };
    }
    
    module.exports = webpack;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    2.2、创建编译器(compiler)对象

    好的程序结构离不开一个实例对象,webpack 同样也不甘示弱,其编译运转是由一个叫做 compiler 的实例对象来驱动运转。

    compiler 实例对象上会记录我们传入的配置参数,以及一些串联插件进行工作的 hooks API。

    同时,还提供了 run 方法启动打包构建,emitAssets 对打包产物进行输出磁盘写入。这部分内容后面介绍。

    // lib/webpack.js
    const Compiler = require('./compiler');
    
    function webpack(options) {
       
      // 1、合并配置项
      const mergeOptions = _mergeOptions(options);
      // 2、创建 compiler
      const compiler = new Compiler(mergeOptions);
      ...
      return compiler;
    }
    
    module.exports = webpack;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    Compiler 构造函数基础结构如下:

    // core/compiler.js
    const fs = require('fs')
    • 1
  • 相关阅读:
    STM32cubeMX详细教学及多个小项目合集(包含RTOS)
    论文研读 - share work - QPipe:一种并行流水线的查询执行引擎
    【C++】内存管理
    猿创征文|深度学习基于前馈神经网络完成鸢尾花分类
    Unity3D教程:调用C++中DLL文件
    [Matlab有限元分析] 1.有限元分析的发展、基本概念和特点
    多测师肖sir_高级讲师_第二个月python讲解02
    电脑显示系统错误怎么办?
    STC设计和RTX51--核心板设计
    Kubernetes控制平面组件:Kubelet
  • 原文地址:https://blog.csdn.net/gogo2027/article/details/127749935