Umi使用webpack5 Module Federation

·  阅读 2610

前言

最近有在做微前端开发,遇到子应用资源问题没办法共享,了解到webpack5的Module Federation可以动态运行另一个JavaScript应用的代码,同时可以共享一依赖。由于框架使用Umi做开发,官方没有提供示例去配置,在使用的过程中遇到一些小坑,记录一下。

Module Federation

Module Federation中文直译为“模块联邦”,而在webpack官方文档中,其实并未给出其真正含义,但给出了使用该功能的motivation, 即动机。

多个独立的构建可以形成一个应用程序。这些独立的构建不会相互依赖,因此可以单独开发和部署它们。
这通常被称为微前端,但并不仅限于此
复制代码

webpack配置Module Federation

  const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");

  module.exports = {
    // 其他webpack配置...
    plugins: [
      new ModuleFederationPlugin({
          name: 'app',
          library: { type: 'umd', name: 'app' },
          filename: 'app.js',
          remotes: {
            app_1: "app_1_remote",
            app_2: "app_2_remote"
          },
          exposes: {
            './Button': 'src/components/Button',
            './Component2': 'src/components/Component2',
          },
          shared: ["react", "react-dom"]
        })
    ]
  }
复制代码

通过以上配置,我们对mf有了一个初步的认识,即如果要使用mf,需要配置好几个重要的属性:

字段名类型含义
namestring必传值,即输出的模块名,被远程引用时路径为name/{name}/{expose}
librarystring声明全局变量的方式,name为umd的name
filenamestring构建输出的文件名
remotesobject远程引用的应用名及其别名的映射,使用时以key值作为name
exposesobject被远程引用时可暴露的资源路径及其别名
sharedobject与其他应用之间可以共享的第三方依赖,使你的代码中不用重复加载同一份依赖

Umi框架使用

首先我们通过脚手架创建两个项目,分别是mf1,端口为3000,导出一些组件Button等,,项目mf2,端口为3001,引入mf1的Button组件。 下面我们来看mf1的配置

项目 mf1 配置

我们项目mf1主要的功能是提供MF能力,导出Button组件,供其他应用使用。

const { ModuleFederationPlugin } = require("webpack").container;
export default defineConfig({
  // 其他umi配置...
  publicPath:'//localhost:3000/',
  webpack5: {},
  chainWebpack(memo) {
    memo
      .plugin('mf')
      .use(ModuleFederationPlugin, [{
        name: "mf1",
        library: { type: 'umd', name: 'mf1' },
        filename: 'remoteEntry.js',
        exposes: {
          "./Button": './src/components/button/index',
        },
        shared: { react: { eager: true }, "react-dom": { eager: true } }
      }])
  },
});

复制代码

在这里umi配置要开启webpack5和添加publicPath,尤其注意要配置publicPath,不配置会出现其它应用加载不到导出的模块。

Button组件

import React, { FC } from 'react';
const Button = props => {
  return (<div><button>我是mf1的button</button></div>);
}
export default Button;
复制代码

项目 mf2 配置

我们项目mf2主要的功能是提供MF能力,引入远程的mf1应用组件,来供当前应用使用。

MF配置
const { ModuleFederationPlugin } = require("webpack").container;
export default defineConfig({
 // 其他umi配置...
  webpack5: {},
  dynamicImport:{},
  chainWebpack(memo) {
    memo
      .plugin('mf')
      .use(ModuleFederationPlugin, [{
        name: "mf2",
        remotes: {
          mf1: "mf1@//localhost:3000/remoteEntry.js"
        }
        shared: { react: { eager: true }, "react-dom": { eager: true } }
      }])
  },
});

复制代码

在这里umi配置要开启webpack5和添加dynamicImport,尤其注意要配置dynamicImport,要不会报错

使用mf1项目组件

由于mf提供的是异步加载,在使用组件的时候我们要借助React的React.lazyReact.Suspense来加载异步组件。

// 导入组件
const Mf1Button = React.lazy(() => import("mf1/Button"));

export default function IndexPage() {
  return (
    <div>
      <React.Suspense fallback='loading'>
        <Mf1Button />
      </React.Suspense>
    </div>
  );
}
复制代码

配置注意项

  • mf1要添加publicPath
  • mf2要开启 dynamicImport

Umi框架使用问题

通过上边简单的配置,MF我们跑起来了,但有以下几个问题

  • mf1要添加publicPath,项目部署会比较麻烦,有场景是我们需要动态配置
  • mf1中的的Button组件不能用hooks,引用hooks会出现。因为违背了我们hooks的使用规则,hooks使用不能用两个React实例, 可能会出现错误。
    Uncaught Error: Shared module is not available for eager consumption
    复制代码
    image.png

解决publiPath

通过查看MF文档, publiPath可以auto。它将为你自动决定一个 publicPath。但我们直接配置会出错,umi框架会验证已/开头才符合publicPath规则。我们尝试更改webpack的方式去解决问题,配置为

chainWebpack(memo) {
    memo.output.publicPath('auto');
    // 其它webpack配置
  },
复制代码

用webpack的方式。问题得以解决,至此部署问题得已解决

解决组件内不能用Hooks

MF要记得添加shared,该配置可以为我们共享依赖。解决该问题,官方需要我们把入口设置成异步导入,参考链接 Uncaught Error: Shared module is not available for eager consumption

但是umi不支持异步启动,我们可以通过写个插件支持特性,插件内容

import { IApi } from 'umi';
import { resolve } from 'path';
import { readFileSync } from 'fs';

export default (api: IApi) => {

  api.onGenerateFiles(() => {
    const buffer= readFileSync(resolve('./src/.umi/umi.ts'))
    const c = String(buffer)
    // console.log()
    api.writeTmpFile({
      path: 'index.ts',
      content: c,
    });
    api.writeTmpFile({
      path: 'umi.ts',
      content: 'import("./index")',
    });
  });
};

复制代码

主要是先拿到umi生成的入口文件,我们自己创建一个文件,把内容写进去,之后改下入口文件的内容,我已经做了一个npm包,大家可以直接用,插件名为umi-plugin-mf-bootstrap, 我们只接通过yarn install umi-plugin-mf-bootstrap。重启项目,umi会自动加载插件

结束语

至此我们已经完成umiModule Federation的使用。我们在做微前端还用了qiankun和umi的qiankun-plugin插件,用我们写的umi-plugin-mf-bootstrap会有不兼容,不能同时使用。我们也正在积极探索,有进展,会发布出来。

参考链接

mf-demo

umi-plugin-mf-bootstrap

Module Federation

umi plugin

如果你觉得该文章不错,不妨

1、点赞,让更多的人也能看到这篇内容

2、关注我,让我们成为长期关系

3、关注公众号「前端有话说」,里面已有多篇原创文章,和开发工具,欢迎各位的关注,第一时间阅读我的文章

分类:
前端
标签:
分类:
前端
标签: