使用联邦模块后当前项目就会有两个依赖,一个是依赖共享模块,一个是依赖远程模块。运行时webpack/runtime/consumes用于解析共享模块,运行时webpack/runtime/remotes 用于解析远程模块。这两个模块对应的方法分别是__webpack_require__.f.consumes ****和__webpack_require__.f.remotes,是的这两个方法都是关在__webpack_require__.f上的,和上一篇文章中的__webpack_require__.f.j在一起,都是用于加载chunk中的一环。

webpack/runtime/remotes

var chunkMapping = {"src_bootstrap_js": ["webpack/container/remote/app2/App","webpack/container/remote/app2/uitls"]
};var idToExternalAndNameMapping = {"webpack/container/remote/app2/App": ["default","./App","webpack/container/reference/app2"],"webpack/container/remote/app2/uitls": ["default","./uitls","webpack/container/reference/app2"]
};__webpack_require__.f.remotes = (chunkId, promises) => {...
}

其中数据chunkMapping使用chunkId作为key,value是module组成的数组。表示该chunk依赖这些模块。

数据idToExternalAndNameMapping则使用module作为key,value是如何加载这个远程模块。

[”default”, “./App”, “webpack/container/reference/app2”] 表示"webpack/container/remote/app2/App"模块可以从”webpack/container/reference/app2”模块的”./App”获取到,并且该远程模块依赖共享作用域”default”

一般下划线分割的字符串是chunkId,斜线分割的是moduleId

在这里__webpack_require__.f.remotes方法做的事情就是当加载chunk src_bootstrap_js时解析该chunk依赖的远程模块"webpack/container/remote/app2/App""webpack/container/remote/app2/uitls",并进行前置加载。加载完成后将这两个模块安装在当前webpack环境的__webpack_require__.modules上让代码可以通过__webpack_require__(’webpack/container/remote/app2/App’)获取到对应的远程模块导出。

**webpack_require**.f.remotes

__webpack_require__.f.remotes = (chunkId, promises) => {if(__webpack_require__.o(chunkMapping, chunkId)) {chunkMapping[chunkId].forEach((id) => {// getScope是干啥的,不知道干啥的,是不是防止有多个入口(entry),因为现在的例子是单入口的// 要把自己也分享出去才能更加复杂var getScope = __webpack_require__.R;if(!getScope) getScope = [];// data是 idToExternalAndNameMapping 的值var data = idToExternalAndNameMapping[id];if(getScope.indexOf(data) >= 0) return;getScope.push(data);if(data.p) return promises.push(data.p);var onError = (error) => {...}// 为什么要识别出是first?var handleFunction = (fn, arg1, arg2, d, next, first) => {...}var onExternal = (external, _, first) => {...}var onInitialized = (_, external, first) => ...var onFactory = (factory) => {...}handleFunction(__webpack_require__, data[2], 0, 0, onExternal, 1);}}
}

就像上面介绍的该方法用于加载入参chunkId依赖的远程模块。其核心方法是handleFunction

webpack_require.R的作用是啥?拿到是防止同一个执行两边?例如index.js中的bootstrap还有别的依赖。这样就会初始化两边了

handleFunction

var handleFunction = (fn, arg1, arg2, d, next, first) => {try {var promise = fn(arg1, arg2);if(promise && promise.then) {var p = promise.then((result) => (next(result, d)), onError);if(first) promises.push(data.p = p); else return p;} else {return next(promise, d, first);}} catch(error) {onError(error);}
}

代码行数不多,想表达的是将arg1和arg2作为入参调用函数fn,并将函数的返回值和参数d和first一起作为函数next的入参,并且兼容fn返回值为promise和非promise的情况。如果first为true,并且其返回值是promise,将其push到promises数组中。promises数组表示chunk bootstrap_js的加载完成依赖promises数组全部完成。

为什么只有first才会将promise push到promises中?

所以简单来讲handleFunction就是等fn执行完将结果作为next入参继续执行,起到了拼接两个函数顺序执行的作用。

handleFunction(__webpack_require__, data[2], 0, 0, onExternal, 1);

__webpack_require__(data[2]) (data[2]为"webpack/container/reference/app2" )执行完成后得到结果作为onExternal的入参执行该方法。

__webpack_modules__ = {"webpack/container/reference/app2": ((module, __unused_webpack_exports, __webpack_require__) => {"use strict";
var __webpack_error__ = new Error();
module.exports = new Promise((resolve, reject) => {if(typeof app2 !== "undefined") return resolve();__webpack_require__.l(app2Url + "/remoteEntry.js", (event) => {if(typeof app2 !== "undefined") return resolve();var errorType = event && (event.type === 'load' ? 'missing' : event.type);var realSrc = event && event.target && event.target.src;__webpack_error__.message = 'Loading script failed.\n(' + errorType + ': ' + realSrc + ')';__webpack_error__.name = 'ScriptExternalLoadError';__webpack_error__.type = errorType;__webpack_error__.request = realSrc;reject(__webpack_error__);}, "app2");
}).then(() => (app2));}

模块"webpack/container/reference/app2"加载完成后会得到一个对象{get: () => get, init: () => init} 这个对象就是onExternal方法的第一个入参external

onExternal

var onExternal = (external, _, first) => (external ? handleFunction(__webpack_require__.I, data[0], 0, external, onInitialized, first) : onError());

一行代码,表达的是初始化远程模块依赖的共享作用域之后执行方法onInitialized

onInitialized

var onInitialized = (_, external, first) => (handleFunction(external.get, data[1]/* ./App */, getScope, 0, onFactory, first));// external.get 方法也就是三方模块的get方法
var get = (module, getScope) => {__webpack_require__.R = getScope;getScope = (__webpack_require__.o(moduleMap, module)? moduleMap[module](): Promise.resolve().then(() => {throw new Error('Module "' + module + '" does not exist in container.');}));__webpack_require__.R = undefined;return getScope;
};var moduleMap = {"./App": () => {return Promise.all([__webpack_require__.e("webpack_sharing_consume_default_react_react-_5e40"),__webpack_require__.e("src_App_js")]).then(() => (/* external.get */**() => ((__webpack_require__(/*! ./src/App */ "./src/App.js")))**));},}

通过三方模块的get方法获取对应模块,例如这里的模块 ./App 得到返回值 () => ((__webpack_require__(/*! ./src/App */ "./App.js"))) 也就是下面的入参factory

最后执行方法onFactory

var onFactory = (factory) => {data.p = 1;// 这里可以通过id拿到远程库中的模块,将id对应的module设置上__webpack_modules__[id] = (module) => {module.exports = factory();}
};

到这里__webpack_modules__[’webpack/container/remote/app2/App’] = (module) => {module.exports = factory()} 这样三方模块安装完毕,可以保证chunk src_bootstrap_js 依赖的三方模块可以通过__webpack_require__正常获取。

__webpack_require__.I 用于初始化共享作用域当前环境和remote

该方法不仅仅会初始化自身的共享作用域,还会初始化当前项目依赖的外部项目的共享作用域:

需要注意的是__webpack_require__.I 中初始化外部作用域,并不是通过入参来知道要初始化哪些外部作用域,而是直接编译到源码中。

initExternal("webpack/container/reference/app2");
// 这样就可以通过id拿到extrnal调用使用__webpack_require__.S调用它的init方法,完成其共享作用域的初始化
__webpack_require__.S = {};
var initPromises = {};
var initTokens = {};__webpack_require__.I = (name, initScope) => {if(!initScope) initScope = [];var initToken = initTokens[name];if(!initToken) initToken = initTokens[name] = {};if(initScope.indexOf(initToken) >= 0) return;initScope.push(initToken);// only runs onceif(initPromises[name]) return initPromises[name];// creates a new share scope if neededif(!__webpack_require__.o(__webpack_require__.S, name)) __webpack_require__.S[name] = {};// runs all init snippets from all modules reachablevar scope = __webpack_require__.S[name];var warn = (msg) => ...var uniqueName = "app1";var register = (name, version, factory, eager) => {...}var initExternal = (id) => {...}var promises = [];switch(name) {case "default": {register("react-dom", "17.0.2", () => (Promise.all([]).then(() => () => {} /* factory */))));register("react", "17.0.2", () => (Promise.all([]).then(() => () /* factory */)));initExternal("webpack/container/reference/app2");}break;}
if(!promises.length) return initPromises[name] = 1;
return initPromises[name] = Promise.all(promises).then(() => (initPromises[name] = 1));

这个方法用于初始化共享作用域。在初始化当前环境共享作用域后使用方法initExternal("webpack/container/reference/app2");初始化远程模块的共享作用域。

var initPromises = {};
var initTokens = {};__webpack_require__.I = (name, initScope) => {if(!initScope) initScope = [];var initToken = initTokens[name];if(!initToken) initToken = initTokens[name] = {};if(initScope.indexOf(initToken) >= 0) return;initScope.push(initToken);...
}var initExternal = (id) => {...module.init(__webpack_require__.S[name], initScope)...
}

这几行代码不太好理解。

首先每个作用域都有一个唯一的token与之对应,在这里表现为对象。

正在初始化的作用域列表存在initScope中,如果initScope中存在这个token直接退出,表示正在初始化。(这种情况会出现在相互依赖的情况下,a 依赖 b 初始化b的共享作用域,这时候b也依赖a,b也会初始化a的作用域,这时候传入的initScope就有代表a作用域的token,否则会一直初始化下去。)

initExternal("webpack/container/reference/app2");

var initExternal = (id) => {var handleError = (err) => (warn("Initialization of sharing external failed: " + err));try {var module = __webpack_require__(id);if(!module) return;var initFn = (module) => (module && module.init && module.init(__webpack_require__.S[name], initScope))if(module.then) return promises.push(module.then(initFn, handleError));var initResult = initFn(module);if(initResult && initResult.then) return promises.push(initResult['catch'](handleError));} catch(err) { handleError(err); }
}

这段代码表达的是,等待远程模块加载完成拿到其init方法,使用当前环境的共享作用域__webpack_require__.S的某个这里是default来初始化远程环境共享作用域。

register("react", "17.0.2", () => (Promise...

var register = (name, version, factory, eager) => {var versions = scope[name] = scope[name] || {};var activeVersion = versions[version];if(!activeVersion || (!activeVersion.loaded && (!eager != !activeVersion.eager ? eager : uniqueName > activeVersion.from))) versions[version] = { get: factory, from: uniqueName, eager: !!eager };
};

向指定的共享作用域上设置包。

共享作用域初始化完成后当前环境的共享作用域形状如下:

__webpack_require__.S.default = {react: {'17.0.2': {get: () => ..., from: uniqueName, eager: boolean}},...
}

到这里加载chunk src_bootstrap_js 依赖的远程模块准备完毕,并且远程模块依赖的共享作用域同样初始化完毕。但是src_bootstrap_js还没有加载完毕,因为虽然远程模块可以通过__webpack_require__方法直接获取到,但是共享模块还无法通过该方法直接获取,因为到目前为止,共享模块只存在于__webpack_require__.S.default 上,而不存在于__webpack_modules__上。要将__webpack_require__.S.default上的共享模块在__webpack_modules__上获取还要借助接下来的方法__webpack_require__.f.consumes = (chunkId, promises) => {...}的帮助。

总结

webpack_require.f.remotes使用chunkId和promises作为入参,解析chunkId依赖的远程模块并加载和安装,让被依赖的远程模块可以通过__webpack_require__方法直接获取。

安装远程依赖的过程包括下载远程依赖和初始化远程依赖依赖的共享作用域,完善远程依赖的执行环境。

webpack联邦模块之remotes方法相关推荐

  1. webpack联邦模块之consumes方法

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

  2. webpack联邦模块之webpack运行时

    webpack是如何打包ES模块的?webpack是如何构建自身的模块运行时的? __webpack_require__ 这是整个webpack运行时的核心. 该函数被用于根据模块Id从变量__web ...

  3. webpack 5 模块联邦实现微前端疑难问题解决

    webpack 5 模块联邦实现微前端疑难问题解决 说明 webpack 5 新增 Module Federation(模块联邦)功能,他可以帮助将多个独立的构建组成一个应用程序,不同的构建可以独立的 ...

  4. webpack组织模块的原理 - 基础篇

    现在前端用Webpack打包JS和其它文件已经是主流了,加上Node的流行,使得前端的工程方式和后端越来越像.所有的东西都模块化,最后统一编译.Webpack因为版本的不断更新以及各种各样纷繁复杂的配 ...

  5. 深入理解Webpack核心模块Tapable钩子[异步版]

    接上一篇文章 深入理解Webpack核心模块WTApable钩子(同步版) tapable中三个注册方法 1 tap(同步) 2 tapAsync(cb) 3 tapPromise(注册的是Promi ...

  6. Discuz! X2.5 添加自定义数据调用模块(简单方法)

    转:http://521-wf.com/archives/46.html Discuz! X2.5 添加自定义数据调用模块(简单方法) Discuz!X系列的diy功能还是相当不错的,在对其进行二次开 ...

  7. 微擎 微赞等系统源码模块若干问题解决方法及说明汇总

    微擎 微赞等系统源码模块若干问题解决方法及说明汇总 参考文章: (1)微擎 微赞等系统源码模块若干问题解决方法及说明汇总 (2)https://www.cnblogs.com/westsoft/p/6 ...

  8. python画图代码turtle-使用Python的turtle模块画图的方法

    简介:turtle是一个简单的绘图工具.它提供了一个海龟,你可以把它理解为一个机器人,只听得懂有限的指令. 1.在文件头写上如下行,这能让我们在语句中插入中文 #-*-coding:utf-8-*- ...

  9. 关于python中requests模块导入问题-python中requests模块的使用方法

    本文实例讲述了python中requests模块的使用方法.分享给大家供大家参考.具体分析如下: 在HTTP相关处理中使用python是不必要的麻烦,这包括urllib2模块以巨大的复杂性代价获取综合 ...

最新文章

  1. 使用文档自动保存功能
  2. 人工智能行业有哪些岗位_电力人有哪些岗位将被人工智能取代?
  3. java类输出_java的输出类
  4. 【字符串全排列】LeetCode 567. Permutation in String
  5. 51单片机学习笔记(7)——74HC138三八译码器
  6. NVR+DVR+CVR
  7. 指数分布的期望和方差推导
  8. 如何将txt格式转epub格式
  9. C#与西门子1500通讯案例——基于S7.net+
  10. Laravel Eloquent If Record Exists
  11. __ratelimit: XXX callbacks suppressed
  12. The Humble Programmer
  13. h5 禁止微信内置浏览器调整字体大小
  14. XIB总结(代码加载xib或xib拖xib)
  15. 修改mysql表字段长度
  16. eclipse发展与简介
  17. 远古守卫/cocos2d-x 源码/塔防游戏/高仿王国保卫战
  18. IDEA 连接数据库时不成功的解决
  19. CentOS 8.3 VNC
  20. 开学季值得买的蓝牙耳机有哪些?适合学生党买的平价蓝牙耳机

热门文章

  1. 具有弹性效果的ListView
  2. c语言中aver是什么意思_Linux系统top命令中的io使用率,到底是什么意思?
  3. 【iCore3 双核心板_ uC/OS-III】例程五:软件定时器
  4. MVC中跳转到其他页面,并传参数
  5. solr4.6本地数据提交异常
  6. 光纤接口怎么接 图解_光纤的数据比网线快很多倍,但为什么没有在家庭局域网中普及呢?...
  7. The selected server is enabled,but is not configured pro
  8. python 与别的程序通信_《Python》进程之间的通信(IPC)、进程之间的数据共享、进程池...
  9. python程序写诗_python写的简单发送邮件的脚本
  10. 高斯曲率求表面极值点