- 安装 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 { NgModuleRef, enableProdMode } from '@angular/core';
// import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
// import { AppModule } from './app/app.module';
// import { environment } from './environments/environment';
// if (environment.production) {
// enableProdMode();
// }
// let app: void | NgModuleRef<AppModule>;
// async function render() {
// app = await platformBrowserDynamic()
// .bootstrapModule(AppModule, { ngZone: (window as any).ngZone })
// .catch(err => {});
// }
// if (!(window as any).__POWERED_BY_QIANKUN__) {
// render();
// }
// export async function bootstrap(props: any) {}
// export async function mount(props: any) {
// render();
// }
// export async function unmount(props: any) {
// if (app) {
// app.destroy();
// }
// }
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 appName = require('../package.json').name;
const DefinePlugin = require('webpack/lib/DefinePlugin');
const { merge } = require('webpack-merge');
module.exports = (angularWebpackConfig, options) => {
const config = {
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
},
},
output: {
library: `${appName}-[name]`,
libraryTarget: 'umd',
chunkLoadingGlobal: `webpackJsonp_${appName}`,
},
};
const mergedConfig = merge(angularWebpackConfig, 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: [],
},
{
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 {}