Skip to content

Commit

Permalink
fix(lapplication): cancel bootstrapped app when next change #264544
Browse files Browse the repository at this point in the history
  • Loading branch information
why520crazy committed Sep 16, 2019
1 parent 869693d commit 8a997a9
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 21 deletions.
128 changes: 119 additions & 9 deletions packages/planet/src/application/planet-application-loader.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const app1 = {
routerPathPrefix: '/app1',
hostClass: 'app1-host',
preload: false,
switchMode: SwitchModes.coexist,
switchMode: SwitchModes.default,
resourcePathPrefix: '/static/app1',
styles: ['styles/main.css'],
scripts: ['vendor.js', 'main.js'],
Expand All @@ -35,7 +35,7 @@ const app2 = {
selector: 'app2-root-container',
routerPathPrefix: '/app2',
hostClass: 'app2-host',
preload: false,
preload: true,
switchMode: SwitchModes.coexist,
resourcePathPrefix: '/static/app2',
styles: ['styles/main.css'],
Expand Down Expand Up @@ -69,6 +69,11 @@ describe('PlanetApplicationLoader', () => {

planetApplicationService.register(app1);
planetApplicationService.register(app2);

// 创建宿主容器
const hostContainer = document.createElement('DIV');
hostContainer.classList.add('host-selector');
document.body.appendChild(hostContainer);
});

afterEach(() => {
Expand All @@ -80,11 +85,6 @@ describe('PlanetApplicationLoader', () => {
const assetsLoaderSpy = spyOn(assetsLoader, 'loadAppAssets');
assetsLoaderSpy.and.returnValue(loadAppAssets$);

// 创建宿主容器
const hostContainer = document.createElement('DIV');
hostContainer.classList.add('host-selector');
document.body.appendChild(hostContainer);

const planetAppRef = mockApplicationRef(app1.name);
const bootstrapSpy = spyOn(planetAppRef, 'bootstrap');

Expand All @@ -108,6 +108,7 @@ describe('PlanetApplicationLoader', () => {
expect(bootstrapSpy).toHaveBeenCalled();

expect(appStatusChangeSpy).toHaveBeenCalledTimes(4);
expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app1, status: ApplicationStatus.bootstrapping });
expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app1, status: ApplicationStatus.bootstrapped });

// 判断是否在宿主元素中创建了应用根节点
Expand All @@ -117,7 +118,7 @@ describe('PlanetApplicationLoader', () => {
tick();
}));

it(`should cancel load app1 which has not loaded when next route (app2) change`, fakeAsync(() => {
it(`should cancel load app1 which assets has not loaded when next route (app2) change`, fakeAsync(() => {
const loadApp1Assets$ = new Subject();
const loadApp2Assets$ = new Subject();

Expand Down Expand Up @@ -153,6 +154,7 @@ describe('PlanetApplicationLoader', () => {
ngZone.onStable.next();

expect(appStatusChangeSpy).toHaveBeenCalledTimes(5);
expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app2, status: ApplicationStatus.bootstrapping });
expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app2, status: ApplicationStatus.bootstrapped });

expect(app1BootstrapSpy).not.toHaveBeenCalled();
Expand All @@ -161,5 +163,113 @@ describe('PlanetApplicationLoader', () => {
tick();
}));

it(`should preload apps`, () => {});
it(`should cancel load app1 which has not bootstrapped when next route (app2) change`, fakeAsync(() => {
const loadApp1Assets$ = new Subject();
const loadApp2Assets$ = new Subject();

const planetApp1Ref = mockApplicationRef(app1.name);
const app1BootstrapSpy = spyOn(planetApp1Ref, 'bootstrap');

const planetApp2Ref = mockApplicationRef(app2.name);
const app2BootstrapSpy = spyOn(planetApp2Ref, 'bootstrap');

const assetsLoaderSpy = spyOn(assetsLoader, 'loadAppAssets');
assetsLoaderSpy.and.returnValues(loadApp1Assets$, loadApp2Assets$);

const appStatusChangeSpy = jasmine.createSpy('app status change spy');
planetApplicationLoader.appStatusChange.subscribe(appStatusChangeSpy);

expect(appStatusChangeSpy).not.toHaveBeenCalled();
planetApplicationLoader.reroute({ url: '/app1' });
expect(appStatusChangeSpy).toHaveBeenCalled();
expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app1, status: ApplicationStatus.assetsLoading });

loadApp1Assets$.next();
loadApp1Assets$.complete();

expect(appStatusChangeSpy).toHaveBeenCalledTimes(2);
expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app1, status: ApplicationStatus.assetsLoaded });

planetApplicationLoader.reroute({ url: '/app2' });
expect(appStatusChangeSpy).toHaveBeenCalledTimes(3);
expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app2, status: ApplicationStatus.assetsLoading });

loadApp2Assets$.next();
loadApp2Assets$.complete();

expect(appStatusChangeSpy).toHaveBeenCalledTimes(4);
expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app2, status: ApplicationStatus.assetsLoaded });

ngZone.onStable.next();

expect(appStatusChangeSpy).toHaveBeenCalledTimes(6);
expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app2, status: ApplicationStatus.bootstrapping });
expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app2, status: ApplicationStatus.bootstrapped });

expect(app1BootstrapSpy).not.toHaveBeenCalled();
expect(app2BootstrapSpy).toHaveBeenCalled();

tick();
}));

describe('preload', () => {
it(`should preload load app2 when after loaded app1`, fakeAsync(() => {
const loadApp1Assets$ = new Subject();
const loadApp2Assets$ = new Subject();

const planetApp1Ref = mockApplicationRef(app1.name);
const app1BootstrapSpy = spyOn(planetApp1Ref, 'bootstrap');

const planetApp2Ref = mockApplicationRef(app2.name);
const app2BootstrapSpy = spyOn(planetApp2Ref, 'bootstrap');

const assetsLoaderSpy = spyOn(assetsLoader, 'loadAppAssets');
assetsLoaderSpy.and.returnValues(loadApp1Assets$, loadApp2Assets$);

const appStatusChangeSpy = jasmine.createSpy('app status change spy');
planetApplicationLoader.appStatusChange.subscribe(appStatusChangeSpy);

expect(appStatusChangeSpy).not.toHaveBeenCalled();
planetApplicationLoader.reroute({ url: '/app1' });
expect(appStatusChangeSpy).toHaveBeenCalled();
expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app1, status: ApplicationStatus.assetsLoading });

loadApp1Assets$.next();
loadApp1Assets$.complete();

expect(appStatusChangeSpy).toHaveBeenCalledTimes(2);
expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app1, status: ApplicationStatus.assetsLoaded });

ngZone.onStable.next();

expect(appStatusChangeSpy).toHaveBeenCalledTimes(4);
expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app1, status: ApplicationStatus.bootstrapping });
expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app1, status: ApplicationStatus.bootstrapped });

expect(app1BootstrapSpy).toHaveBeenCalled();

tick(300);
expect(app2BootstrapSpy).not.toHaveBeenCalled();

// 已经开始加载 App2 静态资源
expect(appStatusChangeSpy).toHaveBeenCalledTimes(5);
expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app2, status: ApplicationStatus.assetsLoading });

// App2 静态资源加载完毕
loadApp2Assets$.next();
loadApp2Assets$.complete();

expect(appStatusChangeSpy).toHaveBeenCalledTimes(6);
expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app2, status: ApplicationStatus.assetsLoaded });

// onStable 开始启动应用
ngZone.onStable.next();
expect(appStatusChangeSpy).toHaveBeenCalledTimes(8);
expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app2, status: ApplicationStatus.bootstrapping });
expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app2, status: ApplicationStatus.bootstrapped });
expect(app2BootstrapSpy).toHaveBeenCalled();
}));

it(`should preload app`, () => {});
});
});
35 changes: 23 additions & 12 deletions packages/planet/src/application/planet-application-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export enum ApplicationStatus {
export class PlanetApplicationLoader {
private firstLoad = true;

private startRouteChangeEvent: PlanetRouterEvent;

private options: PlanetOptions;

private inProgressAppAssetsLoads = new Map<string, Observable<PlanetApplication>>();
Expand Down Expand Up @@ -90,6 +92,7 @@ export class PlanetApplicationLoader {
.pipe(
switchMap(event => {
this.loadingDone = false;
this.startRouteChangeEvent = event;
const shouldLoadApps = this.planetApplicationService.getAppsByMatchedUrl(event.url);
const shouldUnloadApps = this.getUnloadApps(shouldLoadApps);
this.unloadApps(shouldUnloadApps, event);
Expand All @@ -102,7 +105,7 @@ export class PlanetApplicationLoader {
if (shouldLoadApps && shouldLoadApps.length > 0) {
const loadApps$ = shouldLoadApps.map(app => {
const appStatus = this.appsStatus.get(app);
if (!appStatus) {
if (!appStatus || appStatus === ApplicationStatus.assetsLoading) {
return this.startLoadAppAssets(app);
} else {
return of(app);
Expand Down Expand Up @@ -131,17 +134,20 @@ export class PlanetApplicationLoader {

// 切换到应用后会有闪烁现象,所以使用 onStable 后启动应用
this.ngZone.onStable.pipe(take(1)).subscribe(() => {
this.ngZone.runOutsideAngular(() => {
shouldShowApps.forEach(app => {
this.showApp(app);
const appRef = getPlanetApplicationRef(app.name);
appRef.onRouteChange(eventAndApps.event);
});

shouldBootstrapApps.forEach(app => {
this.bootstrapApp(app);
// 此处判断是因为如果静态资源加载完毕还未启动被取消,还是会启动之前的应用,虽然可能性比较小,但是无法排除这种可能性,所以只有当 Event 是最后一个才会启动
if (this.startRouteChangeEvent === eventAndApps.event) {
this.ngZone.runOutsideAngular(() => {
shouldShowApps.forEach(app => {
this.showApp(app);
const appRef = getPlanetApplicationRef(app.name);
appRef.onRouteChange(eventAndApps.event);
});

shouldBootstrapApps.forEach(app => {
this.bootstrapApp(app);
});
});
});
}
});

return eventAndApps.apps;
Expand Down Expand Up @@ -317,7 +323,12 @@ export class PlanetApplicationLoader {
return this.startLoadAppAssets(app).pipe(
map(() => {
this.ngZone.runOutsideAngular(() => {
this.bootstrapApp(app, 'hidden');
this.ngZone.onStable
.asObservable()
.pipe(take(1))
.subscribe(() => {
this.bootstrapApp(app, 'hidden');
});
});
return getPlanetApplicationRef(app.name);
})
Expand Down

0 comments on commit 8a997a9

Please sign in to comment.