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

Commit de33b02

Browse files
Fabian WilesCaerusKaru
authored andcommitted
feat(socket-engine): introduce package (#999)
1 parent 991472d commit de33b02

9 files changed

Lines changed: 333 additions & 54 deletions

File tree

WORKSPACE

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
workspace(name = "nguniversal")
22
http_archive(
33
name = "build_bazel_rules_nodejs",
4-
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.9.1.zip",
5-
strip_prefix = "rules_nodejs-0.9.1",
6-
sha256 = "6139762b62b37c1fd171d7f22aa39566cb7dc2916f0f801d505a9aaf118c117f",
4+
# TODO: upgrade once https://github.com/bazelbuild/rules_nodejs/issues/218#issuecomment-395826361 is released
5+
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.8.0.zip",
6+
strip_prefix = "rules_nodejs-0.8.0",
77
)
88

99
http_archive(

modules/socket-engine/BUILD.bazel

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
load("//tools:defaults.bzl", "ts_library", "ng_module", "ng_package")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
6+
7+
ng_module(
8+
name = "socket-engine",
9+
srcs = glob([
10+
"*.ts",
11+
"src/**/*.ts",
12+
]),
13+
module_name = "@nguniversal/socket-engine",
14+
deps = [
15+
"//modules/common",
16+
],
17+
)
18+
19+
ng_package(
20+
name = "npm_package",
21+
srcs = [
22+
":package.json",
23+
],
24+
entry_point = "modules/socket-engine/index.js",
25+
readme_md = ":README.md",
26+
deps = [
27+
":socket-engine",
28+
"//modules/common",
29+
],
30+
)
31+
32+
ts_library(
33+
name = "unit_test_lib",
34+
testonly = True,
35+
srcs = glob([
36+
"spec/**/*.spec.ts",
37+
]),
38+
deps = [
39+
":socket-engine",
40+
],
41+
)
42+
43+
jasmine_node_test(
44+
name = "unit_test",
45+
srcs = [":unit_test_lib"],
46+
)

modules/socket-engine/README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Angular Universal Socket Engine
2+
3+
Framework and Platform agnostic Angular Universal rendering.
4+
5+
## Usage Server
6+
7+
`npm install @nguniversal/socket-engine @nguniversal/common --save`
8+
9+
```js
10+
const socketEngine = require('@nguniversal/socket-engine');
11+
12+
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
13+
const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main');
14+
15+
socketEngine.startSocketEngine(AppServerModuleNgFactory);
16+
```
17+
This will the socket engine which internally hosts a TCP Socket server.
18+
The default port is `9090` and host of `localhost`
19+
You may want to leave this as a plain `.js` file since it is so simple and to make deploying it easier, but it can be easily transpiled from Typescript.
20+
21+
## Usage Client
22+
23+
Your client can be whatever language, framework or platform you like.
24+
As long as it can connect to a TCP Socket (which all frameworks can) then you're good to go.
25+
26+
This example will use JS for simplicity
27+
```typescript
28+
import * as net from 'net';
29+
30+
const client = net.createConnection(9090, 'localhost', () => {
31+
console.log('connected to SSR server');
32+
});
33+
34+
client.on('data', data => {
35+
const res = JSON.parse(data.toString()) as SocketEngineResponse;
36+
expect(res.id).toEqual(1);
37+
expect(res.html).toEqual(template);
38+
server.close();
39+
done();
40+
});
41+
42+
const renderOptions = {id: 1, url: '/path', document: '<app-root></app-root>'} as SocketEngineRenderOptions;
43+
client.write(JSON.stringify(renderOptions));
44+
```

modules/socket-engine/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
export * from './public_api';

modules/socket-engine/package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "@nguniversal/socket-engine",
3+
"version": "0.0.0-PLACEHOLDER",
4+
"description": "Socket Engine for running Server Angular Apps",
5+
"license": "MIT",
6+
"keywords": [
7+
"socket",
8+
"ssr",
9+
"universal"
10+
],
11+
"peerDependencies": {
12+
"@nguniversal/common": "0.0.0-PLACEHOLDER",
13+
"@angular/core": "NG_VERSION"
14+
},
15+
"ng-update": {
16+
"packageGroup": "NG_UPDATE_PACKAGE_GROUP"
17+
},
18+
"repository": {
19+
"type": "git",
20+
"url": "https://github.com/angular/universal"
21+
},
22+
"bugs": {
23+
"url": "https://github.com/angular/universal/issues"
24+
},
25+
"homepage": "https://github.com/angular/universal"
26+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
export { startSocketEngine, SocketEngineResponse, SocketEngineRenderOptions } from './src/main';
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
2+
import { ServerModule } from '@angular/platform-server';
3+
import { NgModule, Component } from '@angular/core';
4+
import 'zone.js';
5+
6+
import { BrowserModule } from '@angular/platform-browser';
7+
import { startSocketEngine, SocketEngineResponse,
8+
SocketEngineRenderOptions } from '@nguniversal/socket-engine';
9+
import * as net from 'net';
10+
11+
export function makeTestingModule(template: string, component?: any): any {
12+
@Component({
13+
selector: 'root',
14+
template: template
15+
})
16+
class MockComponent {}
17+
@NgModule({
18+
imports: [ServerModule, BrowserModule.withServerTransition({appId: 'mock'})],
19+
declarations: [component || MockComponent],
20+
bootstrap: [component || MockComponent]
21+
})
22+
class MockServerModule {}
23+
return MockServerModule;
24+
}
25+
26+
async function sendAndRecieve(renderOptions: SocketEngineRenderOptions, template = '') {
27+
return new Promise<SocketEngineResponse>(async(resolve, _reject) => {
28+
29+
const appModule = makeTestingModule(template);
30+
const server = await startSocketEngine(appModule);
31+
32+
const client = net.createConnection(9090, 'localhost', () => {
33+
client.write(JSON.stringify(renderOptions));
34+
});
35+
36+
client.on('data', data => {
37+
const res = JSON.parse(data.toString()) as SocketEngineResponse;
38+
server.close();
39+
resolve(res);
40+
});
41+
});
42+
}
43+
44+
describe('test runner', () => {
45+
it('should render a basic template', async (done) => {
46+
const template = `${new Date()}`;
47+
const renderOptions = {id: 1, url: '/path',
48+
document: '<root></root>'} as SocketEngineRenderOptions;
49+
const result = await sendAndRecieve(renderOptions, template);
50+
expect(result.html).toContain(template);
51+
done();
52+
});
53+
it('should return the same id', async(done) => {
54+
const id = Math.random();
55+
const renderOptions = {id , url: '/path',
56+
document: '<root></root>'} as SocketEngineRenderOptions;
57+
const result = await sendAndRecieve(renderOptions);
58+
expect(result.id).toEqual(id);
59+
done();
60+
});
61+
it('should return an error if it cant render', async(done) => {
62+
@Component({
63+
selector: 'root',
64+
template: ''
65+
})
66+
class MockComponent {constructor(_illMakeItThrow: '') {}}
67+
const appModule = makeTestingModule('', MockComponent);
68+
const server = await startSocketEngine(appModule);
69+
70+
const client = net.createConnection(9090, 'localhost', () => {
71+
const renderOptions = {id: 1, url: '/path',
72+
document: '<root></root>'} as SocketEngineRenderOptions;
73+
client.write(JSON.stringify(renderOptions));
74+
});
75+
76+
client.on('data', data => {
77+
const res = JSON.parse(data.toString()) as SocketEngineResponse;
78+
server.close();
79+
expect(res.error).not.toBeNull();
80+
done();
81+
});
82+
});
83+
it('should return an error if it cant render', async(done) => {
84+
const template = `${new Date()}`;
85+
const renderOptions = {id: 1, url: '/path',
86+
document: '<root></root>'} as SocketEngineRenderOptions;
87+
const result = await sendAndRecieve(renderOptions, template);
88+
expect(result.error).toBeUndefined();
89+
done();
90+
});
91+
});

modules/socket-engine/src/main.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import { ɵCommonEngine as CommonEngine,
9+
ɵRenderOptions as RenderOptions } from '@nguniversal/common/engine';
10+
import { NgModuleFactory, Type } from '@angular/core';
11+
import * as net from 'net';
12+
13+
export interface SocketEngineServer {
14+
close: () => void;
15+
}
16+
17+
export interface SocketEngineRenderOptions extends RenderOptions {
18+
id: number;
19+
}
20+
21+
export interface SocketEngineResponse {
22+
id: number;
23+
html: string|null;
24+
error?: Error;
25+
}
26+
27+
export function startSocketEngine(
28+
moduleOrFactory: Type<{}> | NgModuleFactory<{}>,
29+
host = 'localhost',
30+
port = 9090
31+
): Promise<SocketEngineServer> {
32+
return new Promise((resolve, _reject) => {
33+
const engine = new CommonEngine(moduleOrFactory);
34+
35+
const server = net.createServer(socket => {
36+
socket.on('data', async buff => {
37+
const message = buff.toString();
38+
const renderOptions = JSON.parse(message) as SocketEngineRenderOptions;
39+
try {
40+
const html = await engine.render(renderOptions);
41+
socket.write(JSON.stringify({html, id: renderOptions.id} as SocketEngineResponse));
42+
} catch (error) {
43+
// send the error down to the client then rethrow it
44+
socket.write(JSON.stringify({html: null,
45+
id: renderOptions.id, error} as SocketEngineResponse));
46+
throw error;
47+
}
48+
});
49+
});
50+
51+
server.listen(port, host);
52+
resolve({close: () => server.close()});
53+
});
54+
}
55+

0 commit comments

Comments
 (0)