背景
目前我们将公共组件封装到npm包里,在每个应用使用npm包的方式进行消费
问题
每次更新组件需要发布npm包,本地开发不能实时看到组件更新效果,需要重新安装npm包才能生效。
解决方法
模块联邦可以在多个 webpack 编译产物之间共享模块、依赖、页面甚至应用,通过全局变量的组合,还可以在不同模块之前进行数据的获取,让跨应用间做到模块共享真正的插拔式的便捷使用。比如a应用如果想使用b应用中table的组件,通过模块联邦可以直接在a中进行import('b/table')非常的方便。
线上 Runtime(运行时) 的效果,让代码直接在项目间利用 CDN 直接共享,不再需要本地安装npm 包、构建再发布了。
umi提供了**Module Federation 插件**
怎么做
共享应用
封装公共组件
导出的模块按照约定,将源代码目录下的
exposes
一级子目录名作为导出项,导出文件为该目录下的 index 文件
typescript
// /exposesCounter/index.tsx
import React from 'react';
export default (props: { init?: number }) => {
const [c, setC] = React.useState(props.init ?? 10);
return (
<div>
<h1> remote Counter</h1>
<div>
<button
data-testid="remote-button"
onClick={() => {
setC((c) => c + 1);
}}
>
click to add new
</button>
</div>
<div>
remote hooks counter
<span data-testid="remote-counter">{c}</span>
</div>
</div>
);
};
umirc.ts配置
php
import { defineConfig } from '@umijs/max';
const shared = {
react: {
singleton: true,
eager: false,
},
'react-dom': {
singleton: true,
eager: false,
},
};
const moduleFederationName = 'remoteCounter'; // 模块名称
export default defineConfig({
mfsu: false,
mf: {
name: moduleFederationName,
shared,
},
publicPath: 'http://127.0.0.1:9000/',
});
使用方
keyResolver
用于在运行时决定使用entries
哪个 key; 推荐使用 立即调用函数表达式 的形式,可以在函数中实现较复杂的功能。不支持异步的函数。keyResolver
也可以使用静态的值,配置形式keyResolver: '"PROD"'
umirc.ts配置
php
import { defineConfig } from "@umijs/max";
const shared = {
react: {
singleton: true,
eager: false,
},
"react-dom": {
singleton: true,
eager: false,
},
};
export default defineConfig({
mfsu:false,
mf: {
name: "hostUser",
remotes: [
{
name: "remoteCounter",
entries: {
DEV: "http://127.0.0.1:9000/remote.js", // 本地 地址
PROD: "http://127.0.0.1:9000/remote.js",// 线上地址
},
keyResolver: `(()=> 'DEV')()`,
}
],
shared,
},
});
使用公共组件
javascript
import React, { Suspense } from 'react';
const RemoteCounter = React.lazy(() => {
// @ts-ignore
return import('remoteCounter/Counter');
});
export default () => {
<RemoteCounter />
}
配置项说明
(一)webpack ModuleFederationPlugin配置
- name 必须,当前应用的名字,全局唯一ID,通过 name/{expose} 的方式使用
- library 可选,打包方式,与 name 保持一致即可
- filename 可选,打包后的文件名,对应上面的 remoteEntry.js
- remotes 可选,表示当前应用是一个 Host,可以引用 Remote 中 expose 的模块
- exposes 可选,表示当前应用是一个 Remote,exposes 内的模块可以被其他的 Host 引用,引用方式为 import(name/{expose})
- shared 可选,依赖的包(下面包含了 shared 中包含的配置项)
-
如果配置了这个属性。webpack在加载的时候会先判断本地应用是否存在对应的包,如果不存在,则加载远程应用的依赖包。
-
以 app2 来说,因为它是一个远程应用,配置了
["react", "react-dom"]
,而它被 app1 所消费,所以 webpack 会先查找 app1 是否存在这两个包,如果不存在就使用 app2 自带包。 app1里面同样申明了这两个参数,因为 app1 是本地应用,所以会直接用 app1 的依赖。 -
shared 配置项指示 remote 应用的输出内容和 host 应用可以共用哪些依赖。 shared 要想生效,则 host 应用和 remote 应用的 shared 配置的依赖要一致。
-
import 共享依赖的实际的 package name,如果未指定,默认为用户自定义的共享依赖名,即 react-shared。如果是这样的话,webpack 打包是会抛出异常的,因为实际上并没有 react-shared 这个包。
-
singleton 是否开启单例模式,true 则开启。如何启用单例模式,那么 remote 应用组件和 host 应用共享的依赖只加载一次,且与版本无关。 如果版本不一致,会给出警告。不开启单例模式下,如果 remote 应用和 host 应用共享依赖的版本不一致,remote 应用和 host 应用需要分别各自加载依赖。
-
requiredVersion 指定共享依赖的版本,默认值为当前应用的依赖版本。- 如果 requiredVersion 与实际应用的依赖的版本不一致,会给出警告。
-
strictVersion 是否需要严格的版本控制。单例模式下,如果 strictVersion 与实际应用的依赖的版本不一致,会抛出异常。默认值为 false。
-
shareKey 共享依赖的别名, 默认值值 shared 配置项的 key 值。
-
shareScope 当前共享依赖的作用域名称,默认为 default。
-
eager 共享依赖在打包过程中是否被分离为 async chunk。eager 为 false, 共享依赖被单独分离为 async chunk; eager 为 true, 共享依赖会打包到 main、remoteEntry,不会被分离。默认值为 false,如果设置为 true, 共享依赖其实是没有意义的。
-
shareScope 所用共享依赖的作用域名称,默认为 default。如果 shareScope 和 share["xxx"].shareScope 同时存在,share["xxx"].shareScope 的优先级更高。
示例代码
gitlab.evaluateai.cn/liangchaofe...
参考
Mf配置项: ModuleFederationPlugin | webpack
mf配置项的一些解读:juejin.cn