diff --git a/packages/planet/src/application/planet-application-loader.spec.ts b/packages/planet/src/application/planet-application-loader.spec.ts index e276dc9..2187491 100644 --- a/packages/planet/src/application/planet-application-loader.spec.ts +++ b/packages/planet/src/application/planet-application-loader.spec.ts @@ -76,9 +76,9 @@ describe('PlanetApplicationLoader', () => { }); it(`should load and bootstrap app`, fakeAsync(() => { - const loadScriptsAndStyles$ = new Subject(); - const assetsLoaderSpy = spyOn(assetsLoader, 'loadScriptsAndStyles'); - assetsLoaderSpy.and.returnValue(loadScriptsAndStyles$); + const loadAppAssets$ = new Subject(); + const assetsLoaderSpy = spyOn(assetsLoader, 'loadAppAssets'); + assetsLoaderSpy.and.returnValue(loadAppAssets$); const planetAppRef = mockApplicationRef(app1.name); const bootstrapSpy = spyOn(planetAppRef, 'bootstrap'); @@ -92,8 +92,8 @@ describe('PlanetApplicationLoader', () => { expect(appStatusChangeSpy).toHaveBeenCalled(); expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app1, status: ApplicationStatus.assetsLoading }); - loadScriptsAndStyles$.next(); - loadScriptsAndStyles$.complete(); + loadAppAssets$.next(); + loadAppAssets$.complete(); expect(appStatusChangeSpy).toHaveBeenCalledTimes(2); expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app1, status: ApplicationStatus.assetsLoaded }); @@ -108,27 +108,44 @@ describe('PlanetApplicationLoader', () => { tick(); })); - // it(`should cancel load app1 which not returned and start load app2`, fakeAsync(() => { - // const loadScriptsAndStylesApp1$ = new Subject(); - // const loadScriptsAndStylesApp2$ = new Subject(); - // const assetsLoaderSpy = spyOn(assetsLoader, 'loadScriptsAndStyles'); - // assetsLoaderSpy.and.returnValues(loadScriptsAndStylesApp1$, loadScriptsAndStylesApp2$); - // 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 }); - // planetApplicationLoader.reroute({ url: '/app2' }); - // expect(appStatusChangeSpy).toHaveBeenCalledTimes(2); - // expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app2, status: ApplicationStatus.assetsLoading }); - // loadScriptsAndStylesApp1$.next(); - // loadScriptsAndStylesApp1$.complete(); - // expect(appStatusChangeSpy).toHaveBeenCalledTimes(2); - // loadScriptsAndStylesApp2$.next(); - // loadScriptsAndStylesApp1$.complete(); - // expect(appStatusChangeSpy).toHaveBeenCalledTimes(3); - // expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app2, status: ApplicationStatus.bootstrapped }); - // tick(); - // })); + it(`should cancel load app1 which not returned and start load app2`, 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 }); + + planetApplicationLoader.reroute({ url: '/app2' }); + expect(appStatusChangeSpy).toHaveBeenCalledTimes(2); + expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app2, status: ApplicationStatus.assetsLoading }); + loadApp1Assets$.next(); + loadApp1Assets$.complete(); + expect(appStatusChangeSpy).toHaveBeenCalledTimes(2); + loadApp2Assets$.next(); + loadApp2Assets$.complete(); + + expect(appStatusChangeSpy).toHaveBeenCalledTimes(3); + expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app2, status: ApplicationStatus.assetsLoaded }); + + ngZone.onStable.next(); + + expect(appStatusChangeSpy).toHaveBeenCalledTimes(5); + expect(appStatusChangeSpy).toHaveBeenCalledWith({ app: app2, status: ApplicationStatus.bootstrapped }); + + tick(); + })); }); diff --git a/packages/planet/src/application/planet-application-loader.ts b/packages/planet/src/application/planet-application-loader.ts index e6ba33c..18be421 100644 --- a/packages/planet/src/application/planet-application-loader.ts +++ b/packages/planet/src/application/planet-application-loader.ts @@ -23,7 +23,7 @@ export enum ApplicationStatus { export class PlanetApplicationLoader { private options: PlanetOptions; - private inProgressAppLoads = new Map>(); + private inProgressAppAssetsLoads = new Map>(); private appsStatus = new Map(); @@ -93,52 +93,58 @@ export class PlanetApplicationLoader { const shouldLoadApps = this.planetApplicationService.getAppsByMatchedUrl(event.url); const shouldUnloadApps = this.getUnloadApps(shouldLoadApps); this.unloadApps(shouldUnloadApps, event); + + const eventAndApps = { + event: event, + apps: shouldLoadApps + }; + if (shouldLoadApps && shouldLoadApps.length > 0) { - return of(shouldLoadApps).pipe( - switchMap(apps => { - const loadApps$ = apps.map(app => { - const appStatus = this.appsStatus.get(app); - if (!appStatus) { - return this.startLoadAppAssets(app); - } else { - return of(app); - } - }); - return forkJoin(loadApps$); - }), - map(apps => { - const shouldBootstrapApps = []; - const shouldShowApps = []; - apps.forEach(app => { - const appStatus = this.appsStatus.get(app); - if (appStatus === ApplicationStatus.bootstrapped) { - shouldShowApps.push(app); - } else { - shouldBootstrapApps.push(app); - } - }); - - // 切换到应用后会有闪烁现象,所以使用 onStable 后启动应用 - this.ngZone.onStable.pipe(take(1)).subscribe(() => { - this.ngZone.runOutsideAngular(() => { - shouldShowApps.forEach(app => { - this.showApp(app); - const appRef = getPlanetApplicationRef(app.name); - appRef.onRouteChange(event); - }); - - shouldBootstrapApps.forEach(app => { - this.bootstrapApp(app); - }); - }); - }); - - return apps; + const loadApps$ = shouldLoadApps.map(app => { + const appStatus = this.appsStatus.get(app); + if (!appStatus) { + return this.startLoadAppAssets(app); + } else { + return of(app); + } + }); + return forkJoin(loadApps$).pipe( + map(() => { + return eventAndApps; }) ); } else { - return of([]); + return of(eventAndApps); } + }), + map(eventAndApps => { + const shouldBootstrapApps = []; + const shouldShowApps = []; + eventAndApps.apps.forEach(app => { + const appStatus = this.appsStatus.get(app); + if (appStatus === ApplicationStatus.bootstrapped) { + shouldShowApps.push(app); + } else { + shouldBootstrapApps.push(app); + } + }); + + // 切换到应用后会有闪烁现象,所以使用 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); + }); + }); + }); + + return eventAndApps.apps; }) ) .subscribe({ @@ -156,12 +162,12 @@ export class PlanetApplicationLoader { } private startLoadAppAssets(app: PlanetApplication) { - if (this.inProgressAppLoads.get(app.name)) { - return this.inProgressAppLoads.get(app.name); + if (this.inProgressAppAssetsLoads.get(app.name)) { + return this.inProgressAppAssetsLoads.get(app.name); } else { - const loadApp$ = this.loadAppAssets(app).pipe( + const loadApp$ = this.assetsLoader.loadAppAssets(app).pipe( tap(() => { - this.inProgressAppLoads.delete(app.name); + this.inProgressAppAssetsLoads.delete(app.name); this.setAppStatus(app, ApplicationStatus.assetsLoaded); }), map(() => { @@ -169,26 +175,12 @@ export class PlanetApplicationLoader { }), share() ); - this.inProgressAppLoads.set(app.name, loadApp$); + this.inProgressAppAssetsLoads.set(app.name, loadApp$); this.setAppStatus(app, ApplicationStatus.assetsLoading); return loadApp$; } } - private loadAppAssets(app: PlanetApplication): Observable<[AssetsLoadResult[], AssetsLoadResult[]]> { - if (app.manifest) { - return this.assetsLoader.loadManifest(`${app.manifest}?t=${new Date().getTime()}`).pipe( - switchMap(manifestResult => { - const { scripts, styles } = getScriptsAndStylesFullPaths(app, manifestResult); - return this.assetsLoader.loadScriptsAndStyles(scripts, styles, app.loadSerial); - }) - ); - } else { - const { scripts, styles } = getScriptsAndStylesFullPaths(app); - return this.assetsLoader.loadScriptsAndStyles(scripts, styles, app.loadSerial); - } - } - private hideApp(planetApp: PlanetApplication) { const appRootElement = document.querySelector(planetApp.selector); if (appRootElement) { diff --git a/packages/planet/src/assets-loader.ts b/packages/planet/src/assets-loader.ts index ca2dc2f..5bda7c5 100644 --- a/packages/planet/src/assets-loader.ts +++ b/packages/planet/src/assets-loader.ts @@ -1,8 +1,9 @@ import { Injectable } from '@angular/core'; -import { hashCode, isEmpty } from './helpers'; +import { hashCode, isEmpty, getScriptsAndStylesFullPaths } from './helpers'; import { of, Observable, Observer, forkJoin, concat, merge } from 'rxjs'; import { tap, shareReplay, map, switchMap, switchAll, concatMap, concatAll, scan, reduce } from 'rxjs/operators'; import { HttpClient } from '@angular/common/http'; +import { PlanetApplication } from './planet.class'; export interface AssetsLoadResult { src: string; @@ -152,6 +153,20 @@ export class AssetsLoader { return forkJoin(this.loadScripts(scripts, serial), this.loadStyles(styles)); } + loadAppAssets(app: PlanetApplication) { + if (app.manifest) { + return this.loadManifest(`${app.manifest}?t=${new Date().getTime()}`).pipe( + switchMap(manifestResult => { + const { scripts, styles } = getScriptsAndStylesFullPaths(app, manifestResult); + return this.loadScriptsAndStyles(scripts, styles, app.loadSerial); + }) + ); + } else { + const { scripts, styles } = getScriptsAndStylesFullPaths(app); + return this.loadScriptsAndStyles(scripts, styles, app.loadSerial); + } + } + loadManifest(url: string): Observable<{ [key: string]: string }> { return this.http.get(url).pipe( map((response: any) => {