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

Commit e889652

Browse files
authored
refactor(express-engine, hapi-engine): use CommonEngine (#1246)
* refactor(express-engine, hapi-engine): use CommonEngine * fix: avoid memory leak through opts.providers Don't mutate `options` being passed into the render call(especially concatting to the providers array). Mark `options` as `Readonly` so that it is not accidentally mutated in the future.
1 parent 728f86d commit e889652

File tree

7 files changed

+42
-166
lines changed

7 files changed

+42
-166
lines changed

modules/aspnetcore-engine/src/main.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ function _getUniversalData(doc: Document): UniversalData {
9191
};
9292
}
9393

94-
export function ngAspnetCoreEngine(options: IEngineOptions): Promise<IEngineRenderResult> {
95-
94+
export function ngAspnetCoreEngine(options: Readonly<IEngineOptions>)
95+
: Promise<IEngineRenderResult> {
9696
if (!options.appSelector) {
9797
const selector = `" appSelector: '<${appSelector}></${appSelector}>' "`;
9898
throw new Error(`appSelector is required! Pass in ${selector},
@@ -119,9 +119,9 @@ export function ngAspnetCoreEngine(options: IEngineOptions): Promise<IEngineRend
119119
throw new Error('You must pass in a NgModule or NgModuleFactory to be bootstrapped');
120120
}
121121

122-
options.providers = options.providers || [];
122+
let extraProviders = options.providers || [];
123123

124-
const extraProviders = options.providers.concat(getReqResProviders(options.request.origin,
124+
extraProviders = extraProviders.concat(getReqResProviders(options.request.origin,
125125
options.request.data.request));
126126

127127
getFactory(moduleOrFactory, compiler)

modules/common/engine/src/engine.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export class CommonEngine {
3131
private factoryCacheMap = new Map<Type<{}>, NgModuleFactory<{}>>();
3232
private templateCache: {[key: string]: string} = {};
3333

34-
constructor(private moduleOrFactory: Type<{}> | NgModuleFactory<{}>,
34+
constructor(private moduleOrFactory?: Type<{}> | NgModuleFactory<{}>,
3535
private providers: StaticProvider[] = []) {}
3636

3737
/**
@@ -53,14 +53,14 @@ export class CommonEngine {
5353
}
5454
];
5555

56-
const factory = await this.getFactory();
56+
const moduleOrFactory = this.moduleOrFactory || opts.bootstrap;
57+
const factory = await this.getFactory(moduleOrFactory);
5758
return renderModuleFactory(factory, {extraProviders});
5859
}
5960

6061
/** Return the factory for a given engine instance */
61-
getFactory(): Promise<NgModuleFactory<{}>> {
62+
getFactory(moduleOrFactory: Type<{}> | NgModuleFactory<{}>): Promise<NgModuleFactory<{}>> {
6263
// If module has been compiled AoT
63-
const moduleOrFactory = this.moduleOrFactory;
6464
if (moduleOrFactory instanceof NgModuleFactory) {
6565
return Promise.resolve(moduleOrFactory);
6666
} else {

modules/express-engine/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ ng_module(
1010
]),
1111
module_name = "@nguniversal/express-engine",
1212
deps = [
13+
"//modules/common/engine",
1314
"//modules/express-engine/tokens",
1415
"@npm//@angular/compiler",
1516
"@npm//@angular/core",

modules/express-engine/src/main.ts

Lines changed: 19 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,8 @@
88
import * as fs from 'fs';
99
import { Request, Response } from 'express';
1010

11-
import { NgModuleFactory, Type, CompilerFactory, Compiler, StaticProvider } from '@angular/core';
12-
import { ResourceLoader } from '@angular/compiler';
13-
import {
14-
INITIAL_CONFIG,
15-
renderModuleFactory,
16-
platformDynamicServer
17-
} from '@angular/platform-server';
18-
19-
import { FileLoader } from './file-loader';
11+
import { NgModuleFactory, Type, StaticProvider } from '@angular/core';
12+
import { ɵCommonEngine as CommonEngine } from '@nguniversal/common/engine';
2013
import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens';
2114

2215
/**
@@ -42,101 +35,39 @@ export interface RenderOptions extends NgSetupOptions {
4235
*/
4336
const templateCache: { [key: string]: string } = {};
4437

45-
/**
46-
* Map of Module Factories
47-
*/
48-
const factoryCacheMap = new Map<Type<{}>, NgModuleFactory<{}>>();
49-
5038
/**
5139
* This is an express engine for handling Angular Applications
5240
*/
53-
export function ngExpressEngine(setupOptions: NgSetupOptions) {
54-
55-
const compilerFactory: CompilerFactory = platformDynamicServer().injector.get(CompilerFactory);
56-
const compiler: Compiler = compilerFactory.createCompiler([
57-
{
58-
providers: [
59-
{ provide: ResourceLoader, useClass: FileLoader, deps: [] }
60-
]
61-
}
62-
]);
41+
export function ngExpressEngine(setupOptions: Readonly<NgSetupOptions>) {
42+
const engine = new CommonEngine(setupOptions.bootstrap, setupOptions.providers);
6343

6444
return function (filePath: string,
65-
options: RenderOptions,
45+
options: Readonly<RenderOptions>,
6646
callback: (err?: Error | null, html?: string) => void) {
67-
68-
options.providers = options.providers || [];
69-
7047
try {
71-
const moduleOrFactory = options.bootstrap || setupOptions.bootstrap;
72-
73-
if (!moduleOrFactory) {
48+
if (!setupOptions.bootstrap && !options.bootstrap) {
7449
throw new Error('You must pass in a NgModule or NgModuleFactory to be bootstrapped');
7550
}
7651

77-
setupOptions.providers = setupOptions.providers || [];
78-
7952
const req = options.req;
80-
const extraProviders = setupOptions.providers.concat(
81-
options.providers,
82-
getReqResProviders(options.req, options.res),
83-
[
84-
{
85-
provide: INITIAL_CONFIG,
86-
useValue: {
87-
document: options.document || getDocument(filePath),
88-
url: options.url || `${req.protocol}://${(req.get('host') || '')}${req.originalUrl}`
89-
}
90-
}
91-
]);
9253

93-
getFactory(moduleOrFactory, compiler)
94-
.then(factory => {
95-
return renderModuleFactory(factory, {
96-
extraProviders
97-
});
98-
})
99-
.then((html: string) => {
100-
callback(null, html);
101-
}, (err) => {
102-
callback(err);
103-
});
104-
} catch (err) {
105-
callback(err);
106-
}
107-
};
108-
}
54+
const renderOptions: RenderOptions = Object.assign({}, options);
10955

110-
/**
111-
* Get a factory from a bootstrapped module/ module factory
112-
*/
113-
function getFactory(
114-
moduleOrFactory: Type<{}> | NgModuleFactory<{}>, compiler: Compiler
115-
): Promise<NgModuleFactory<{}>> {
116-
return new Promise<NgModuleFactory<{}>>((resolve, reject) => {
117-
// If module has been compiled AoT
118-
if (moduleOrFactory instanceof NgModuleFactory) {
119-
resolve(moduleOrFactory);
120-
return;
121-
} else {
122-
let moduleFactory = factoryCacheMap.get(moduleOrFactory);
56+
renderOptions.url =
57+
options.url || `${req.protocol}://${(req.get('host') || '')}${req.originalUrl}`;
58+
renderOptions.document = options.document || getDocument(filePath);
12359

124-
// If module factory is cached
125-
if (moduleFactory) {
126-
resolve(moduleFactory);
127-
return;
128-
}
60+
renderOptions.providers = options.providers || [];
61+
renderOptions.providers =
62+
renderOptions.providers.concat(getReqResProviders(options.req, options.res));
12963

130-
// Compile the module and cache it
131-
compiler.compileModuleAsync(moduleOrFactory)
132-
.then((factory) => {
133-
factoryCacheMap.set(moduleOrFactory, factory);
134-
resolve(factory);
135-
}, (err => {
136-
reject(err);
137-
}));
64+
engine.render(renderOptions)
65+
.then(html => callback(null, html))
66+
.catch(callback);
67+
} catch (err) {
68+
callback(err);
13869
}
139-
});
70+
};
14071
}
14172

14273
/**

modules/hapi-engine/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ ng_module(
1010
]),
1111
module_name = "@nguniversal/hapi-engine",
1212
deps = [
13+
"//modules/common/engine",
1314
"//modules/hapi-engine/tokens",
1415
"@npm//@angular/platform-server",
1516
"@npm//@types/hapi",

modules/hapi-engine/src/main.ts

Lines changed: 11 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,8 @@
88
import * as fs from 'fs';
99
import { Request, ResponseToolkit } from 'hapi';
1010

11-
import { NgModuleFactory, Type, CompilerFactory, Compiler, StaticProvider } from '@angular/core';
12-
import { ResourceLoader } from '@angular/compiler';
13-
import {
14-
INITIAL_CONFIG,
15-
renderModuleFactory,
16-
platformDynamicServer
17-
} from '@angular/platform-server';
18-
19-
import { FileLoader } from './file-loader';
11+
import { NgModuleFactory, Type, StaticProvider } from '@angular/core';
12+
import { ɵCommonEngine as CommonEngine } from '@nguniversal/common/engine';
2013
import { REQUEST, RESPONSE } from '@nguniversal/hapi-engine/tokens';
2114

2215
/**
@@ -43,34 +36,23 @@ export interface RenderOptions extends NgSetupOptions {
4336
const templateCache: { [key: string]: string } = {};
4437

4538
/**
46-
* Map of Module Factories
39+
* The CommonEngine with module to facory map in case of JIT.
4740
*/
48-
const factoryCacheMap = new Map<Type<{}>, NgModuleFactory<{}>>();
41+
const commonEngine = new CommonEngine(undefined);
4942

5043
/**
5144
* This is an express engine for handling Angular Applications
5245
*/
53-
export function ngHapiEngine(options: RenderOptions) {
54-
46+
export function ngHapiEngine(options: Readonly<RenderOptions>) {
5547
const req = options.req;
56-
const compilerFactory: CompilerFactory = platformDynamicServer().injector.get(CompilerFactory);
57-
const compiler: Compiler = compilerFactory.createCompiler([
58-
{
59-
providers: [
60-
{ provide: ResourceLoader, useClass: FileLoader, deps: [] }
61-
]
62-
}
63-
]);
64-
6548
if (req.raw.req.url === undefined) {
6649
return Promise.reject(new Error('url is undefined'));
6750
}
6851

6952
const protocol = req.server.info.protocol;
7053
const filePath = <string> req.raw.req.url;
71-
const url = `${protocol}://${req.info.host}${req.path}`;
7254

73-
options.providers = options.providers || [];
55+
const renderOptions: RenderOptions = Object.assign({}, options);
7456

7557
return new Promise((resolve, reject) => {
7658
const moduleOrFactory = options.bootstrap;
@@ -79,52 +61,13 @@ export function ngHapiEngine(options: RenderOptions) {
7961
return reject(new Error('You must pass in a NgModule or NgModuleFactory to be bootstrapped'));
8062
}
8163

82-
const extraProviders = options.providers!.concat(
83-
options.providers!,
84-
getReqProviders(options.req),
85-
[
86-
{
87-
provide: INITIAL_CONFIG,
88-
useValue: {
89-
document: options.document || getDocument(filePath),
90-
url: options.url || url
91-
}
92-
}
93-
]);
94-
95-
getFactory(moduleOrFactory, compiler)
96-
.then(factory => renderModuleFactory(factory, {extraProviders}))
97-
.then(resolve, reject);
98-
});
99-
}
100-
101-
/**
102-
* Get a factory from a bootstrapped module/ module factory
103-
*/
104-
function getFactory(
105-
moduleOrFactory: Type<{}> | NgModuleFactory<{}>, compiler: Compiler
106-
): Promise<NgModuleFactory<{}>> {
107-
return new Promise<NgModuleFactory<{}>>((resolve, reject) => {
108-
// If module has been compiled AoT
109-
if (moduleOrFactory instanceof NgModuleFactory) {
110-
resolve(moduleOrFactory);
111-
return;
112-
} else {
113-
let moduleFactory = factoryCacheMap.get(moduleOrFactory);
64+
renderOptions.url = options.url || `${protocol}://${req.info.host}${req.path}`;
65+
renderOptions.document = options.document || getDocument(filePath);
11466

115-
// If module factory is cached
116-
if (moduleFactory) {
117-
resolve(moduleFactory);
118-
return;
119-
}
67+
renderOptions.providers = options.providers || [];
68+
renderOptions.providers = renderOptions.providers.concat(getReqProviders(options.req));
12069

121-
// Compile the module and cache it
122-
compiler.compileModuleAsync(moduleOrFactory)
123-
.then((factory) => {
124-
factoryCacheMap.set(moduleOrFactory, factory);
125-
resolve(factory);
126-
}, reject);
127-
}
70+
commonEngine.render(renderOptions).then(resolve, reject);
12871
});
12972
}
13073

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@
6969
"bazel:lint": "yarn bazel:format --lint=warn",
7070
"bazel:lint-fix": "yarn bazel:format --lint=fix",
7171
"bazel:build": "bazel build //...",
72-
"test": "bazel test //...",
72+
"test": "bazel test //modules/...",
7373
"build:watch": "ibazel build //...",
74-
"test:watch": "ibazel test //...",
74+
"test:watch": "ibazel test //modules/...",
7575
"preinstall": "node ./tools/npm/check-npm.js",
7676
"bazel": "bazel"
7777
},

0 commit comments

Comments
 (0)