webpack 资源加载-分析Webpack中import、require、按需加载的执行流程

2023-09-03 0 4,513 百度已收录

最近,由于一篇分享春节电商项目中使用的后端技术的文章的影响,我重新审视了项目中的CSS架构。 本来是打算写一篇文章的,但是写到一半,突然发现自己在写一篇文档介绍。 所以后来就放弃了。而且我觉得过程中研究过的Webpack可以拿出来单独讨论。

在此特别感谢imprint英文团队翻译的Webpack文档。 搭建简单环境 npminitnpminstallcss-loaderhtml-webpack-pluginstyle-loaderwebpackwebpack-cli

// Webpack 4.0
const htmlPlugin = require("html-webpack-plugin");
module.exports = {
  mode: "development",
  entry: "./src/index.js",
  output: {
    filename: "[name].js",
    path: __dirname + "/dist"
  },
  module: {
    rules: [
      {
        test: /.css$/,
        use: [
          {
            loader: "style-loader"
          },
          {
            loader: "css-loader",
          },
        ]
      }
    ]
  },
  plugins: [
    new htmlPlugin({
      title: "Test Webpack",
      filename: "index.html"
    })
  ]
};

基本配置已设置。 详细的配置内容我就不介绍了。 之后我们在src/index.js中编写测试代码,并在dist/main.js中看看webpack的实现原理。 所以目前我们的项目结构是这样的

|-- project
    |-- dist
    |-- src
        |-- index.js
    |-- node_modules
    |-- webpack.config.js

webpack中require和import的执行流程

在开始讲解按需加载之前,我们需要先看一个问题:webpack中require和import的执行流程是怎样的? 现在我们在src中构建两个文件index.js、module-es6.js和module-commonjs.js。我们通过这三个文件来分析require和import的执行过程。

我们首先需要区分的是 CommonJS 和 ES6 模块导入的区别。 在 CommonJS 中,您通过更改 module.exports 的形式导入模块,而 ES6 中没有模块变量。 它的导入方法是通过一个关键字Export来实现的。当我们编写JS文件时,发现无论是CommonJS还是ES6导入都可以导入。 这是因为Webpack做了兼容处理

我们搭建一个小DEMO来看看吧。 我们现在构建的三个文件的代码如下

// index.js
// import moduleDefault, { moduleValue } from "./module-es6.js";
// import moduleDefault, { moduleValue1, moduleValue2 } from "./module-commanjs.js";

// module-es6.js
export let moduleValue = "moduleValue" //ES6模块导出
export default "ModuleDefaultValue"

// module-commonjs.js
exports.moduleValue1 = "moduleValue1"
exports.moduleValue2 = "moduleValue2"

现在我们打开index.js中加载module-commonjs.js的代码时,首先会用ES6模块的标识符来标记当前模块,然后在index中形成两个变量A和BA来保存导入结果module-commonjswebpack 资源加载,而 B 是兼容CommonJs中通过exportdefault导入的ES6的结果webpack 资源加载,其值与A相同。使用B可以兼容exportdefault的结果。

之后我们重新注释一下代码,打开index.js中加载module-es6.js的代码。

这次和之前一样,当前模块会被打上ES6模块标识符,然后加载module-es6来获取其import值。而浏览器无法识别export关键字,所以Webpack会对代码进行解释。 首先,设置 module.exports 的导入值。 如果是exportdefault,则直接将参数交给module.exports。 如果是其他方法,就会给 module.exports 的导入的 key 设置一个 getter,getter 的返回值就是 import 的结果

对于require来说,整个执行过程和import是一样的。

对于webpack来说,只要使用import或者export等关键字,它就会在 module.exports 中添加一个 __esModule:true 来标识这是一个 ES6 模块,并利用这个值做一些特殊的处理

如果你觉得我没明白我在说什么,你可以看一下下面的代码

let commonjs = {
  "./src/index.js": function(module, __webpack_exports__, __webpack_require__) {
    "use strict";
    //给当前模块打上 `ES6`模块的标识符
    __webpack_require__.r(__webpack_exports__); //给当前模块打上 `ES6`模块的标识符
    // 执行 ./src/module-commonjs.js 的代码 获取导出值
    var A = __webpack_require__("./src/module-commonjs.js");
    // 根据 ./src/module-commonjs.js 是否为ES6模块 给返回值增加不同的 getter函数
    var B = __webpack_require__.n(A);
  },
  "./src/module-commonjs.js": function(module, exports) {
    exports.moduleValue1 = "moduleValue1";
    exports.moduleValue2 = "moduleValue2";
  }
};
let es6 = {
  "./src/index.js": function(module, __webpack_exports__, __webpack_require__) {
    "use strict";
    //给当前模块打上 `ES6`模块的标识符
    __webpack_require__.r(__webpack_exports__);
    // 执行 ./src/module-commonjs.js 的代码 获取导出值
    var A = __webpack_require__("./src/module-es6.js");
  },
  "./src/module-es6.js": function(module, __webpack_exports__, __webpack_require__) {
    //给当前模块打上 `ES6`模块的标识符
    __webpack_require__.r(__webpack_exports__);
    // 设置 __webpack_exports__.moduleValue 的 getter
    __webpack_require__.d(__webpack_exports__, "moduleValue", function() {
      return moduleValue;z
    });
    __webpack_exports__["default"] = "ModuleDefaultValue";
    let moduleValue = "moduleValue";
  }
};

按需加载的执行流程

看完前面的require和import,我们再回到按需加载的执行过程。 webpack的按需加载是通过import()或者require.ensure()来实现的。 有些读者可能对require.ensure比较熟悉,那么我们先看一下require.ensure的执行流程,

现在我们更改并构建一个 module-dynamic.js 文件,然后更改 index.js 文件

这里有一个问题。 require.ensure的第一个参数是一个尴尬的存在。 写与不写没有区别。 如果你填写这个参数,webpack会帮你将文件加载到最近的位置,而不是执行它。 一堆不可执行的代码是没有意义的。 如果想让它执行,就必须再次require(),执行require也会帮助你加载文件。所以完全没有区别,而上面可能涉及到模块加载顺序的问题。 这个我还没有深入研究过,因为require.ensure()使用的场景越来越小。

// index.js
setTimeout(function() {
  require.ensure([], function() {
    let d = require("./module2")
  });
}, 1000);
// module2.js
module.exports = {
  name : "Jason"
}

执行 require.ensure(dependencies, callback, errorCallback, chunkName) 实际上会返回一个promise。 上面的实现逻辑是首先判断依赖是否已经加载。 如果加载,则接受缓存值的承诺。 如果没有加载,则生成一个promise,并将resolve、reject和promise本身保存在一个链表中,然后缓存起来。 然后生成一个script标签,填写信息并添加到HTML文件中。 脚本的 src 属性就是我们按需加载的文件(module2),webpack会监听到这个脚本标签的错误和加载时间,因此会做出相应的处理。

webpack打包过程中,会在module2中添加一些代码,主要是主动触发window["webpackJsonp"].push函数,会通过

两个参数文件ID和文件内容对象。 如果不配置文件ID,则会根据加载序号手动递减。 文件内容对象其实就是上面提到的require.ensure第一个参数依赖的文件内容,或者说是回调,errorCallback上要加载的文件,以key(文件路径)的形式出现---值(文件内容)。 上面的执行好像是执行前面创建的promise的resolve函数,这样上面的require.ensure回调就被执行了,然后执行就和我里面的require和import一样了。

其实虽然说这么长的 require.ensure 没什么用,因为这个函数已经被 import() 取代了,而且考虑到之前的版本很多人都要使用 require.ensure 来加载,所以还是讲一下关于它。 我们看一下,不过import的执行过程虽然和require.ensure一样,只是用了更友好的句型,所以对于import的执行过程我没什么好说的。 有兴趣的可以看看这两篇 API 的介绍就太好了。

我即将结束这里。 如果有对此深有体会的朋友,看到有什么不对的地方,希望大家帮我强调一下。 特别感谢!!!

再次感谢 imprint 英文团队翻译的 Webpack 文档

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 webpack webpack 资源加载-分析Webpack中import、require、按需加载的执行流程 https://www.wkzy.net/game/192268.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务