多个单独的构建应该形成一个应用程序。这些独立的构建不应该相互依赖,因此可以单独开发和部署它们。
这通常被称为微前端,但不限于此。
配置
ModuleFederationPlugin插件配置如下:
{
name: "app1", // 当前应用名称
remotes: { // 引用的远程应用
在当前应用中使用的名称:远程应用名称@远程应用入口
app2: "app2@http://localhost:3002/remoteEntry.js",
},
library: { // 参考 https://www.webpackjs.com/configuration/output/#output-library
type: "var",
name: "app1"
},
exposes: { // 导出项
"./Button": "./src/Button",
},
shared: { // 需要共享的资源
react: {
import: "react", // “react”包将使用一个提供的备用模块
shareKey: "newReact", // 在此名称下,共享模块将被置于共享范围中
shareScope: "default", // 将使用具有此名称的共享作用域。注意:经测试该值不能修改。
singleton: true // 只允许共享模块的单一版本
requiredVersion: deps.react, // 需要的版本
},
"react-dom": { singleton: true } },
}
基本使用
主应用
webpackconfig.js
const { ModuleFederationPlugin } = require("webpack").container;
plugins: [
new ModuleFederationPlugin({
name: "app1", // 当前应用名称
remotes: { //
app2: "app2@http://localhost:3002/remoteEntry.js",
},
shared: {
react: { singleton: true },
"react-dom": { singleton: true } },
})
]
app.js
const RemoteButton = React.lazy(() => import("app2/Button"));
远程应用
webpackconfig.js
plugins: [
new ModuleFederationPlugin({
name: "app2",
library: { type: "var", name: "app2" },
filename: "remoteEntry.js",
exposes: {
"./Button": "./src/Button",
},
shared: { react: { singleton: true }, "react-dom": { singleton: true } },
})
],
动态主机
动态加载远程。
- 首先加载远程代码
- 其次加载远程导出
注意:webpack模块联合配置不需要配置remotes选项。
代码实例:
const useDynamicScript = (args) => { // 加载脚本
return new Promise((resolve, reject) => {
if (!args.url) {
reject()
}
const element = document.createElement("script");
element.src = args.url;
element.type = "text/javascript";
element.async = true;
element.onload = () => {
console.log(`Dynamic Script Loaded: ${args.url}`);
resolve()
};
element.onerror = () => {
console.error(`Dynamic Script Error: ${args.url}`);
reject()
};
document.head.appendChild(element);
})
};
function loadComponent(scope, module) { // 加载模块
return async () => {
// Initializes the share scope. This fills it with known provided modules from this build and all remotes
await __webpack_init_sharing__("default");
const container = window[scope]; // or get the container somewhere else
// Initialize the container, it may provide shared modules
await container.init(__webpack_share_scopes__.default);
const factory = await window[scope].get(module);
const Module = factory();
return Module;
};
}
// 调用
await useDynamicScript({url: 'http://localhost:8080/remoteEntry.js'})
let moudle = await loadComponent('remote', './remoteModule')()
嵌套远程
案例描述:A应用依赖B应用,B应用依赖C应用。A应用引用B应用。
使用注意:当A应用引用B应用时,需要在A的webpack模块联合配置remotes选项时同时配置上B和C。
共享srote
redux存储并动态地注入redux存储。
vuex存储。
方案一: 保证多个应用之间共享同一个vue、vuex实例。最终的结果为,在vue组件中,谁启动vuex实例归属于谁。
远程组件代码:
<template>
<div>
{{$store.state.test}}
</div>
</template>
主机引用代码:
<template>
<div>
<Home></Home>
</div>
</template>
<script>
async function getRemote() { // 加载远程组件
let service = await import("Base/BaseMainApp"); // 加载远程导出
let { // 取出home组件
default: { Home },
} = service;
return Home
}
export default {
components: { // 注册为本地组件
Home: () => getRemote()
}
}
</script>
此时$store.state.test展示的数据为主机中的store数据
方案二: vuex srote进行应用级别的隔离。远程应用需将store或者vue实例导出,此时直接操作store或者使用vue实例进行操作即可。
动态注册vuex文档模块动态注册部分。
自我修复
remote应用依赖并期望在host 应用中提供共享依赖项,但是host 应用并没有提供相关的共享依赖。此时,当host应用引用remote应用时,程序会自动查找依赖。官方代码实例
共享路由
仅导出路由配置,在单独的应用中路由实例仅有一个。
共享不同版本依赖
方案一
两个使用两个不同版本lodash的联合应用程序。
-app1使用lodash@4.10.0。
-app2使用lodash@4.11.0以及lodash.nth--功能在lodash@4.10.0中不可用。
在如下shared的配置中声明不同的依赖后,应用程序会自动选择相应版本。
主应用 webpack.config.js
new ModuleFederationPlugin({
name: "app1",
remotes: {
app2: "app2@http://localhost:3002/remoteEntry.js",
},
shared: {
react: "react",
"react-dom": "react-dom",
[`lodash-${require("lodash").VERSION}`]: "lodash",
},
}),
远程应用 webpack.config.js
new ModuleFederationPlugin({
name: "app2",
filename: "remoteEntry.js",
exposes: {
"./Example": "./src/Example",
},
shared: {
react: "react",
"react-dom": "react-dom",
[`lodash-${require("lodash").VERSION}`]: "lodash",
},
}),
** 注意:经实验发现程序始终会寻找当前应用下的相应版本。**
方案二
将不同版本的依赖作为依赖进行导出,供其他应用使用。
webpack.config.js
new ModuleFederationPlugin({
name: "app2",
library: { type: "var", name: "app2" },
filename: "remoteEntry.js",
exposes: {
"./Button": "./src/Button",
"./ModernComponent": "./src/ModernReactComponent",
"./newReact": require.resolve("react"),
"./newReactDOM": require.resolve("react-dom"),
},
shared: [
"react-dom",
"react"
],
}),
自动共享依赖
将所有依赖项添加为共享模块,版本推断自package.json的依赖关系中 ,使用版本来自package.json,依赖项将自动使用最高可用包。在联合应用程序中,基于package.json,联合应用程序中可能存在多个不同的版本
-
注意,这不会影响嵌套路径,如“lodash/pull”
-
请注意,这将禁用对这些包的某些优化
-
可能会导致bundle大小问题
** 注意:经实验发现程序始终会寻找当前应用下的相应版本。**
webpack.config.js
const deps = require("./package.json").dependencies;
new ModuleFederationPlugin({
name: "app1",
filename: "remoteEntry.js",
remotes: {
app2: "app2@http://localhost:3002/remoteEntry.js",
},
exposes: {
"./Button": "./src/Button",
},
shared: {
...deps,
react: {
singleton: true,
},
"react-dom": {
singleton: true,
},
},
}),