webpack联邦模块之webpack运行时

webpack是如何打包ES模块的?webpack是如何构建自身的模块运行时的?

__webpack_require__

这是整个webpack运行时的核心。

该函数被用于根据模块Id从变量__webpack_module_cache__获取模块对应导出:

  1. 有,直接返回
  2. 没有,去__webpack_modules__上找到模块id对应的模块,获取对应模块导出。

所以可以看出来__webpack_require__方法不用管模块具体是怎么来的。该方法调用的时候,调用方需要确保该模块id对应的模块已经存在__webpack_modules__上了。

模块id是什么?
模块id是模块文件名,也就是相对项目根目录的完整路径。
例如:import React from ‘react’,模块react的id是./node_modules/react/index.js。
src/index.js下代码 import Component from ‘./component/Abc’;
则模块Component的id是 ./src/component/Abc/index.js
所以模块id是唯一的

__webpack_require__.m = __webpack_modules__中的模块是怎么来的?

首先看打包文件main.js最后有入口模块的导入,

var __webpack_exports__ = __webpack_require__("./src/index.js");

这里的模块 ./src/index.js 就是webpack配置文件中配置的入口模块,而“./src/index.js” 则是模块id,这个模块被打包进了main.js这个入口chunk里,直接挂在了 __webpack_modules__下。

来源1:到这里我们知道了入口模块会直接挂在__webpack_modules__下

‘./src/index.js’这个模块的源码是import(’./bootstrap’)

所以这个入口模块依赖模块’./src/bootstrap.js’,并且这个是需要分割出去的异步模块,所以不在入口chunk(main.js)的__webpack_modules__对象中。那么它是怎么被加载进来的呢?

来源2:异步加载的分割模块

这个流程很有意思,也很厉害。

打包后的入口模块./src/index.js 内容

__webpack_require__.e(/*! import() */ "src_bootstrap_js").then(__webpack_require__.bind(__webpack_require__, /*! ./bootstrap */ "./src/bootstrap.js"));

其源码很简单就是import(’./bootstrap’),而编码出来的内容如上也很简单。

可以看出来在__webpack_require__.e执行完成之后就可以通过__webpack_require__去获取’./src/bootstrap.js’ 模块的内容。

也就是说__wepack_require__.e和模块’./src/bootstrap.js’的安装有关系。

webpack_require.e的入参是chunkId,模块存在于chunk中

__webpack_require__.e 是整个webpack运行时的基石

先看它的源码

__webpack_require__.f = {};
// This file contains only the entry chunk.
// The chunk loading function for additional chunks
__webpack_require__.e = (chunkId) => {return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {__webpack_require__.f[key](chunkId, promises);return promises;}, []));
};

这段代码格式化后也才几行,但是确实复杂。

这是一个设计模式,将依赖和实际依赖解耦。

__webpack_require__.f 上挂载了多个方法。其含义是,我要加载这个chunk了,你们要做什么吗?

  1. 要做。生成一个promise push到promises数组内
  2. 不做。无视入参promises

__webpack_require__.f上的方法remotes、consumes和j

其中remotes方法和consumes方法是联邦模块的核心,而方法j用于加载webpackchunk,也就是根据入参chunkId加载对应的chunk文件。

__webpack_require__.f.j 本质上就是在加载chunkid对应的JS文件

首先有一个chunk安装情况索引:

// object to store loaded and loading chunks
// undefined = chunk not loaded, null = chunk preloaded/prefetched
// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded
var installedChunks = {"main": 0
};

undefined: chunk没加载

null: chunk preloaded/prefetched

0: chunk加载完成

其中main是入口模块,被打包进入口chunk,所以其值是0,表示加载完成

__webpack_require__.f.j方法执行流程:

installedChunkData中的值

  1. =0,结束

  2. !=0

    再判断installedChunkData = installedChunks[chunkId]的值

    真值: promises.push(installedChunkData[2])

    假值:开始加载chunk

    构建一个Promise实例 installedChunkData = installedChunks[chunkId] = [resolve, reject] ,并且将promise实例push到installedChunks[chunkId] 之后作为数组第三项,这样chunkId 对应的模块就表示在加载中。并且将Promise实例push到promises数组中,表示这个模块在加载了,等会吧,好了通知你。

    通过chunkId构建出文件地址url

    构建文件加载完成函数loadingEnded

    __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId);

    函数loadingEnded

    该函数的执行时机是完成,也就是说无论成功还是失败都会被执行。

    如果 installedChunkData = installedChunks[chunkId]的值

    1. ≠ 0,installedChunks[chunkId] = undefined; 文件加载失败,将chunk重置成未加载
    2. = 0,加载成功什么都不做

    如果 installedChunkData 的值(ps: 这一步和上一步是顺序发生的,只是目的不同)

    假值:什么都不做,因为假值是0,undefined,null,表示加载完成

    真值:还在加载中,但其实文件的加载已经失败了。因为加载有结果了,但是没有成功,成功的话其值会被设置成0,所以就表示失败。这时候将installedChunkData的值,也就是之前的数组[resolve, reject, promise]中的reject拿出来,结束这个promise。

    loadingEnded函数中并没有调用promise的resolve函数,那么是在哪里调用的呢?这个要看下面的函数webpackJsonpCallback

webpackJsonpCallback

这个函数是webpackchunk加载成功的回调。

var webpackJsonpCallback = (parentChunkLoadingFunction, data) {...}var chunkLoadingGlobal = self["webpackChunkapp3"] = self["webpackChunkapp3"] || [];chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));

这些代码行数不多,但是做的事情却不太好理解。要理解它在做什么需要了解它加载的chunk是什么形状:

// src_bootstrap_js.js
(self["webpackChunkapp3"] = self["webpackChunkapp3"] || []).push([["src_bootstrap_js"],{
"./node_modules/abc/index.js": (() => {console.log('zhouzhuoxin')
}),
"./src/App.js":((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {...})
}])

可以观察这两大段代码,

第一段:来自webpack运行时,chunk下载相关代码,和__webpack_require__.f.j 相关

第二段:来自被下载的chunk src_bootstrap_js.js

先看第二段代码

self["webpackChunkapp3"] = self["webpackChunkapp3"] || []

先看下有没有全局变量webpackChunkapp3 有的话直接使用,没有就赋值空数组。

向数组中添加chunk数据,.push([[”chunkId”], {moduleId1() {}, moduleId2() {}, ...}]) 。数组的第一项交代了这个文件中包含了哪些chunk,第二项交代了所有chunk中包含的所有模块。

chunk是模块的容器

再看第一段代码

var chunkLoadingGlobal = self["webpackChunkapp3"] = self["webpackChunkapp3"] || [];

webpackChunkapp3:

  1. 存在:不赋值。注:说明有前置chunk被加载完成,或者有多个入口。
  2. 不存在:赋值空数组

chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));

为已经存在的chunk调用webpackJsonpCallback 回调函数,后面详细介绍这个函数的作用。

chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));

这一行最烧脑,这是一个责任链模式,它将chunkLoadingGlobal (这是一个数组)的push方法改写成调用webpackJsonpCallback 方法,并且将原来的push方法(真的是数组的push方法吗?)作为该函数的第一个入参。

达到的效果就是,调用chunkLoadingGlobal.push 时会先调用webpackJsonpCallback 方法,然后在webpackJsonpCallback方法中再将chunkData push到数组chunkLoadingGlobal 中。

到这里在入口chunk加载完成之前chunkLoadingGlobal 中的数据经历过了webpackJsonpCallback 函数,在入口chunk加载完成之后也会经历webpackJsonpCallback 这个函数。

webpackJsonpCallback(parentChunkLoadingFunction, data) 这个函数在做啥?

换句话说chunk加载完成后它会调用该函数,只是它的调用不是在监听文件加载的地方,而是在加载的文件中。就是self["webpackChunkapp3"].push方法的调用。

如果加载的chunk中携带的chunk组中有一个chunkid没有被安装过

将chunk中携带的module,全部都重新挂载在__webpack_require__.m上。(我理解是因为无法区分这个module是哪一个chunk的,导致要全部重新挂载)

parentChunkLoadingFunction

  • 存在:使用data调用它

  • 不存在:什么都不做

开始处理这个文件中chunk组对应的chunkIds

installedChunks中对应chunk的值:

  • = resolve, reject, promise,调用resolve

  • installedChunks[chunkId] = 0,设置为这个chunk加载完成

到这里整个chunk加载完成,chunk中携带的模块都被安装到了__webpack_modules__中,也就是说__webpack_require__可以获取到模块id对用的模块了。

总结

再简单梳理下模块加载整个流程如下:

// index.js
import('./bootstrap.js')

会被编译成

__webpack_require__.e(/*! import() */ "src_bootstrap_js").then(__webpack_require__.bind(__webpack_require__, /*! ./bootstrap */ "./src/bootstrap.js"));

方法e的入参是chunkId。

  1. 方法e会构建一个promise组成的数组promises
  2. 遍历对象f上挂载的方法(remotes、consumers和j)
  3. 使用promises作为入参调用以上方法
  4. 等待所有push到promises中的promise加载完成
  5. 调用方法j,
    1. 构建一个promise并将其push到promises中
    2. 开始加载通过chunkid得到的文件名
    3. chunkid加载完成
    4. 解析chunk中包含的模块,将其设置到__webpack_require__.m中
  6. 通过__webpack_require__.bind(__webpack_require__, /*! ./bootstrap */ "./src/bootstrap.js") 引用模块

以上是正常webpack运行时的运作流程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/361922.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学习笔记-AngularJs(四)

之前学习的事视图与模版,我们在控制器文件中直接定义一个数组,让其在模版文件中用ng-repeat指令构造一个迭代器,定义的数组http://t.cn/RUbL4rP如同以下: $scope.phones [{name:xioabin,number:18824863682,age:12},{name:xioalo…

3使用Jsoup解析Java中HTML文件的示例

HTML是Web的核心,无论您是通过JavaScript,JSP,PHP,ASP还是任何其他Web技术动态生成的,您在Internet上看到的所有页面都是基于HTML的。 您的浏览器实际上是解析HTML并为您呈现。 但是,如果需要解析HTML文档并…

转载:闲话权限设计三层境界

转自:http://www.cnblogs.com/tsoukw/archive/2010/09/27/1836485.html喜欢金庸的武侠,对他那几部小说也是乐此不疲拿独孤求败来说,他的剑,从无名利剑,玄铁重剑,到木剑乃至最后的无剑,不知道破世…

webpack联邦模块之remotes方法

使用联邦模块后当前项目就会有两个依赖,一个是依赖共享模块,一个是依赖远程模块。运行时webpack/runtime/consumes用于解析共享模块,运行时webpack/runtime/remotes 用于解析远程模块。这两个模块对应的方法分别是__webpack_require__.f.cons…

径向菜单的制作

最终效果: 在径向菜单的制作前,首先需要知道几点知识点: Math.sin(x) x 的正玄值。返回值在 -1.0 到 1.0 之间; Math.cos(x) x 的余弦值。返回的是 -1.0 到 1.0 之间的数; 这两个函数中的X 都是指的“弧度”…

吉首大学2019年程序设计竞赛-F 天花乱坠

题目链接:https://ac.nowcoder.com/acm/contest/992/F 题意:给定正n边形,边长为100,以每条边的中点连线构成新的正n边形,无限循环下去,求所有边的长度和。 思路:简单数学计算题,可以…

SqliteHelper整理

刚开通博客不久,还没有发过文。这是第一篇,要鼓励我自己再接再厉。 另外,我也是刚刚踏上程序员这条路,有赖各位多多提携! 闲话不多说,最近参与的项目包含本地化存储这一块。昨天就园子里找了些资料有另外补…

webpack联邦模块之consumes方法

对于使用联邦模块的项目会有两个依赖,一个是远程模块,一个是共享模块。上一篇文章解释了远程模块的加载和安装并初始化共享作用域。consumes则是共享模块的解决方案,用于在运行时加载并安装依赖的共享模块。 为什么叫consumes?我…

vue-cli使用说明

一、安装npm install -g vue-cli 推荐使用国内镜像 先设置cnpm npm install -g cnpm --registryhttps://registry.npm.taobao.org 如果安装失败,可以使用 npm cache clean 清理缓存,然后再重新安装 然后使用 cnpm 安装 vue-cli 和 webpack cnpm inst…

OptaPlanner –具有真实道路距离的车辆路线

在现实世界中,车辆路径问题(VRP)中的车辆必须走这条路:它们不能在客户之间直线行驶。 大多数VRP研究论文和演示都乐于忽略此实现细节。 和我一样,过去。 尽管使用道路距离(而不是空中距离)不会对…

自旋锁

什么是自旋锁? 自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。 …

关于如何在PSA众多请求号中查找数据是属于哪一条。

其中有两个TCODE: RSTSODS与RSTSODS,我们可以查找数据源的PSA表,然后在SE16中可以看到。 另外我们对PSA点击管理,一般会出现在窗口上面出现PSA的表名。 当然有些不在的话,那就去查找那两个TCODE。转载于:https://www.cnblogs.com/…

TCP握手为什么需要三次通信

TCP三步握手three way (or three message) handshake 是TCP核心知识点,很长一段时间内我无法理解为什么TCP建立连接需要三次通信,而不是两次或者四次或者更多次。我翻了很多问答和博客,他们说的都很有道理,但是借来的火&#xff0…

小程序用户拒绝授权解决方法

众所周知,小程序进入首先都要进行微信授权的,那万一用户不小心点了拒绝按钮怎么办呢?不要慌,官方早已预料到此情况,并提供了api供开发者使用,下面就一起来研究下api吧 一、API接口 wx.openSetting(OBJECT)…

揭示垃圾收集暂停的时间长度

有几种方法可以改善您的产品。 一种方法是仔细跟踪用户的体验并在此基础上进行改进。 我们确实自己应用了此技术,并再次花了一些时间查看不同的数据 除了我们追求的许多其他方面之外,我们还提出了一个问题“延迟GC触发应用程序的最坏情况是什么”。 为了…

异步导致UI句柄增加的解决办法

在很多操作中,都会使用到异步线程,具体怎样使用在这不说了,网上有很好的说明;本人通过Delegate.BeginInvoke实现异步调用,完成后对UI控件进行设值等,还有System.Timers.Timer都一样,使用的是线程…

[转]android ListView详解

本文转自:http://www.cnblogs.com/allin/archive/2010/05/11/1732200.html 由于google doc 很多人都打不开,故更新了源码下载地址 【源码下载】----2011-01-18 在android开发中ListView是比较常用的组件,它以列表的形式展示具体内容&#xff…

JS对象操作

一、String常用操作 1.截取 substr(start,length) //返回从指定位置开始的指定长度的字符串。 substring(start,end) //返回两个指定的位置之间的字符串。 slice(start,end) //包括字符串 stringObject 从 start 开始(包括 start)到 end 结束&#xff0…

JBoss BPM Suite 6.0.3版本的5个实用技巧

上周,红帽发布了标记为6.0.3的JBoss BPM Suite的下一版本,已订阅的用户可以在其客户门户中使用。 如果您对该版本的新增功能感到好奇,请在客户门户网站上在线查看版本说明和其余文档 。 我们正在寻找一些简单的方法来开始使用此新版本&…

package-lock.json

package.json确定依赖的范围,package-lock.json将这个范围精确到具体版本。主要是为了解决在各个环境中得到确定的node_modules,如果只依赖package.json因为该文件声明的是直接依赖的范围,它无法将直接依赖固定在某个特定版本,也无…