Module Federation 微前端框架搭建实战

530 阅读3分钟

微前端是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。各个前端应用还可以独立运行、独立开发、独立部署

不同于市面上的Qiankun、MicroApp等框架,本期的框架为基于Webpack5的一个插件,Module Federation 构建的一套微前端体系。

主要有两个概念 HostWebApplication(主工程)、 RemoteWebApplicaton (远程库工程、依赖工程,可多个,库工程之间亦可作为主副依赖)

下面主要以三个独立工程做例子讲解 HostEntry工程,Common库工程,Module 模块工程。

项目核心结构

本文项目均基于 TS+React Hooks 讲述。

项目通用代码结构

---- ${Project name}
----dist
----node_modules
----public
----src
----.gitgnore
----babel.config.js
----index.html
----package-lock.json
----package.json
----README.md
----tsconfig.json
----webpack.config.js

**与常规项目的主要差异在webpack.config.js 及 /src下的入口 **

下面我们看下代码,首先是webpack.config部分,为了便利,我们省去其他代码

库工程配置文件解析

//通用库的 webpack.config.js
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = () => {
    return {
      devServer: {
        open: true,
        port: 3003,   //项目端口,用于区分不同前端应用
        proxy: {
          "/": "http://${your_api_url}}:80", //网络请求base url
        },
      },
      ...
      plugins [
        ...
        new ModuleFederationPlugin({
          name: "***_***_***", // 与package.json内name保持一致
          filename: "remote.js", //远程应用(模块)构建出来的文件名称,可提供该文件给其他应用(模块)使用
          exposes: {
            //对外提供的组件,表示远程应用可以使用哪些内容
            "./****EntryRCTSX" : "./src/***/***EntryRC.tsx",
            "./****EntryRCJSX" : "./src/***/***EntryRC.jsx",
            "./utils": "./src/***/***utils.js",
          }
          shared: {
            ...dependencies,
            react: {
              eager: true,
            },
          },
        }),
      ].filter(Boolean),
    };
};

以上是作为通用库工程的基本配置,何谓通用库,就是只允许被其他微前端应用调用,而不调用任何库工程的项目, 即前文提到过的 *Common库工程 *。

Module工程配置简述

接下来,我们谈一下 *Module 模块工程 * 的配置

//库工程的 webpack.config.js
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = () => {
    return {
      devServer: {
        open: true,
        port: 3005,   //项目端口,用于区分不同前端应用
        proxy: {
          "/": "http://${your_api_url}}:80", //网络请求base url
        },
      },
      ...
      plugins [
        ...
        new ModuleFederationPlugin({
          name: "***_***_***", // 与package.json内name保持一致
          filename: "remote.js", //远程应用(模块)构建出来的文件名称,可提供该文件给其他应用(模块)使用
          exposes: {
            //对外提供的组件,表示远程应用可以使用哪些内容
            "./****EntryRCTSX" : "./src/*ModuleName*/index.tsx",
            "./****EntryRCJSX" : "./src/*ModuleName*/index.jsx",
          },
           remotes: {
            *库工程name*:"*库工程name*@http://${lib_app_url}/remote.js",
        },   
          shared: {
            react: {
              requiredVersion: false,
              singleton: true,
            },
          },
        }),
      ].filter(Boolean),
    };
};

细心的读者注意应该注意到了,以上配置项多了remotes: {...} 配置项,即我们所依赖的远程微前端工程。

与单纯发布库工程不同,作为需要调用依赖common库的Module工程,需要对常规的入口文件index.tsx做细微调整,增加 ** bootstrap.js** 文件,并将其作为应用入口,其代码如下:

//bootstrap.js
import App from  './App';
import React from  'react';
import { createRoot } from  'react-dom/client';
import { ConfigProvider } from  'antd';
import zhCN from  'antd/es/locale/zh_CN';
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<ConfigProvider  locale={zhCN}>
    <App  />
</ConfigProvider>);

 

index.tsx更新如下:

import("./bootstrap");
// eslint-disable-next-line import/no-anonymous-default-export
export default {};

以上操作是为了保证项目所依赖的远程应用都加载完毕,再启动本应用。

 

主入口工程配置简述

最后,我们来看如何集成上述项目到我们对用户展示的应用上面

主工程通用拥有上述配置文件,内容大体与 Module 工程相同,下面我们来看样例:

//Host主入口工程的 webpack.config.js
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = () => {
    return {
      devServer: {
        open: true,
        port: 3099,   //项目端口,用于区分不同前端应用
        proxy: {
          "/": "http://${your_api_url}}:80", //网络请求base url
        },
      },
      ...
      plugins [
        new HtmlWebpackPlugin({
             template: "index.html", //发布应用的入口模板
            }),
        ...
        new ModuleFederationPlugin({
          name: "***_***_entry", // 与package.json内name保持一致
          remotes: {
            *库工程name*:"*库工程name*@http://${lib_app_url}/remote.js",
            *模块工程name*:"*模块工程name*@http://${md_app_url}/remote.js",
        },   
          shared: {
            react: {
              requiredVersion: false,
              singleton: true,
            },
          },
        }),
      ].filter(Boolean),
    };
};

细心的朋友应该注意到了,本项目没有 filename、exposes 选项,因为我们不需要让其他应用调用。

同样,由于引用的远程项目,我们需要通过bootstrap.js启动应用。

代码如下:

//bootstrap.js
import React from  'react';
import { createRoot } from  'react-dom/client';
import { HashRouter } from  'react-router-dom';
import BootIndex from  './BootIndex';
const container = document.getElementById('root');
const root = createRoot(container);
root.render(
    <HashRouter>
      <BootIndex/>
    </HashRouter>
);

index.tsx更新如下:

import("./bootstrap");
// eslint-disable-next-line import/no-anonymous-default-export
export default {};

以上操作是为了保证项目所依赖的远程应用都加载完毕,再启动本应用。

小结

前文所述,为微前端的项目配置,分为Host、Module、CommonLib三个项目,建议大家多动手尝试,下一期,会为大家简单讲述本文最后部分提到的 项目引用、路由配置等问题。