当前位置: 首页 > news >正文

解析 Webpack中import、require、按需加载的执行过程

最近由于一篇分享手淘过年项目中采用到的前端技术的影响,重新研究了一下项目中CSS的架构.本来打算写一篇文章,但是写到一半突然发现自己像在写文档介绍一样,所以后来就放弃了。但是觉得过程中研究的 Webpack 倒是可以单独拿出来讲一讲

在这里非常感谢 印记中文 团队翻译的 Webpack 文档.

搭建一个简单环境

  1. npm init
  2. npm install css-loader html-webpack-plugin style-loader webpack webpack-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 的执行过程

在进入按需加载的讲解之前,我们需要看一个问题 requireimportwebpack 的执行过程是怎样的呢 ?现在我们在 src建立两个文件 index.jsmodule-es6.jsmodule-commonjs.js。我们通过这三个文件解析 requireimport 的执行过程

首先我们要区分的是 CommonJSES6 模块导出之间的区别,在 CommonJS 中你导出模块方式是改变 module.exports,但是对于 ES6 来说并不存在 module 这个变量,他的导出方式是通过一个关键词 export来实现的。在我们书写 JS文件的时候,我们发现无论是以 CommomJS 还是 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 则会产生两个变量 AB. A 保存 module-commonjs 的导出的结果,B 则是兼容 CommonJs中没有 ES6通过 export default导出的结果,其值跟 A一样. 用B来兼容 export default 的结果

然后我们重新注释代码,再打开 index.js 中加载 module-es6.js 的代码

这次和上面一样会先给当前模块打上 ES6模块的标识符,然后去加载 module-es6,获取他的导出值。但是浏览器是不识别 export 这个关键词的所以 Webpack 会对的代码进行解释,首先给 module.exports 设定导出的值,如果是 export default 会直接赋值给 module.exports,如果是其他形式,则给module.exports的导出的key设定一个 getter,该 getter 的返回值就是导出的结果

而对于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";
  }
};

按需加载的执行过程

看完上面的 requireimport,我们回到 按需加载 这个执行过程. webpack 的按需加载是通过 import() 或者 require.ensure()来实现的,有些读者可能对于 require.ensure 比较熟悉,所以我们先看看 require.ensure 的执行过程,
现在我们修改建立一个 module-dynamic.js文件,然后修改 index.js文件

这里吐槽一个问题,require.ensure 第一个参数是一个尴尬的存在,写和不写根本没差,如果你填了的这个参数,webpack 会帮你把文件加载近来,但是不执行。一堆不执行的代码是没有意义的,你想让他执行就必须 require() 一遍,但是执行力 require 也会帮你加载文件。所以根本没差
// 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 , 里面的实现逻辑是 先判断 dependencies 是否已经被加载过,如果加载过则取缓存值的 promise, 如果没有被加载过 则生成一个 promise 并将 promise 里面的 resolve,rejectpromise本身 存入一个数组,然后缓存起来.接着生成一个 script 标签,填充完信息之后添加到HTML文件上,其中的 scriptsrc属性 就是我们按需加载的文件(module2),webpack 会对这个 script 标签监听 errorload时间,从而做相应的处理。

webpack打包过程中会给 module2 添加一些代码,主要就是主动触发 window["webpackJsonp"].push这个函数,这个函数会传递
两个参数 文件ID文件内容对象,其中 文件标示如果没有配置的话,会按载入序号自动增长,文件内容对象实际上就是上文说的 require.ensure第一个参数dependencies的文件内容,或者是 callback,errorCallback里面需要加载的文件,以 key(文件路径) --- value(文件内容)的形式出现.里面执行的事情其实就是执行上面创建的promiseresolve函数,让require.ensure里面的callback执行,之后的执行情况就跟我上面将 requirimport 一样了

当然其实讲了那么长的 require.ensure并没有什么用,因为这个函数已经被 import() 取代了,但是考虑到之前的版本应该有很多人都是用 require.ensure 方法去加载的,所以还是讲一下,而且其实 import 的执行过程跟 require.ensure 是一样的,只不过用了更友好的语法而已,所以关于 import 的执行流程我也没啥好讲的了,感兴趣的人看一下两者的 API介绍就好了。

到这里就正式讲完了,如果有大牛路过看到有不对的地方,希望能帮我指出来.非常谢谢!!!

然后再次感谢印记中文 团队翻译的 Webpack 文档

相关文章:

  • Android使用TextView,设置onClick属性无效解决办法
  • Python:SMOTE算法——样本不均衡时候生成新样本的算法
  • 前端之css
  • Quicklz压缩算法
  • CMD命令
  • (C#)Windows Shell 外壳编程系列9 - QueryInfo 扩展提示
  • mysqli简介
  • hibernate多对多
  • QASymphony发布新的中心化自动测试及行为驱动测试产品
  • Reac 学习 初识(一)
  • ThinkPHP3 2 国际化功能实现
  • VR版《五十度黑》尺度大?心疼被套路的观众
  • 求解,如何在js中快速的给名字相似的变量赋值相似的字符串
  • 【Oracle12C】部署服务建立用户及建库建表中遇到的问题以及12C的一些新特性
  • iis和apache共存,解决带端口访问问题
  • Angular 4.x 动态创建组件
  • Apache的基本使用
  • CentOS7简单部署NFS
  • Gradle 5.0 正式版发布
  • Laravel深入学习6 - 应用体系结构:解耦事件处理器
  • nginx(二):进阶配置介绍--rewrite用法,压缩,https虚拟主机等
  • Python - 闭包Closure
  • Vim 折腾记
  • 极限编程 (Extreme Programming) - 发布计划 (Release Planning)
  • 警报:线上事故之CountDownLatch的威力
  • 猫头鹰的深夜翻译:JDK9 NotNullOrElse方法
  • 排序(1):冒泡排序
  • 算法---两个栈实现一个队列
  • 提醒我喝水chrome插件开发指南
  • Semaphore
  • 你学不懂C语言,是因为不懂编写C程序的7个步骤 ...
  • ​2021半年盘点,不想你错过的重磅新书
  • !! 2.对十份论文和报告中的关于OpenCV和Android NDK开发的总结
  • #、%和$符号在OGNL表达式中经常出现
  • #NOIP 2014# day.1 T3 飞扬的小鸟 bird
  • #stm32驱动外设模块总结w5500模块
  • #我与Java虚拟机的故事#连载11: JVM学习之路
  • (3)llvm ir转换过程
  • (4.10~4.16)
  • (板子)A* astar算法,AcWing第k短路+八数码 带注释
  • (二)【Jmeter】专栏实战项目靶场drupal部署
  • (附源码)springboot 基于HTML5的个人网页的网站设计与实现 毕业设计 031623
  • (六)vue-router+UI组件库
  • (四)鸿鹄云架构一服务注册中心
  • (一)Neo4j下载安装以及初次使用
  • ***利用Ms05002溢出找“肉鸡
  • .CSS-hover 的解释
  • .NET MVC、 WebAPI、 WebService【ws】、NVVM、WCF、Remoting
  • .NET 的程序集加载上下文
  • .net 验证控件和javaScript的冲突问题
  • .NET 依赖注入和配置系统
  • .NET中的十进制浮点类型,徐汇区网站设计
  • [ C++ ] STL priority_queue(优先级队列)使用及其底层模拟实现,容器适配器,deque(双端队列)原理了解
  • [C#]winform利用seetaface6实现C#人脸检测活体检测口罩检测年龄预测性别判断眼睛状态检测
  • [Django 0-1] Core.Handlers 模块