对于使用联邦模块的项目会有两个依赖,一个是远程模块,一个是共享模块。上一篇文章解释了远程模块的加载和安装并初始化共享作用域。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 codea=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 codeif(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"
关联起来了。