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资源被正确打包并可访问,同时保持相对路径的使用:
- 安装插件:
npm install copy-webpack-plugin --save-dev
- 修改远程应用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容器挂载到远程应用的根组件内,避免与壳应用的事件系统冲突:
- 创建自定义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; } }
- 在远程应用的模块中提供该容器:
@NgModule({ // ... 其他模块配置 providers: [ { provide: OverlayContainer, useClass: RemoteOverlayContainer } ] }) export class AssetModule { }
内容的提问来源于stack exchange,提问作者Neha
相关产品推荐
相关产品推荐

