From 88da9e69560f8d15238ed60761fca5bef7c27e09 Mon Sep 17 00:00:00 2001 From: Mark Pieszak Date: Tue, 10 Oct 2017 19:01:52 -0400 Subject: [PATCH] feat(aspnetcore): update to angular 5.0 --- .../src/interfaces/engine-options.ts | 10 +- modules/aspnetcore-engine/src/main.ts | 214 ++++++++---------- tslint.json | 1 + 3 files changed, 99 insertions(+), 126 deletions(-) diff --git a/modules/aspnetcore-engine/src/interfaces/engine-options.ts b/modules/aspnetcore-engine/src/interfaces/engine-options.ts index 790e278de..b51a8fbc0 100644 --- a/modules/aspnetcore-engine/src/interfaces/engine-options.ts +++ b/modules/aspnetcore-engine/src/interfaces/engine-options.ts @@ -2,8 +2,8 @@ import { IRequestParams } from "./request-params"; import { Type, NgModuleFactory, StaticProvider } from '@angular/core'; export interface IEngineOptions { - appSelector: string; - request: IRequestParams; - ngModule: Type<{}> | NgModuleFactory<{}>; - providers?: StaticProvider[]; -}; + appSelector: string; // e.g., + request: IRequestParams; // e.g., params + ngModule: Type<{}> | NgModuleFactory<{}>; // e.g., AppModule + providers?: StaticProvider[]; // StaticProvider[] +} diff --git a/modules/aspnetcore-engine/src/main.ts b/modules/aspnetcore-engine/src/main.ts index db197040d..0aa73ad50 100644 --- a/modules/aspnetcore-engine/src/main.ts +++ b/modules/aspnetcore-engine/src/main.ts @@ -1,19 +1,80 @@ -import { Type, NgModuleFactory, NgModuleRef, ApplicationRef, CompilerFactory, Compiler } from '@angular/core'; -import { platformServer, platformDynamicServer, PlatformState, INITIAL_CONFIG } from '@angular/platform-server'; +import { Type, NgModuleFactory, CompilerFactory, Compiler } from '@angular/core'; +import { platformDynamicServer, BEFORE_APP_SERIALIZED, renderModuleFactory } from '@angular/platform-server'; import { ResourceLoader } from '@angular/compiler'; import { REQUEST, ORIGIN_URL } from './tokens'; import { FileLoader } from './file-loader'; import { IEngineOptions } from './interfaces/engine-options'; +import { DOCUMENT } from '@angular/platform-browser'; + +/* @internal */ +export class UniversalData { + public static appNode = ''; + public static title = ''; + public static scripts = ''; + public static styles = ''; + public static meta = ''; + public static links = ''; +} + +/* @internal */ +let appSelector = 'app-root'; // default + +/* @internal */ +function beforeAppSerialized( + doc: any /* TODO: type definition for Domino - DomAPI Spec (similar to "Document") */ +) { + + return () => { + const STYLES = []; + const SCRIPTS = []; + const META = []; + const LINKS = []; + + for (let i = 0; i < doc.head.children.length; i++) { + const element = doc.head.children[i]; + const tagName = element.tagName.toUpperCase(); + + switch (tagName) { + case 'SCRIPT': + SCRIPTS.push(element.outerHTML); + break; + case 'STYLE': + STYLES.push(element.outerHTML); + break; + case 'LINK': + LINKS.push(element.outerHTML); + break; + case 'META': + META.push(element.outerHTML); + break; + default: + break; + } + } + + UniversalData.title = doc.title; + UniversalData.appNode = doc.querySelector(appSelector).outerHTML; + UniversalData.scripts = SCRIPTS.join(' '); + UniversalData.styles = STYLES.join(' '); + UniversalData.meta = META.join(' '); + UniversalData.links = LINKS.join(' '); + }; +} -import 'rxjs/add/operator/filter'; -import 'rxjs/add/operator/first'; export function ngAspnetCoreEngine( options: IEngineOptions ): Promise<{ html: string, globals: { styles: string, title: string, meta: string, transferData?: {}, [key: string]: any } }> { + if (!options.appSelector) { + throw new Error(`appSelector is required! Pass in " appSelector: '' ", for your root App component.`); + } + + // Grab the DOM "selector" from the passed in Template for example = "app-root" + appSelector = options.appSelector.substring(1, options.appSelector.indexOf('>')); + const compilerFactory: CompilerFactory = platformDynamicServer().injector.get(CompilerFactory); const compiler: Compiler = compilerFactory.createCompiler([ { @@ -34,133 +95,43 @@ export function ngAspnetCoreEngine( options.providers = options.providers || []; const extraProviders = options.providers.concat( - options.providers, - [ - { - provide: INITIAL_CONFIG, - useValue: { - document: options.appSelector, - url: options.request.url - } - }, - { + ...options.providers, + [{ provide: ORIGIN_URL, useValue: options.request.origin }, { provide: REQUEST, useValue: options.request.data.request + }, { + provide: BEFORE_APP_SERIALIZED, + useFactory: beforeAppSerialized, multi: true, deps: [ DOCUMENT ] } ] ); - const platform = platformServer(extraProviders); - getFactory(moduleOrFactory, compiler) - .then((factory: NgModuleFactory<{}>) => { - - return platform.bootstrapModuleFactory(factory).then((moduleRef: NgModuleRef<{}>) => { - - const state: PlatformState = moduleRef.injector.get(PlatformState); - const appRef: ApplicationRef = moduleRef.injector.get(ApplicationRef); - - appRef.isStable - .filter((isStable: boolean) => isStable) - .first() - .subscribe(() => { - - // Fire the TransferState Cache - const bootstrap = (<{ ngOnBootstrap?: Function }> moduleRef.instance).ngOnBootstrap; - bootstrap && bootstrap(); - - // The Document itself - const AST_DOCUMENT = state.getDocument(); - - // Strip out the Angular application - const htmlDoc = state.renderToString(); - - const APP_HTML = htmlDoc.substring( - htmlDoc.indexOf('') + 6, - htmlDoc.indexOf('') - ); - - // Strip out Styles / Meta-tags / Title - // const STYLES = []; - const META = []; - const LINKS = []; - let TITLE = ''; - - let STYLES_STRING = htmlDoc.substring( - htmlDoc.indexOf('`; - // STYLES.push(styleTag); - // } - - if (element.name === 'meta') { - count = count + 1; - let metaString = '\n`); - } - - if (element.name === 'link') { - let linkString = '\n`); - } - } - - // Return parsed App - resolve({ - html: APP_HTML, - globals: { - styles: STYLES_STRING, - title: TITLE, - meta: META.join(' '), - links: LINKS.join(' ') - } - }); - - moduleRef.destroy(); - - }, (err) => { - reject(err); - }); + .then(factory => { + return renderModuleFactory(factory, { + document: options.appSelector, + url: options.request.url, + extraProviders: extraProviders + }); + }) + .then(() => { + + resolve({ + html: UniversalData.appNode, + globals: { + styles: UniversalData.styles, + title: UniversalData.title, + scripts: UniversalData.scripts, + meta: UniversalData.meta, + links: UniversalData.links + } }); + }, (err) => { + reject(err); }); } catch (ex) { @@ -168,14 +139,15 @@ export function ngAspnetCoreEngine( } }); -} -/* ********************** Private / Internal ****************** */ +} +/* @internal */ const factoryCacheMap = new Map, NgModuleFactory<{}>>(); function getFactory( moduleOrFactory: Type<{}> | NgModuleFactory<{}>, compiler: Compiler ): Promise> { + return new Promise>((resolve, reject) => { // If module has been compiled AoT if (moduleOrFactory instanceof NgModuleFactory) { diff --git a/tslint.json b/tslint.json index fb9d90006..487c5186f 100644 --- a/tslint.json +++ b/tslint.json @@ -1,4 +1,5 @@ { + "defaultSeverity": "warning", "rules": { "comment-format": [true, "check-space"], "class-name": true,