Skip to content

Commit

Permalink
feat(model-ad): enable SSR (#2701)
Browse files Browse the repository at this point in the history
  • Loading branch information
tschaffter authored Jun 8, 2024
1 parent 045c55c commit 40bcf76
Show file tree
Hide file tree
Showing 20 changed files with 387 additions and 150 deletions.
42 changes: 37 additions & 5 deletions apps/model-ad/app/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,44 @@
"jestConfig": "apps/model-ad/app/jest.config.ts"
}
},
"serve-static": {
"executor": "@nx/web:file-server",
"server": {
"dependsOn": [
"build"
],
"executor": "@angular-devkit/build-angular:server",
"options": {
"buildTarget": "model-ad-app:build",
"staticFilePath": "dist/apps/model-ad/app/browser"
}
"outputPath": "dist/apps/model-ad/app/browser/server",
"main": "apps/model-ad/app/server.ts",
"tsConfig": "apps/model-ad/app/tsconfig.server.json",
"inlineStyleLanguage": "scss"
},
"configurations": {
"production": {
"outputHashing": "media"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"sourceMap": true,
"extractLicenses": false,
"vendorChunk": true
}
},
"defaultConfiguration": "production"
},
"serve-ssr": {
"executor": "@angular-devkit/build-angular:ssr-dev-server",
"configurations": {
"development": {
"browserTarget": "model-ad-app:build:development",
"serverTarget": "model-ad-app:server:development"
},
"production": {
"browserTarget": "model-ad-app:build:production",
"serverTarget": "model-ad-app:server:production"
}
},
"defaultConfiguration": "development"
}
},
"implicitDependencies": [
Expand Down
65 changes: 50 additions & 15 deletions apps/model-ad/app/server.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,44 @@
import 'zone.js/node';

import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
import express from 'express';
import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
import * as express from 'express';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import bootstrap from './src/main.server';

const PORT = process.env['PORT'] || '4200';
console.log(`server.ts: ${PORT}`);

// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
const server = express();
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');
const indexHtml = join(serverDistFolder, 'index.server.html');
const distFolder = join(
process.cwd(),
'dist/apps/model-ad/app/browser/browser'
);
const indexHtml = existsSync(join(distFolder, 'index.original.html'))
? join(distFolder, 'index.original.html')
: join(distFolder, 'index.html');

const commonEngine = new CommonEngine();

server.set('view engine', 'html');
server.set('views', browserDistFolder);
server.set('views', distFolder);

// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get(
'*.*',
express.static(browserDistFolder, {
express.static(distFolder, {
maxAge: '1y',
})
);

// Health endpoint used by the container
server.get('/health', (_req, res) => res.status(200).json({ status: 'UP' }));

// All regular routes use the Angular engine
server.get('*', (req, res, next) => {
const { protocol, originalUrl, baseUrl, headers } = req;
Expand All @@ -36,8 +48,22 @@ export function app(): express.Express {
bootstrap,
documentFilePath: indexHtml,
url: `${protocol}://${headers.host}${originalUrl}`,
publicPath: browserDistFolder,
providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
publicPath: distFolder,
providers: [
{ provide: APP_BASE_HREF, useValue: baseUrl },
// The base URL enables the app to load the app config file during server-side rendering.
{
provide: 'APP_BASE_URL',
// the format of ${host} is `host:port`
useFactory: () => `${protocol}://${headers.host}`,
deps: [],
},
{
provide: 'APP_PORT',
useValue: PORT,
deps: [],
},
],
})
.then((html) => res.send(html))
.catch((err) => next(err));
Expand All @@ -47,13 +73,22 @@ export function app(): express.Express {
}

function run(): void {
const port = process.env['PORT'] || 4000;

// Start up the Node server
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
server.listen(PORT, () => {
console.log(`Node Express server listening on http://localhost:${PORT}`);
});
}

run();
// 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 camelcase,no-undef */
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = (mainModule && mainModule.filename) || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
run();
}

export default bootstrap;
21 changes: 16 additions & 5 deletions apps/model-ad/app/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"types": ["node"]
"types": [
"node"
]
},
"files": ["src/main.ts", "src/main.server.ts", "server.ts"],
"include": ["src/**/*.d.ts"],
"exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"]
}
"files": [
"src/main.ts",
"src/main.server.ts",
],
"include": [
"src/**/*.d.ts"
],
"exclude": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
]
}
5 changes: 4 additions & 1 deletion apps/model-ad/app/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
},
{
"path": "./tsconfig.spec.json"
},
{
"path": "./tsconfig.server.json"
}
],
"extends": "../../../tsconfig.base.json",
Expand All @@ -30,4 +33,4 @@
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
}
16 changes: 16 additions & 0 deletions apps/model-ad/app/tsconfig.server.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.app.json",
"compilerOptions": {
"outDir": "../../out-tsc/server",
"target": "ES2022",
"types": [
"node"
],
"esModuleInterop": false,
},
"files": [
"src/main.server.ts",
"server.ts"
]
}
97 changes: 0 additions & 97 deletions apps/openchallenges/app/server.ts.bak

This file was deleted.

2 changes: 1 addition & 1 deletion apps/openchallenges/app/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
}
12 changes: 10 additions & 2 deletions libs/model-ad/not-found/src/lib/not-found.component.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { Component, Renderer2 } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { MatCardModule } from '@angular/material/card';
import { FooterComponent } from '@sagebionetworks/model-ad/ui';
import { ConfigService } from '@sagebionetworks/model-ad/config';
import { SeoService } from '@sagebionetworks/shared/util';
import { getSeoData } from './seo-data';

@Component({
selector: 'model-ad-not-found',
Expand All @@ -26,11 +28,17 @@ export class NotFoundComponent {
public termsOfUseUrl: string;
public apiDocsUrl: string;

constructor(private readonly configService: ConfigService) {
constructor(
private readonly configService: ConfigService,
private seoService: SeoService,
private renderer2: Renderer2
) {
this.appVersion = this.configService.config.appVersion;
this.dataUpdatedOn = this.configService.config.dataUpdatedOn;
this.privacyPolicyUrl = this.configService.config.privacyPolicyUrl;
this.termsOfUseUrl = this.configService.config.termsOfUseUrl;
this.apiDocsUrl = this.configService.config.apiDocsUrl;

this.seoService.setData(getSeoData(), this.renderer2);
}
}
8 changes: 8 additions & 0 deletions libs/model-ad/not-found/src/lib/seo-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { SeoData } from '@sagebionetworks/shared/util';
import { getDefaultSeoData } from '@sagebionetworks/model-ad/util';

export const getSeoData = (): SeoData => {
return Object.assign(getDefaultSeoData(), {
title: 'Not Found | MODEL-AD',
} as SeoData);
};
Loading

0 comments on commit 40bcf76

Please sign in to comment.