返回 1. 调整共享库的
5. 使用
Angular 12 Host 应用无法渲染 Angular 15 MFE 嵌套组件的解决方案
javascript
2025-03-22 22:46:00
Angular 12 Host 应用无法渲染 Angular 15 MFE 的嵌套组件问题排查及解决方案
遇到问题了?用 Angular 12 做的 Host 应用,加载 Angular 15 写的微前端(MFE)时,MFE 的主页面能显示,但里面的嵌套组件却渲染不出来? 别急,咱们一步步来解决。
一、 问题现象
Host 应用是 Angular 12.1.5, MFE 是 Angular 15.2.10。 MFE 能成功加载到 Host 应用,主页面的元素也能正常显示,但嵌套组件却不见踪影。 就好像<test-root>
这个标签能识别,里面的Hello World !!!!!!!!
没了。
二、 问题原因分析
这种情况,多半跟版本差异、模块联邦配置,或者组件的声明方式有关。可能的原因包括但不限于:
- Angular 版本差异导致的不兼容: Angular 12 和 15 跨度有点大, 某些 API 或者渲染机制可能有变化。
- 模块联邦 (Module Federation) 配置问题:
webpack.config.js
里的共享库配置不当,可能导致组件无法正确加载。 - 组件未正确导出或声明: MFE 中的嵌套组件可能没有在其模块中正确声明或导出。
- Zone.js 相关问题: 不同版本的 Angular 可能使用了不同版本的 Zone.js,处理异步任务的方式可能不同。
- 构建配置差异: 开发环境和生产环境的构建配置有差别, 可能会导致仅在特定环境重现此问题。
- CSS 样式冲突或缺失: Host 和 MFE 样式可能会冲突,导致样式被覆盖,显示出现异常。
三、 解决方案
下面提供几种排查和解决问题的方案,可以逐一尝试。
1. 调整共享库的 requiredVersion
- 原理: 严格的版本控制可能会阻止低版本的 Host 应用加载高版本的 MFE 的共享库。
- 操作: 在 MFE 的
webpack.config.js
中,将requiredVersion
设置为一个更宽松的范围, 或者直接移除requiredVersion
,改成eager: true
。 - 代码示例 (MFE 的 webpack.config.js):
```javascript
shared: share({
"@angular/core": {
singleton: true,
strictVersion: true,
//requiredVersion: ">=1.0.0", // 移除此行, 或者用下面的范围
requiredVersion: '^12.0.0 || ^15.0.0', // 或者允许这两个主版本
eager: true, // 如果可行, 改为eager.
},
"@angular/common": {
singleton: true,
strictVersion: true,
//requiredVersion: ">=1.0.0",// 移除此行
requiredVersion: '^12.0.0 || ^15.0.0',
eager: true,
},
"@angular/common/http": {
singleton: true,
strictVersion: true,
//requiredVersion: ">=1.0.0",// 移除此行
requiredVersion: '^12.0.0 || ^15.0.0',
eager: true,
},
"@angular/router": {
singleton: true,
strictVersion: true,
//requiredVersion: ">=1.0.0",// 移除此行
requiredVersion: '^12.0.0 || ^15.0.0',
eager: true,
},
...sharedMappings.getDescriptors()
})
```
- 安全建议: 使用
eager: true
时,要保证你的 Host 应用知道并能处理所有可能的来自 MFE 的依赖。 这种方法可能会增大初始包的大小,但减少运行时依赖问题的风险。
2. 确保嵌套组件已正确声明
- 原理: Angular 需要知道哪些组件属于哪个模块。 如果组件没有在模块的
declarations
数组中声明,就无法被渲染。 - 操作: 检查 MFE 的
admin.module.ts
(或者你暴露的模块),确保TestComponent
(或任何嵌套组件)已在declarations
数组中。 - 代码示例 (MFE 的 admin.module.ts):
```typescript
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HomeComponent } from './home/home.component'; // 假设的 Home 组件路径
import { TestComponent } from './test/test.component'; // 假设的 Test 组件路径
import { AdminRoutingModule } from './admin-routing.module'; //你的路由配置
@NgModule({
declarations: [
HomeComponent,
TestComponent // 确保这里有 TestComponent
],
imports: [
CommonModule,
AdminRoutingModule
],
// 如果有需要外部使用的组件,在这里导出
})
export class AdminModule { }
```
3. 调整 MFE 的启动方式 (bootstrap)
- 原理: 默认的启动方式可能在版本差异下出现问题,尝试更显式的启动方式.
- 操作: 在MFE 的
main.ts
文件中, 不直接使用platformBrowserDynamic().bootstrapModule(AppModule)
。而是导出 bootstrap 函数。 - 代码示例(MFE 的 main.ts):
import { enableProdMode, NgZone } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { Router } from '@angular/router';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { singleSpaAngular, getSingleSpaExtraProviders } from 'single-spa-angular';
if (environment.production) {
enableProdMode();
}
const lifecycles = singleSpaAngular({
bootstrapFunction: async (singleSpaProps) => {
const ngZone = new NgZone({ enableLongStackTrace: true }); // 或许你可以显式的创建一个新的NgZone实例.
const moduleRef = await platformBrowserDynamic(getSingleSpaExtraProviders()).bootstrapModule(AppModule,{ngZone:ngZone});
return moduleRef;
},
template: '<app-root />', // 或者你MFE根组件的selector
Router,
NgZone: NgZone, //将 NgZone 传入.
});
export const bootstrap = lifecycles.bootstrap;
export const mount = lifecycles.mount;
export const unmount = lifecycles.unmount;
- 在你的MFE webpack config 中
new ModuleFederationPlugin({
library: { type: "var", name: "mfe1" }, //增加 library 属性, 其中mfe1 是你remote 的名字。
//... 其他配置.
}),
4. 检查 Zone.js 的兼容性
* **原理:** Angular 使用 Zone.js 来管理异步操作。不同版本 Angular 的 Zone.js 实现可能不完全兼容,有时会导致变更检测问题。
* **操作** : 尝试将 Host 和 MFE 的 Zone.js 版本统一。或者,在 Host 应用的 `polyfills.ts` 中显式导入特定版本的 Zone.js。
```typescript
// polyfills.ts
import 'zone.js/dist/zone'; // 或 'zone.js'; 较新版本的angular 使用 'zone.js';
```
也可以尝试在 MFE 的`main.ts`文件手动创建一个新的 NgZone 实例,然后在启动 AppModule 的时候传入:
```typescript
//main.ts MFE 中
platformBrowserDynamic().bootstrapModule(AppModule, { ngZone: new NgZone({ enableLongStackTrace: true }) })
.catch(err => console.error(err));
```
* **安全建议:** 手动管理Zone.js需要对 Zone.js 有较深入的了解,否则容易引发其他问题,只在必要时这样做。
5. 使用 ngDoBootstrap
- 原理 : 对于一些更复杂的场景,可以用
ngDoBootstrap
来手动控制启动过程. - 操作:
在你的 MFE 的模块中,实现DoBootstrap
接口,并手动引导组件.import { ApplicationRef, DoBootstrap, NgModule } from '@angular/core'; import { createCustomElement } from '@angular/elements'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; //导入根组件 @NgModule({ declarations: [AppComponent], imports: [BrowserModule], // 不需要 providers: [], 因为我们要手动操作 entryComponents: [AppComponent] //非常重要 }) export class AppModule implements DoBootstrap { constructor() {} ngDoBootstrap(appRef: ApplicationRef) { const element = createCustomElement(AppComponent, { injector: appRef.injector }); customElements.define('test-root', element); // 使用 createCustomElement, 把你的test-root 定义为一个custom element. // 注意: 不要在这里调用 appRef.bootstrap(AppComponent); // 因为你已经将组件定义成了 custom element // 在 Host 应用加载的时候,`<test-root>`会根据这个定义, 自动初始化. } }
- 进阶: 这种方法比较适合复杂的控制场景,例如,你希望在 MFE 完全启动之前做一些事情,或者需要更精细地控制组件的创建。
6. 启用prodMode
- 原理: prodMode 能优化代码, 可能在开发环境下正常, 但是生产环境失败。
- 操作: Host/Shell 及Remote均进行启用prodMode
7. 查看构建日志
- 原理: 在 Host 应用重新 build MFE,仔细查看有没有任何 warning 或者 error, 可能会有提示
- 操作: 重新
ng build
。
通过上面这些步骤,基本可以解决由于版本差异及配置原因导致的问题。如果问题仍然存在, 就需要更多信息 (例如更完整的项目结构、错误截图等等), 来做进一步诊断。