Skip to content

Commit

Permalink
feat(demo): add SSR
Browse files Browse the repository at this point in the history
Only for testing purposes, otherwise the site stays static.
  • Loading branch information
daelmaak committed Nov 11, 2024
1 parent db260da commit 47e3e19
Show file tree
Hide file tree
Showing 11 changed files with 577 additions and 60 deletions.
64 changes: 63 additions & 1 deletion apps/demo/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"build": {
"executor": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/apps/demo",
"outputPath": "dist/apps/demo/browser",
"index": "apps/demo/src/index.html",
"main": "apps/demo/src/main.ts",
"polyfills": "apps/demo/src/polyfills.ts",
Expand Down Expand Up @@ -100,6 +100,68 @@
},
"lint": {
"executor": "@nx/eslint:lint"
},
"server": {
"dependsOn": ["build"],
"executor": "@angular-devkit/build-angular:server",
"options": {
"outputPath": "dist/apps/demo/server",
"main": "apps/demo/server.ts",
"tsConfig": "apps/demo/tsconfig.server.json",
"buildOptimizer": false,
"optimization": false,
"sourceMap": true,
"extractLicenses": false,
"vendorChunk": true
},
"configurations": {
"production": {
"buildOptimizer": true,
"outputHashing": "media",
"fileReplacements": [
{
"replace": "apps/demo/src/environments/environment.ts",
"with": "apps/demo/src/environments/environment.prod.ts"
}
],
"optimization": true,
"sourceMap": true,
"extractLicenses": true,
"vendorChunk": false
}
},
"defaultConfiguration": "production"
},
"serve-ssr": {
"executor": "@angular-devkit/build-angular:ssr-dev-server",
"configurations": {
"development": {
"browserTarget": "demo:build:development",
"serverTarget": "demo:server:development"
},
"production": {
"browserTarget": "demo:build:production",
"serverTarget": "demo:server:production"
}
},
"defaultConfiguration": "development"
},
"prerender": {
"executor": "@angular-devkit/build-angular:prerender",
"options": {
"routes": ["/"]
},
"configurations": {
"development": {
"browserTarget": "demo:build:development",
"serverTarget": "demo:server:development"
},
"production": {
"browserTarget": "demo:build:production",
"serverTarget": "demo:server:production"
}
},
"defaultConfiguration": "production"
}
}
}
72 changes: 72 additions & 0 deletions apps/demo/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'zone.js/node';

import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
import * as express from 'express';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import AppServerModule from './src/main.server';

// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
const server = express();
const distFolder = join(process.cwd(), 'dist/apps/demo/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', distFolder);

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

// All regular routes use the Angular engine
server.get('*', (req, res, next) => {
const { protocol, originalUrl, baseUrl, headers } = req;

commonEngine
.render({
bootstrap: AppServerModule,
documentFilePath: indexHtml,
url: `${protocol}://${headers.host}${originalUrl}`,
publicPath: distFolder,
providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
})
.then(html => res.send(html))
.catch(err => next(err));
});

return server;
}

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}`);
});
}

// 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.
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 AppServerModule;
36 changes: 19 additions & 17 deletions apps/demo/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {
afterNextRender,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
OnInit,
} from '@angular/core';
import { GalleryItem } from '@daelmaak/ngx-gallery';

Expand All @@ -12,7 +12,7 @@ import { GalleryItem } from '@daelmaak/ngx-gallery';
styleUrls: ['./app.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent implements OnInit {
export class AppComponent {
installScript = `
yarn add @daelmaak/ngx-gallery
`;
Expand Down Expand Up @@ -133,23 +133,25 @@ export class AppComponent implements OnInit {
mobile: boolean;
tablet: boolean;

constructor(private cd: ChangeDetectorRef) {}
constructor(private cd: ChangeDetectorRef) {
afterNextRender({
read: () => {
const mobileMatcher = matchMedia('(max-width: 768px)');
const tabletMatcher = matchMedia('(max-width: 1024px)');

ngOnInit(): void {
const mobileMatcher = matchMedia('(max-width: 768px)');
const tabletMatcher = matchMedia('(max-width: 1024px)');
mobileMatcher.onchange = e => {
this.mobile = e.matches;
this.cd.detectChanges();
};

mobileMatcher.onchange = e => {
this.mobile = e.matches;
this.cd.detectChanges();
};
tabletMatcher.onchange = e => {
this.tablet = e.matches;
this.cd.detectChanges();
};

tabletMatcher.onchange = e => {
this.tablet = e.matches;
this.cd.detectChanges();
};

this.mobile = mobileMatcher.matches;
this.tablet = tabletMatcher.matches;
this.mobile = mobileMatcher.matches;
this.tablet = tabletMatcher.matches;
},
});
}
}
6 changes: 5 additions & 1 deletion apps/demo/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { BrowserModule } from '@angular/platform-browser';
import {
BrowserModule,
provideClientHydration,
} from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
Expand Down Expand Up @@ -58,6 +61,7 @@ const materialModules = [
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
useValue: { appearance: 'outline' },
},
provideClientHydration(),
],
bootstrap: [AppComponent],
})
Expand Down
11 changes: 11 additions & 0 deletions apps/demo/src/app/app.server.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';

import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
imports: [AppModule, ServerModule],
bootstrap: [AppComponent],
})
export class AppServerModule {}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
afterNextRender,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Expand All @@ -16,7 +17,6 @@ import {
Orientation,
VerticalOrientation,
} from '@daelmaak/ngx-gallery';
import { GalleryItemInternal } from '@daelmaak/ngx-gallery/lib/core/gallery-item';

interface GalleryConfig {
arrows: boolean;
Expand Down Expand Up @@ -52,7 +52,7 @@ export class DemoWholeConfigComponent implements OnInit {
displayGallery = true;
imageLoadingLatency = 0;

mobile = matchMedia('(max-width: 767px)').matches;
mobile?: boolean;

galleryConfig: GalleryConfig = {
arrows: !this.mobile,
Expand All @@ -78,7 +78,13 @@ export class DemoWholeConfigComponent implements OnInit {
@ViewChild(GalleryComponent) gallery: GalleryComponent;

constructor(private cd: ChangeDetectorRef) {
this.galleryConfig = this.getGalleryConfig() || this.galleryConfig;
afterNextRender({
read: () => {
this.galleryConfig = this.getGalleryConfig() || this.galleryConfig;
this.mobile = matchMedia('(max-width: 767px)').matches;
window.addEventListener('pagehide', this.storeGalleryConfig);
},
});
}

ngOnInit() {
Expand All @@ -87,8 +93,6 @@ export class DemoWholeConfigComponent implements OnInit {
defer(() => of(items).pipe(delay(this.imageLoadingLatency)))
)
);

window.addEventListener('pagehide', this.storeGalleryConfig);
}

onConfigChange(prop: keyof GalleryConfig, value: unknown) {
Expand Down
1 change: 1 addition & 0 deletions apps/demo/src/main.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { AppServerModule as default } from './app/app.server.module';
10 changes: 10 additions & 0 deletions apps/demo/tsconfig.server.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.app.json",
"compilerOptions": {
"outDir": "../../out-tsc/server",
"target": "es2019",
"types": ["node"]
},
"files": ["src/main.server.ts", "server.ts"]
}
3 changes: 3 additions & 0 deletions nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
"@nx/eslint:lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
"cache": true
},
"server": {
"cache": true
}
},
"nxCloudAccessToken": "Zjg2NjM5OWYtOWNjYi00ZGJhLWFmNDUtYWIwZTdjYjE0NDZmfHJlYWQtd3JpdGU=",
Expand Down
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
"@angular/material": "18.2.9",
"@angular/platform-browser": "18.2.9",
"@angular/platform-browser-dynamic": "18.2.9",
"@angular/platform-server": "~18.2.0",
"@angular/router": "18.2.9",
"@angular/ssr": "18.2.9",
"express": "~4.18.2",
"rxjs": "~6.6.7",
"zone.js": "0.14.10"
},
Expand All @@ -57,6 +61,7 @@
"@nx/workspace": "20.0.12",
"@schematics/angular": "18.2.9",
"@types/estree": "^1.0.1",
"@types/express": "4.17.14",
"@types/jasmine": "~3.10.2",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^18.16.9",
Expand All @@ -65,6 +70,7 @@
"@typescript-eslint/parser": "7.18.0",
"@typescript-eslint/utils": "^7.16.0",
"autoprefixer": "^10.4.0",
"browser-sync": "^3.0.0",
"cypress": "13.15.2",
"eslint": "9.14.0",
"eslint-config-prettier": "9.1.0",
Expand Down
Loading

0 comments on commit 47e3e19

Please sign in to comment.