文章目录

TypeScript模块联邦在微前端架构中的配置

发布于 2026-04-03 09:02:23 · 浏览 8 次 · 评论 0 条

TypeScript模块联邦在微前端架构中的配置

微前端架构允许将一个大型前端应用拆分为多个独立开发、部署的小型应用。TypeScript结合Webpack 5的模块联邦(Module Federation)插件,能高效实现跨应用共享代码与组件。以下步骤教你从零开始配置。


准备工作

  1. 确保环境:安装 Node.js >=16 和 npm >=8
  2. 创建两个项目:一个作为宿主应用(Host),另一个作为远程应用(Remote)。例如:
    mkdir host-app remote-app
  3. 分别初始化项目
    cd host-app && npm init -y
    cd ../remote-app && npm init -y

配置远程应用(提供共享模块)

  1. 安装依赖

    cd remote-app
    npm install typescript ts-loader webpack webpack-cli html-webpack-plugin --save-dev
    npm install react react-dom  # 假设使用 React,可替换为其他框架
  2. 创建 TypeScript 配置文件 tsconfig.json

    {
      "compilerOptions": {
        "target": "ES2020",
        "module": "ESNext",
        "moduleResolution": "bundler",
        "jsx": "react-jsx",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "outDir": "./dist"
      }
    }
  3. 创建入口文件 src/bootstrap.tsx(避免直接暴露顶层组件):

    import React from 'react';
    import ReactDOM from 'react-dom/client';
    
    const RemoteButton = () => <button>来自 Remote 的按钮</button>;
    
    export { RemoteButton };
    
    // 仅在独立运行时挂载
    if (!window.remoteAppLoaded) {
      const root = ReactDOM.createRoot(document.getElementById('root')!);
      root.render(<RemoteButton />);
      window.remoteAppLoaded = true;
    }
  4. 配置 Webpack webpack.config.js

    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
    
    module.exports = {
      entry: './src/bootstrap.tsx',
      mode: 'development',
      devServer: {
        port: 3001,
        historyApiFallback: true,
      },
      resolve: {
        extensions: ['.ts', '.tsx', '.js'],
      },
      module: {
        rules: [
          {
            test: /\.(ts|tsx)$/,
               loader: 'ts-loader',
             },
           ],
         },
         plugins: [
           new HtmlWebpackPlugin({
             template: './public/index.html',
           }),
           new ModuleFederationPlugin({
             name: 'remoteApp',
             filename: 'remoteEntry.js',
             exposes: {
               './Button': './src/bootstrap.tsx',
             },
             shared: {
               react: { singleton: true, requiredVersion: '^18.0.0' },
               'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
             },
           }),
         ],
       };
       ```
    
    5. **创建 `public/index.html`**:
       ```html
       <!DOCTYPE html>
       <html>
         <head><title>Remote App</title></head>
         <body><div id="root"></div></body>
       </html>
       ```
    
    6. **添加启动脚本到 `package.json`**:
       ```json
       "scripts": {
         "start": "webpack serve"
       }
       ```
    
    7. **启动远程应用**:
       ```bash
       npm start
       ```
       此时应用运行在 `http://localhost:3001`,并生成联邦入口 `http://localhost:3001/remoteEntry.js`。
    
    ---
    
    ## 配置宿主应用(消费远程模块)
    
    1. **安装相同依赖**(版本需一致):
       ```bash
       cd host-app
       npm install typescript ts-loader webpack webpack-cli html-webpack-plugin --save-dev
       npm install react react-dom
       ```
    
    2. **创建 `tsconfig.json`**(内容同远程应用)。
    
    3. **创建入口文件 `src/index.tsx`**:
       ```tsx
       import React from 'react';
       import ReactDOM from 'react-dom/client';
    
       // 动态导入远程模块
       const loadRemoteButton = async () => {
         const remoteModule = await import('remoteApp/Button');
         return remoteModule.RemoteButton;
       };
    
       const App = () => {
         const [RemoteButton, setRemoteButton] = React.useState<any>(null);
    
         React.useEffect(() => {
           loadRemoteButton().then(setRemoteButton);
         }, []);
    
         return (
           <div>
             <h1>宿主应用</h1>
             {RemoteButton ? <RemoteButton /> : <p>加载中...</p>}
           </div>
         );
       };
    
       const root = ReactDOM.createRoot(document.getElementById('root')!);
       root.render(<App />);
       ```
    
    4. **配置 Webpack `webpack.config.js`**:
       ```js
       const HtmlWebpackPlugin = require('html-webpack-plugin');
       const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
    
       module.exports = {
         entry: './src/index.tsx',
         mode: 'development',
         devServer: {
           port: 3000,
         },
         resolve: {
           extensions: ['.ts', '.tsx', '.js'],
           alias: {
             // 确保类型检查通过(开发时)
             'remoteApp/Button': 'remoteApp/Button',
           },
         },
         module: {
           rules: [
             {
               test: /\.(ts|tsx)$/,
            loader: 'ts-loader',
          },
        ],
      },
      plugins: [
        new HtmlWebpackPlugin({
          template: './public/index.html',
        }),
        new ModuleFederationPlugin({
          name: 'hostApp',
          remotes: {
            remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js',
          },
          shared: {
            react: { singleton: true, requiredVersion: '^18.0.0' },
            'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
          },
        }),
      ],
    };
  5. 创建 public/index.html(内容同远程应用)。

  6. 添加启动脚本到 package.json

    "scripts": {
      "start": "webpack serve"
    }
  7. 声明远程模块类型(解决 TypeScript 报错)
    创建 src/types.d.ts

    declare module 'remoteApp/Button' {
      export const RemoteButton: React.FC;
    }
  8. 启动宿主应用

    npm start

    访问 http://localhost:3000,页面将显示“宿主应用”和“来自 Remote 的按钮”。


关键配置说明

模块联邦的核心在于 ModuleFederationPlugin 的参数:

参数 作用 示例值
name 当前应用的全局唯一名称 'hostApp'
filename (Remote 端)生成的联邦入口文件名 'remoteEntry.js'
exposes (Remote 端)对外暴露的模块路径映射 { './Button': './src/bootstrap.tsx' }
remotes (Host 端)声明可消费的远程应用 { remoteApp: 'remoteApp@http://.../remoteEntry.js' }
shared 共享依赖,避免重复加载 { react: { singleton: true, requiredVersion: '^18.0.0' } }

类型安全增强

为避免 any 类型,可提取共享组件的接口:

  1. 在远程应用中创建 src/components/Button.tsx

    import React from 'react';
    
    export interface ButtonProps {
      onClick?: () => void;
    }
    
    export const RemoteButton: React.FC<ButtonProps> = ({ onClick }) => (
      <button onClick={onClick}>远程按钮</button>
    );
  2. 修改 bootstrap.tsx 导出

    export { RemoteButton, type ButtonProps } from './components/Button';
  3. 在宿主应用的 types.d.ts 中同步类型

    declare module 'remoteApp/Button' {
      import type { ButtonProps } from 'remoteApp/Button';
      export const RemoteButton: React.FC<ButtonProps>;
      export type { ButtonProps };
    }
  4. 在宿主应用中使用强类型

    import type { ButtonProps } from 'remoteApp/Button';
    
    const MyButton: React.FC<ButtonProps> = (props) => {
      const RemoteButton = ...; // 从异步加载获取
      return RemoteButton ? <RemoteButton {...props} /> : null;
    };

生产环境注意事项

  1. 动态远程地址:不要硬编码 http://localhost:3001。可通过环境变量注入:

    // webpack.config.js
    const remoteUrl = process.env.REMOTE_URL || 'http://localhost:3001';
    new ModuleFederationPlugin({
      remotes: {
        remoteApp: `remoteApp@${remoteUrl}/remoteEntry.js`,
      },
    });
  2. 错误边界处理:远程模块加载失败时降级:

    const App = () => {
      const [error, setError] = React.useState<Error | null>(null);
      const [RemoteButton, setRemoteButton] = React.useState<any>(null);
    
      React.useEffect(() => {
        loadRemoteButton()
          .then(setRemoteButton)
          .catch(setError);
      }, []);
    
      if (error) return <div>加载远程组件失败</div>;
      return <>{RemoteButton ? <RemoteButton /> : <p>加载中...</p>}</>;
    };
  3. 共享依赖版本对齐:所有微应用必须使用兼容的 reactreact-dom 版本,否则因 singleton: true 导致运行时冲突。

启动宿主与远程应用后,浏览器控制台无报错且按钮正常渲染,即表示配置成功。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文