diff --git a/modules/common/BUILD.bazel b/modules/common/BUILD.bazel index bb08d8712..ddf0ba844 100644 --- a/modules/common/BUILD.bazel +++ b/modules/common/BUILD.bazel @@ -11,7 +11,9 @@ ng_module( "src/**/*.ts", ]), module_name = "@nguniversal/common", - deps = [], + deps = [ + "//modules/common/tokens", + ], ) ng_package( @@ -19,7 +21,10 @@ ng_package( srcs = [":package.json"], entry_point = "modules/common/index.js", readme_md = ":README.md", - deps = [":common"], + deps = [ + ":common", + "//modules/common/tokens", + ], ) ts_library( diff --git a/modules/common/private_api.ts b/modules/common/private_api.ts new file mode 100644 index 000000000..dfd2d5d77 --- /dev/null +++ b/modules/common/private_api.ts @@ -0,0 +1,8 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +export { FileLoader as ɵFileLoader, CommonEngine as ɵCommonEngine } from './src/common-engine'; diff --git a/modules/common/public_api.ts b/modules/common/public_api.ts index befe74ee7..09b8c3b81 100644 --- a/modules/common/public_api.ts +++ b/modules/common/public_api.ts @@ -7,3 +7,4 @@ */ export { TransferHttpCacheModule } from './src/transfer_http'; export { StateTransferInitializerModule } from './src/state-transfer-initializer/module'; +export * from './private_api'; diff --git a/modules/common/src/common-engine/engine.ts b/modules/common/src/common-engine/engine.ts new file mode 100644 index 000000000..09992b8dd --- /dev/null +++ b/modules/common/src/common-engine/engine.ts @@ -0,0 +1,92 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import {ResourceLoader} from '@angular/compiler'; +import {Compiler, Type, NgModuleFactory, CompilerFactory, StaticProvider} from '@angular/core'; +import {INITIAL_CONFIG, renderModuleFactory, platformDynamicServer} from '@angular/platform-server'; +import * as fs from 'fs'; + +import {FileLoader} from './file-loader'; +import {RenderOptions} from './interfaces'; + +/** + * A common rendering engine utility. This abstracts the logic + * for handling the platformServer compiler, the module cache, and + * the document loader + */ +export class CommonEngine { + + /** Return an instance of the platformServer compiler */ + getCompiler(): Compiler { + const compilerFactory: CompilerFactory = platformDynamicServer().injector.get(CompilerFactory); + return compilerFactory.createCompiler([ + {providers: [{provide: ResourceLoader, useClass: FileLoader, deps: []}]} + ]); + } + + private factoryCacheMap = new Map, NgModuleFactory<{}>>(); + private templateCache: {[key: string]: string} = {}; + + constructor(private moduleOrFactory: Type<{}> | NgModuleFactory<{}>, + private providers: StaticProvider[] = []) {} + + /** + * Render an HTML document for a specific URL with specified + * render options + */ + render(filePath: string, opts: RenderOptions): Promise { + const extraProviders = [ + ...(opts.providers || []), + ...(this.providers || []), + [ + { + provide: INITIAL_CONFIG, + useValue: { + document: opts.document || this.getDocument(filePath), + url: opts.url + } + } + ] + ]; + + return this.getFactory() + .then(factory => renderModuleFactory(factory, {extraProviders})); + } + + /** Return the factory for a given engine instance */ + getFactory(): Promise> { + // If module has been compiled AoT + const moduleOrFactory = this.moduleOrFactory; + if (moduleOrFactory instanceof NgModuleFactory) { + return Promise.resolve(moduleOrFactory); + } else { + // we're in JIT mode + let moduleFactory = this.factoryCacheMap.get(moduleOrFactory); + + // If module factory is cached + if (moduleFactory) { + return Promise.resolve(moduleFactory); + } + + // Compile the module and cache it + return this.getCompiler().compileModuleAsync(moduleOrFactory) + .then((factory) => { + this.factoryCacheMap.set(moduleOrFactory, factory); + return factory; + }); + } + } + + /** Retrieve the document from the cache or the filesystem */ + private getDocument(filePath: string): Promise { + const doc = this.templateCache[filePath] = this.templateCache[filePath] || + fs.readFileSync(filePath).toString(); + + // As promise so we can change the API later without breaking + return Promise.resolve(doc); + } +} diff --git a/modules/common/src/common-engine/file-loader.ts b/modules/common/src/common-engine/file-loader.ts new file mode 100644 index 000000000..351460a21 --- /dev/null +++ b/modules/common/src/common-engine/file-loader.ts @@ -0,0 +1,27 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import * as fs from 'fs'; +import { ResourceLoader } from '@angular/compiler'; + +/** + * ResourceLoader implementation for loading files + * @internal + */ +export class FileLoader implements ResourceLoader { + get(url: string): Promise { + return new Promise((resolve, reject) => { + fs.readFile(url, (err: NodeJS.ErrnoException, buffer: Buffer) => { + if (err) { + return reject(err); + } + + resolve(buffer.toString()); + }); + }); + } +} diff --git a/modules/common/src/common-engine/index.ts b/modules/common/src/common-engine/index.ts new file mode 100644 index 000000000..3aa25ddb5 --- /dev/null +++ b/modules/common/src/common-engine/index.ts @@ -0,0 +1,10 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +export * from './interfaces'; +export * from './file-loader'; +export * from './engine'; diff --git a/modules/common/src/common-engine/interfaces.ts b/modules/common/src/common-engine/interfaces.ts new file mode 100644 index 000000000..8fcee65e7 --- /dev/null +++ b/modules/common/src/common-engine/interfaces.ts @@ -0,0 +1,22 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import {NgModuleFactory, StaticProvider, Type} from '@angular/core'; + +/** These are the allowed options for the engine */ +export interface NgSetupOptions { + bootstrap: Type<{}> | NgModuleFactory<{}>; + providers?: StaticProvider[]; +} + +/** These are the allowed options for the render */ +export interface RenderOptions extends NgSetupOptions { + req: any; + res?: any; + document?: string; + url?: string; +} diff --git a/modules/common/tokens/BUILD.bazel b/modules/common/tokens/BUILD.bazel new file mode 100644 index 000000000..0540382fd --- /dev/null +++ b/modules/common/tokens/BUILD.bazel @@ -0,0 +1,11 @@ +package(default_visibility = ["//visibility:public"]) + +load("//tools:defaults.bzl", "ng_module") + +ng_module( + name = "tokens", + srcs = glob([ + "src/*.ts", + ]), + module_name = "@nguniversal/common/tokens", +) diff --git a/modules/common/tokens/private_api.ts b/modules/common/tokens/private_api.ts new file mode 100644 index 000000000..9a662390c --- /dev/null +++ b/modules/common/tokens/private_api.ts @@ -0,0 +1,8 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +export {ORIGIN_URL as ɵORIGIN_URL, REQUEST as ɵREQUEST, RESPONSE as ɵRESPONSE} from './src/tokens'; diff --git a/modules/common/tokens/public_api.ts b/modules/common/tokens/public_api.ts new file mode 100644 index 000000000..64280dd13 --- /dev/null +++ b/modules/common/tokens/public_api.ts @@ -0,0 +1,8 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +export * from './private_api'; diff --git a/modules/common/tokens/src/tokens.ts b/modules/common/tokens/src/tokens.ts new file mode 100644 index 000000000..671ddbd11 --- /dev/null +++ b/modules/common/tokens/src/tokens.ts @@ -0,0 +1,12 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import { InjectionToken } from '@angular/core'; + +export const ORIGIN_URL = new InjectionToken('ORIGIN_URL'); +export const REQUEST = new InjectionToken('REQUEST'); +export const RESPONSE = new InjectionToken('RESPONSE');