webpack联邦模块之remotes方法

使用联邦模块后当前项目就会有两个依赖,一个是依赖共享模块,一个是依赖远程模块。运行时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 once
	if(initPromises[name]) return initPromises[name];
	// creates a new share scope if needed
	if(!__webpack_require__.o(__webpack_require__.S, name)) __webpack_require__.S[name] = {};
	// runs all init snippets from all modules reachable
	var 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__方法直接获取。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值