Data Fetching Logic
或其他业务逻辑容器
远程
的,容器的使用者是主机
远程
可以将模块公开给主机
主机
可以使用此类模块,它们被称为远程模块
公开
的模块和共享
的模块异步
方式公开模块HomePage
(来自团队A)使用Dropdown
组件()来自团队B)HomePage
上的Login
链接按需加载LoginModal
(来自团队A),LoginModal
使用Button
组件(来自团队B)react
Module Federation
应用于此应用程序...容器入口
,生成的模块将包含对所有公开和共享模块以及如何加载它们的引用Button
时,它将仅加载按钮块和react
,加载Dropdown
时,它将仅加载Dropdown
和react
Dropdown
时,但是另一方提供了另一个react版本(可能更高),它将加载Dropdown
和另一方提供的react版本的块(实际上,它将加载操作委托给另一方)容器
,因此标记其某些模块。 Button
和Dropdown
被标记为已公开
,因为它们应由其他团队使用react
被标记为共享
,以便可以与其他团队共享Login
链接,该链接打开LoginModal
.单击链接时,将并行下载LoginModal
的代码和Button
的代码ModuleFederationPlugin
可以使用模块联邦。使用不同的属性来设置不同的部分暴露
属性是重要的remotes
属性是goto属性。 它是一个对象,其中的所有容器都应在当前版本中可用。关键是模块作用域,在该作用域中,应在自己的代码库中访问容器公开的模块。任何以此键开头的请求都将创建一个远程模块,该模块将在运行时加载。该值是容器的位置。默认情况下,脚本外部变量用作容器位置。这里将指定脚本文件的URL和全局文件。该脚本将在运行时加载,并且可以从全局访问容器。singleton:true
选项。确保在运行时仅创建模块的单个实例。对于某些库,例如。不喜欢在同一应用程序中多次实例化的react。在这种情况下,无效的版本范围只会在运行时导致警告。requiredVersion
或文件名,并允许使用库和外部的不同方式。例如用于在Node.js中使用或选择加入更严格的版本检查(当版本范围无效时,这会导致错误而不是警告)mkdir teama
cd teama
npm init -y
mkdir teamb
cd teamb
npm init -y
cd teama
cd teamb
npm install webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev
npm install is-array --save
配置参数
字段 | 类型 | 含义 |
---|---|---|
name | string | 项目名称,应用的身份证,在应用分享资源的时候使用的标识,被远程引用时路径为${name}/${expose} |
library | object | 暴露项目的全局变量名 格式为 {type:'var',name:projectName} |
filename | string | 构建后的文件名,也是远程引入的文件名 |
remotes | object | 远程引用的应用名及其别名的映射,格式为 {远程项目别名:远程引入的项目名(其它应用name字段)} |
exposes | object | 被远程引用时可暴露的资源路径及其别名,格式为{别名:组件的路径} |
shared | object | 与其他应用之间可以共享的第三方依赖,可以在此控制版本号 |
title | string | head的标题 |
files | object | 远程调用项目的文件链接 |
teamb\src\Button.js
import isArray from 'is-array';
export default `(Button[${isArray.name}])`;
teamb\src\Dropdown.js
import isArray from 'is-array';
import ArrowIcon from './ArrowIcon';
export default `(Dropdown[${ArrowIcon}][${isArray.name}])`;
teamb\src\ArrowIcon.js
export default 'ArrowIcon';
teamb\src\bootstrap.js
import isArray from 'is-array';
let Dropdown = await import('./Dropdown');
let Button = await import('./Button');
console.log(Dropdown.default);
console.log(Button.default);
console.log(isArray.name);
teamb\src\index.js
import("./bootstrap");
let HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
mode: "development",
devtool: false,
devServer: {
port: 8000
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
}),
new ModuleFederationPlugin({
name: "teamb",
filename: "remoteEntry.js",
library: { type: 'var', name: 'teambVar' },
exposes: {
"./Dropdown": "./src/Dropdown.js",
"./Button": "./src/Button.js",
},
shared: ["is-array"]
})
],
target: ['es6', 'web'],
experiments: {
topLevelAwait: true
},
}
teamb\public\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>teamb</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
{
"scripts": {
"build": "webpack",
"start": "webpack serve"
},
}
npm run build
npm run start
teama\src\LoginModal.js
import isArray from 'is-array';
let Button = await import('teamb/Button');
export default `(LoginModal[${Button.default}][${isArray.name}])`;
teama\src\HomePage.js
import isArray from 'is-array';
let Dropdown = await import('teamb/Dropdown');
let LoginModal = await import('./LoginModal');
export default `(HomePage[${Dropdown.default}][${LoginModal.default}][${isArray.name}])`
teama\src\bootstrap.js
import HomePage from './HomePage';
console.log(HomePage);
teama\src\index.js
import("./bootstrap");
teama\webpack.config.js
let HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
mode: "development",
devtool: false,
output: {
publicPath: "http://localhost:3000/"
},
target: ['es6', 'web'],
experiments: {
topLevelAwait: true
},
devServer: {
port: 3000
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
}),
new ModuleFederationPlugin({
filename: "remoteEntry.js",
name: "teama",
library: { type: 'var', name: 'teamaVar' },
remotes: {
teamb: "teambVar@http://localhost:8000/remoteEntry.js"
},
shared: ["is-array"]
})
]
}
window.teamb
,他有两个函数属性,init
和 get
init
方法用于初始化作用域对象,get
方法用于下载 moduleMap
中导出的远程模块。teamb
到本地模块teamb.init
的执行环境,收集依赖到共享作用域对象 shareScope
teamb.init
,初始化作用域对象import
远程模块时调用 teamb.get(moduleName)
通过 JSONP
懒加载远程模块,然后缓存在全局对象 window[‘webpackChunk’ + appName]
webpack_require
方法读取缓存中的模块,执行用户回调window.teamb = (() => {
var modules = ({
"webpack/container/entry/teamb":
((module, exports, require) => {
var moduleMap = {
"./Dropdown": () => {
return Promise.all([require.e("webpack_sharing_consume_default_is-array_is-array"), require.e("src_Dropdown_js")]).then(() => () => require("./src/Dropdown.js"));
},
"./Button": () => {
return Promise.all([require.e("webpack_sharing_consume_default_is-array_is-array"), require.e("src_Button_js")]).then(() => () => require("./src/Button.js"));
}
};
var get = (module) => {
return moduleMap[module]();
};
//初始化作用域
var init = (shareScope) => {
var name = "default";
require.S[name] = shareScope;
return require.I(name);
};
require.d(exports, {
get: () => get,
init: () => init
});
})
});
var cache = {};
function require(moduleId) {
if (cache[moduleId]) {
return cache[moduleId].exports;
}
var module = cache[moduleId] = {
exports: {}
};
modules[moduleId](module, module.exports, require);
return module.exports;
}
require.n = (module) => {
var getter = module && module.__esModule ?
() => module['default'] :
() => module;
return getter;
};
require.d = (exports, definition) => {
for (var key in definition) {
Object.defineProperty(exports, key, { get: definition[key] });
}
};
require.f = {};
require.e = (chunkId) => {
return Promise.all(Object.keys(require.f).reduce((promises, key) => {
require.f[key](chunkId, promises);
return promises;
}, []));
};
require.u = (chunkId) => {
return "" + chunkId + ".js";
};
require.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop)
require.l = (url, done) => {
var script = document.createElement('script');
script.src = url;
script.onload = done
document.head.appendChild(script);
};
require.r = (exports) => {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
Object.defineProperty(exports, '__esModule', { value: true });
};
require.S = {};
require.I = (name) => {
if (require.S[name])
return Promise.resolve();
};
require.p = "http://localhost:8000/";
//初始化作用域
var init = (fn) => function (scopeName, key, version) {
//先保证作用域存在
return require.I(scopeName).then(() => {
//返回作用域指定版本对应的模块
return fn(require.S[scopeName], key, version);
});
};
//加载共享作用域
var loadShareScope = init((scope, key, version) => {
//获取版本
var versions = scope[key];
//获取指定版本
var entry = versions[version];
return entry.get()
});
(() => {
var moduleToHandlerMapping = {
"webpack/sharing/consume/default/is-array/is-array": () => loadShareScope("default", "is-array", '1.0.1')
};
var chunkMapping = {
"webpack_sharing_consume_default_is-array_is-array": [
"webpack/sharing/consume/default/is-array/is-array"
]
};
require.f.consumes = (chunkId, promises) => {
if (require.o(chunkMapping, chunkId)) {
chunkMapping[chunkId].forEach((id) => {
let promise = moduleToHandlerMapping[id]().then((factory) => {
modules[id] = (module) => {
module.exports = factory();
}
})
promises.push(promise);
});
}
}
})();
var installedChunks = {
"teamb": 0
};
require.f.j = (chunkId, promises) => {
if ("webpack_sharing_consume_default_is-array_is-array" != chunkId) {
var promise = new Promise((resolve, reject) => {
installedChunks[chunkId] = [resolve, reject];
});
promises.push(promise);
var url = require.p + require.u(chunkId);
require.l(url);
}
};
var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
var [chunkIds, moreModules, runtime] = data;
var moduleId, chunkId, i = 0, resolves = [];
for (; i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if (require.o(installedChunks, chunkId) && installedChunks[chunkId]) {
resolves.push(installedChunks[chunkId][0]);
}
installedChunks[chunkId] = 0;
}
for (moduleId in moreModules) {
if (require.o(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
if (runtime) runtime(require);
if (parentChunkLoadingFunction) parentChunkLoadingFunction(data);
while (resolves.length) {
resolves.shift()();
}
}
var chunkLoadingGlobal = self["webpackChunkteamb"] = self["webpackChunkteamb"] || [];
chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
return require("webpack/container/entry/teamb");
})();
teamb\dist\src_Dropdown_js.js
(self["webpackChunkteamb"] = self["webpackChunkteamb"] || []).push([["src_Dropdown_js"], {
"./src/ArrowIcon.js":
((module, exports, require) => {
"use strict";
require.r(exports);
require.d(exports, {
"default": () => DEFAULT_EXPORT
});
const DEFAULT_EXPORT = ('ArrowIcon');
}),
"./src/Dropdown.js":
((module, exports, require) => {
"use strict";
require.r(exports);
require.d(exports, {
"default": () => DEFAULT_EXPORT
});
var is_array_0__ = require("webpack/sharing/consume/default/is-array/is-array");
var is_array_0___default = require.n(is_array_0__);
var _ArrowIcon_1__ = require("./src/ArrowIcon.js");
const DEFAULT_EXPORT = (`(Dropdown[${_ArrowIcon_1__.default}][${(is_array_0___default().name)}])`);
})
}]);
teamb\dist\src_Button_js.js
(self["webpackChunkteamb"] = self["webpackChunkteamb"] || []).push([["src_Button_js"], {
"./src/Button.js":
((module, exports, require) => {
require.r(exports);
require.d(exports, {
"default": () => DEFAULT_EXPORT
});
var is_array_0__ = require("webpack/sharing/consume/default/is-array/is-array");
var is_array_0___default = require.n(is_array_0__);
const DEFAULT_EXPORT = (`(Button[${(is_array_0___default().name)}])`);
})
}]);
/**
console.log(require.cache);
./src/ArrowIcon.js: {exports: Module}
./src/Button.js: {exports: Module}
./src/Dropdown.js: {exports: Module}
webpack/container/entry/teamb: {exports: {…}}
webpack/sharing/consume/default/is-array/is-array: {exports: ƒ}
*/
teama\dist\main.js
(() => {
//模块定义
var modules = ({
"webpack/container/reference/teamb": ((module, exports, require) => {
//加载远程脚本,返回 window.teamb
module.exports = new Promise((resolve) => {
require.l("http://localhost:8000/remoteEntry.js", resolve);
}).then(() => window.teamb);
})
});
var cache = {};
function require(moduleId) {
if (cache[moduleId]) {
return cache[moduleId].exports;
}
var module = cache[moduleId] = {
exports: {}
};
modules[moduleId](module, module.exports, require);
return module.exports;
}
//如果在ES module,取default,否则取自己
require.n = (module) => {
var getter = module && module.__esModule ?
() => module['default'] :
() => module;
return getter;
};
require.d = (exports, definition) => {
for (var key in definition) {
Object.defineProperty(exports, key, { get: definition[key] });
}
};
require.u = (chunkId) => {
return "" + chunkId + ".js";
};
require.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
require.l = (url, done) => {
var script = document.createElement('script');
script.src = url;
script.onload = done
document.head.appendChild(script);
};
require.r = (exports) => {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
Object.defineProperty(exports, '__esModule', { value: true });
};
require.f = {};
require.e = (chunkId) => {
return Promise.all(Object.keys(require.f).reduce((promises, key) => {
require.f[key](chunkId, promises);
return promises;
}, []));
};
(() => {
//远程模块的代码码映射
var chunkMapping = {
"webpack_container_remote_teamb_Dropdown": [
"webpack/container/remote/teamb/Dropdown"
],
"webpack_container_remote_teamb_Button": [
"webpack/container/remote/teamb/Button"
]
};
var idToExternalAndNameMapping = {
"webpack/container/remote/teamb/Dropdown": [
"default",//命名空间
"./Dropdown",//导出的名称
"webpack/container/reference/teamb"//来自哪个外部模块
],
"webpack/container/remote/teamb/Button": [
"default",
"./Button",
"webpack/container/reference/teamb"
]
};
//加载远程模块
require.f.remotes = (chunkId, promises) => {
//如果是外部模块,则加载
if (require.o(chunkMapping, chunkId)) {
//获取外部模块的id
chunkMapping[chunkId].forEach((id) => {
//获取外部模块的名称 scopeName 作用域名称 remoteExposeName远程暴露的名称 远程ID
var [scopeName, remoteExposeName, remoteId] = idToExternalAndNameMapping[id];
//获取外部模块的代码
let promise = require(remoteId).then(external => {
//获取external外部变量,初始化作用域
return require.I(scopeName).then(() => {
//获取远程暴露的模块定义
return external.get(remoteExposeName).then(factory => {
//获取远程暴露的模块实例
modules[id] = (module) => {
//执行工厂方法,获取远程模块实例
module.exports = factory();
}
});
});
});
promises.push(promise);
});
}
}
})();
//存放scope
require.S = {};
//初始化scope
require.I = (name) => {
if (require.S[name])
return Promise.resolve();
var scope = require.S[name] = {};
//注册共享模块
var register = (name, version, factory) => {
var currentScope = scope[name] = scope[name] || {};
currentScope[version] = { get: factory };
};
var promises = [];
//初始化远程外部模块
var initExternal = (id) => {
var module = require(id);
let promise = module.then(module => module.init(scope));
promises.push(promise);
}
//scope的名称
switch (name) {
case "default": {
register("is-array", "1.0.1", () => require.e("node_modules_is-array_index_js").then(() => () => require("./node_modules/is-array/index.js")));
initExternal("webpack/container/reference/teamb");
}
break;
}
return Promise.all(promises)
};
require.p = "http://localhost:3000/";
var init = (fn) => function (scopeName, key, version) {
return require.I(scopeName).then(() => {
return fn(require.S[scopeName], key, version);
});
};
var loadShareScope = init((scope, key, version) => {
var versions = scope[key];
var entry = versions[version];
return entry.get()
});
(() => {
//share scope consumed modules
var moduleToHandlerMapping = {
"webpack/sharing/consume/default/is-array/is-array": () => loadShareScope("default", "is-array", '1.0.1')
};
var chunkMapping = {
"src_bootstrap_js": [
"webpack/sharing/consume/default/is-array/is-array"
]
};
require.f.consumes = (chunkId, promises) => {
if (require.o(chunkMapping, chunkId)) {
chunkMapping[chunkId].forEach((id) => {
let promise = moduleToHandlerMapping[id]().then((factory) => {
modules[id] = (module) => {
module.exports = factory();
}
})
promises.push(promise);
});
}
}
})();
var installedChunks = {
"main": 0
};
require.f.j = (chunkId, promises) => {
if (!/^webpack_container_remote_teamb_(Button|Dropdown)$/.test(chunkId)) {
var promise = new Promise((resolve, reject) => {
installedChunks[chunkId] = [resolve, reject];
});
promises.push(promise);
var url = require.p + require.u(chunkId);
require.l(url);
}
};
var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
var [chunkIds, moreModules] = data;
var moduleId, chunkId, i = 0, resolves = [];
for (; i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if (require.o(installedChunks, chunkId) && installedChunks[chunkId]) {
resolves.push(installedChunks[chunkId][0]);
}
installedChunks[chunkId] = 0;
}
for (moduleId in moreModules) {
if (require.o(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
if (parentChunkLoadingFunction) parentChunkLoadingFunction(data);
while (resolves.length) {
resolves.shift()();
}
}
var chunkLoadingGlobal = self["webpackChunkteama"] = self["webpackChunkteama"] || [];
chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
require.e("src_bootstrap_js").then(require.bind(require, "./src/bootstrap.js"));
})();
teama\dist\src_bootstrap_js.js
(self["webpackChunkteama"] = self["webpackChunkteama"] || []).push([["src_bootstrap_js"], {
"./src/HomePage.js":
((module, exports, require) => {
"use strict";
module.exports = (async () => {
require.r(exports);
require.d(exports, {
"default": () => DEFAULT_EXPORT
});
var is_array_0__ = require("webpack/sharing/consume/default/is-array/is-array");
var is_array_0___default = require.n(is_array_0__);
let Dropdown = await require.e("webpack_container_remote_teamb_Dropdown").then(require.bind(require, "webpack/container/remote/teamb/Dropdown"));
let LoginModal = await require.e("src_LoginModal_js").then(require.bind(require, "./src/LoginModal.js"));
const DEFAULT_EXPORT = (`(HomePage[${Dropdown.default}][${LoginModal.default}][${(is_array_0___default().name)}])`);
return exports;
})();
}),
"./src/bootstrap.js":
((module, exports, require) => {
"use strict";
module.exports = (async () => {
require.r(exports);
var _HomePage_0__ = require("./src/HomePage.js");
_HomePage_0__ = await Promise.resolve(_HomePage_0__);
console.log(_HomePage_0__.default);
return exports;
})();
})
}]);
teama\dist\src_LoginModal_js.js
(self["webpackChunkteama"] = self["webpackChunkteama"] || []).push([["src_LoginModal_js"], {
"./src/LoginModal.js":
((module, exports, require) => {
"use strict";
module.exports = (async () => {
require.r(exports);
require.d(exports, {
"default": () => DEFAULT_EXPORT
});
var is_array_0__ = require("webpack/sharing/consume/default/is-array/is-array");
var is_array_0___default = require.n(is_array_0__);
let Button = await require.e("webpack_container_remote_teamb_Button").then(require.bind(require, "webpack/container/remote/teamb/Button"));
const DEFAULT_EXPORT = (`(LoginModal[${Button.default}][${(is_array_0___default().name)}])`);
return exports;
})();
})
}]);
teama\dist\node_modules_is-array_index_js.js
(self["webpackChunkteama"] = self["webpackChunkteama"] || []).push([["node_modules_is-array_index_js"], {
"./node_modules/is-array/index.js":
((module) => {
console.log('双方共享的是teama的isArray');
var isArray = Array.isArray;
var str = Object.prototype.toString;
module.exports = isArray || function (val) {
return !!val && '[object Array]' == str.call(val);
};
})
}]);