webpack是-Webpack原理

它是如何工作的 概述基本概念

在了解Webpack原理之前,需要先掌握以下几个核心概念,以方便理解:

流程概览

Webpack的运行过程是一个串行过程,从开始到结束会依次执行以下过程:

初始化参数:从配置文件和Shell语句中读取并组合参数,得到最终参数; 开始编译:用上一步获得的参数初始化Compiler对象,加载所有配置的插件,并执行该对象的运行模式开始编译; 确定入口:根据配置中的入口找到所有入口文件; 编译模块:从入口文件开始,调用所有配置的Loader对模块进行翻译,然后找出该模块所依赖的模块,然后递归这一步,直到所有入口依赖的所有文件都在这一步处理完毕; 模块编译完成:在步骤4中使用Loader翻译完所有模块后,得到每个模块最终翻译的内容以及它们之间的依赖关系; 输出资源:根据条目和模块之间的依赖关系组装成包含多个模块的chunk,然后将每个chunk转换为单独的文件并添加到输出列表中。 这一步是改变输出内容的最后机会; 输出完成:确定输出内容后,根据配置确定输出路径和文件名,并将文件内容写入文件系统。

上述过程中webpack是,Webpack会在特定的时间点广播特定的风暴,插件监听到感兴趣的风暴后会执行特定的逻辑,插件可以调用Webpack提供的API来改变运行的方式Webpack 的结果。

工艺细节

Webpack的建立过程可以分为以下三个阶段:

初始化:启动并构建、读取并合并配置参数、加载Plugin、实例化Compiler。 编译:从Entry发出,为每个Module串行调用对应的Loader翻译文件内容,然后找到该Module所依赖的Module,并递归编译。 输出:将编译后的Module组合成Chunk,将Chunk转换成文件,输出到文件系统。

如果只执行一次构建,则上述阶段将按顺序执行。 但当开启窃听模式时webpack是,流程就会变成如下:

每个主要阶段都会产生很多扰动,Webpack会将这些扰动广播出来供Plugin使用。

初始化阶段风暴名称解释

初始化参数

从配置文件和shell语句中读取并组合参数,得到最终的参数。 在这个过程中,会执行配置文件中的插件实例化语句newPlugin()。

实例化编译器

使用上一步获得的参数初始化Compiler实例,Compiler负责文件窃听并启动编译。 Compiler实例包含完整的Webpack配置,并且全局只有一个Compiler实例。

加载插件

依次调用插件的apply方法,使得插件能够监听后续所有的storm节点。 同时将编译器实例的引用传递给插件,方便插件通过编译器调用Webpack提供的API。

环境

开始将Node.js风格的文件系统应用到编译对象上,方便后续的文件查找和读取。

进入选项

读取配置好的Entry,为每个Entry实例化一个对应的EntryPlugin,并规划前一个Entry的递归解析。

后插件

调用所有外部和配置的插件的apply方法后。

后分解器

解析器根据配置初始化后,负责查找文件系统中指定路径下的文件。

编译阶段风暴名称解释

跑步

开始新的编译。

观察运行

与run类似,不同的是它是以窃听模式启动的编译。 在这场风暴中,您可以获取哪些文件发生了更改并导致重新启动新的编译。

编译

这次storm的目的是告诉插件新的编译即将开始,同时将编译器对象带到插件中。

汇编

当 Webpack 在开发模式下运行时,每次检测到文件更改时,都会创建一个新的 Compilation。 一个Compilation对象包含了当前的模块资源、编译后的资源、变化的文件等。Compilation对象还提供了很多风暴反弹供插件扩展。

制作

创建新的Compilation后,正式从Entry中读取文件,根据文件类型和配置的Loader对文件进行编译。 编译完成后,找到该文件所依赖的文件,递归编译并解析。

编译后

编译执行完成。

无效的

webpack是_全局安装webpack_webpack教程

当遇到文件不存在、文件编译错误等异常时会触发该事件,该事件不会导致Webpack退出。

在编译阶段,最重要的就是编译干扰。 由于在编译阶段调用Loader来完成各个模块的转换操作,因此编译阶段存在很多小扰动。 他们是:

风暴名称解释

构建模块

使用相应的Loader来转换模块。

普通模块加载器

用Loader转换一个模块后,使用acorn解析转换后的内容,输出对应的具体句子树(AST),方便Webpack后的代码分析。

程序

从配置好的入口模块开始,分析它的AST。 当遇到require等导出的模块语句时,就会被添加到依赖模块列表中。 同时,它会递归地分析新发现的依赖模块,最终识别出所有模块的依赖关系。 。

海豹

所有模块及其依赖模块经过Loader转换后,根据依赖关系生成chunk。

输出阶段风暴名称解释

应该发出

全局安装webpack_webpack是_webpack教程

需要输出的文件都已经生成了,所以询问插件哪些文件需要输出,哪些不需要输出。

发射

确定输出什么文件后,执行文件输出,在这里可以获取和更改输出内容。

后发射

文件输出完成。

完毕

成功完成了一个完整的编译和输出过程。

失败的

如果编译输出过程中遇到异常导致Webpack退出,会直接跳转到这一步,插件可以获取本次风暴中错误的具体原因。

在输出阶段,已经得到了各个模块的转换结果及其依赖关系,只是将相关模块一一组合起来生成Chunk。 在输出阶段,根据Chunk的类型,将使用相应的模板生成最终的输出文件内容。

输出文件的剖析

虽然我们知道如何使用 Webpack 并且大致知道它是如何工作的,但是你有没有想过 Webpack 输出的bundle.js 是什么样子的? 为什么原来的模块文件合并成一个文件? 为什么bundle.js可以直接在浏览器中运行?

我们看一下最简单的项目创建的bundle.js文件的内容。 代码如下:

全局安装webpack_webpack是_webpack教程

(
    // webpackBootstrap 启动函数
    // modules 即为存放所有模块的数组,数组中的每一个元素都是一个函数
    function (modules) {
        // 安装过的模块都存放在这里面
        // 作用是把已经加载过的模块缓存在内存中,提升性能
        var installedModules = {};
        // 去数组中加载一个模块,moduleId 为要加载模块在数组中的 index
        // 作用和 Node.js 中 require 语句相似
        function __webpack_require__(moduleId) {
            // 如果需要加载的模块已经被加载过,就直接从内存缓存中返回
            if (installedModules[moduleId]) {
                return installedModules[moduleId].exports;
            }
            // 如果缓存中不存在需要加载的模块,就新建一个模块,并把它存在缓存中
            var module = installedModules[moduleId] = {
                // 模块在数组中的 index
                i: moduleId,
                // 该模块是否已经加载完毕
                l: false,
                // 该模块的导出值
                exports: {}
            };
            // 从 modules 中获取 index 为 moduleId 的模块对应的函数
            // 再调用这个函数,同时把函数需要的参数传入
            modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
            // 把这个模块标记为已加载
            module.l = true;
            // 返回这个模块的导出值
            return module.exports;
        }
        // Webpack 配置中的 publicPath,用于加载被分割出去的异步代码
        __webpack_require__.p = "";
        // 使用 __webpack_require__ 去加载 index 为 0 的模块,并且返回该模块导出的内容
        // index 为 0 的模块就是 main.js 对应的文件,也就是执行入口模块
        // __webpack_require__.s 的含义是启动模块对应的 index
        return __webpack_require__(__webpack_require__.s = 0);
    })(
    // 所有的模块都存放在了一个数组里,根据每个模块在数组的 index 来区分和定位模块
    [
        /* 0 */
        (function (module, exports, __webpack_require__) {
            // 通过 __webpack_require__ 规范导入 show 函数,show.js 对应的模块 index 为 1
            const show = __webpack_require__(1);
            // 执行 show 函数
            show('Webpack');
        }),
        /* 1 */
        (function (module, exports) {
            function show(content) {
                window.document.getElementById('app').innerText = 'Hello,' + content;
            }
            // 通过 CommonJS 规范导出 show 函数
            module.exports = show;
        })
    ]
);

上面看似复杂的代码毕竟是一个立即执行的函数,可以简写如下:

(function(modules) {
  // 模拟 require 语句
  function __webpack_require__() {
  }
  // 执行存放所有模块数组中的第0个模块
  __webpack_require__(0);
})([/*存放所有模块的数组*/])

Bundle.js之所以能直接在浏览器中运行,是因为在输出文件中,__webpack_require__函数定义了一个可以在浏览器中执行的加载函数,以模拟Node.js中的require语句。

原来独立的模块文件被合并到一个bundle.js中,因为浏览器无法像Node.js一样快地在本地加载模块文件,而必须通过网络请求加载未获取的文件。 。 如果模块很多,加载时间会很长,所以将所有模块存放在链表中,进行网络加载。

如果仔细分析__webpack_require__函数的实现,你还会发现Webpack做了缓存优化:加载的模块不会被第二次执行,执行结果会缓存在显存中。 当第二次访问某个模块时,会直接去显存读取缓存的返回值。

分割代码时的输出

例如,将源码中的main.js修改为:

// 异步加载 show.js
import('./show').then((show) => {
  // 执行 show 函数
  show('Webpack');
});

重新建立后会输出两个文件,分别是执行入口文件bundle.js和异步加载文件0.bundle.js。

0.bundle.js内容如下:

// 加载在本文件(0.bundle.js)中包含的模块
webpackJsonp(
  // 在其它文件中存放着的模块的 ID
  [0],
  // 本文件所包含的模块
  [
    // show.js 所对应的模块
    (function (module, exports) {
      function show(content) {
        window.document.getElementById('app').innerText = 'Hello,' + content;
      }
      module.exports = show;
    })
  ]
);

Bundle.js的内容如下:

(function (modules) {
  /***
   * webpackJsonp 用于从异步加载的文件中安装模块。
   * 把 webpackJsonp 挂载到全局是为了方便在其它文件中调用。
   *
   * @param chunkIds 异步加载的文件中存放的需要安装的模块对应的 Chunk ID
   * @param moreModules 异步加载的文件中存放的需要安装的模块列表
   * @param executeModules 在异步加载的文件中存放的需要安装的模块都安装成功后,需要执行的模块对应的 index
   */
  window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
    // 把 moreModules 添加到 modules 对象中
    // 把所有 chunkIds 对应的模块都标记成已经加载成功 
    var moduleId, chunkId, i = 0, resolves = [], result;
    for (; i  {
        // 执行 show 函数
        show('Webpack');
      });
    })
  ]
);

这里的bundle.js和上面提到的bundle.js很相似,不同的是:

使用CommonsChunkPlugin提取公共代码时,输​​出文件与使用异步加载时相同,都会有__webpack_require__.e和webpackJsonp。 原因是提取公共代码和异步加载本质上是代码拆分。