Skip to content
This repository has been archived by the owner on Nov 22, 2024. It is now read-only.

Commit

Permalink
feat(common): add CommonEngine to encapsulate rendering (#996)
Browse files Browse the repository at this point in the history
  • Loading branch information
Fabian Wiles authored May 19, 2018
1 parent 90d0221 commit 439b306
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 2 deletions.
9 changes: 7 additions & 2 deletions modules/common/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,20 @@ ng_module(
"src/**/*.ts",
]),
module_name = "@nguniversal/common",
deps = [],
deps = [
"//modules/common/tokens",
],
)

ng_package(
name = "npm_package",
srcs = [":package.json"],
entry_point = "modules/common/index.js",
readme_md = ":README.md",
deps = [":common"],
deps = [
":common",
"//modules/common/tokens",
],
)

ts_library(
Expand Down
8 changes: 8 additions & 0 deletions modules/common/private_api.ts
Original file line number Diff line number Diff line change
@@ -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';
1 change: 1 addition & 0 deletions modules/common/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
*/
export { TransferHttpCacheModule } from './src/transfer_http';
export { StateTransferInitializerModule } from './src/state-transfer-initializer/module';
export * from './private_api';
92 changes: 92 additions & 0 deletions modules/common/src/common-engine/engine.ts
Original file line number Diff line number Diff line change
@@ -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<Type<{}>, 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<string> {
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<NgModuleFactory<{}>> {
// 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<string> {
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);
}
}
27 changes: 27 additions & 0 deletions modules/common/src/common-engine/file-loader.ts
Original file line number Diff line number Diff line change
@@ -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<string> {
return new Promise((resolve, reject) => {
fs.readFile(url, (err: NodeJS.ErrnoException, buffer: Buffer) => {
if (err) {
return reject(err);
}

resolve(buffer.toString());
});
});
}
}
10 changes: 10 additions & 0 deletions modules/common/src/common-engine/index.ts
Original file line number Diff line number Diff line change
@@ -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';
22 changes: 22 additions & 0 deletions modules/common/src/common-engine/interfaces.ts
Original file line number Diff line number Diff line change
@@ -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;
}
11 changes: 11 additions & 0 deletions modules/common/tokens/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -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",
)
8 changes: 8 additions & 0 deletions modules/common/tokens/private_api.ts
Original file line number Diff line number Diff line change
@@ -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';
8 changes: 8 additions & 0 deletions modules/common/tokens/public_api.ts
Original file line number Diff line number Diff line change
@@ -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';
12 changes: 12 additions & 0 deletions modules/common/tokens/src/tokens.ts
Original file line number Diff line number Diff line change
@@ -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<any>('ORIGIN_URL');
export const REQUEST = new InjectionToken<any>('REQUEST');
export const RESPONSE = new InjectionToken<any>('RESPONSE');

0 comments on commit 439b306

Please sign in to comment.