umi+模块联邦-配置指南

背景

目前我们将公共组件封装到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配置

  1. name 必须,当前应用的名字,全局唯一ID,通过 name/{expose} 的方式使用
  2. library 可选,打包方式,与 name 保持一致即可
  3. filename 可选,打包后的文件名,对应上面的 remoteEntry.js
  4. remotes 可选,表示当前应用是一个 Host,可以引用 Remote 中 expose 的模块
  5. exposes 可选,表示当前应用是一个 Remote,exposes 内的模块可以被其他的 Host 引用,引用方式为 import(name/{expose})
  6. 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...

参考

umijs.org/docs/max/mf...

Mf配置项: ModuleFederationPlugin | webpack

mf配置项的一些解读:juejin.cn

mf原理:Module Federation | webpack 中文文档

相关推荐
墨绿色的摆渡人7 分钟前
论文笔记(七十五)Auto-Encoding Variational Bayes
前端·论文阅读·chrome
今晚吃什么呢?28 分钟前
前端面试题之CSS中的box属性
前端·css
我是大龄程序员30 分钟前
Babel工作理解
前端
CopyLower44 分钟前
提升 Web 性能:使用响应式图片优化体验
前端
南通DXZ1 小时前
Win7下安装高版本node.js 16.3.0 以及webpack插件的构建
前端·webpack·node.js
Mintopia1 小时前
深入理解 Three.js 中的 Mesh:构建 3D 世界的基石
前端·javascript·three.js
前端太佬2 小时前
暂时性死区(Temporal Dead Zone, TDZ)
前端·javascript·node.js
Mintopia2 小时前
Node.js 中 http.createServer API 详解
前端·javascript·node.js
xRainco2 小时前
Redux从简单到进阶(Redux、React-redux、Redux-toolkit)
前端
印第安老斑鸠啊2 小时前
由一次CI流水线失败引发的对各类构建工具的思考
前端