Skip to content

Commit

Permalink
feat(loader): cancel last app loader which is loading when next change
Browse files Browse the repository at this point in the history
  • Loading branch information
why520crazy committed Sep 15, 2019
1 parent 62eed19 commit d737713
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 90 deletions.
73 changes: 45 additions & 28 deletions packages/planet/src/application/planet-application-loader.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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 });
Expand All @@ -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();
}));
});
114 changes: 53 additions & 61 deletions packages/planet/src/application/planet-application-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export enum ApplicationStatus {
export class PlanetApplicationLoader {
private options: PlanetOptions;

private inProgressAppLoads = new Map<string, Observable<PlanetApplication>>();
private inProgressAppAssetsLoads = new Map<string, Observable<PlanetApplication>>();

private appsStatus = new Map<PlanetApplication, ApplicationStatus>();

Expand Down Expand Up @@ -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({
Expand All @@ -156,39 +162,25 @@ 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(() => {
return app;
}),
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) {
Expand Down
17 changes: 16 additions & 1 deletion packages/planet/src/assets-loader.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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) => {
Expand Down

0 comments on commit d737713

Please sign in to comment.