技术选型#
- 微前端框架:qiankun、single-spa-angular
- 主应用:angular 16
- 子应用:angular 16
项目搭建#
主应用配置#
- 安装 qiankun
- 增加子应用加载入口组件
micro-app.component.ts
import { Component, ElementRef, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { MicroApp, loadMicroApp } from 'qiankun'; @Component({ selector: 'micro-app', template: ` <div id="haydnSccMicroApp"></div> `, }) export class MicroAppComponent { public loading = true; public microApp: MicroApp; public port = this.route.snapshot.data.port; public name = this.route.snapshot.data.name; constructor( private el: ElementRef, private route: ActivatedRoute ) {} ngAfterViewInit() { this.microApp = loadMicroApp( { name: this.name, entry: this.getSubAppEntry(), container: this.el.nativeElement, }, { singular: true, }, { afterMount: () => { this.loading = false; return Promise.resolve(); }, } ); } ngOnDestroy() { this.microApp.unmount(); } getSubAppEntry() { // 开发模式加载子应用 if (location.hostname === 'localhost') { const { hostname } = location; return `//${hostname}:${this.port}`; } // 线上环境根据项目部署方式调整 } }
- 路由配置
import { MicroAppComponent } from './micro-app.component'; const defaultRoutes: Routes = [ { path: 'sub-app', canActivateChild: [], children: [ { path: '**', component: MicroAppComponent, data: { port: '4200', name: 'sub-app' }, // name值与子应用package.json中name对应 }, ], }, ]
- 在入口启动 qiankun
import { start } from 'qiankun'; // start(); // 这里踩坑了,具体问题参考 https://github.com/single-spa/single-spa-angular/issues/529#issuecomment-2503746846 start({ urlRerouteOnly: false });
子应用配置#
- 添加文件
src/public-path.js
if (window.__POWERED_BY_QIANKUN__) { // eslint-disable-next-line no-undef __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }
- 修改
main.ts
import './public-path'; import { enableProdMode, NgZone } from '@angular/core'; import { Router } from '@angular/router'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { singleSpaAngular, getSingleSpaExtraProviders } from 'single-spa-angular'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; if (environment.production) { enableProdMode(); } if (!(window as any).__POWERED_BY_QIANKUN__) { platformBrowserDynamic() .bootstrapModule(AppModule) .catch(err => {}); } const { bootstrap, mount, unmount } = singleSpaAngular({ bootstrapFunction: singleSpaProps => { return platformBrowserDynamic(getSingleSpaExtraProviders()).bootstrapModule(AppModule); }, template: '<sub-app-root />', Router, NgZone, }); export { bootstrap, mount, unmount };
- 修改
src\polyfills.ts
,注释掉zone.js
的引入 - 修改 webpack 配置
const singleSpaAngularWebpack = require('single-spa-angular/lib/webpack').default; const appName = require('../package.json').name; const DefinePlugin = require('webpack/lib/DefinePlugin'); const { merge } = require('webpack-merge'); module.exports = (angularWebpackConfig, options) => { const singleSpaWebpackConfig = singleSpaAngularWebpack(angularWebpackConfig, options); const config = { devServer: { headers: { 'Access-Control-Allow-Origin': '*', }, }, output: { library: `${appName}-[name]`, libraryTarget: 'umd', chunkLoadingGlobal: `webpackJsonp_${appName}`, }, }; const mergedConfig = merge(singleSpaWebpackConfig, config); return mergedConfig; };
- 修改路由配置
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { LayoutEmptyComponent } from '../layout/empty/empty.component'; const routes: Routes = [ { path: 'sub-app', // 此处需要定义与主项目中相同的路由 children: [], // 路由定义放到children下 }, { path: '**', // 必须要定义,否则匹配不到路由时会报错 component: LayoutEmptyComponent, }, ]; @NgModule({ imports: [ RouterModule.forRoot(routes, { useHash: true, scrollPositionRestoration: 'top', }), ], exports: [RouterModule], // 为什么不采用下面的方式,而是重复定义与主项目相同的路由名称? // 通过 routerLink 跳转时会自动带上 /sub-app 前缀,要跳转到非当前子应用路由时路径会错误,而使用上面的方法可以直接使用routerLink跳转到非当前子应用定义的路由页面 providers: [ { provide: APP_BASE_HREF, useValue: window.__POWERED_BY_QIANKUN__ ? '/sub-app' : '/' }, ], }) export class RouteRoutingModule {}