-
Notifications
You must be signed in to change notification settings - Fork 1
/
server.ts
142 lines (123 loc) · 4.86 KB
/
server.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/*
* This is the server for the server side rendered (SSR) version of the app.
* It is an express server that allows for rendering a page while the rest of
* the application bundle downloads.
*/
import "reflect-metadata";
import "zone.js/node";
import { existsSync, readFileSync } from "fs";
import { join } from "path";
import { APP_BASE_HREF } from "@angular/common";
import { Configuration } from "@helpers/app-initializer/app-initializer";
import { CommonEngine } from "@angular/ssr";
import { assetRoot } from "@services/config/config.service";
import express from "express";
import { environment } from "src/environments/environment";
import { API_CONFIG } from "@services/config/config.tokens";
import { AppServerModule } from "./src/main.server";
import { REQUEST, RESPONSE } from "./src/express.tokens";
import angularConfig from "./angular.json";
// The Express app is exported so that it can be used by serverless Functions.
export function app(path: string): express.Express {
const server = express();
const distFolder = join(process.cwd(), "dist/workbench-client/browser");
const indexHtml = existsSync(join(distFolder, "index.original.html"))
? join(distFolder, "index.original.html")
: join(distFolder, "index.html");
const configPath = [
path,
// default path for config in docker container
"/environment.json",
// development settings
join(distFolder, "assets", "environment.json"),
].find((x) => existsSync(x));
// eslint-disable-next-line no-console
console.log("Using config file ", configPath);
const rawConfig = readFileSync(configPath, "utf-8");
const config = new Configuration(JSON.parse(rawConfig));
const apiConfig = { provide: API_CONFIG, useValue: config };
const commonEngine = new CommonEngine({
bootstrap: AppServerModule,
providers: [apiConfig],
});
server.set("view engine", "html");
server.set("views", distFolder);
/*
* This allows us to reduce the chances of click-jacking by ensuring that the
* site cannot be embedded into another site
*/
server.get("*", (_, res, next) => {
res.setHeader("X-Frame-Options", "SAMEORIGIN");
next();
});
// we add the COOP and COEP headers so that we can use SharedArrayBuffer
// if you want to update these headers, update the angular.json file
// so that the headers are updated in the dev server as well
server.use((_, res, next) => {
// we use the angular.json config as the source of truth for headers
// so that we don't have to maintain the same headers for both the dev
// server and the production SSR server
const serverHeaders = angularConfig.projects["workbench-client"].architect.serve.options.headers;
for (const [key, value] of Object.entries(serverHeaders)) {
res.setHeader(key, value);
}
next();
})
// special case rendering our settings file - we already have it loaded
server.get(`${assetRoot}/environment.json`, (request, response) => {
response.type("application/json");
response.send(rawConfig);
});
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get(
"*.*",
express.static(distFolder, {
maxAge: "1y",
})
);
// All regular routes use the Angular engine
server.get("*", (req, res, next) => {
const { protocol, originalUrl, baseUrl, headers } = req;
commonEngine
.render({
bootstrap: AppServerModule,
documentFilePath: indexHtml,
url: `${protocol}://${headers.host}${originalUrl}`,
publicPath: distFolder,
providers: [
{ provide: APP_BASE_HREF, useValue: baseUrl },
{ provide: RESPONSE, useValue: res },
{ provide: REQUEST, useValue: req },
apiConfig,
],
})
.then((html) => res.send(html))
.catch((err) => next(err));
});
return server;
}
function run(configPath: string): void {
const port = Number(process.env.PORT) || 4000;
// eslint-disable-next-line no-console
console.log("Is production?", environment.production);
// Start up the Node server
const server = app(configPath);
server.listen(port, "0.0.0.0", () => {
// eslint-disable-next-line no-console
console.log(`Node Express server listening on http://0.0.0.0:${port}`);
});
}
// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
// eslint-disable-next-line @typescript-eslint/naming-convention
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = (mainModule && mainModule.filename) || "";
if (moduleFilename === __filename || moduleFilename.includes("iisnode")) {
// first argument after this script's name
run(process.argv[2]);
}
export * from "./src/main.server";