Skip to content

Commit 45acf58

Browse files
authored
chore(dev): added ssr mode (#DS-3828) (#799)
1 parent 68fc744 commit 45acf58

File tree

12 files changed

+374
-8
lines changed

12 files changed

+374
-8
lines changed

.github/dependabot.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,7 @@ updates:
115115
patterns:
116116
- 'luxon'
117117
- '@types/luxon'
118+
express:
119+
patterns:
120+
- 'express'
121+
- '@types/express'

angular.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1873,6 +1873,41 @@
18731873
}
18741874
}
18751875
},
1876+
"dev-ssr": {
1877+
"projectType": "application",
1878+
"root": "packages/components-dev/ssr",
1879+
"sourceRoot": "packages/components-dev/ssr",
1880+
"architect": {
1881+
"build": {
1882+
"builder": "@angular-devkit/build-angular:application",
1883+
"options": {
1884+
"outputPath": {
1885+
"base": "dist/components-dev/ssr"
1886+
},
1887+
"tsConfig": "packages/components-dev/ssr/tsconfig.app.json",
1888+
"index": "packages/components-dev/index.html",
1889+
"styles": ["packages/components-dev/main.scss"],
1890+
"polyfills": ["zone.js"],
1891+
"extractLicenses": false,
1892+
"sourceMap": true,
1893+
"optimization": false,
1894+
"namedChunks": true,
1895+
"browser": "packages/components-dev/ssr/main.ts",
1896+
"server": "packages/components-dev/ssr/main.server.ts",
1897+
"prerender": true,
1898+
"ssr": {
1899+
"entry": "packages/components-dev/ssr/server.ts"
1900+
}
1901+
}
1902+
},
1903+
"serve": {
1904+
"builder": "@angular-devkit/build-angular:dev-server",
1905+
"options": {
1906+
"buildTarget": "dev-ssr:build"
1907+
}
1908+
}
1909+
}
1910+
},
18761911
"dev-table": {
18771912
"projectType": "application",
18781913
"root": "packages/components-dev/table",

docs/guides/09-ssr-dev.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
## Server-side rendering (SSR) setup for local development
2+
3+
This guide explains how to enable SSR for your component development.
4+
5+
### Set up server config
6+
7+
Update `main.server.ts` with specific component development app:
8+
9+
```typescript
10+
// ...other imports
11+
import { DevApp } from '../<component>/module';
12+
// ...other code
13+
```
14+
15+
Replace `<component>` with the actual component name (e.g., `button`, `modal`, etc.).
16+
17+
---
18+
19+
### Client Hydration with live server
20+
21+
Update `main.ts` with specific component development app:
22+
23+
```typescript
24+
// ...other imports
25+
import { DevApp } from '../<component>/module';
26+
// ...other code
27+
```
28+
29+
Replace `<component>` with the actual component name (e.g., `button`, `modal`, etc.).
30+
31+
---
32+
33+
### How to run SSR locally
34+
35+
Build and run the server using the following commands:
36+
37+
```bash
38+
yarn run dev:ssr-start
39+
```
40+
41+
or for live server with client hydration
42+
43+
```bash
44+
yarn run dev:ssr
45+
```

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"@angular/platform-browser-dynamic": "18.2.13",
5959
"@angular/platform-server": "18.2.13",
6060
"@angular/router": "18.2.13",
61+
"@angular/ssr": "18.2.19",
6162
"@commitlint/cli": "^19.8.1",
6263
"@commitlint/config-conventional": "^19.8.1",
6364
"@cspell/dict-markdown": "^2.0.10",
@@ -85,6 +86,7 @@
8586
"@types/conventional-changelog": "^3.1.5",
8687
"@types/conventional-changelog-writer": "^4.0.10",
8788
"@types/eslint": "^8.56.12",
89+
"@types/express": "^4",
8890
"@types/fs-extra": "^5.1.0",
8991
"@types/glob": "^7.1.4",
9092
"@types/inquirer": "^7.3.3",
@@ -113,6 +115,7 @@
113115
"eslint-plugin-prettier": "^5.4.1",
114116
"eslint-plugin-promise": "^7.2.1",
115117
"eslint-plugin-rxjs": "^5.0.3",
118+
"express": "^4.18.2",
116119
"firebase-tools": "^13.35.1",
117120
"fs-extra": "^5.0.0",
118121
"glob": "^7.1.3",
@@ -245,6 +248,8 @@
245248
"dev:sidebar": "ng serve dev-sidebar --port 3003",
246249
"dev:sidepanel": "ng serve dev-sidepanel --port 3003",
247250
"dev:splitter": "ng serve dev-splitter --port 3003",
251+
"dev:ssr": "ng serve dev-ssr --port 3003",
252+
"dev:ssr-start": "ng build dev-ssr && node dist/components-dev/ssr/server/server.mjs",
248253
"dev:table": "ng serve dev-table --port 3003",
249254
"dev:tabs": "ng serve dev-tabs --port 3003",
250255
"dev:tag": "ng serve dev-tag --port 3003",

packages/components-dev/breadcrumbs/main.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import { ApplicationConfig } from '@angular/core';
12
import { bootstrapApplication } from '@angular/platform-browser';
23
import { provideAnimations } from '@angular/platform-browser/animations';
34
import { provideRouter, Routes } from '@angular/router';
45
import { DevAboutPage, DevApp, DevProductDetailsPage, DevProductsPage } from './module';
56

6-
const routes: Routes = [
7+
export const devRoutes: Routes = [
78
{
89
path: '',
910
data: { breadcrumb: 'Home' },
@@ -29,8 +30,10 @@ const routes: Routes = [
2930
}
3031
];
3132

32-
bootstrapApplication(DevApp, {
33+
export const devAppConfig: ApplicationConfig = {
3334
providers: [
3435
provideAnimations(),
35-
provideRouter(routes)]
36-
}).catch((error) => console.error(error));
36+
provideRouter(devRoutes)]
37+
};
38+
39+
bootstrapApplication(DevApp, devAppConfig).catch((error) => console.error(error));
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { ApplicationConfig } from '@angular/core';
2+
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
3+
import { provideServerRendering } from '@angular/platform-server';
4+
import { provideRouter } from '@angular/router';
5+
6+
export const devConfig: ApplicationConfig = {
7+
providers: [
8+
provideClientHydration(withEventReplay()),
9+
provideServerRendering(),
10+
provideRouter([])]
11+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { bootstrapApplication } from '@angular/platform-browser';
2+
import { DevApp } from '../breadcrumbs/module';
3+
import { devConfig } from './app.config.server';
4+
5+
const devBootstrap = () => bootstrapApplication(DevApp, devConfig);
6+
7+
export default devBootstrap;

packages/components-dev/ssr/main.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { bootstrapApplication } from '@angular/platform-browser';
2+
import { devAppConfig } from '../breadcrumbs/main';
3+
import { DevApp } from '../breadcrumbs/module';
4+
5+
bootstrapApplication(DevApp, devAppConfig).catch((error) => console.error(error));

packages/components-dev/ssr/server.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { APP_BASE_HREF } from '@angular/common';
2+
import { CommonEngine } from '@angular/ssr';
3+
import express from 'express';
4+
import { dirname, join, resolve } from 'node:path';
5+
import { fileURLToPath } from 'node:url';
6+
import devBootstrap from './main.server';
7+
8+
// The Express app is exported so that it can be used by serverless Functions.
9+
export function devApp(): express.Express {
10+
const server = express();
11+
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
12+
const browserDistFolder = resolve(serverDistFolder, '../browser');
13+
const indexHtml = join(serverDistFolder, 'index.server.html');
14+
15+
const commonEngine = new CommonEngine();
16+
17+
server.set('view engine', 'html');
18+
server.set('views', browserDistFolder);
19+
20+
// Example Express Rest API endpoints
21+
// server.get('/api/**', (req, res) => { });
22+
// Serve static files from /browser
23+
server.get(
24+
'**',
25+
express.static(browserDistFolder, {
26+
maxAge: '1y',
27+
index: 'index.html'
28+
})
29+
);
30+
31+
// All regular routes use the Angular engine
32+
server.get('**', (req, res, next) => {
33+
const { protocol, originalUrl, baseUrl, headers } = req;
34+
35+
commonEngine
36+
.render({
37+
bootstrap: devBootstrap,
38+
documentFilePath: indexHtml,
39+
url: `${protocol}://${headers.host}${originalUrl}`,
40+
publicPath: browserDistFolder,
41+
providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }]
42+
})
43+
.then((html) => res.send(html))
44+
// eslint-disable-next-line promise/no-callback-in-promise
45+
.catch((err) => next(err));
46+
});
47+
48+
return server;
49+
}
50+
51+
function run(): void {
52+
const port = process.env['PORT'] || 4000;
53+
54+
// Start up the Node server
55+
const server = devApp();
56+
57+
server.listen(port, () => {
58+
console.log(`Node Express server listening on http://localhost:${port}`);
59+
});
60+
}
61+
62+
run();
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"extends": "../tsconfig.json",
3+
"include": ["**/*.d.ts"],
4+
"files": [
5+
"main.ts",
6+
"main.server.ts",
7+
"server.ts",
8+
"../breadcrumbs/main.ts",
9+
"../breadcrumbs/module.ts"
10+
],
11+
"compilerOptions": {
12+
"types": [
13+
"node"
14+
]
15+
}
16+
}

tools/cspell-locales/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"timepicker",
3232
"unclickable",
3333
"overlayscrollbars",
34-
"Topbar"
34+
"Topbar",
35+
"prerender"
3536
]
3637
}

0 commit comments

Comments
 (0)