待完善
1.引言
webpack5新特性 模块邦联(module-federation)让 Webpack 项目可以实现线上 Runtime 的效果,让代码组件直接在项目间共享,不再需要本地安装 Npm 包、构建再发布了
在以往技术中,大多数项目中通过DLL或者引入npm包等来复用组件,提高开发效率。而多个项目之间使用的相同组件大多通过copy实现。模块邦联内置于webpack5,让项目间组件共享成为了可能。
2.线上使用情况
umi中的mfsu就是依赖于module-federation实现
3.邦联整体加载流程学习
首先了解一下webpack模块加载流程__webpack_require__
加载第一步
var __webpack_exports__ = __webpack_require__(60119);
// The module cache
var __webpack_module_cache__ = {};
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
// Create a new module (and put it into the cache)
var module = __webpack_module_cache__[moduleId] = {
// no module.id needed
// no module.loaded needed
exports: {}
};
// Execute the module function
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
// Return the exports of the module
return module.exports;
}
// expose the modules object (__webpack_modules__)
__webpack_require__.m = __webpack_modules__;
// expose the module cache
__webpack_require__.c = __webpack_module_cache__;
通过__webpack_require__之后,开始渲染页面
import React, { FC, Suspense } from "react";
import ReactDOM from "react-dom";
const MFC = React.lazy(() => import('MFComponents/MFC'));
import "./index.less";
const Test: FC<any> = () => {
return <div>
<Suspense fallback={""}>
<MFC />
</Suspense>
</div>;
};
export default ReactDOM.render(<Test />, document.querySelector("#root"));
当执行const MFC = React.lazy(() => import('MFComponents/MFC')); 时,会触发webpack插件中配置的邦联的remote
new ModuleFederationPlugin({
name: "weimaiUI",
remotes: {
MFComponents: 'url or promise'
},
shared: { react: { singleton: true }, "react-dom": { singleton: true } },
})
此时会触发__webpack_require__.e
__webpack_require__.f = {};
// This file contains only the entry chunk.
// The chunk loading function for additional chunks
__webpack_require__.e = function(chunkId) {
return Promise.all(Object.keys(__webpack_require__.f).reduce(function(promises, key) {
__webpack_require__.f[key](chunkId, promises);
return promises;
}, []));
};
__webpack_require__.e中包含j和remote,来加载邦联组件js
加载完之后,页面html上会新增对于邦联组件js地址引用的
到此主机引用步骤完成,进入远端组件渲染流程
进入远端组件渲染流程
远端js加载完成后,会挂载get与init
var get = function(module, getScope) {
__webpack_require__.R = getScope;
getScope = (
__webpack_require__.o(moduleMap, module)
? moduleMap[module]()
: Promise.resolve().then(function() {
throw new Error('Module "' + module + '" does not exist in container.');
})
);
__webpack_require__.R = undefined;
return getScope;
};
var init = function(shareScope, initScope) {
if (!__webpack_require__.S) return;
var name = "default"
var oldScope = __webpack_require__.S[name];
if(oldScope && oldScope !== shareScope) throw new Error("Container initialization failed as it has already been initialized with a different share scope");
__webpack_require__.S[name] = shareScope;
return __webpack_require__.I(name, initScope);
};
// This exports getters to disallow modifications
__webpack_require__.d(exports, {
get: function() { return get; },
init: function() { return init; }
});
挂载完成后,执行init完成邦联初始化工作
此时window上已经可以获取到邦联模块,window[remote]
获取组件
完成挂载后,此时可以通过window[remote].get('./componentName')获取对应组件
通过__webpack_require__.e执行__webpack_require__.f中的j和compat方法
__webpack_require__.f.j = function(chunkId, promises) {
// JSONP chunk loading for javascript
var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;
if(installedChunkData !== 0) { // 0 means "already installed".
// a Promise means "currently loading".
if(installedChunkData) {
promises.push(installedChunkData[2]);
} else {
if(true) { // all chunks have JS
// setup Promise in chunk cache
var promise = new Promise(function(resolve, reject) { installedChunkData = installedChunks[chunkId] = [resolve, reject]; });
promises.push(installedChunkData[2] = promise);
// start chunk loading
var url = __webpack_require__.p + __webpack_require__.u(chunkId);
// create error before stack unwound to get useful stacktrace later
var error = new Error();
var loadingEnded = function(event) {
if(__webpack_require__.o(installedChunks, chunkId)) {
installedChunkData = installedChunks[chunkId];
if(installedChunkData !== 0) installedChunks[chunkId] = undefined;
if(installedChunkData) {
var errorType = event && (event.type === 'load' ? 'missing' : event.type);
var realSrc = event && event.target && event.target.src;
error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
error.name = 'ChunkLoadError';
error.type = errorType;
error.request = realSrc;
installedChunkData[1](error);
}
}
};
__webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId);
} else installedChunks[chunkId] = 0;
}
}
};
__webpack_require__.f.compat = function(chunkId, promises) {
// mini-css-extract-plugin CSS loading
var cssChunks = {"213":1,"533":1,"608":1};
if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);
else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {
promises.push(installedCssChunks[chunkId] = new Promise(function(resolve, reject) {
var href = "" + ({}[chunkId]||chunkId) + ".css";
var fullhref = __webpack_require__.p + href;
var existingLinkTags = document.getElementsByTagName("link");
for(var i = 0; i < existingLinkTags.length; i++) {
var tag = existingLinkTags[i];
var dataHref = tag.getAttribute("data-href") || tag.getAttribute("href");
if(tag.rel === "stylesheet" && (dataHref === href || dataHref === fullhref)) return resolve();
}
var existingStyleTags = document.getElementsByTagName("style");
for(var i = 0; i < existingStyleTags.length; i++) {
var tag = existingStyleTags[i];
var dataHref = tag.getAttribute("data-href");
if(dataHref === href || dataHref === fullhref) return resolve();
}
var linkTag = document.createElement("link");
linkTag.rel = "stylesheet";
linkTag.type = "text/css";
linkTag.onload = resolve;
linkTag.onerror = function(event) {
var request = event && event.target && event.target.src || fullhref;
var err = new Error("Loading CSS chunk " + chunkId + " failed.\n(" + request + ")");
err.code = "CSS_CHUNK_LOAD_FAILED";
err.request = request;
delete installedCssChunks[chunkId]
linkTag.parentNode.removeChild(linkTag)
reject(err);
};
linkTag.href = fullhref;
var head = document.getElementsByTagName("head")[0];
head.appendChild(linkTag);
}).then(function() {
installedCssChunks[chunkId] = 0;
}));
}
};
通过__webpack_require__.l加载插入组件对应的js以及css
"./MFC": function() {
return Promise.all([__webpack_require__.e(38), __webpack_require__.e(690), __webpack_require__.e(533)]).then(function() { return function() { return (__webpack_require__(62035)); }; });
},
"./Test": function() {
return __webpack_require__.e(608).then(function() { return function() { return (__webpack_require__(55608)); }; });
}
};
此时页面html上会新增 对组件css的引用链接
加载组件仅在被使用时,未使用的组件不会加载