模块联邦在微前端架构中的实践

180 阅读8分钟

背景

我们有一个用React编写的管理面板,在这里我们跟踪和管理Trendyol GO(Hızlı Market和Trendyol Yemek)中所有阶段的订单。单一仓库很好地满足了我们的期望,因为一开始我们是唯一的团队,员工数量不多。

单一的仓库并不总是坏的。如果它能满足你的期望,如果它不会给你带来负面影响,那么你可以使用各种方法。重要的是;找到以最有效的方式达到你的目标的方法。

大约1.5年后,当我们的队友人数变得足够多时,我们以领域驱动设计(DDD)的理念将我们的团队划分为多个小团队。在这一点上,我们必须设计微前端的结构,使每个团队可以独立开发应用。

我之前在几个不同的项目中有过微前端的经验。我也曾自己设计过一个微前端架构,但研究所有其他的替代方案并选择最能满足我们需求的方案是更合理的。我们探索了所有的替代方案,权衡了每个方案的利弊(我不会在这篇文章中谈论所有的替代方案,因为那是另一个话题。),在评估的最后,我们发现Webpack 模块联邦可以很好地满足我们的需求。

为什么是Webpack模块联邦?

当我们研究了所有的替代方案后,出于以下原因,选择Webpack模块联邦更有意义。

  • 没有维护成本(如果你自己建立一个架构,会有维护成本)
  • 没有团队特定的学习成本(如果你自己建立一个架构,会有学习成本)
  • 向模块联邦过渡的成本很小
  • 不需要对每个项目进行重新架构
  • 所有的需求都在构建时得到满足
  • 在运行时不需要额外的工作
  • 分享依赖的成本低
  • 库/框架独立
  • 你不需要处理所有的压缩和缓存问题
  • 你不需要处理路由问题
  • Shell和Micro Apps不是紧耦合的,而是松耦合的

怎么使用模块联邦

有以下三种形式: image.png

  1. 域名

通过这种方式,你可以创建尽可能多的微型前端(应用程序),并通过Shell App管理完全独立的域。例如,想象一下,在Shell App中有一个菜单,当链接被点击时,它将在右边带出相关的应用程序。

image.png

image.png

2.微件

通过这种方式,你可以从任何应用程序中添加任何微件/组件(即一小段代码)到任何应用程序。你可以在产品应用中的用户应用中公开UserDetail组件。

image.png

image.png

  1. 混合型

你可以同时使用第一和第二种方式。

开始实践

首先创建一个app 命名为shell,并且以相同的方式创建应用product & user

npx create-react-app shellcd shellyarn add webpack webpack-cli webpack-server html-webpack-plugin css-loader style-loader babel-loader webpack-dev-server
复制代码

第一步:初始化项目

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';ReactDOM.render(
  <App />,
  document.getElementById('root')
);
复制代码

第二步:配置webpack

const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const deps = require('./package.json').dependencies;

module.exports = {
  mode'development',
  devServer: {
    port3001,
  },
  module: {
    rules: [
      {
        test/.js?$/,
        exclude/node_modules/,
        loader'babel-loader',
        options: {
          presets: [
            '@babel/preset-env',
            '@babel/preset-react',
          ],
        },
      },
      {
        test/.css$/i,
        use: ["style-loader""css-loader"],
      },
    ],
  },
  plugins: [
    new ModuleFederationPlugin(
      {
        name'SHELL',
        filename'remoteEntry.js',
        shared: [
          {
            ...deps,
            react: { requiredVersion: deps.reactsingletontrue },
            'react-dom': {
              requiredVersion: deps['react-dom'],
              singletontrue,
            },
          },
        ],
      }
    ),
    new HtmlWebpackPlugin({
      template:
        './public/index.html',
    }),
  ],
};
复制代码

其中关于模块联邦的配置项,解释如下:

  • name: 我们用它来确定应用程序的名称。我们将通过这个名称与其他应用程序进行交流。
  • filename: 我们用它作为一个入口文件。在这个例子中,其他应用程序将能够通过输入 "SHELL@http://localhost:3001/remoteEntry.js "访问SHELL应用程序。
  • shared(共享)。我们用它来指定这个应用程序将与其他应用程序共享哪些依赖。这里需要注意的是 "singleton: true"。如果你不写 "singleton: true",每个应用程序将在一个单独的React实例上运行

把同样的文件复制到UserProduct项目,但不要忘记增加端口和改变名称字段。

第三步:设计

// app.js
import React from 'react';
import './App.css';const App = () => (
  <div className="shell-app">
    <h2>Hi from Shell App</h2>
  </div>
);export default App;
复制代码
// app.css
.shell-app {  
margin5px;  
text-align: center;  
background#FFF3E0;  
border1px dashed #FFB74D;  
border-radius5px;  
color#FFB74D;  
}
复制代码

上面两个文件都放到src下,Product 以及 User 项目也做同样的更改,只是将src命名为shell。 三个项目依次运行以下的命令

yarn webpack server
复制代码

image.png

image.png

现在,我们所有的应用程序都为Micro Frontends架构做好了准备,并且可以相互独立运行。🎉

第四步:整合

是时候提到模块联邦的两个伟大的功能了 :)

expose:它允许你从任何应用程序到另一个应用程序共享一个组件、一个页面或整个应用程序。你所暴露的一切都被创建为一个单独的构建,从而创造了一个自然的tree shaking每个构建都以文件的MD5哈希值命名,所以你不必担心缓存的问题。

remote: 它决定了你将从哪些应用程序接收一个组件、一个页面或应用程序本身。 每个应用程序都可以同时暴露和定义一个远程,并多次进行。

现在让我们把Product应用暴露给Shell应用,让我们做第一个微前端连接。

让我们打开产品 repo 中的 webpack.config.js 文件,并改变其中传给ModuleFederationPlugin中的对象,如下。exposes对象中的值决定了它在repo中共享哪个组件,而对象中的key决定了其他应用程序可以访问这个组件的名称。

百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度 百度