[DURACOM-247] Refactoring SSR configuration in order to align it with new nomenclature

This commit is contained in:
Giuseppe Digilio
2024-04-11 18:14:34 +02:00
parent 7b1f264812
commit d7d8067006
10 changed files with 90 additions and 114 deletions

101
server.ts
View File

@@ -17,7 +17,6 @@
import 'zone.js/node'; import 'zone.js/node';
import 'reflect-metadata'; import 'reflect-metadata';
import 'rxjs';
/* eslint-disable import/no-namespace */ /* eslint-disable import/no-namespace */
import * as morgan from 'morgan'; import * as morgan from 'morgan';
@@ -40,26 +39,25 @@ import { join } from 'path';
import { enableProdMode } from '@angular/core'; import { enableProdMode } from '@angular/core';
import { environment } from './src/environments/environment'; import { environment } from './src/environments/environment';
import { createProxyMiddleware } from 'http-proxy-middleware'; import { createProxyMiddleware } from 'http-proxy-middleware';
import { hasNoValue, hasValue } from './src/app/shared/empty.util'; import { hasValue } from './src/app/shared/empty.util';
import { UIServerConfig } from './src/config/ui-server-config.interface'; import { UIServerConfig } from './src/config/ui-server-config.interface';
import bootstrap from './src/main.server'; import bootstrap from './src/main.server';
import { buildAppConfig } from './src/config/config.server'; import { buildAppConfig } from './src/config/config.server';
import { APP_CONFIG, AppConfig } from './src/config/app-config.interface'; import {
APP_CONFIG,
AppConfig,
} from './src/config/app-config.interface';
import { extendEnvironmentWithAppConfig } from './src/config/config.util'; import { extendEnvironmentWithAppConfig } from './src/config/config.util';
import { logStartupMessage } from './startup-message'; import { logStartupMessage } from './startup-message';
import { TOKENITEM } from './src/app/core/auth/models/auth-token-info.model'; import { TOKENITEM } from './src/app/core/auth/models/auth-token-info.model';
import { CommonEngine } from '@angular/ssr'; import { CommonEngine } from '@angular/ssr';
import { APP_BASE_HREF } from '@angular/common'; import { APP_BASE_HREF } from '@angular/common';
import { REQUEST, RESPONSE } from './src/express.tokens'; import {
import { dirname, resolve } from 'node:path'; REQUEST,
import { fileURLToPath } from 'node:url'; RESPONSE,
} from './src/express.tokens';
/* /*
* Set path for the browser application's dist folder * Set path for the browser application's dist folder
@@ -92,9 +90,6 @@ export function app() {
* Create a new express application * Create a new express application
*/ */
const server = express(); const server = express();
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');
const commonEngine = new CommonEngine();
// Tell Express to trust X-FORWARDED-* headers from proxies // Tell Express to trust X-FORWARDED-* headers from proxies
// See https://expressjs.com/en/guide/behind-proxies.html // See https://expressjs.com/en/guide/behind-proxies.html
@@ -134,27 +129,6 @@ export function app() {
*/ */
server.use(json()); server.use(json());
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
/* server.engine('html', (_, options, callback) =>
ngExpressEngine({
bootstrap,
providers: [
{
provide: REQUEST,
useValue: (options as any).req,
},
{
provide: RESPONSE,
useValue: (options as any).req.res,
},
{
provide: APP_CONFIG,
useValue: environment,
},
],
})(_, (options as any), callback),
);*/
server.engine('ejs', ejs.renderFile); server.engine('ejs', ejs.renderFile);
/* /*
@@ -234,12 +208,7 @@ export function app() {
* copy of the page (see cacheCheck()) * copy of the page (see cacheCheck())
*/ */
router.get('*', cacheCheck, ngApp); router.get('*', cacheCheck, ngApp);
// All regular routes use the Angular engine
// server.get('*', (req, res, next) => {
// const { protocol, originalUrl, baseUrl, headers } = req;
//
//
// });
server.use(environment.ui.nameSpace, router); server.use(environment.ui.nameSpace, router);
return server; return server;
@@ -249,7 +218,7 @@ export function app() {
* The callback function to serve server side angular * The callback function to serve server side angular
*/ */
function ngApp(req, res, next) { function ngApp(req, res, next) {
if (environment.universal.preboot) { if (environment.ssr.enabled) {
// Render the page to user via SSR (server side rendering) // Render the page to user via SSR (server side rendering)
serverSideRender(req, res, next); serverSideRender(req, res, next);
} else { } else {
@@ -264,17 +233,19 @@ function ngApp(req, res, next) {
* returned to the user. * returned to the user.
* @param req current request * @param req current request
* @param res current response * @param res current response
* @param next the next function
* @param sendToUser if true (default), send the rendered content to the user. * @param sendToUser if true (default), send the rendered content to the user.
* If false, then only save this rendered content to the in-memory cache (to refresh cache). * If false, then only save this rendered content to the in-memory cache (to refresh cache).
*/ */
function serverSideRender(req, res, next, sendToUser: boolean = true) { function serverSideRender(req, res, next, sendToUser: boolean = true) {
const { protocol, originalUrl, baseUrl, headers } = req; const { protocol, originalUrl, baseUrl, headers } = req;
const commonEngine = new CommonEngine(); const commonEngine = new CommonEngine({ enablePerformanceProfiler: environment.ssr.enablePerformanceProfiler });
// Render the page via SSR (server side rendering) // Render the page via SSR (server side rendering)
commonEngine commonEngine
.render({ .render({
bootstrap, bootstrap,
documentFilePath: indexHtml, documentFilePath: indexHtml,
inlineCriticalCss: environment.ssr.inlineCriticalCss,
url: `${protocol}://${headers.host}${originalUrl}`, url: `${protocol}://${headers.host}${originalUrl}`,
publicPath: DIST_FOLDER, publicPath: DIST_FOLDER,
providers: [ providers: [
@@ -293,29 +264,19 @@ function serverSideRender(req, res, next, sendToUser: boolean = true) {
}, },
], ],
}) })
.then((html) => res.send(html)) .then((html) => {
.catch((err) => next(err)); if (hasValue(html)) {
/* res.render(indexHtml, {
req,
res,
preboot: environment.universal.preboot,
async: environment.universal.async,
time: environment.universal.time,
baseUrl: environment.ui.nameSpace,
originUrl: environment.ui.baseUrl,
requestUrl: req.originalUrl,
}, (err, data) => {
if (hasNoValue(err) && hasValue(data)) {
// save server side rendered page to cache (if any are enabled) // save server side rendered page to cache (if any are enabled)
saveToCache(req, data); saveToCache(req, html);
if (sendToUser) { if (sendToUser) {
res.locals.ssr = true; // mark response as SSR (enables text compression) res.locals.ssr = true; // mark response as SSR (enables text compression)
// send rendered page to user // send rendered page to user
res.send(data); res.send(html);
} }
} else if (hasValue(err) && err.code === 'ERR_HTTP_HEADERS_SENT') { }
})
.catch((err) => {
if (hasValue(err) && err.code === 'ERR_HTTP_HEADERS_SENT') {
// When this error occurs we can't fall back to CSR because the response has already been // When this error occurs we can't fall back to CSR because the response has already been
// sent. These errors occur for various reasons in universal, not all of which are in our // sent. These errors occur for various reasons in universal, not all of which are in our
// control to solve. // control to solve.
@@ -330,7 +291,8 @@ function serverSideRender(req, res, next, sendToUser: boolean = true) {
clientSideRender(req, res); clientSideRender(req, res);
} }
} }
});*/ next(err);
});
} }
/** /**
@@ -388,7 +350,7 @@ function initCache() {
function botCacheEnabled(): boolean { function botCacheEnabled(): boolean {
// Caching is only enabled if SSR is enabled AND // Caching is only enabled if SSR is enabled AND
// "max" pages to cache is greater than zero // "max" pages to cache is greater than zero
return environment.universal.preboot && environment.cache.serverSide.botCache.max && (environment.cache.serverSide.botCache.max > 0); return environment.ssr.enabled && environment.cache.serverSide.botCache.max && (environment.cache.serverSide.botCache.max > 0);
} }
/** /**
@@ -397,7 +359,7 @@ function botCacheEnabled(): boolean {
function anonymousCacheEnabled(): boolean { function anonymousCacheEnabled(): boolean {
// Caching is only enabled if SSR is enabled AND // Caching is only enabled if SSR is enabled AND
// "max" pages to cache is greater than zero // "max" pages to cache is greater than zero
return environment.universal.preboot && environment.cache.serverSide.anonymousCache.max && (environment.cache.serverSide.anonymousCache.max > 0); return environment.ssr.enabled && environment.cache.serverSide.anonymousCache.max && (environment.cache.serverSide.anonymousCache.max > 0);
} }
/** /**
@@ -410,9 +372,9 @@ function cacheCheck(req, res, next) {
// If the bot cache is enabled and this request looks like a bot, check the bot cache for a cached page. // If the bot cache is enabled and this request looks like a bot, check the bot cache for a cached page.
if (botCacheEnabled() && isbot(req.get('user-agent'))) { if (botCacheEnabled() && isbot(req.get('user-agent'))) {
cachedCopy = checkCacheForRequest('bot', botCache, req, res); cachedCopy = checkCacheForRequest('bot', botCache, req, res, next);
} else if (anonymousCacheEnabled() && !isUserAuthenticated(req)) { } else if (anonymousCacheEnabled() && !isUserAuthenticated(req)) {
cachedCopy = checkCacheForRequest('anonymous', anonymousCache, req, res); cachedCopy = checkCacheForRequest('anonymous', anonymousCache, req, res, next);
} }
// If cached copy exists, return it to the user. // If cached copy exists, return it to the user.
@@ -448,9 +410,10 @@ function cacheCheck(req, res, next) {
* @param cache LRU cache to check * @param cache LRU cache to check
* @param req current request to look for in the cache * @param req current request to look for in the cache
* @param res current response * @param res current response
* @param next the next function
* @returns cached copy (if found) or undefined (if not found) * @returns cached copy (if found) or undefined (if not found)
*/ */
function checkCacheForRequest(cacheName: string, cache: LRU<string, any>, req, res): any { function checkCacheForRequest(cacheName: string, cache: LRU<string, any>, req, res, next): any {
// Get the cache key for this request // Get the cache key for this request
const key = getCacheKey(req); const key = getCacheKey(req);
@@ -466,7 +429,7 @@ function checkCacheForRequest(cacheName: string, cache: LRU<string, any>, req, r
// Update cached copy by rerendering server-side // Update cached copy by rerendering server-side
// NOTE: In this scenario the currently cached copy will be returned to the current user. // NOTE: In this scenario the currently cached copy will be returned to the current user.
// This re-render is peformed behind the scenes to update cached copy for next user. // This re-render is peformed behind the scenes to update cached copy for next user.
serverSideRender(req, res, null, false); serverSideRender(req, res, next, false);
} }
} else { } else {
if (environment.cache.serverSide.debug) { console.log(`CACHE MISS FOR ${key} in ${cacheName} cache.`); } if (environment.cache.serverSide.debug) { console.log(`CACHE MISS FOR ${key} in ${cacheName} cache.`); }
@@ -570,7 +533,7 @@ function createHttpsServer(keys) {
const listener = createServer({ const listener = createServer({
key: keys.serviceKey, key: keys.serviceKey,
cert: keys.certificate, cert: keys.certificate,
}, app).listen(environment.ui.port, environment.ui.host, () => { }, app()).listen(environment.ui.port, environment.ui.host, () => {
serverStarted(); serverStarted();
}); });

View File

@@ -1,6 +1,6 @@
import { AppConfig } from './app-config.interface'; import { AppConfig } from './app-config.interface';
import { UniversalConfig } from './universal-config.interface'; import { SSRConfig } from './ssr-config.interface';
export interface BuildConfig extends AppConfig { export interface BuildConfig extends AppConfig {
universal: UniversalConfig; ssr: SSRConfig;
} }

View File

@@ -34,7 +34,7 @@ export class DefaultAppConfig implements AppConfig {
// NOTE: will log all redux actions and transfers in console // NOTE: will log all redux actions and transfers in console
debug = false; debug = false;
// Angular Universal server settings // Angular express server settings
// NOTE: these must be 'synced' with the 'dspace.ui.url' setting in your backend's local.cfg. // NOTE: these must be 'synced' with the 'dspace.ui.url' setting in your backend's local.cfg.
ui: UIServerConfig = { ui: UIServerConfig = {
ssl: false, ssl: false,

View File

@@ -0,0 +1,21 @@
import { Config } from './config.interface';
export interface SSRConfig extends Config {
/**
* A boolean flag indicating whether the SSR configuration is enabled
* Defaults to true.
*/
enabled: boolean;
/**
* Enable request performance profiling data collection and printing the results in the server console.
* Defaults to false.
*/
enablePerformanceProfiler: boolean;
/**
* Reduce render blocking requests by inlining critical CSS.
* Defaults to true.
*/
inlineCriticalCss: boolean;
}

View File

@@ -1,7 +0,0 @@
import { Config } from './config.interface';
export interface UniversalConfig extends Config {
preboot: boolean;
async: boolean;
time: boolean;
}

View File

@@ -3,10 +3,10 @@ import { BuildConfig } from '../config/build-config.interface';
export const environment: Partial<BuildConfig> = { export const environment: Partial<BuildConfig> = {
production: true, production: true,
// Angular Universal settings // Angular SSR settings
universal: { ssr: {
preboot: true, enabled: true,
async: true, enablePerformanceProfiler: false,
time: false, inlineCriticalCss: true,
}, },
}; };

View File

@@ -7,14 +7,14 @@ import { NotificationAnimationsType } from '../app/shared/notifications/models/n
export const environment: BuildConfig = { export const environment: BuildConfig = {
production: false, production: false,
// Angular Universal settings // Angular SSR settings
universal: { ssr: {
preboot: true, enabled: true,
async: true, enablePerformanceProfiler: false,
time: false, inlineCriticalCss: true,
}, },
// Angular Universal server settings. // Angular express server settings.
ui: { ui: {
ssl: false, ssl: false,
host: 'dspace.com', host: 'dspace.com',

View File

@@ -8,11 +8,11 @@ import { BuildConfig } from '../config/build-config.interface';
export const environment: Partial<BuildConfig> = { export const environment: Partial<BuildConfig> = {
production: false, production: false,
// Angular Universal settings // Angular SSR settings
universal: { ssr: {
preboot: false, enabled: false,
async: true, enablePerformanceProfiler: false,
time: false, inlineCriticalCss: true,
}, },
}; };

View File

@@ -14,5 +14,4 @@ import { serverAppConfig } from './modules/app/server-app.config';
const bootstrap = () => bootstrapApplication(AppComponent, serverAppConfig); const bootstrap = () => bootstrapApplication(AppComponent, serverAppConfig);
export { renderModule } from '@angular/platform-server';
export default bootstrap; export default bootstrap;