From 78cf3ada16bbc5b9affdeb3da049fa4baf02a1da Mon Sep 17 00:00:00 2001 From: erdemcaygor Date: Fri, 9 May 2025 16:05:48 +0300 Subject: [PATCH] init ssr --- npm/ng-packs/apps/dev-app/project.json | 83 +++++++++++++++++-- .../apps/dev-app/src/app/app.config.server.ts | 11 +++ npm/ng-packs/apps/dev-app/src/main.server.ts | 7 ++ npm/ng-packs/apps/dev-app/src/main.ts | 4 +- npm/ng-packs/apps/dev-app/src/server.ts | 69 +++++++++++++++ .../apps/dev-app/tsconfig.server.json | 16 ++++ npm/ng-packs/migrations.json | 79 ++++++++++++++++-- npm/ng-packs/package.json | 80 ++++++++++-------- 8 files changed, 297 insertions(+), 52 deletions(-) create mode 100644 npm/ng-packs/apps/dev-app/src/app/app.config.server.ts create mode 100644 npm/ng-packs/apps/dev-app/src/main.server.ts create mode 100644 npm/ng-packs/apps/dev-app/src/server.ts create mode 100644 npm/ng-packs/apps/dev-app/tsconfig.server.json diff --git a/npm/ng-packs/apps/dev-app/project.json b/npm/ng-packs/apps/dev-app/project.json index 1213acbb2f..f29030a862 100644 --- a/npm/ng-packs/apps/dev-app/project.json +++ b/npm/ng-packs/apps/dev-app/project.json @@ -7,16 +7,24 @@ "targets": { "build": { "executor": "@angular-devkit/build-angular:browser", - "outputs": ["{options.outputPath}"], + "outputs": [ + "{options.outputPath}" + ], "options": { - "outputPath": "dist/apps/dev-app", + "outputPath": "dist/dev-app/browser", "index": "apps/dev-app/src/index.html", "main": "apps/dev-app/src/main.ts", "polyfills": "apps/dev-app/src/polyfills.ts", "tsConfig": "apps/dev-app/tsconfig.app.json", "inlineStyleLanguage": "scss", - "allowedCommonJsDependencies": ["chart.js", "js-sha256"], - "assets": ["apps/dev-app/src/favicon.ico", "apps/dev-app/src/assets"], + "allowedCommonJsDependencies": [ + "chart.js", + "js-sha256" + ], + "assets": [ + "apps/dev-app/src/favicon.ico", + "apps/dev-app/src/assets" + ], "styles": [ { "input": "node_modules/bootstrap/dist/css/bootstrap.rtl.min.css", @@ -174,10 +182,73 @@ }, "test": { "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/apps/dev-app"], + "outputs": [ + "{workspaceRoot}/coverage/apps/dev-app" + ], "options": { "jestConfig": "apps/dev-app/jest.config.ts" } + }, + "server": { + "executor": "@angular-devkit/build-angular:server", + "options": { + "outputPath": "dist/dev-app/server", + "main": "apps/dev-app/src/server.ts", + "tsConfig": "apps/dev-app/tsconfig.server.json", + "inlineStyleLanguage": "scss" + }, + "configurations": { + "production": { + "outputHashing": "media", + "fileReplacements": [ + { + "replace": "apps/dev-app/src/environments/environment.ts", + "with": "apps/dev-app/src/environments/environment.prod.ts" + } + ] + }, + "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": "dev-app:build:development", + "serverTarget": "dev-app:server:development" + }, + "production": { + "browserTarget": "dev-app:build:production", + "serverTarget": "dev-app:server:production" + } + }, + "defaultConfiguration": "development" + }, + "prerender": { + "executor": "@angular-devkit/build-angular:prerender", + "options": { + "routes": [ + "/" + ] + }, + "configurations": { + "production": { + "browserTarget": "dev-app:build:production", + "serverTarget": "dev-app:server:production" + }, + "development": { + "browserTarget": "dev-app:build:development", + "serverTarget": "dev-app:server:development" + } + }, + "defaultConfiguration": "production" } } -} +} \ No newline at end of file diff --git a/npm/ng-packs/apps/dev-app/src/app/app.config.server.ts b/npm/ng-packs/apps/dev-app/src/app/app.config.server.ts new file mode 100644 index 0000000000..b4d57c9423 --- /dev/null +++ b/npm/ng-packs/apps/dev-app/src/app/app.config.server.ts @@ -0,0 +1,11 @@ +import { mergeApplicationConfig, ApplicationConfig } from '@angular/core'; +import { provideServerRendering } from '@angular/platform-server'; +import { appConfig } from './app.config'; + +const serverConfig: ApplicationConfig = { + providers: [ + provideServerRendering() + ] +}; + +export const config = mergeApplicationConfig(appConfig, serverConfig); diff --git a/npm/ng-packs/apps/dev-app/src/main.server.ts b/npm/ng-packs/apps/dev-app/src/main.server.ts new file mode 100644 index 0000000000..4b9d4d1545 --- /dev/null +++ b/npm/ng-packs/apps/dev-app/src/main.server.ts @@ -0,0 +1,7 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { AppComponent } from './app/app.component'; +import { config } from './app/app.config.server'; + +const bootstrap = () => bootstrapApplication(AppComponent, config); + +export default bootstrap; diff --git a/npm/ng-packs/apps/dev-app/src/main.ts b/npm/ng-packs/apps/dev-app/src/main.ts index 670b03a58e..7463a75903 100644 --- a/npm/ng-packs/apps/dev-app/src/main.ts +++ b/npm/ng-packs/apps/dev-app/src/main.ts @@ -1,6 +1,6 @@ import { enableProdMode } from '@angular/core'; import { provideRouter } from '@angular/router'; -import { bootstrapApplication } from '@angular/platform-browser'; +import { bootstrapApplication, provideClientHydration, withEventReplay } from '@angular/platform-browser'; import { environment } from './environments/environment'; import { APP_ROUTE_PROVIDER } from './app/route.provider'; @@ -38,6 +38,6 @@ bootstrapApplication(AppComponent, { provideAccountConfig(), provideIdentityConfig(), provideTenantManagementConfig(), - provideFeatureManagementConfig(), + provideFeatureManagementConfig(), provideClientHydration(withEventReplay()), ], }).catch(err => console.error(err)); diff --git a/npm/ng-packs/apps/dev-app/src/server.ts b/npm/ng-packs/apps/dev-app/src/server.ts new file mode 100644 index 0000000000..1302ee41a3 --- /dev/null +++ b/npm/ng-packs/apps/dev-app/src/server.ts @@ -0,0 +1,69 @@ +import 'zone.js/node'; + +import { APP_BASE_HREF } from '@angular/common'; +import { CommonEngine } from '@angular/ssr/node'; +import express from 'express'; +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; +import bootstrap from './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/dev-app/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, + 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 bootstrap; diff --git a/npm/ng-packs/apps/dev-app/tsconfig.server.json b/npm/ng-packs/apps/dev-app/tsconfig.server.json new file mode 100644 index 0000000000..eee4cb963a --- /dev/null +++ b/npm/ng-packs/apps/dev-app/tsconfig.server.json @@ -0,0 +1,16 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.app.json", + "compilerOptions": { + "outDir": "../../out-tsc/server", + "types": [ + "node", + "@angular/localize" + ] + }, + "files": [ + "src/main.server.ts", + "src/server.ts" + ] +} diff --git a/npm/ng-packs/migrations.json b/npm/ng-packs/migrations.json index e89bc86d78..750737c559 100644 --- a/npm/ng-packs/migrations.json +++ b/npm/ng-packs/migrations.json @@ -1,19 +1,80 @@ { "migrations": [ { - "version": "20.3.0-beta.1", - "description": "Update ESLint flat config to include .cjs, .mjs, .cts, and .mts files in overrides (if needed)", - "implementation": "./src/migrations/update-20-3-0/add-file-extensions-to-overrides", - "package": "@nx/eslint", - "name": "add-file-extensions-to-overrides" + "version": "21.0.0-beta.8", + "description": "Removes the legacy cache configuration from nx.json", + "implementation": "./src/migrations/update-21-0-0/remove-legacy-cache", + "package": "nx", + "name": "remove-legacy-cache" + }, + { + "version": "21.0.0-beta.8", + "description": "Removes the legacy cache configuration from nx.json", + "implementation": "./src/migrations/update-21-0-0/remove-custom-tasks-runner", + "package": "nx", + "name": "remove-custom-tasks-runner" + }, + { + "version": "21.0.0-beta.11", + "description": "Updates release version config based on the breaking changes in Nx v21", + "implementation": "./src/migrations/update-21-0-0/release-version-config-changes", + "package": "nx", + "name": "release-version-config-changes" + }, + { + "version": "21.0.0-beta.11", + "description": "Updates release changelog config based on the breaking changes in Nx v21", + "implementation": "./src/migrations/update-21-0-0/release-changelog-config-changes", + "package": "nx", + "name": "release-changelog-config-changes" + }, + { + "version": "21.0.0-beta.10", + "description": "Removes the `tsConfig` and `copyFiles` options from the `@nx/cypress:cypress` executor.", + "implementation": "./src/migrations/update-21-0-0/remove-tsconfig-and-copy-files-options-from-cypress-executor", + "package": "@nx/cypress", + "name": "remove-tsconfig-and-copy-files-options-from-cypress-executor" + }, + { + "cli": "nx", + "version": "21.0.0-beta.9", + "description": "Replace usage of `getJestProjects` with `getJestProjectsAsync`.", + "implementation": "./src/migrations/update-21-0-0/replace-getJestProjects-with-getJestProjectsAsync", + "package": "@nx/jest", + "name": "replace-getJestProjects-with-getJestProjectsAsync-v21" + }, + { + "version": "21.0.0-beta.10", + "description": "Remove the previously deprecated and unused `tsConfig` option from the `@nx/jest:jest` executor.", + "implementation": "./src/migrations/update-21-0-0/remove-tsconfig-option-from-jest-executor", + "package": "@nx/jest", + "name": "remove-tsconfig-option-from-jest-executor" + }, + { + "cli": "nx", + "version": "20.4.0-beta.1", + "requires": { "@angular/core": ">=19.1.0" }, + "description": "Update the @angular/cli package version to ~19.1.0.", + "factory": "./src/migrations/update-20-4-0/update-angular-cli", + "package": "@nx/angular", + "name": "update-angular-cli-version-19-1-0" + }, + { + "cli": "nx", + "version": "20.5.0-beta.5", + "requires": { "@angular/core": ">=19.2.0" }, + "description": "Update the @angular/cli package version to ~19.2.0.", + "factory": "./src/migrations/update-20-5-0/update-angular-cli", + "package": "@nx/angular", + "name": "update-angular-cli-version-19-2-0" }, { "cli": "nx", - "version": "20.3.0-beta.2", - "description": "If workspace includes Module Federation projects, ensure the new @nx/module-federation package is installed.", - "factory": "./src/migrations/update-20-3-0/ensure-nx-module-federation-package", + "version": "21.0.0-beta.3", + "description": "Set the `continuous` option to `true` for continuous tasks.", + "factory": "./src/migrations/update-21-0-0/set-continuous-option", "package": "@nx/angular", - "name": "ensure-nx-module-federation-package" + "name": "set-continuous-option" } ] } diff --git a/npm/ng-packs/package.json b/npm/ng-packs/package.json index e17d6c6f18..735bd5f9ef 100644 --- a/npm/ng-packs/package.json +++ b/npm/ng-packs/package.json @@ -40,53 +40,61 @@ "lerna": "lerna", "migrate-nx": "yarn nx migrate --run-migrations", "copy-to:app": "cd scripts && yarn && yarn copy-to-templates -t app", - "update-version": "nx generate @abp/nx.generators:update-version" + "update-version": "nx generate @abp/nx.generators:update-version", + "dev:ssr": "ng run dev-app:serve-ssr", + "serve:ssr": "node dist/dev-app/server/main.js", + "build:ssr": "ng build && ng run dev-app:server", + "prerender": "ng run dev-app:prerender" }, "private": true, "devDependencies": { "@abp/ng.theme.lepton-x": "~4.2.0-rc.2", "@abp/utils": "~9.2.0-rc.2", - "@angular-devkit/build-angular": "~19.1.0", - "@angular-devkit/core": "~19.1.0", - "@angular-devkit/schematics": "~19.1.0", + "@angular-devkit/build-angular": "19.2.9", + "@angular-devkit/core": "19.2.9", + "@angular-devkit/schematics": "19.2.9", "@angular-devkit/schematics-cli": "~19.1.0", - "@angular-eslint/eslint-plugin": "~19.0.0", - "@angular-eslint/eslint-plugin-template": "~19.0.0", - "@angular-eslint/template-parser": "~19.0.0", - "@angular/animations": "~19.1.0", + "@angular-eslint/eslint-plugin": "19.2.0", + "@angular-eslint/eslint-plugin-template": "19.2.0", + "@angular-eslint/template-parser": "19.2.0", + "@angular/animations": "19.2.10", "@angular/cli": "~19.1.0", - "@angular/common": "~19.1.0", - "@angular/compiler": "~19.1.0", - "@angular/compiler-cli": "~19.1.0", - "@angular/core": "~19.1.0", - "@angular/forms": "~19.1.0", - "@angular/language-service": "~19.1.0", - "@angular/localize": "~19.1.0", - "@angular/platform-browser": "~19.1.0", - "@angular/platform-browser-dynamic": "~19.1.0", - "@angular/router": "~19.1.0", + "@angular/common": "19.2.10", + "@angular/compiler": "19.2.10", + "@angular/compiler-cli": "19.2.10", + "@angular/core": "19.2.10", + "@angular/forms": "19.2.10", + "@angular/language-service": "19.2.10", + "@angular/localize": "19.2.10", + "@angular/platform-browser": "19.2.10", + "@angular/platform-browser-dynamic": "19.2.10", + "@angular/router": "19.2.10", + "@angular/ssr": "^19.2.11", + "@angular/platform-server": "19.2.10", + "express": "^4.18.2", "@fortawesome/fontawesome-free": "^6.0.0", "@ng-bootstrap/ng-bootstrap": "~18.0.0", "@ngneat/spectator": "~19.1.0", "@ngx-validate/core": "^0.2.0", - "@nx/angular": "~20.3.0", - "@nx/cypress": "~20.3.0", - "@nx/devkit": "~20.3.0", - "@nx/eslint": "~20.3.0", - "@nx/eslint-plugin": "~20.3.0", - "@nx/jest": "~20.3.0", - "@nx/js": "~20.3.0", - "@nx/plugin": "~20.3.0", - "@nx/workspace": "~20.3.0", + "@nx/angular": "21.0.3", + "@nx/cypress": "21.0.3", + "@nx/devkit": "21.0.3", + "@nx/eslint": "21.0.3", + "@nx/eslint-plugin": "21.0.3", + "@nx/jest": "21.0.3", + "@nx/js": "21.0.3", + "@nx/plugin": "21.0.3", + "@nx/workspace": "21.0.3", "@popperjs/core": "~2.11.0", - "@schematics/angular": "~19.0.0", + "@schematics/angular": "19.2.9", "@swc-node/register": "1.9.2", - "@swc/cli": "0.3.12", + "@swc/cli": "0.6.0", "@swc/core": "~1.5.0", "@swc/helpers": "~0.5.0", "@swimlane/ngx-datatable": "^20.0.0", + "@types/express": "^4.17.17", "@types/jest": "29.5.14", - "@types/node": "^20.0.0", + "@types/node": "^18.18.0", "@typescript-eslint/eslint-plugin": "7.16.0", "@typescript-eslint/parser": "7.16.0", "@typescript-eslint/utils": "^7.16.0", @@ -94,11 +102,12 @@ "autoprefixer": "^10.0.0", "bootstrap": "^5.0.0", "bootstrap-icons": "^1.0.0", + "browser-sync": "^3.0.0", "chart.js": "^4.0.0", "cypress": "^7.0.0", "dotenv": "10.0.0", "eslint": "^8.0.0", - "eslint-config-prettier": "9.0.0", + "eslint-config-prettier": "10.0.0", "eslint-plugin-cypress": "^2.10.3", "got": "^11.0.0", "jest": "^29.0.0", @@ -111,9 +120,9 @@ "just-compare": "^2.0.0", "lerna": "^4.0.0", "lint-staged": "^13.0.0", - "ng-packagr": "~19.1.0", + "ng-packagr": "19.2.2", "ng-zorro-antd": "~19.0.0", - "nx": "~20.3.0", + "nx": "21.0.3", "postcss": "^8.0.0", "postcss-import": "14.1.0", "postcss-preset-env": "7.5.0", @@ -127,7 +136,7 @@ "ts-toolbelt": "^9.0.0", "tslib": "^2.0.0", "tslint": "~6.1.0", - "typescript": "~5.6.0", + "typescript": "5.7.3", "zone.js": "~0.15.0" }, "lint-staged": { @@ -135,5 +144,6 @@ "npx prettier --write --config .prettierrc " ] }, - "dependencies": {} + "dependencies": { + } }