对于使用联邦模块的项目会有两个依赖,一个是远程模块,一个是共享模块。上一篇文章解释了远程模块的加载和安装并初始化共享作用域。consumes则是共享模块的解决方案,用于在运行时加载并安装依赖的共享模块。
为什么叫consumes?我理解是因为共享模块的获取是在__webpack_require__.I
中完成的,而consumes只是使用__webpack_require__.S
中的数据来安装对应模块。所以consumes是在消费__webpack_require__.S
中的数据。
__webpack_require__S.default = {react: {version: {get() {}, ...}}}
以上信息并没有在__webpack_require__.m上,所以当前环境并不能使用共享作用域中的内容
var versionLt = (a, b) => {
// see webpack/lib/util/semver.js for original code
a=parseVersion(a),b=parseVersion(b);for(var r=0;;){if(r>=a.length)return r<b.length&&"u"!=(typeof b[r])[0];var e=a[r],n=(typeof e)[0];if(r>=b.length)return"u"==n;var t=b[r],f=(typeof t)[0];if(n!=f)return"o"==n&&"n"==f||("s"==f||"u"==n);if("o"!=n&&"u"!=n&&e!=t)return e<t;r++}
}
var findSingletonVersionKey = (scope, key) => {
var versions = scope[key];
return Object.keys(versions).reduce((a, b) => {
return !a || (!versions[a].loaded && versionLt(a, b)) ? b : a;
}, 0);
};
var satisfy = (range, version) => {
// see webpack/lib/util/semver.js for original code
if(0 in range){version=parseVersion(version);var e=range[0],r=e<0;r&&(e=-e-1);for(var n=0,i=1,a=!0;;i++,n++){var f,s,g=i<range.length?(typeof range[i])[0]:"";if(n>=version.length||"o"==(s=(typeof(f=version[n]))[0]))return!a||("u"==g?i>e&&!r:""==g!=r);if("u"==s){if(!a||"u"!=g)return!1}else if(a)if(g==s)if(i<=e){if(f!=range[i])return!1}else{if(r?f>range[i]:f<range[i])return!1;f!=range[i]&&(a=!1)}else if("s"!=g&&"n"!=g){if(r||i<=e)return!1;a=!1,i--}else{if(i<=e||s<g!=r)return!1;a=!1}else"s"!=g&&"n"!=g&&(a=!1,i--)}}var t=[],o=t.pop.bind(t);for(n=1;n<range.length;n++){var u=range[n];t.push(1==u?o()|o():2==u?o()&o():u?satisfy(u,version):!o())}return!!o();
}
var getSingletonVersion = (scope, scopeName, key, requiredVersion) => {
var version = findSingletonVersionKey(scope, key);
if (!satisfy(requiredVersion, version)) typeof console !== "undefined" && console.warn && console.warn(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion));
return get(scope[key][version]);
};
var get = (entry) => {
entry.loaded = 1;
return entry.get()
};
// 给入参函数注入依赖项共享作用域scope,封装的是scope的获取,
// 因为scope可能在初始化中,也可能初始化完成
var init = (fn) => (function(scopeName, a, b, c) {
var promise = __webpack_require__.I(scopeName);
// 等待共享作用域初始化完成
if (promise && promise.then) return promise.then(fn.bind(fn, scopeName, __webpack_require__.S[scopeName], a, b, c));
return fn(scopeName, __webpack_require__.S[scopeName], a, b, c);
});
var loadSingletonVersionCheckFallback = /*#__PURE__*/ init((scopeName, scope, key, version, fallback) => {
if(!scope || !__webpack_require__.o(scope, key)) return fallback();
return getSingletonVersion(scope, scopeName, key, version);
});
var installedModules = {};
var moduleToHandlerMapping = {
"webpack/sharing/consume/default/react/react?5e40": () => (loadSingletonVersionCheckFallback("default", "react", [1,17,0,2], () => (Promise.all([__webpack_require__.e("vendors-node_modules_react_index_js"), __webpack_require__.e("node_modules_object-assign_index_js")]).then(() => (() => (__webpack_require__(/*! react */ "../node_modules/react/index.js"))))))),
"webpack/sharing/consume/default/react-dom/react-dom": () => (loadSingletonVersionCheckFallback("default", "react-dom", [1,17,0,2], () => (Promise.all([__webpack_require__.e("vendors-node_modules_react-dom_index_js"), __webpack_require__.e("webpack_sharing_consume_default_react_react")]).then(() => (() => (__webpack_require__(/*! react-dom */ "../node_modules/react-dom/index.js"))))))),
"webpack/sharing/consume/default/react/react?7071": () => (loadSingletonVersionCheckFallback("default", "react", [4,17,0,2], () => (__webpack_require__.e("vendors-node_modules_react_index_js").then(() => (() => (__webpack_require__(/*! react */ "../node_modules/react/index.js")))))))
};
// no consumes in initial chunks
var chunkMapping = {
"src_bootstrap_js": [
"webpack/sharing/consume/default/react/react?5e40",
"webpack/sharing/consume/default/react-dom/react-dom"
],
"webpack_sharing_consume_default_react_react": [
"webpack/sharing/consume/default/react/react?7071"
]
};
__webpack_require__.f.consumes = (chunkId, promises) => {
if(__webpack_require__.o(chunkMapping, chunkId)) {
chunkMapping[chunkId].forEach((id) => {
if(__webpack_require__.o(installedModules, id)) return promises.push(installedModules[id]);
var onFactory = (factory) => {
installedModules[id] = 0;
__webpack_require__.m[id] = (module) => {
delete __webpack_require__.c[id];
module.exports = factory();
}
};
var onError = (error) => {
delete installedModules[id];
__webpack_require__.m[id] = (module) => {
delete __webpack_require__.c[id];
throw error;
}
};
try {
var promise = moduleToHandlerMapping[id]();
if(promise.then) {
promises.push(installedModules[id] = promise.then(onFactory)['catch'](onError));
} else onFactory(promise);
} catch(e) { onError(e); }
});
}
}
chunkMapping中存着chunk对应共享模块的依赖,而moduleToHandlerMapping中存着共享模块的获取方式,通过方法__webpack_require__.f.consumes入参这两个数据就能解析chunk依赖的模块,并对模块进行安装。
(loadSingletonVersionCheckFallback("default", "react", [1,17,0,2], fallback))
这一行表达的是在共享作用域default上寻找版本为1.17.0.2的react,如果找不到使用自带的模块fallback。
var init = (fn) => (function(scopeName, a, b, c) {
var promise = __webpack_require__.I(scopeName);
// 等待共享作用域初始化完成
if (promise && promise.then) return promise.then(fn.bind(fn, scopeName, __webpack_require__.S[scopeName], a, b, c));
return fn(scopeName, __webpack_require__.S[scopeName], a, b, c);
});
var loadSingletonVersionCheckFallback = /*#__PURE__*/ init((scopeName, scope, key, version, fallback) => {
if(!scope || !__webpack_require__.o(scope, key)) return fallback();
return getSingletonVersion(scope, scopeName, key, version);
});
init方法用于初始化对应共享作用域,共享作用域加载完成后将相关作用域信息作为入参调用init的入参函数。
拿到作用域信息后调用方法getSingletonVersion(scope, scopeName, key, version)
getSingletonVersion
function getSingletonVersion(scope, scopeName, key, requiredVersion) {
...
return get(scope[key][version]);
}
var get = (entry) => {
entry.loaded = 1;
return entry.get();
};
get方法的返回值会作为方法onFactory的入参
var onFactory = (factory) => {
installedModules[id] = 0;
__webpack_require__.m[id] = (module) => {
delete __webpack_require__.c[id];
module.exports = factory();
}
};
onFactory执行完成后__webpack_require__.m
上就会出现对应的模块信息。到这里共享模块也安装完成了。
等待chunk src_bootstrap_js文件加载完成,外部依赖准备完成,共享模块准备完成后chunk src_bootstrap_js 则表示加载完成可以开始执行业务代码了。
名字的翻译
在源码中使用的名字是 import React from ‘react’,而通过webpack编译后变成了”webpack/sharing/consume/default/react/react?5e40”,并且在__webpack_require__.S上的名字也是react,这些是怎么关联起来的呢?
该问题同样存在于远程模块加载场景中。
”webpack/sharing/consume/default/react/react?5e40”是怎么和共享作用域中的”react”关联起来的?
在上面源码中
var moduleToHandlerMapping = {
"webpack/sharing/consume/default/react/react?5e40": () => (loadSingletonVersionCheckFallback("default", "react", [1,17,0,2], () => (Promise.all([__webpack_require__.e("vendors-node_modules_react_index_js"), __webpack_require__.e("node_modules_object-assign_index_js")]).then(() => (() => (__webpack_require__(/*! react */ "../node_modules/react/index.js"))))))),
}
表示模块"webpack/sharing/consume/default/react/react?5e40"
需要在共享作用域default
中的1.17.0.2
版本的react
,这里这两个名字就关联起来了。
源码中的 import React from ‘react’
是怎么和 "webpack/sharing/consume/default/react/react?5e40"
关联起来的?
源码中的
import React from 'react'
经过编译变成
var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "webpack/sharing/consume/default/react/react?5e40");
var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
这样源码中的名字 ”react”
就和"webpack/sharing/consume/default/react/react?5e40"
关联起来了。