微前端 - Native Federation使用完整示例

发布于:2025-06-09 ⋅ 阅读:(19) ⋅ 点赞:(0)

       这是一个极简化的 Angular 使用@angular-architects/native-federation 插件的微前端示例,只包含一个主应用和一个远程应用。

完整示例展示

项目结构

federation-simple/
├── host-app/      # 主应用
└── remote-app/    # 远程应用

创建远程应用 (remote-app)

 初始化项目
ng new remote-app --standalone --minimal --style=css --routing=false
cd remote-app
npm install @angular-architects/native-federation --save-dev
配置 Native Federation
ng add @angular-architects/native-federation --project remote-app --port 4201
创建远程组件

编辑 src/app/hello.component.ts:

import { Component } from '@angular/core';

@Component({
  standalone: true,
  template: `
    <div style="
      border: 2px solid blue;
      padding: 20px;
      margin: 10px;
      border-radius: 5px;
    ">
      <h2>Hello from Remote App!</h2>
      <p>This component is loaded from the remote app</p>
    </div>
  `
})
export class HelloComponent {}
配置暴露的模块

编辑 federation.config.js:

module.exports = {
  name: 'remoteApp',
  exposes: {
    './Hello': './src/app/hello.component.ts'
  },
  shared: {
    '@angular/core': { singleton: true, strictVersion: true },
    '@angular/common': { singleton: true, strictVersion: true },
    'rxjs': { singleton: true, strictVersion: true }
  }
};

创建主应用 (host-app)

初始化项目
ng new host-app --standalone --minimal --style=css --routing=true
cd host-app
npm install @angular-architects/native-federation @angular-architects/module-federation-tools --save-dev
配置 Native Federation
ng add @angular-architects/native-federation --project host-app --port 4200
创建主页面

编辑 src/app/app.component.ts:

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';

@Component({
  standalone: true,
  imports: [CommonModule, RouterOutlet],
  template: `
    <div style="padding: 20px;">
      <h1>Host Application</h1>
      <nav>
        <a routerLink="/" style="margin-right: 15px;">Home</a>
        <a routerLink="/remote">Load Remote Component</a>
      </nav>
      <router-outlet></router-outlet>
    </div>
  `
})
export class AppComponent {}
创建远程组件加载页面

创建 src/app/remote.component.ts:

在这里使用了<app-remote-hello> 那这个怎么来的呢?请见下面的关键点说明第四点!!!

import { Component, OnInit } from '@angular/core';
import { loadRemoteModule } from '@angular-architects/module-federation';

@Component({
  standalone: true,
  template: `
    <div style="margin-top: 20px;">
      <h2>Remote Component</h2>
      <div *ngIf="loading">Loading remote component...</div>
      <div *ngIf="error" style="color: red;">Failed to load remote component: {{error}}</div>
      <ng-container *ngIf="!loading && !error">
        <app-remote-hello></app-remote-hello>
      </ng-container>
    </div>
  `
})
export class RemoteComponent implements OnInit {
  loading = true;
  error: string | null = null;

  async ngOnInit() {
    try {
      await loadRemoteModule({
        remoteEntry: 'http://localhost:4201/remoteEntry.js',
        remoteName: 'remoteApp',
        exposedModule: './Hello'
      });
      this.loading = false;
    } catch (err) {
      this.error = err instanceof Error ? err.message : String(err);
      this.loading = false;
    }
  }
}
配置路由

编辑 src/app/app.routes.ts:

import { Routes } from '@angular/router';
import { RemoteComponent } from './remote.component';

export const APP_ROUTES: Routes = [
  { 
    path: 'remote', 
    component: RemoteComponent,
    providers: [
      // 注册远程组件
      {
        provide: 'remote-hello',
        useValue: {
          remoteEntry: 'http://localhost:4201/remoteEntry.js',
          remoteName: 'remoteApp',
          exposedModule: './Hello',
          componentName: 'Hello'
        }
      }
    ]
  },
  { path: '**', redirectTo: 'remote' }
];
配置应用

编辑 src/app/app.config.ts:

import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { provideRouter } from '@angular/router';
import { APP_ROUTES } from './app.routes';
import { RemoteComponent } from '@angular-architects/module-federation-tools';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(APP_ROUTES),
    importProvidersFrom(
      RemoteComponent.forRemote({
        type: 'module',
        remoteEntry: 'http://localhost:4201/remoteEntry.js',
        exposedModule: './Hello',
        componentName: 'Hello'
      })
    )
  ]
};
配置 federation

编辑 federation.config.js: 

module.exports = {
  name: 'hostApp',
  remotes: {
    'remoteApp': 'http://localhost:4201/remoteEntry.js'
  },
  shared: {
    '@angular/core': { singleton: true, strictVersion: true },
    '@angular/common': { singleton: true, strictVersion: true },
    '@angular/router': { singleton: true, strictVersion: true },
    'rxjs': { singleton: true, strictVersion: true }
  }
};

运行应用

  1. 打开两个终端窗口
  2. 在第一个终端中运行远程应用
  3. 在第二个终端中运行主应用
  4. 访问应用
  5. 在主应用中,点击 "Load Remote Component" 链接将加载并显示远程组件

关键点说明

项目简单说明下面三点:

  1. 远程应用: remote中暴露一个了简单的HelloCompoent并且配置了共享的 Angular 核心库;

  2. 主应用: host中使用 了loadRemoteModule动态加载远程组件,同时通过路由导航到远程组件,并且配置了远程模块的引用;

  3. 共享依赖: remote项目和Host项目Angular 核心库和 RxJS 被标记为共享单例,并且确保了版本兼容性

  4. <app-remote-hello> 这个selector怎么来的

app-remote-hello 是自定义元素,通过下面的方式创建和使用的:

组件名称的生成规则: 当使用 @angular-architects/module-federation-tools 的 RemoteComponent时,组件名称会自动按照以下规则生成:

app-remote-<componentName>
  • app 是 Angular 默认的前缀
  • remote 表示这是一个远程组件
  • <componentName> 是你在配置中指定的组件名称(这里是 hello

具体配置来源:在示例中,这个名称来源于下面几个地方的配置:

在 app.config.ts 中:

importProvidersFrom(
  RemoteComponent.forRemote({
    type: 'module',
    remoteEntry: 'http://localhost:4201/remoteEntry.js',
    exposedModule: './Hello',
    componentName: 'Hello'  // ← 这里定义了基础名称
  })
)

在 remote.component.ts 中:

<app-remote-hello></app-remote-hello>

完整的名称转换过程:

  • 你配置了 componentName: Hello
  • 系统会自动转换为小写形式:hello
  • 加上前缀 app-remote- 形成最终标签名:app-remote-hello

如何自定义这个名称:如果想使用不同的标签名,可以这样修改

// 在 app.config.ts 中
RemoteComponent.forRemote({
  // ...其他配置
  componentName: 'MyCustomHello',  // 自定义名称
  elementName: 'my-hello-element'  // 自定义元素名(可选)
})

// 然后在模板中使用
<my-hello-element></my-hello-element>

为什么能这样使用:这是因为 @angular-architects/module-federation-tools 在底层做了以下工作:

  • 动态注册了一个新的 Angular 组件

  • 将该组件定义为自定义元素(Custom Element)

  • 自动处理了组件名称的转换

  • 设置了与远程组件的连接

验证方法:如果你想确认这个组件是如何被注册的,可以在浏览器开发者工具中:

  • 打开 Elements 面板

  • 找到 <app-remote-hello> 元素

  • 查看它的属性,会发现它是一个 Angular组件

    • Angular 组件会有特殊属性

      • 查看是否有 _nghost-* 和 _ngcontent-* 这类 Angular 特有的属性

      • 例如:<app-remote-hello _ngcontent-abc="" _nghost-def="">

    • 检查自定义元素定义

      • 在 Console 中输入:document.querySelector('app-remote-hello').constructor.name

      • 如果是 Angular 组件,通常会显示 HTMLElement(因为 Angular 组件最终是自定义元素)

 参考资料:

核心资源

  1. @angular-architects/native-federation 官方文档
    📖 GitHub 仓库 & 文档

    • 包含安装指南、配置选项和基本用法

  2. Module Federation 概念解释
    📖 Webpack 官方文档

    • 理解微前端的核心机制


教程文章

  1. Angular 微前端完整指南
    📖 Angular Architects 博客

    • 含代码示例和架构图

  2. 实战案例分步教程
    📖 Dev.to 详细教程

    • 从零开始的实现步骤


视频资源

  1. 官方演示视频
    ▶️ YouTube 教程

    • 30分钟实战演示(Angular团队录制)

  2. 模块联邦深度解析
    ▶️ Webpack 官方频道

    • 底层原理讲解


扩展工具

  1. 模块联邦工具库
    📦 npm @angular-architects/module-federation-tools
    • 简化动态加载的工具

  2. 微前端状态管理方案
    📖 NgRx 集成指南
    • 跨应用状态管理建议


常见问题

  1. 共享依赖解决方案
    ❓ Stack Overflow 热门讨论

    • 版本冲突处理方案

  2. 生产环境部署指南
    📖 Angular 部署文档

    • 包含微前端部署注意事项


示例代码库

  1. 官方示例项目:可直接运行的完整项目
    💻 GitHub 代码库