forked from rangle/angular-ssr
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Major overhaul of ApplicationBuilder API to separate out Application …
…from ApplicationBuilder Previously, ApplicationBuilder was called builder but had no build() method. This didn't make a lot of sense, so now all ApplicationBuilder implementations have a build() function that returns an Application, which itself can be used to discover routes, pre-render, and demand render URLs. The Application object returned from build() must be disposed when you are finished with it using application.dispose().
- Loading branch information
Showing
38 changed files
with
698 additions
and
621 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import {NgModuleFactory} from '@angular/core'; | ||
|
||
import {Observable, Subject} from 'rxjs'; | ||
|
||
import {Disposable} from './../../disposable'; | ||
import {PlatformImpl, bootstrapWithExecute, forkZone} from '../../platform'; | ||
import {RenderOperation, RenderVariantOperation} from '../operation'; | ||
import {Snapshot, snapshot} from '../../snapshot'; | ||
import {Route, applicationRoutes, renderableRoutes} from '../../route'; | ||
import {baseUri} from '../../static'; | ||
import {composeTransitions} from '../../variants'; | ||
import {fork} from './fork'; | ||
|
||
import uri = require('url'); | ||
|
||
export abstract class Application<V, M> implements Disposable { | ||
constructor( | ||
private platformImpl: PlatformImpl, | ||
private render: RenderOperation, | ||
private moduleFactory: () => Promise<NgModuleFactory<M>> | ||
) {} | ||
|
||
abstract dispose(): void; | ||
|
||
async prerender(): Promise<Observable<Snapshot<V>>> { | ||
if (this.render.routes == null || this.render.routes.length === 0) { | ||
this.render.routes = renderableRoutes(await this.discoverRoutes()); | ||
|
||
if (this.render.routes.length === 0) { | ||
return Observable.of(); | ||
} | ||
} | ||
|
||
return this.renderToStream(this.render); | ||
} | ||
|
||
renderUri(uri: string, variant?: V): Promise<Snapshot<V>> { | ||
uri = resolveToAbsoluteUri(uri); | ||
|
||
const transition = composeTransitions(this.render.variants, variant); | ||
|
||
const vop: RenderVariantOperation<V> = {scope: this.render, uri, variant, transition}; | ||
|
||
return this.renderVariant(vop); | ||
} | ||
|
||
async discoverRoutes(): Promise<Array<Route>> { | ||
const moduleFactory = await this.moduleFactory(); | ||
|
||
return await applicationRoutes(this.platformImpl, moduleFactory, this.render.templateDocument); | ||
} | ||
|
||
private renderToStream(operation: RenderOperation): Observable<Snapshot<V>> { | ||
const subject = new Subject<Snapshot<V>>(); | ||
|
||
const bind = async (suboperation: RenderVariantOperation<V>) => { | ||
try { | ||
subject.next(await this.renderVariant(suboperation)); | ||
} | ||
catch (exception) { | ||
subject.error(exception); | ||
} | ||
}; | ||
|
||
const promises = fork<V>(operation).map(suboperation => bind(suboperation)); | ||
|
||
Promise.all(promises).then(() => subject.complete()); | ||
|
||
return subject.asObservable(); | ||
} | ||
|
||
private async renderVariant(operation: RenderVariantOperation<V>): Promise<Snapshot<V>> { | ||
const {uri, scope: {templateDocument}} = operation; | ||
|
||
const moduleFactory = await this.moduleFactory(); | ||
|
||
const instantiate = async () => | ||
await bootstrapWithExecute<M, Snapshot<V>>(this.platformImpl, moduleFactory, ref => snapshot(ref, operation)); | ||
|
||
return await forkZone(templateDocument, uri, instantiate); | ||
} | ||
} | ||
|
||
let relativeUriWarning = false; | ||
|
||
const resolveToAbsoluteUri = (relativeUri: string): string => { | ||
if (relativeUri == null || | ||
relativeUri.length === 0 || | ||
relativeUri === '/') { | ||
return baseUri; | ||
} | ||
|
||
const resolved = uri.resolve(baseUri, relativeUri); | ||
|
||
if (resolved !== relativeUri) { | ||
if (relativeUriWarning === false) { | ||
console.warn(`It is best to avoid using relative URIs like ${relativeUri} when requesting render results`); | ||
console.warn('The reason is that your application may key its service URIs from "window.location" in some manner'); | ||
console.warn(`I have resolved this relative URI to ${resolved} and this may impact your application`); | ||
relativeUriWarning = true; | ||
} | ||
} | ||
|
||
return resolved; | ||
}; |
Oops, something went wrong.