You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular模块联邦中如何从远程应用向壳应用提供资源

Angular模块联邦微前端:SVG图标加载异常与Material组件渲染问题解决

问题描述

我基于Angular实现了模块联邦微前端架构,但遇到两个核心问题:

  • 远程应用中的SVG图标在壳应用中渲染破损,单独运行远程应用时显示正常,需要解决远程应用向壳应用提供资源文件的问题。
  • Angular Material组件出现渲染异常,例如点击下拉框选择选项后,下拉框无法自动收起。

相关代码

SVG图标服务代码

import {MatIconRegistry} from '@angular/material/icon';
import {DomSanitizer} from '@angular/platform-browser';
import {Injectable} from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class IconService {

  constructor(
    private matIconRegistry: MatIconRegistry,
    private domSanitizer: DomSanitizer
  ) {
  }

  public registerIcons(): void {
    this.loadIcons(Object.values(Icons), './assets/svg/');
  }

  private loadIcons(iconKeys: string[], iconUrl: string): void {
    iconKeys.forEach(key => {
      this.matIconRegistry.addSvgIcon(key, this.domSanitizer.bypassSecurityTrustResourceUrl(`${iconUrl}/${key}.svg`))
    });
  }
}

export enum Icons {
  DeleteIcon = 'DeleteIcon',
  DownloadIcon = 'DownloadIcon',
  EditIcon = 'EditIcon',
  UploadIcon = 'UploadIcon',
}

HTML使用代码

<mat-icon svgIcon="UploadIcon" class="image-upload-icon-sizing" (click)="fileUploadInput.click()" *ngIf="imgSourceError"></mat-icon>

壳应用Web配置

const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
const mf = require("@angular-architects/module-federation/webpack");
const path = require("path");
const share = mf.share;

const sharedMappings = new mf.SharedMappings();
sharedMappings.register(
  path.join(__dirname, '../../tsconfig.json'),
  [/* mapped paths to share */]);

module.exports = {
  output: {
    uniqueName: "parent",
    publicPath: 'http://localhost:4400/'
  },
  optimization: {
    runtimeChunk: false
  },   
  resolve: {
    alias: {
      ...sharedMappings.getAliases(),
    }
  },
  experiments: {
    outputModule: true
  },
  plugins: [
    new ModuleFederationPlugin({
        library: { type: "module" },

        // For hosts (please adjust)
        remotes: {
            "contentEditorTestApp": "http://localhost:4222/remoteEntry.js",
        },

        shared: share({
          "@ngrx/store": { singleton: true, strictVersion: false, requiredVersion: 'auto' }, 
          "@ngrx/effects": { singleton: true, strictVersion: false, requiredVersion: 'auto' }, 
          "@ngrx/store-devtools": { singleton: true, strictVersion: false, requiredVersion: 'auto' },
          "@angular/core": { singleton: true, strictVersion: false, requiredVersion: 'auto' }, 
          "@angular/common": { singleton: true, strictVersion: false, requiredVersion: 'auto' }, 
          "@angular/common/http": { singleton: true, strictVersion: false, requiredVersion: 'auto' }, 
          "@angular/router": { singleton: true, strictVersion: false, requiredVersion: 'auto' },
          "@angular/material": {eager:true,singleton: true, strictVersion: false, requiredVersion: 'auto',includeSecondaries: true},
          "@angular/cdk": {eager:true,singleton: true, strictVersion: false, requiredVersion: 'auto',includeSecondaries: true},
          "@angular/forms": {eager:true,singleton: true, strictVersion: false, requiredVersion: 'auto',includeSecondaries: true},
          ...sharedMappings.getDescriptors()
        })
        
    }),
    sharedMappings.getPlugin()
  ],
};

远程应用Web配置

const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const mf = require("@angular-architects/module-federation/webpack");
const path = require("path");
const share = mf.share;

const sharedMappings = new mf.SharedMappings();
sharedMappings.register(
  path.join(__dirname, '../../tsconfig.json'),
  [/* mapped paths to share */]);

module.exports = {
  output: {
    uniqueName: "contentEditorTestApp",
    publicPath: "http://localhost:4222/",
  },
  optimization: {
    runtimeChunk: false
  },   
  resolve: {
    alias: {
      ...sharedMappings.getAliases(),
    }
  },
  experiments: {
    outputModule: true
  },
  plugins: [
    new ModuleFederationPlugin({
        library: { type: "module" },

        // For remotes (please adjust)
        name: "contentEditorTestApp",
        filename: "remoteEntry.js",
        exposes: {
            './Module': './projects/content-editor-test-app/src/app/assets/asset.module.ts',
        },        

        shared: share({
          "@ngrx/store": { singleton: true, strictVersion: false, requiredVersion: 'auto' }, 
          "@ngrx/effects": { singleton: true, strictVersion: false, requiredVersion: 'auto' }, 
          "@ngrx/store-devtools": { singleton: true, strictVersion: false, requiredVersion: 'auto' },
          "@angular/core": { singleton: true, strictVersion: false, requiredVersion: 'auto' }, 
          "@angular/common": { singleton: true, strictVersion: false, requiredVersion: 'auto' }, 
          "@angular/common/http": { singleton: true, strictVersion: false, requiredVersion: 'auto' }, 
          "@angular/router": { singleton: true, strictVersion: false, requiredVersion: 'auto' },
          "@angular/material/tabs":{singleton: true, strictVersion: false, requiredVersion: 'auto'},
          "@angular/material/list":{singleton: true, strictVersion: false, requiredVersion: 'auto'},
          "@angular/material/icon":{singleton: true, strictVersion: false, requiredVersion: 'auto'},
          "@angular/material/input":{singleton: true, strictVersion: false, requiredVersion: 'auto'},
          "@angular/material/form-field":{singleton: true, strictVersion: false, requiredVersion: 'auto'},
          "@angular/material/select":{singleton: true, strictVersion: false, requiredVersion: 'auto'},
          "@angular/cdk": {singleton: true, strictVersion: false, requiredVersion: 'auto',includeSecondaries: true},
          "@angular/forms": {singleton: true, strictVersion: false, requiredVersion: 'auto',includeSecondaries: true},
          ...sharedMappings.getDescriptors()
        })
        
    }),
    sharedMappings.getPlugin()
  ],
};

解决方案

一、解决SVG图标加载异常问题

问题根源:远程应用中使用相对路径注册SVG图标时,壳应用会尝试从自身域名下加载资源,而非远程应用的资源地址。

方案1:使用远程应用绝对路径注册图标

修改IconService中的loadIcons方法,将资源路径替换为远程应用的publicPath:

private loadIcons(iconKeys: string[], iconUrl: string): void {
  // 可通过环境变量动态配置,避免硬编码
  const remotePublicPath = 'http://localhost:4222/';
  iconKeys.forEach(key => {
    this.matIconRegistry.addSvgIcon(
      key, 
      this.domSanitizer.bypassSecurityTrustResourceUrl(`${remotePublicPath}${iconUrl}/${key}.svg`)
    )
  });
}

方案2:通过Webpack配置暴露资源(更优雅)

使用copy-webpack-plugin确保远程应用的assets资源被正确打包并可访问,同时保持相对路径的使用:

  1. 安装插件:
npm install copy-webpack-plugin --save-dev
  1. 修改远程应用Webpack配置,添加资源复制规则:
const CopyPlugin = require('copy-webpack-plugin');

module.exports = {
  // ... 现有配置
  plugins: [
    // ... 现有插件
    new CopyPlugin({
      patterns: [
        { from: './projects/content-editor-test-app/src/assets', to: 'assets' },
      ],
    }),
  ],
};

此时,Angular会结合远程应用的publicPath自动生成正确的资源地址,无需修改IconService代码。

二、解决Angular Material组件渲染异常问题

问题根源:微前端环境下,Material的Overlay容器默认挂载到壳应用的body,导致事件上下文冲突;或Shared配置不一致导致Material/CDK存在多个实例。

方案1:统一Material与CDK的Shared配置

确保壳应用和远程应用的@angular/material、@angular/cdk配置完全一致,避免多实例冲突:
修改远程应用的Shared配置,移除单独的Material子模块配置,统一使用@angular/material的全局配置:

shared: share({
  // ... 其他Shared配置
  "@angular/material": { eager: true, singleton: true, strictVersion: false, requiredVersion: 'auto', includeSecondaries: true },
  "@angular/cdk": { eager: true, singleton: true, strictVersion: false, requiredVersion: 'auto', includeSecondaries: true },
  // 移除以下单独配置:
  // "@angular/material/tabs": {...},
  // "@angular/material/list": {...},
  // ... 其他Material子模块配置
})

方案2:自定义Overlay容器挂载位置

将Material的Overlay容器挂载到远程应用的根组件内,避免与壳应用的事件系统冲突:

  1. 创建自定义Overlay容器类:
import { OverlayContainer } from '@angular/cdk/overlay';

export class RemoteOverlayContainer extends OverlayContainer {
  protected _createContainer(): void {
    const container = document.createElement('div');
    container.id = 'remote-overlay-container';
    // 替换为你的远程应用根组件选择器
    const remoteRoot = document.querySelector('app-content-editor-root'); 
    if (remoteRoot) {
      remoteRoot.appendChild(container);
    } else {
      document.body.appendChild(container);
    }
    this._containerElement = container;
  }
}
  1. 在远程应用的模块中提供该容器:
@NgModule({
  // ... 其他模块配置
  providers: [
    { provide: OverlayContainer, useClass: RemoteOverlayContainer }
  ]
})
export class AssetModule { }

内容的提问来源于stack exchange,提问作者Neha

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 14:16:47