在上一篇《从一个需求开始慢慢了解 webpack 5 的Module Federation(模块联邦)》 我们讲解了需求的演变过程
也通过webpack官网基本的了解了一些信息,最终决定尝试使用 模块联邦 这个方案进行技术预演,看看可行性;
那我们先简单搭建一个Demo 来 1:1 还原开发环境的场景;
系统甲(模块A),系统乙(引用系统甲的模块A);
我们在网上溜达溜达发现了这个项目:module-federation-examples
赶紧点进去看看,这不就是我们真正需要的吗??? 里面各种example 应有尽有啊
那就赶紧clone 下来选择我们想要的example
我们最终使用 module-federation-examples/vue-cli 这个,因为他无限接近于我们现场的开发环境的场景;
该Demo 包含了 三个 目录 consumer , core 以及 other;
我们简单的安装完后跑起来看看样子:
首先 consumer 是这个样子的:
core 是这个样子的:
other 是这个样子的:
我们看到 consumer 中包含了 core 和 other 的内容
再看看那个 core 中的红色 border的按钮是多么的显眼;
修改了 core 中的按钮为红色边框,刷新consumer 立马就开到了 红色的边框按钮;
接下来 我们看一下 这个三个项目的具体配置;
首先看看 consumer 中的 vue.config.js
const ModuleFederationPlugin =
require("webpack").container.ModuleFederationPlugin;
module.exports = {
publicPath: "http://localhost:9999/",
configureWebpack: {
plugins: [
new ModuleFederationPlugin({
name: "consumer",
filename: "remoteEntry.js",
remotes: {
core: "core@http://localhost:9000/remoteEntry.js",
other : 'other@http://localhost:9001/remoteEntry.js'
},
shared: require("./package.json").dependencies,
}),
],
},
};
我们发现 他仅仅配置了 ModuleFederationPlugin;
其中 包含了 name, filename, remotes, shared 这几个选项;
配置相当简洁 猜想 其中的remotes 配置 就是表示了从远端加载 core , 和 other 的模块组件组件以使用; 那我们看看 代码中是如何引入的呢:
export default {
name: "App",
components: {
Button: () => import("core/Button"),
Section: () => import("core/Section"),
MainComponent: () => import("other/MainComponent"),
},
};
我们发现在 components 选项中 import 了core/Button. 和 Section 以及other/MainComponent
那我们看看 core 中的配置呢:
new ModuleFederationPlugin({
name: "core",
filename: "remoteEntry.js",
exposes: {
"./Button": "./src/components/Button",
"./Section": "./src/components/Section",
},
shared: require("./package.json").dependencies,
}),
发现 在配置 exposes 中到导出了 Button,Section 这才使 consumer 中能够配置 remotes ,也才能在组件中 通过 import 进行引入,在template 中使用;
通过查看 consumer 应用的控制面板我们发现 加载了 几个组件文件,而且文件名称尤为的明显,就是定义的那几个组件的名称组合;
这是开发环境下我们看到的的样子,我们继续build 一下该应用看一下 构建后的产物;
这里我们就先build , core 这个;
我们发现 除了常规熟悉的文件外 还多了一个 remoteEntry.js 文件;
这个文件 就是 我们在配置选项中配置的 filename ,也是在 remotes 声明中的 入口文件;
打开 remoteEntry.js 格式化后 我们可以看到如下图
首先定义了 core 这个是在配置中的 name 选择; 接着的定义的 r 对象有两个 键 "./Button" ,"./Section" 就是我们在 expoese 中配置的需要导出的组件模块;都return 了 一个 promise 的函数,对应了应用方 使用 import 方法的导入,如下:
components: {
Button: () => import("core/Button"),
Section: () => import("core/Section"),
MainComponent: () => import("other/MainComponent"),
},
都是我们眼熟的一些信息;
既然是 var 申明的 core , 那我们去控制台 看看 code 是不是在 window 上呢;
确实存在 core , 而且挂载在 window 上的
再简单看下 get 方法呢
var core;
/******/ (function() { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ "webpack/container/entry/core":
/*!***********************!*\
!*** container entry ***!
\***********************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
var moduleMap = {
"./Button": function() {
return Promise.all([__webpack_require__.e("webpack_sharing_consume_default_vue_vue"), __webpack_require__.e("src_components_Button_vue")]).then(function() { return function() { return (__webpack_require__(/*! ./src/components/Button */ "./src/components/Button.vue")); }; });
},
"./Section": function() {
return Promise.all([__webpack_require__.e("webpack_sharing_consume_default_vue_vue"), __webpack_require__.e("src_components_Section_vue")]).then(function() { return function() { return (__webpack_require__(/*! ./src/components/Section */ "./src/components/Section.vue")); }; });
}
};
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 oldScope = __webpack_require__.S["default"];
var name = "default"
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; }
});
/***/ })
/******/ });
截取了部分代码:
moduleMap ,get, init 等都写的比较简洁 。
我们知道了大概的工作流程后,对后面的深入了解其原理就显得不那么陌生了。
还有没有其他方案?
接下来 就会吧 按该方案(模块联邦)用于生产环境进行验证 同时也将去使用 UMD方式共享模块 进行实践 最终选择一个适合当前环境以及团队的合理方案;
最后的最后
下面请上我们今天的主角:有请小趴菜