npm i qiankun -S
在app.component.ts中
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'reactApp',
entry: '//localhost:3000',
container: '#container',
activeRule: '/app-react',
},
{
name: 'vueApp',
entry: '//localhost:8080',
container: '#container',
activeRule: '/app-vue',
},
{
name: 'angularApp',
entry: '//localhost:4200',
container: '#container',
activeRule: '/app-angular',
},
]);
// 启动 qiankun
start();
npm i qiankun-ng-common -S
在app-routing.module.ts中
const routes: Routes = [
{
path: '**',
component: EmptyComponent // 配置默认路由,避免路由到子项目报错
}
];
src
目录新增 public-path.js
文件,内容为:if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
history
模式路由的 base
,src/app/app-routing.module.ts
文件:+ import { APP_BASE_HREF } from '@angular/common';
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
// @ts-ignore
+ providers: [{ provide: APP_BASE_HREF, useValue: window.__POWERED_BY_QIANKUN__ ? '/app-angular' : '/' }]
})
src/main.ts
文件。import './public-path';
import { enableProdMode, NgModuleRef } 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)
.catch((err) => console.error(err));
}
if (!(window as any).__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap(props: Object) {
console.log(props);
}
export async function mount(props: Object) {
render();
}
export async function unmount(props: Object) {
console.log(props);
// @ts-ignore
app.destroy();
}
webpack
打包配置npm i @angular-builders/custom-webpack@9 -D
先安装 @angular-builders/custom-webpack
插件,注意:angular 9
项目只能安装 9.x
版本,angular 10
项目可以安装最新版。
npm i @angular-builders/custom-webpack@9.2.0 -D
在根目录增加 custom-webpack.config.js
,内容为:
const appName = require('./package.json').name;
module.exports = {
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
},
},
output: {
library: `${appName}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${appName}`,
},
};
修改 angular.json
,将 [packageName] > architect > build > builder
和 [packageName] > architect > serve > builder
的值改为我们安装的插件,将我们的打包配置文件加入到 [packageName] > architect > build > options
。
- "builder": "@angular-devkit/build-angular:browser",
+ "builder": "@angular-builders/custom-webpack:browser",
"options": {
+ "customWebpackConfig": {
+ "path": "./custom-webpack.config.js"
+ }
}
- "builder": "@angular-devkit/build-angular:dev-server",
+ "builder": "@angular-builders/custom-webpack:dev-server",
修改architect下build以及serve的builder配置
zone.js
的问题
Angular
运行依赖于zone.js
。
qiankun
基于single-spa
实现,single-spa
明确指出一个项目的zone.js
只能存在一份实例,所以我们在主应用注入zone.js
。
// micro-app-main/src/main.js
// 为 Angular 微应用所做的 zone 包注入
import "zone.js/dist/zone";
将微应用的 src/polyfills.ts
里面的引入 zone.js
代码删掉。
在微应用的 src/index.html
里面的 <head>
标签加上下面内容,微应用独立访问时使用。
<!-- 也可以使用其他的CDN/本地的包 -->
<script src="https://unpkg.com/zone.js" ignore></script>
ng build
打包报错问题,修改 tsconfig.json
文件,参考issues/431- "target": "es2015",
+ "target": "es5",
+ "typeRoots": [
+ "node_modules/@types"
+ ],
angular
时,<app-root></app-root>
会冲突的问题,建议给<app-root>
加上一个唯一的 id,比如说当前应用名称。src/index.html :
- <app-root></app-root>
+ <app-root id="desk"></app-root>
src/app/app.component.ts :
- selector: 'app-root',
+ selector: '#desk app-root',
上述子应用创建走不通 用作参考
下述流程文档参考地址
https://juejin.cn/post/6844904158085021704#heading-13
结合着一起看
在主应用的工作完成后,我们还需要对微应用进行一系列的配置。首先,我们使用 single-spa-angular
生成一套配置,在命令行运行以下命令:
安装 single-spa
yarn add single-spa -S
添加 single-spa-angular
ng add single-spa-angular
npm i 安装需要的依赖
npm i @angular-builders/custom-webpack@9 -D
在生成 single-spa
配置后,我们需要进行一些 qiankun
的接入配置。我们在 Angular
微应用的入口文件 main.single-spa.ts
中,导出 qiankun
主应用所需要的三个生命周期钩子函数,代码实现如下:
main.single-spa.ts中需要加入
// 微应用单独启动时运行
if (!(window as any).__POWERED_BY_QIANKUN__) {
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
}
完整代码实现如下:
import { enableProdMode, NgZone } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { Router, NavigationStart } from '@angular/router';
import { ɵAnimationEngine as AnimationEngine } from '@angular/animations/browser';
import { singleSpaAngular, getSingleSpaExtraProviders } from 'single-spa-angular';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { singleSpaPropsSubject } from './single-spa/single-spa-props';
if (environment.production) {
enableProdMode();
}
// 微应用单独启动时运行
if (!(window as any).__POWERED_BY_QIANKUN__) {
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
}
const lifecycles = singleSpaAngular({
bootstrapFunction: singleSpaProps => {
singleSpaPropsSubject.next(singleSpaProps);
return platformBrowserDynamic(getSingleSpaExtraProviders()).bootstrapModule(AppModule);
},
template: "<app-root id='desk' />",
Router,
NavigationStart,
NgZone,
AnimationEngine,
});
export const bootstrap = lifecycles.bootstrap;
export const mount = lifecycles.mount;
export const unmount = lifecycles.unmount;
在配置好了入口文件 main.single-spa.ts
后,我们还需要配置 webpack
,使 main.single-spa.ts
导出的生命周期钩子函数可以被 qiankun
识别获取。
我们直接配置 extra-webpack.config.js
即可,代码实现如下:
// micro-app-angular/extra-webpack.config.js
const singleSpaAngularWebpack = require("single-spa-angular/lib/webpack")
.default;
const webpackMerge = require("webpack-merge");
module.exports = (angularWebpackConfig, options) => {
const singleSpaWebpackConfig = singleSpaAngularWebpack(
angularWebpackConfig,
options
);
const singleSpaConfig = {
output: {
// 微应用的包名,这里与主应用中注册的微应用名称一致
library: "AngularMicroApp", //这里要改成自己的应用名
// 将你的 library 暴露为所有的模块定义下都可运行的方式
libraryTarget: "umd",
},
};
const mergedConfig = webpackMerge.smart(
singleSpaWebpackConfig,
singleSpaConfig
);
return mergedConfig;
};
我们需要重点关注一下 output
选项,当我们把 libraryTarget
设置为 umd
后,我们的 library
就暴露为所有的模块定义下都可运行的方式了,主应用就可以获取到微应用的生命周期钩子函数了。
在 extra-webpack.config.js
修改完成后,我们还需要修改一下 package.json
中的启动命令,修改如下:
// micro-app-angular/package.json
{
//...
"script": {
//...
// --disable-host-check: 关闭主机检查,使微应用可以被 fetch
// --port: 监听端口
// --base-href: 站点的起始路径,与主应用中配置的一致
"start": "ng serve --disable-host-check --port 10300 --base-href /angular"
}
}
为了防止主应用或其他微应用也为 angular
时,<app-root></app-root>
会冲突的问题,建议给<app-root>
加上一个唯一的 id,或者修改为其他名字
比如说当前应用名称。
src/app/app.component.ts :
- selector: 'app-root',
+ selector: '#desk app-root',
或
- selector: 'app-root',
+ selector: 'desk-root',
index.html、main.single-spa.ts也需要修改
index.html
<app-root id="desk"></app-root>
main.single-spa.ts
template: "<app-root id='desk' />",
删除empty-route文件夹,添加公共的空组件
app1/app-routing.module.ts
const routes: Routes = [
{
path: '**',
component: EmptyComponent
}
]
此时npm start就可以了
首先,我们在主应用中注册一个 MicroAppStateActions
实例并导出,代码实现如下:
// micro-app-main/src/shared/actions.ts
import { initGlobalState, MicroAppStateActions } from "qiankun";
const initialState = {};
const actions: MicroAppStateActions = initGlobalState(initialState);
export default actions;
在需要传输数据的地方发送即可
send(){
actions.setGlobalState({'id': '123'});
}
首先来改造我们的 子应用,首先我们设置一个 Actions
实例,代码实现如下:
// /src/shared/actions.ts
function emptyAction(...args) {
// 警告:提示当前使用的是空 Action
console.warn("Current execute action is empty!");
}
class Actions {
// 默认值为空 Action
actions = {
onGlobalStateChange: emptyAction,
setGlobalState: emptyAction
};
/**
* 设置 actions
*/
setActions(actions) {
this.actions = actions;
}
/**
* 映射
*/
onGlobalStateChange(...args) {
return this.actions.onGlobalStateChange(...args);
}
/**
* 映射
*/
setGlobalState(...args) {
return this.actions.setGlobalState(...args);
}
}
const actions = new Actions();
export default actions;
创建 actions
实例后,我们需要为其注入真实 Actions
。我们在入口文件 main.single-spa.ts
中注入
加入以下代码
bootstrapFunction: singleSpaProps => {
if (singleSpaProps) {
// 注入 actions 实例
actions.setActions(singleSpaProps);
}
singleSpaPropsSubject.next(singleSpaProps);
return platformBrowserDynamic(getSingleSpaExtraProviders()).bootstrapModule(AppModule);
},
完整代码为
import { enableProdMode, NgZone } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { Router, NavigationStart } from '@angular/router';
import { ɵAnimationEngine as AnimationEngine } from '@angular/animations/browser';
import { singleSpaAngular, getSingleSpaExtraProviders } from 'single-spa-angular';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { singleSpaPropsSubject } from './single-spa/single-spa-props';
import actions from "./shared/actions";
if (environment.production) {
enableProdMode();
}
// 微应用单独启动时运行
if (!(window as any).__POWERED_BY_QIANKUN__) {
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
}
const lifecycles = singleSpaAngular({
bootstrapFunction: singleSpaProps => {
if (singleSpaProps) {
// 注入 actions 实例
actions.setActions(singleSpaProps);
}
singleSpaPropsSubject.next(singleSpaProps);
return platformBrowserDynamic(getSingleSpaExtraProviders()).bootstrapModule(AppModule);
},
template: "<app-root id='desk' />",
Router,
NavigationStart,
NgZone,
AnimationEngine,
});
export const bootstrap = lifecycles.bootstrap;
export const mount = lifecycles.mount;
export const unmount = lifecycles.unmount;
在要使用到的地方注册观察者函数即可
import {Component, OnInit} from '@angular/core';
import actions from "../shared/actions";
@Component({
selector: '#desk app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit{
title = 'csdeskweb-desk';
ngOnInit(): void {
// 注册观察者函数
// onGlobalStateChange 第二个参数为 true,表示立即执行一次观察者函数
actions.onGlobalStateChange(state => {
console.log('csdeskweb-desk获取到信息', state);
}, true);
}
}
上述信息共享方式是基于Actions通信 适用于较为简单的通信
若要使用到较为复杂的带状态的通信可以参考shared通信
文章地址
https://juejin.cn/post/6844904151231496200#heading-2