From d7d8067006332516ffb6cb487c640f8bd01d4d55 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 11 Apr 2024 18:14:34 +0200 Subject: [PATCH] [DURACOM-247] Refactoring SSR configuration in order to align it with new nomenclature --- server.ts | 135 ++++++++------------- src/config/build-config.interface.ts | 4 +- src/config/default-app-config.ts | 2 +- src/config/ssr-config.interface.ts | 21 ++++ src/config/store/devtools.ts | 2 +- src/config/universal-config.interface.ts | 7 -- src/environments/environment.production.ts | 10 +- src/environments/environment.test.ts | 12 +- src/environments/environment.ts | 10 +- src/main.server.ts | 1 - 10 files changed, 90 insertions(+), 114 deletions(-) create mode 100644 src/config/ssr-config.interface.ts delete mode 100644 src/config/universal-config.interface.ts diff --git a/server.ts b/server.ts index 34be71837f..22f3423287 100644 --- a/server.ts +++ b/server.ts @@ -17,7 +17,6 @@ import 'zone.js/node'; import 'reflect-metadata'; -import 'rxjs'; /* eslint-disable import/no-namespace */ import * as morgan from 'morgan'; @@ -40,26 +39,25 @@ import { join } from 'path'; import { enableProdMode } from '@angular/core'; - import { environment } from './src/environments/environment'; 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 bootstrap from './src/main.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 { logStartupMessage } from './startup-message'; import { TOKENITEM } from './src/app/core/auth/models/auth-token-info.model'; import { CommonEngine } from '@angular/ssr'; import { APP_BASE_HREF } from '@angular/common'; -import { REQUEST, RESPONSE } from './src/express.tokens'; -import { dirname, resolve } from 'node:path'; -import { fileURLToPath } from 'node:url'; - +import { + REQUEST, + RESPONSE, +} from './src/express.tokens'; /* * Set path for the browser application's dist folder @@ -92,9 +90,6 @@ export function app() { * Create a new express application */ 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 // See https://expressjs.com/en/guide/behind-proxies.html @@ -134,27 +129,6 @@ export function app() { */ 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); /* @@ -234,12 +208,7 @@ export function app() { * copy of the page (see cacheCheck()) */ 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); return server; @@ -249,7 +218,7 @@ export function app() { * The callback function to serve server side angular */ function ngApp(req, res, next) { - if (environment.universal.preboot) { + if (environment.ssr.enabled) { // Render the page to user via SSR (server side rendering) serverSideRender(req, res, next); } else { @@ -264,17 +233,19 @@ function ngApp(req, res, next) { * returned to the user. * @param req current request * @param res current response + * @param next the next function * @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). */ function serverSideRender(req, res, next, sendToUser: boolean = true) { 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) commonEngine .render({ bootstrap, documentFilePath: indexHtml, + inlineCriticalCss: environment.ssr.inlineCriticalCss, url: `${protocol}://${headers.host}${originalUrl}`, publicPath: DIST_FOLDER, providers: [ @@ -293,44 +264,35 @@ function serverSideRender(req, res, next, sendToUser: boolean = true) { }, ], }) - .then((html) => res.send(html)) - .catch((err) => next(err)); - - -/* 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) - saveToCache(req, data); - if (sendToUser) { - res.locals.ssr = true; // mark response as SSR (enables text compression) - // send rendered page to user - res.send(data); + .then((html) => { + if (hasValue(html)) { + // save server side rendered page to cache (if any are enabled) + saveToCache(req, html); + if (sendToUser) { + res.locals.ssr = true; // mark response as SSR (enables text compression) + // send rendered page to user + res.send(html); + } } - } else 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 - // sent. These errors occur for various reasons in universal, not all of which are in our - // control to solve. - console.warn('Warning [ERR_HTTP_HEADERS_SENT]: Tried to set headers after they were sent to the client'); - } else { - console.warn('Error in server-side rendering (SSR)'); - if (hasValue(err)) { - console.warn('Error details : ', err); + }) + .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 + // sent. These errors occur for various reasons in universal, not all of which are in our + // control to solve. + console.warn('Warning [ERR_HTTP_HEADERS_SENT]: Tried to set headers after they were sent to the client'); + } else { + console.warn('Error in server-side rendering (SSR)'); + if (hasValue(err)) { + console.warn('Error details : ', err); + } + if (sendToUser) { + console.warn('Falling back to serving direct client-side rendering (CSR).'); + clientSideRender(req, res); + } } - if (sendToUser) { - console.warn('Falling back to serving direct client-side rendering (CSR).'); - clientSideRender(req, res); - } - } - });*/ + next(err); + }); } /** @@ -388,7 +350,7 @@ function initCache() { function botCacheEnabled(): boolean { // Caching is only enabled if SSR is enabled AND // "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 { // Caching is only enabled if SSR is enabled AND // "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 (botCacheEnabled() && isbot(req.get('user-agent'))) { - cachedCopy = checkCacheForRequest('bot', botCache, req, res); + cachedCopy = checkCacheForRequest('bot', botCache, req, res, next); } 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. @@ -448,9 +410,10 @@ function cacheCheck(req, res, next) { * @param cache LRU cache to check * @param req current request to look for in the cache * @param res current response + * @param next the next function * @returns cached copy (if found) or undefined (if not found) */ -function checkCacheForRequest(cacheName: string, cache: LRU, req, res): any { +function checkCacheForRequest(cacheName: string, cache: LRU, req, res, next): any { // Get the cache key for this request const key = getCacheKey(req); @@ -466,7 +429,7 @@ function checkCacheForRequest(cacheName: string, cache: LRU, req, r // Update cached copy by rerendering server-side // 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. - serverSideRender(req, res, null, false); + serverSideRender(req, res, next, false); } } else { if (environment.cache.serverSide.debug) { console.log(`CACHE MISS FOR ${key} in ${cacheName} cache.`); } @@ -570,7 +533,7 @@ function createHttpsServer(keys) { const listener = createServer({ key: keys.serviceKey, cert: keys.certificate, - }, app).listen(environment.ui.port, environment.ui.host, () => { + }, app()).listen(environment.ui.port, environment.ui.host, () => { serverStarted(); }); diff --git a/src/config/build-config.interface.ts b/src/config/build-config.interface.ts index beb8097c9e..9501ae5e28 100644 --- a/src/config/build-config.interface.ts +++ b/src/config/build-config.interface.ts @@ -1,6 +1,6 @@ import { AppConfig } from './app-config.interface'; -import { UniversalConfig } from './universal-config.interface'; +import { SSRConfig } from './ssr-config.interface'; export interface BuildConfig extends AppConfig { - universal: UniversalConfig; + ssr: SSRConfig; } diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index d148dee148..da12c62935 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -34,7 +34,7 @@ export class DefaultAppConfig implements AppConfig { // NOTE: will log all redux actions and transfers in console 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. ui: UIServerConfig = { ssl: false, diff --git a/src/config/ssr-config.interface.ts b/src/config/ssr-config.interface.ts new file mode 100644 index 0000000000..ae4ee76a72 --- /dev/null +++ b/src/config/ssr-config.interface.ts @@ -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; +} diff --git a/src/config/store/devtools.ts b/src/config/store/devtools.ts index 18bbcf78f7..e37ff9dd58 100644 --- a/src/config/store/devtools.ts +++ b/src/config/store/devtools.ts @@ -6,5 +6,5 @@ export const StoreDevModules = [ StoreDevtoolsModule.instrument({ maxAge: 1000, logOnly: false, - connectInZone: true}), + connectInZone: true }), ]; diff --git a/src/config/universal-config.interface.ts b/src/config/universal-config.interface.ts deleted file mode 100644 index c088dcd657..0000000000 --- a/src/config/universal-config.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Config } from './config.interface'; - -export interface UniversalConfig extends Config { - preboot: boolean; - async: boolean; - time: boolean; -} diff --git a/src/environments/environment.production.ts b/src/environments/environment.production.ts index e9028b2828..6db1bcfdf3 100644 --- a/src/environments/environment.production.ts +++ b/src/environments/environment.production.ts @@ -3,10 +3,10 @@ import { BuildConfig } from '../config/build-config.interface'; export const environment: Partial = { production: true, - // Angular Universal settings - universal: { - preboot: true, - async: true, - time: false, + // Angular SSR settings + ssr: { + enabled: true, + enablePerformanceProfiler: false, + inlineCriticalCss: true, }, }; diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 0fc03f2a4c..0229a3aaf8 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -7,14 +7,14 @@ import { NotificationAnimationsType } from '../app/shared/notifications/models/n export const environment: BuildConfig = { production: false, - // Angular Universal settings - universal: { - preboot: true, - async: true, - time: false, + // Angular SSR settings + ssr: { + enabled: true, + enablePerformanceProfiler: false, + inlineCriticalCss: true, }, - // Angular Universal server settings. + // Angular express server settings. ui: { ssl: false, host: 'dspace.com', diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 4aa175ebe6..69cbc47a06 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -8,11 +8,11 @@ import { BuildConfig } from '../config/build-config.interface'; export const environment: Partial = { production: false, - // Angular Universal settings - universal: { - preboot: false, - async: true, - time: false, + // Angular SSR settings + ssr: { + enabled: false, + enablePerformanceProfiler: false, + inlineCriticalCss: true, }, }; diff --git a/src/main.server.ts b/src/main.server.ts index d699d1e484..6c47ae374d 100644 --- a/src/main.server.ts +++ b/src/main.server.ts @@ -14,5 +14,4 @@ import { serverAppConfig } from './modules/app/server-app.config'; const bootstrap = () => bootstrapApplication(AppComponent, serverAppConfig); -export { renderModule } from '@angular/platform-server'; export default bootstrap;