import * as fs from 'fs' ; import * as domino from 'ivy-domino' ; Object . assign ( global , ( domino as any ) . impl ) ; import * as express from 'express' ; import * as compression from 'compression' ; import { join } from 'path' ; import { getRendererFactory } from './lib/server/server_renderer_factory' ; import { patchDocument } from './lib/server/custom_elements_shim' ; import { waitForFetches , getPendingFetchCount , getFetch } from './lib/server/fetch' ; const { registerCustomElement } = require ( './lib/elements/register-custom-element' ) ; const { registerRouterElement } = require ( './lib/router-impl/router' ) ; const NG_BITS = require ( './lib/elements/angular-ivy-bits' ) ; import { ROUTES } from './routes' ; import { ELEMENTS_MAP } from './elements.server' ; import { ɵComponentType as ComponentType } from '@angular/core' ; import { ViewEncapsulation } from '@angular/core' ; import { getCache , CacheEntry } from './lib/data-cache' ; ( global as any ) . ngDevMode = false ; const app = express ( ) ; app . use ( compression ( ) ) ; const PORT = parseInt ( process . env . PORT ) || 4200 ; const DIST_FOLDER = join ( process . cwd ( ) , process . env . RUNFILES ? 'src/package' : 'dist/ivy' ) ; ( global as any ) [ 'fetch' ] = getFetch ( 'localhost' , PORT . toString ( ) ) ; function waitForNextTick ( ) { return new Promise ( ( resolve , _ ) => { setTimeout ( resolve , 0 ) ; } ) ; } async function waitForRenderComplete ( doc ) { do { await waitForFetches ( doc ) ; await waitForNextTick ( ) ; } while ( getPendingFetchCount ( doc ) > 0 ) ; } let actionIndex = 0 ; function patchedAddEventListener ( type , listener , options ) { const el = this ; const doc = el . ownerDocument ; const localName = doc . __current_element__ . localName ; type = type === 'click' ? '' : `${ type }:` ; el . setAttribute ( 'tsaction' , `${ type }${ localName }.${ actionIndex ++ }` ) ; oldEventListener . call ( this , type , listener , options ) ; } ; const oldEventListener = Node . prototype . addEventListener ; Node . prototype . addEventListener = patchedAddEventListener ; export function escapeHtml ( text ) { const escapedText = { '&' : '&a;' , '"' : '&q;' , '\'' : '&s;' , '<' : '&l;' , '>' : '&g;' , } ; return text . replace ( /[&"'<>]/g , s => escapedText [ s ] ) ; } function serializeSeenElements ( doc , shellEl ) { const elements = { } ; const d = doc as any ; d . _seenElements . forEach ( ( val , key ) => { elements [ key ] = val ; } ) ; const script = doc . createElement ( 'script' ) ; script . type = 'application/json' ; script . id = '_elements' ; script . appendChild ( doc . createTextNode ( JSON . stringify ( elements ) ) ) ; shellEl . insertAdjacentElement ( 'afterend' , script ) ; } function serializeCachedData ( doc ) { const cache = getCache ( doc ) ; if ( cache . size == 0 ) { return ; } const cacheObj = { } ; for ( const key of Array . from ( cache . keys ( ) ) ) { cacheObj [ key ] = cache . get ( key ) . value ; } const dataEl = doc . createElement ( 'data-cache' ) ; const script = doc . createElement ( 'script' ) ; script . type = 'application/json' ; script . appendChild ( doc . createTextNode ( escapeHtml ( JSON . stringify ( cacheObj ) ) ) ) ; dataEl . appendChild ( script ) ; doc . body . insertAdjacentElement ( 'beforeend' , dataEl ) ; } app . engine ( 'html' , ( filePath , options , callback ) => { try { const doc = domino . createDocument ( getDocument ( filePath ) ) ; patchDocument ( doc ) ; ( doc as any ) . __elements_map__ = ELEMENTS_MAP ; ( doc as any ) . _seenElements = new Map ( ) ; ( doc as any ) . _nextCompId = 0 ; const elements = Object . keys ( ELEMENTS_MAP ) ; for ( const element of elements ) { const componentType = ELEMENTS_MAP [ element ] ; const encapsulation = componentType . ngComponentDef [ 'encapsulation' ] ; const scoped = encapsulation != null ? encapsulation === ViewEncapsulation . Emulated : false ; const rendererFactory = getRendererFactory ( doc , scoped ) ; registerCustomElement ( doc , ( doc as any ) . __ce__ , ( ) => NG_BITS , element , ELEMENTS_MAP [ element ] , rendererFactory ) ; } registerRouterElement ( doc , ( doc as any ) . __ce__ , options . req . originalUrl , ROUTES ) ; const shell = doc . createElement ( 'shell-root' ) ; doc . body . insertAdjacentElement ( 'afterbegin' , shell ) ; waitForRenderComplete ( doc ) . then ( ( ) => { serializeSeenElements ( doc , shell ) ; serializeCachedData ( doc ) ; callback ( null , doc . documentElement . outerHTML ) ; } ) ; } catch ( e ) { callback ( e ) ; } } ) ; app . set ( 'view engine' , 'html' ) ; app . set ( 'views' , DIST_FOLDER ) ; app . get ( '*.*' , express . static ( DIST_FOLDER , { maxAge : '1y' , fallthrough : false } ) ) ; app . get ( '*' , ( req , res ) => { res . render ( 'index' , { req } ) ; } ) ; app . listen ( PORT , ( ) => { console . log ( `Node Express server listening on http://localhost:${ PORT }` ) ; } ) ; /**
 * This holds a cached version of each index used.
 */ const templateCache = { } ; /**
 * Get the document at the file path
 */ function getDocument ( filePath ) { if ( templateCache [ filePath ] ) { return templateCache [ filePath ] ; } let contents = fs . readFileSync ( filePath ) . toString ( ) ; if ( process . env [ 'DEV_MODE' ] ) { contents = contents . replace ( '</head>' , `<script src="http://localhost:35729/livereload.js?snipver=1"></script></head>` ) ; } templateCache [ filePath ] = contents ; return contents ; }