[DURACOM-234] WIP fix SSR

This commit is contained in:
Giuseppe Digilio
2024-03-28 16:37:42 +01:00
parent 3cb000db7c
commit abae9b9246
14 changed files with 87 additions and 37 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
/.angular/cache /.angular/cache
/.nx
/__build__ /__build__
/__server_build__ /__server_build__
/node_modules /node_modules

View File

@@ -109,15 +109,15 @@
"serve": { "serve": {
"builder": "@angular-builders/custom-webpack:dev-server", "builder": "@angular-builders/custom-webpack:dev-server",
"options": { "options": {
"browserTarget": "dspace-angular:build", "buildTarget": "dspace-angular:build",
"port": 4000 "port": 4000
}, },
"configurations": { "configurations": {
"development": { "development": {
"browserTarget": "dspace-angular:build:development" "buildTarget": "dspace-angular:build:development"
}, },
"production": { "production": {
"browserTarget": "dspace-angular:build:production" "buildTarget": "dspace-angular:build:production"
} }
} }
}, },
@@ -219,13 +219,13 @@
"serve-ssr": { "serve-ssr": {
"builder": "@angular-devkit/build-angular:ssr-dev-server", "builder": "@angular-devkit/build-angular:ssr-dev-server",
"options": { "options": {
"browserTarget": "dspace-angular:build", "buildTarget": "dspace-angular:build",
"serverTarget": "dspace-angular:server", "serverTarget": "dspace-angular:server",
"port": 4000 "port": 4000
}, },
"configurations": { "configurations": {
"production": { "production": {
"browserTarget": "dspace-angular:build:production", "buildTarget": "dspace-angular:build:production",
"serverTarget": "dspace-angular:server:production" "serverTarget": "dspace-angular:server:production"
} }
} }
@@ -233,7 +233,7 @@
"prerender": { "prerender": {
"builder": "@angular-devkit/build-angular:prerender", "builder": "@angular-devkit/build-angular:prerender",
"options": { "options": {
"browserTarget": "dspace-angular:build:production", "buildTarget": "dspace-angular:build:production",
"serverTarget": "dspace-angular:server:production", "serverTarget": "dspace-angular:server:production",
"routes": [ "routes": [
"/" "/"

View File

@@ -200,7 +200,7 @@
"sass-loader": "^12.6.0", "sass-loader": "^12.6.0",
"sass-resources-loader": "^2.2.5", "sass-resources-loader": "^2.2.5",
"ts-node": "^8.10.2", "ts-node": "^8.10.2",
"typescript": "~5.4.2", "typescript": "~5.3.3",
"webpack": "5.76.1", "webpack": "5.76.1",
"webpack-bundle-analyzer": "^4.8.0", "webpack-bundle-analyzer": "^4.8.0",
"webpack-cli": "^4.2.0", "webpack-cli": "^4.2.0",

View File

@@ -39,8 +39,7 @@ import { join } from 'path';
import { enableProdMode } from '@angular/core'; import { enableProdMode } from '@angular/core';
import { ngExpressEngine } from '@nguniversal/express-engine';
import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens';
import { environment } from './src/environments/environment'; import { environment } from './src/environments/environment';
import { createProxyMiddleware } from 'http-proxy-middleware'; import { createProxyMiddleware } from 'http-proxy-middleware';
@@ -55,6 +54,11 @@ 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 { APP_BASE_HREF } from '@angular/common';
import { REQUEST, RESPONSE } from './src/express.tokens';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
/* /*
@@ -88,6 +92,9 @@ 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
@@ -128,7 +135,7 @@ export function app() {
server.use(json()); server.use(json());
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine) // Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine('html', (_, options, callback) => /* server.engine('html', (_, options, callback) =>
ngExpressEngine({ ngExpressEngine({
bootstrap, bootstrap,
providers: [ providers: [
@@ -146,7 +153,7 @@ export function app() {
}, },
], ],
})(_, (options as any), callback), })(_, (options as any), callback),
); );*/
server.engine('ejs', ejs.renderFile); server.engine('ejs', ejs.renderFile);
@@ -227,7 +234,12 @@ 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;
@@ -236,10 +248,10 @@ export function app() {
/* /*
* The callback function to serve server side angular * The callback function to serve server side angular
*/ */
function ngApp(req, res) { function ngApp(req, res, next) {
if (environment.universal.preboot) { if (environment.universal.preboot) {
// Render the page to user via SSR (server side rendering) // Render the page to user via SSR (server side rendering)
serverSideRender(req, res); serverSideRender(req, res, next);
} else { } else {
// If preboot is disabled, just serve the client // If preboot is disabled, just serve the client
console.log('Universal off, serving for direct client-side rendering (CSR)'); console.log('Universal off, serving for direct client-side rendering (CSR)');
@@ -255,9 +267,37 @@ function ngApp(req, res) {
* @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, sendToUser: boolean = true) { function serverSideRender(req, res, next, sendToUser: boolean = true) {
const { protocol, originalUrl, baseUrl, headers } = req;
const commonEngine = new CommonEngine();
// Render the page via SSR (server side rendering) // Render the page via SSR (server side rendering)
res.render(indexHtml, { commonEngine
.render({
bootstrap,
documentFilePath: indexHtml,
url: `${protocol}://${headers.host}${originalUrl}`,
publicPath: DIST_FOLDER,
providers: [
{ provide: APP_BASE_HREF, useValue: baseUrl },
{
provide: REQUEST,
useValue: req,
},
{
provide: RESPONSE,
useValue: res,
},
{
provide: APP_CONFIG,
useValue: environment,
},
],
})
.then((html) => res.send(html))
.catch((err) => next(err));
/* res.render(indexHtml, {
req, req,
res, res,
preboot: environment.universal.preboot, preboot: environment.universal.preboot,
@@ -290,7 +330,7 @@ function serverSideRender(req, res, sendToUser: boolean = true) {
clientSideRender(req, res); clientSideRender(req, res);
} }
} }
}); });*/
} }
/** /**
@@ -426,7 +466,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, false); serverSideRender(req, res, null, 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.`); }

View File

@@ -9,10 +9,6 @@ import {
select, select,
Store, Store,
} from '@ngrx/store'; } from '@ngrx/store';
import {
REQUEST,
RESPONSE,
} from '../../../express.tokens';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CookieAttributes } from 'js-cookie'; import { CookieAttributes } from 'js-cookie';
import { import {
@@ -28,6 +24,10 @@ import {
} from 'rxjs/operators'; } from 'rxjs/operators';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import {
REQUEST,
RESPONSE,
} from '../../../express.tokens';
import { AppState } from '../../app.reducer'; import { AppState } from '../../app.reducer';
import { import {
hasNoValue, hasNoValue,

View File

@@ -8,9 +8,10 @@ import {
Inject, Inject,
Injectable, Injectable,
} from '@angular/core'; } from '@angular/core';
import { REQUEST } from '../../../express.tokens';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { REQUEST } from '../../../express.tokens';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
/** /**
* Http Interceptor intercepting Http Requests, adding the client's IP to their X-Forwarded-For header * Http Interceptor intercepting Http Requests, adding the client's IP to their X-Forwarded-For header

View File

@@ -3,7 +3,6 @@ import {
Inject, Inject,
Injectable, Injectable,
} from '@angular/core'; } from '@angular/core';
import { REQUEST } from '../../../express.tokens';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { import {
combineLatest, combineLatest,
@@ -16,6 +15,7 @@ import {
take, take,
} from 'rxjs/operators'; } from 'rxjs/operators';
import { REQUEST } from '../../../express.tokens';
import { import {
hasValue, hasValue,
isEmpty, isEmpty,

View File

@@ -2,13 +2,14 @@ import {
Inject, Inject,
Injectable, Injectable,
} from '@angular/core'; } from '@angular/core';
import { REQUEST } from '../../../express.tokens';
import { CookieAttributes } from 'js-cookie'; import { CookieAttributes } from 'js-cookie';
import { import {
Observable, Observable,
Subject, Subject,
} from 'rxjs'; } from 'rxjs';
import { REQUEST } from '../../../express.tokens';
export interface ICookieService { export interface ICookieService {
readonly cookies$: Observable<{ readonly [key: string]: any }>; readonly cookies$: Observable<{ readonly [key: string]: any }>;

View File

@@ -2,15 +2,15 @@ import {
Inject, Inject,
Injectable, Injectable,
} from '@angular/core'; } from '@angular/core';
import {
REQUEST,
RESPONSE,
} from '../../../express.tokens';
import { import {
Request, Request,
Response, Response,
} from 'express'; } from 'express';
import {
REQUEST,
RESPONSE,
} from '../../../express.tokens';
import { HardRedirectService } from './hard-redirect.service'; import { HardRedirectService } from './hard-redirect.service';
/** /**

View File

@@ -2,12 +2,12 @@ import {
Inject, Inject,
Injectable, Injectable,
} from '@angular/core'; } from '@angular/core';
import { REQUEST } from '../../../express.tokens';
import { import {
Observable, Observable,
of as observableOf, of as observableOf,
} from 'rxjs'; } from 'rxjs';
import { REQUEST } from '../../../express.tokens';
import { ReferrerService } from './referrer.service'; import { ReferrerService } from './referrer.service';
/** /**

8
src/express.tokens.ts Normal file
View File

@@ -0,0 +1,8 @@
import { InjectionToken } from '@angular/core';
import {
Request,
Response,
} from 'express';
export const REQUEST: InjectionToken<Request> = new InjectionToken<Request>('REQUEST');
export const RESPONSE: InjectionToken<Response> = new InjectionToken<Response>('RESPONSE');

View File

@@ -15,5 +15,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 { renderModule } from '@angular/platform-server';
export { ngExpressEngine } from '@angular/ssr';
export default bootstrap; export default bootstrap;

View File

@@ -19,7 +19,6 @@ import {
StoreConfig, StoreConfig,
StoreModule, StoreModule,
} from '@ngrx/store'; } from '@ngrx/store';
import { REQUEST } from '../../express.tokens';
import { import {
MissingTranslationHandler, MissingTranslationHandler,
TranslateLoader, TranslateLoader,
@@ -56,6 +55,7 @@ import { KlaroService } from '../../app/shared/cookies/klaro.service';
import { MissingTranslationHelper } from '../../app/shared/translate/missing-translation.helper'; import { MissingTranslationHelper } from '../../app/shared/translate/missing-translation.helper';
import { GoogleAnalyticsService } from '../../app/statistics/google-analytics.service'; import { GoogleAnalyticsService } from '../../app/statistics/google-analytics.service';
import { SubmissionService } from '../../app/submission/submission.service'; import { SubmissionService } from '../../app/submission/submission.service';
import { REQUEST } from '../../express.tokens';
import { TranslateBrowserLoader } from '../../ngx-translate-loaders/translate-browser.loader'; import { TranslateBrowserLoader } from '../../ngx-translate-loaders/translate-browser.loader';
import { BrowserInitService } from './browser-init.service'; import { BrowserInitService } from './browser-init.service';

View File

@@ -11484,10 +11484,10 @@ typescript@^2.5.0:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c"
integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w== integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==
typescript@~5.4.2: typescript@~5.3.3:
version "5.4.2" version "5.3.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.2.tgz#0ae9cebcfae970718474fe0da2c090cad6577372" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"
integrity sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ== integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
ua-parser-js@^0.7.30: ua-parser-js@^0.7.30:
version "0.7.37" version "0.7.37"