From 32d1a410ba8d1df9acd032e5f105dab59493edd0 Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 28 Jul 2023 02:33:40 +0200 Subject: [PATCH 01/13] refactor: RFC onStable replacement --- projects/movies/src/app/app.config.ts | 15 ++++--- .../movie-list-page.component.html | 4 +- .../application-renderd-token.ts | 3 ++ .../element-timing/wait-for-element-timing.ts | 33 +++++++++++++++ .../{custom-zone.ts => provide-ngZone.ts} | 42 +++++++++++++++---- .../movie-list/movie-list.component.ts | 1 + projects/movies/src/index.ts | 5 +++ projects/movies/src/main.ts | 2 +- .../src/app/app.config.ts | 9 ++-- .../src/app/provide-ngZone.ts | 39 +++++++++++++++++ projects/ng-universal-express/src/index.ts | 34 ++++----------- 11 files changed, 140 insertions(+), 47 deletions(-) create mode 100644 projects/movies/src/app/shared/cdk/application-rendered/application-renderd-token.ts create mode 100644 projects/movies/src/app/shared/cdk/element-timing/wait-for-element-timing.ts rename projects/movies/src/app/shared/zone-less/{custom-zone.ts => provide-ngZone.ts} (51%) create mode 100644 projects/ng-universal-express/src/app/provide-ngZone.ts diff --git a/projects/movies/src/app/app.config.ts b/projects/movies/src/app/app.config.ts index 7edc6373e..6b798475f 100644 --- a/projects/movies/src/app/app.config.ts +++ b/projects/movies/src/app/app.config.ts @@ -1,4 +1,4 @@ -import {ApplicationConfig, NgZone} from '@angular/core'; +import {ApplicationConfig} from '@angular/core'; import {mergeBaseConfig} from './app.base.config'; import {provideFastSVG} from '@push-based/ngx-fast-svg'; import {provideHttpClient, withInterceptors} from '@angular/common/http'; @@ -6,15 +6,17 @@ import {provideClientHydration} from '@angular/platform-browser'; import {RX_RENDER_STRATEGIES_CONFIG} from '@rx-angular/cdk/render-strategies'; import {tmdbContentTypeInterceptor} from './data-access/api/tmdbContentTypeInterceptor'; import {tmdbReadAccessInterceptor} from './auth/tmdb-http-interceptor.feature'; -import {CustomNgZone} from './shared/zone-less/custom-zone'; import {provideServiceWorker} from '@angular/service-worker'; import {environment} from '../environments/environment'; +import {ApplicationRendered} from "./shared/cdk/application-rendered/applicationRenderdToken"; +import {waitForElementTiming} from "./shared/cdk/element-timing/wait-for-element-timing"; +import {provideNgZoneZoneless} from "./shared/zone-less/provideNgZone"; const browserConfig: ApplicationConfig = { providers: [ provideClientHydration(), provideHttpClient( - withInterceptors([tmdbContentTypeInterceptor, tmdbReadAccessInterceptor]) + withInterceptors([tmdbContentTypeInterceptor, tmdbReadAccessInterceptor]) ), provideFastSVG({ url: (name: string) => `assets/svg-icons/${name}.svg`, @@ -29,9 +31,10 @@ const browserConfig: ApplicationConfig = { useValue: {patchZone: false}, }, { - provide: NgZone, - useClass: CustomNgZone, + provide: ApplicationRendered, + useFactory: () => () => waitForElementTiming(['header-main', 'tile-img']) }, + provideNgZoneZoneless(), provideServiceWorker('ngsw-worker.js', { enabled: environment.production, // Register the ServiceWorker as soon as the app is stable @@ -41,4 +44,4 @@ const browserConfig: ApplicationConfig = { ], }; // We provide the config function as closure to be able to inject configuration from the consuming end -export const appConfig = () => mergeBaseConfig(browserConfig); +export const appConfig = mergeBaseConfig(browserConfig); diff --git a/projects/movies/src/app/pages/movie-list-page/movie-list-page.component.html b/projects/movies/src/app/pages/movie-list-page/movie-list-page.component.html index 963e03897..508400513 100644 --- a/projects/movies/src/app/pages/movie-list-page/movie-list-page.component.html +++ b/projects/movies/src/app/pages/movie-list-page/movie-list-page.component.html @@ -1,7 +1,7 @@
-

{{ headings.main }}

-

{{ headings.sub }}

+

{{ headings.main }}

+

{{ headings.sub }}

diff --git a/projects/movies/src/app/shared/cdk/application-rendered/application-renderd-token.ts b/projects/movies/src/app/shared/cdk/application-rendered/application-renderd-token.ts new file mode 100644 index 000000000..1739a9ba7 --- /dev/null +++ b/projects/movies/src/app/shared/cdk/application-rendered/application-renderd-token.ts @@ -0,0 +1,3 @@ +import {InjectionToken} from '@angular/core'; + +export const ApplicationRendered = new InjectionToken<() => Promise>(''); diff --git a/projects/movies/src/app/shared/cdk/element-timing/wait-for-element-timing.ts b/projects/movies/src/app/shared/cdk/element-timing/wait-for-element-timing.ts new file mode 100644 index 000000000..d3622e9a0 --- /dev/null +++ b/projects/movies/src/app/shared/cdk/element-timing/wait-for-element-timing.ts @@ -0,0 +1,33 @@ +declare const ngDevMode: boolean; + +export function waitForElementTiming( + identifiers: string[] +): Promise { + return new Promise((resolve, reject) => { + try { + const performanceObserver = new PerformanceObserver((l) => { + if (identifiers.length > 0 && l.getEntries().length) { + const observedIdentifiers = l.getEntries() + .map(entry => (entry as any).identifier as string) + .filter(i => identifiers.includes(i)); + if (ngDevMode && observedIdentifiers.length) + console.log("Observed Elements: ", observedIdentifiers); + // remove found identifiers + identifiers = identifiers.filter(i => !observedIdentifiers.includes(i)); + // If all identifiers are found + if (identifiers.length === 0) { + if (ngDevMode) + console.log("All elements observed"); + performanceObserver.disconnect(); + resolve(); + } + } + }); + // performanceObserver.observe({type: 'largest-contentful-paint', buffered: true}); + performanceObserver.observe({type: 'element', buffered: true}); + } catch (e) { + console.log('waitForElementTiming failed'); + reject(e); + } + }) +} diff --git a/projects/movies/src/app/shared/zone-less/custom-zone.ts b/projects/movies/src/app/shared/zone-less/provide-ngZone.ts similarity index 51% rename from projects/movies/src/app/shared/zone-less/custom-zone.ts rename to projects/movies/src/app/shared/zone-less/provide-ngZone.ts index 5c3e789d8..1a792ad4c 100644 --- a/projects/movies/src/app/shared/zone-less/custom-zone.ts +++ b/projects/movies/src/app/shared/zone-less/provide-ngZone.ts @@ -1,11 +1,30 @@ -import {EventEmitter} from '@angular/core'; -import {BehaviorSubject, timer} from 'rxjs'; +import {waitForElementTiming} from "../cdk/element-timing/wait-for-element-timing"; +import {EventEmitter, inject, Injectable, NgZone} from "@angular/core"; +import {ApplicationRendered} from "../cdk/application-rendered/applicationRenderdToken"; +import {BehaviorSubject} from "rxjs"; + +declare const ngDevMode: boolean; + +export function provideNgZoneZoneless() { + return [{ + provide: ApplicationRendered, + useFactory: () => () => waitForElementTiming(['header-main', 'tile-img']) + }, + { + provide: NgZone, + useClass: AppRenderedNgZoneZoneless, + }] +} /** * Provides a noop like implementation of `NgZone` which does nothing and provides a way to customize behavior. * This zone requires explicit calls to framework to perform rendering. */ -export class CustomNgZone { +@Injectable({ + providedIn: "root", +}) +export class AppRenderedNgZoneZoneless { + applicationRenderDone = inject(ApplicationRendered); hasPendingMicrotasks = true; hasPendingMacrotasks = true; @@ -21,16 +40,23 @@ export class CustomNgZone { onError = new EventEmitter(); constructor() { + if (ngDevMode) + console.time("onStableTrigger"); + /** * Notice: * This is a hack to delay the emission of isStable for a micro task * This helps HttpTransferCache to get its values first from the cache */ - timer(2000).subscribe(() => { - this.hasPendingMicrotasks = false; - this.hasPendingMacrotasks = false; - this.onStable.next(true); - }); + this.applicationRenderDone() + .then(() => { + if (ngDevMode) + console.timeEnd('onStableTrigger'); + + this.hasPendingMicrotasks = false; + this.hasPendingMacrotasks = false; + this.onStable.next(true); + }); } run(fn: () => unknown, applyThis: unknown, applyArgs: []) { diff --git a/projects/movies/src/app/ui/pattern/movie-list/movie-list.component.ts b/projects/movies/src/app/ui/pattern/movie-list/movie-list.component.ts index 9d858a907..729429a98 100644 --- a/projects/movies/src/app/ui/pattern/movie-list/movie-list.component.ts +++ b/projects/movies/src/app/ui/pattern/movie-list/movie-list.component.ts @@ -48,6 +48,7 @@ type UiActions = { paginate: boolean }; This avoids bootstrap and template evaluation time and reduces scripting time in general. --> bootstrapApplication(AppStandaloneComponent, mergeBaseConfig(config)); + export default bootstrap; +export {environment} from "./environments/environment"; +export {tmdbContentTypeInterceptor} from "./app/data-access/api/tmdbContentTypeInterceptor"; +export {tmdbReadAccessInterceptor} from "./app/auth/tmdb-http-interceptor.feature"; +export {ApplicationRendered} from "./app/shared/cdk/application-rendered/application-renderd-token"; diff --git a/projects/movies/src/main.ts b/projects/movies/src/main.ts index 7b7f3bb43..3d068e536 100644 --- a/projects/movies/src/main.ts +++ b/projects/movies/src/main.ts @@ -10,7 +10,7 @@ import {RxLet} from '@rx-angular/template/let'; @NgModule({ declarations: [AppComponent], imports: [BrowserModule, RouterOutlet, AppShellComponent, RxLet], - providers: [...appConfig().providers], + providers: [...appConfig.providers], bootstrap: [AppComponent], }) export class AppModule { diff --git a/projects/ng-universal-express/src/app/app.config.ts b/projects/ng-universal-express/src/app/app.config.ts index 7f04c2e74..295b3c8b1 100644 --- a/projects/ng-universal-express/src/app/app.config.ts +++ b/projects/ng-universal-express/src/app/app.config.ts @@ -3,10 +3,9 @@ import {provideServerRendering} from '@angular/platform-server'; import {provideFastSVG} from '@push-based/ngx-fast-svg'; import {RX_RENDER_STRATEGIES_CONFIG} from '@rx-angular/cdk/render-strategies'; import {provideHttpClient, withInterceptors} from '@angular/common/http'; -import {provideISR} from 'ngx-isr'; import {IconLoadStrategySsr} from './icon-load.ssr.strategy'; -import {tmdbContentTypeInterceptor} from '../../../movies/src/app/data-access/api/tmdbContentTypeInterceptor'; -import {tmdbReadAccessInterceptor} from '../../../movies/src/app/auth/tmdb-http-interceptor.feature'; +import {tmdbContentTypeInterceptor, tmdbReadAccessInterceptor} from "angular-movies"; +import {provideNgZone} from "./provide-ngZone"; const serverConfig: ApplicationConfig = { providers: [ @@ -14,16 +13,16 @@ const serverConfig: ApplicationConfig = { provideHttpClient( withInterceptors([tmdbContentTypeInterceptor, tmdbReadAccessInterceptor]) ), - provideISR(), provideFastSVG({ url: (name: string) => `dist/projects/movies/browser/assets/svg-icons/${name}.svg`, svgLoadStrategy: IconLoadStrategySsr, }), + provideNgZone(), { provide: RX_RENDER_STRATEGIES_CONFIG, useValue: {primaryStrategy: 'native'}, - }, + } ], }; diff --git a/projects/ng-universal-express/src/app/provide-ngZone.ts b/projects/ng-universal-express/src/app/provide-ngZone.ts new file mode 100644 index 000000000..73f2ef61a --- /dev/null +++ b/projects/ng-universal-express/src/app/provide-ngZone.ts @@ -0,0 +1,39 @@ +import {EventEmitter, inject, Injectable, NgZone} from "@angular/core"; +import {ApplicationRendered} from "../../../movies/src/app/shared/cdk/application-rendered/application-renderd-token"; + +declare const ngDevelopmentMode: boolean; + +export function provideNgZone() { + return [{ + provide: ApplicationRendered, + useFactory: () => () => Promise.resolve() + }, + { + provide: NgZone, + useClass: AppRenderedNgZone, + }] +} + +/** + * Provides a noop like implementation of `NgZone` which does nothing and provides a way to customize behavior. + * This zone requires explicit calls to framework to perform rendering. + */ +@Injectable({ + providedIn: "root", +}) +export class AppRenderedNgZone extends NgZone { + applicationRenderDone = inject(ApplicationRendered); + // eslint-disable-next-line unicorn/prefer-event-target + onStable = new EventEmitter(); + + constructor() { + super({}); + + this.applicationRenderDone() + .then(() => { + if (ngDevelopmentMode) + console.timeEnd('onStableTrigger'); + }) + } + +} diff --git a/projects/ng-universal-express/src/index.ts b/projects/ng-universal-express/src/index.ts index a0ae6b6c7..c84ed9d1e 100644 --- a/projects/ng-universal-express/src/index.ts +++ b/projects/ng-universal-express/src/index.ts @@ -4,11 +4,10 @@ import 'zone.js/dist/zone-node'; import express from 'express'; import {existsSync} from 'node:fs'; import {join} from 'node:path'; -import {ISRHandler} from 'ngx-isr'; -import {environment} from '../../movies/src/environments/environment'; import {ngExpressEngine} from '@nguniversal/express-engine'; import bootstrap from './app/bootstrap'; import {useCompression, useTiming} from './app/utils'; +import {APP_BASE_HREF} from "@angular/common"; // bootstrap needs to get exported for the pre-render task // The Express app is exported so that it can be used by serverless Functions. @@ -24,13 +23,6 @@ export function app(): express.Express { ? 'index.html' : 'index'; - // @ts-ignore - const isr = new ISRHandler({ - indexHtml, - invalidateSecretToken: 'MY_TOKEN', - enableLogging: !environment.production, - }); - // use gzip useCompression(server); // use server-timing @@ -57,28 +49,20 @@ export function app(): express.Express { server.get( '*', - /* + // Version with server timings for SSR - (req, res) => { + (request, response) => { // return rendered HTML including Angular generated DOM - console.log('SSR for route', req.url); - res.startTime('SSR', 'Total SSR Time'); - res.render( + console.log('SSR for route', request.url); + response.startTime('SSR', 'Total SSR Time'); + response.render( indexHtml, - {req, providers: [{provide: APP_BASE_HREF, useValue: req.baseUrl}],}, + {req: request, providers: [{provide: APP_BASE_HREF, useValue: request.baseUrl}]}, (_, html) => { - res.endTime('SSR') - res.send(html); + response.endTime('SSR') + response.send(html); } ); - },*/ - // Serve page if it exists in cache - async (request, response, next) => { - return await isr.serveFromCache(request, response, next); - }, - // Server side render the page and add to cache if needed - async (request, response, next) => { - return await isr.render(request, response, next); } ); From c378cb149a71ec46ceee2efb228183ce7784ddb0 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 31 Jul 2023 16:39:05 +0300 Subject: [PATCH 02/13] refactor: wip2 --- projects/movies/project.json | 2 +- projects/movies/src/app/app.base.config.ts | 38 ++-------- projects/movies/src/app/app.config.ts | 43 +++++++++-- .../application-renderd-token.ts | 3 - .../element-timing/wait-for-element-timing.ts | 13 +++- .../app/shared/zone-less/provide-ngZone.ts | 54 ++++++++------ .../state/state-app-initializer.provider.ts | 12 ++-- projects/movies/src/index.ts | 1 - projects/ng-universal-express/.eslintrc.json | 1 - projects/ng-universal-express/project.json | 16 ++--- .../src/app/app.config.ts | 2 - projects/ng-universal-express/src/app/app.ts | 65 +++++++++++++++++ .../ng-universal-express/src/app/bootstrap.ts | 1 - projects/ng-universal-express/src/index.ts | 72 +------------------ projects/ng-universal-express/src/main.ts | 2 +- 15 files changed, 168 insertions(+), 157 deletions(-) delete mode 100644 projects/movies/src/app/shared/cdk/application-rendered/application-renderd-token.ts create mode 100644 projects/ng-universal-express/src/app/app.ts diff --git a/projects/movies/project.json b/projects/movies/project.json index 1b26b7287..90bb03268 100644 --- a/projects/movies/project.json +++ b/projects/movies/project.json @@ -50,7 +50,7 @@ "buildOptimizer": false, "optimization": false, "serviceWorker": false, - "budgets": false + "budgets": [] }, "production": { "serviceWorker": true, diff --git a/projects/movies/src/app/app.base.config.ts b/projects/movies/src/app/app.base.config.ts index bb4c642ba..2a400805b 100644 --- a/projects/movies/src/app/app.base.config.ts +++ b/projects/movies/src/app/app.base.config.ts @@ -1,15 +1,15 @@ import {provideHttpClient, withInterceptors} from '@angular/common/http'; import {APP_INITIALIZER, ApplicationConfig, mergeApplicationConfig,} from '@angular/core'; import {provideClientHydration} from '@angular/platform-browser'; -import {provideRouter, withDisabledInitialNavigation, withInMemoryScrolling,} from '@angular/router'; -import {provideFastSVG} from '@push-based/ngx-fast-svg'; +import {provideRouter, withInMemoryScrolling,} from '@angular/router'; import {RxActionFactory} from '@rx-angular/state/actions'; import {ROUTES} from './routes'; -import {withGobalStateInitializer} from './state/state-app-initializer.provider'; -import {RX_RENDER_STRATEGIES_CONFIG} from '@rx-angular/cdk/render-strategies'; import {tmdbReadAccessInterceptor} from './auth/tmdb-http-interceptor.feature'; import {tmdbContentTypeInterceptor} from './data-access/api/tmdbContentTypeInterceptor'; import {provideTmdbImageLoader} from './data-access/images/image-loader'; +import {withGlobalStateInitializer} from "./state/state-app-initializer.provider"; + +APP_INITIALIZER; const appConfig: ApplicationConfig = { providers: [ @@ -26,7 +26,7 @@ const appConfig: ApplicationConfig = { * * Disable initial sync navigation in router config and schedule it in router-outlet container component */ - withDisabledInitialNavigation(), + // withDisabledInitialNavigation(), withInMemoryScrolling({ /** * **💡 UX Tip for InfiniteScroll:** @@ -39,9 +39,6 @@ const appConfig: ApplicationConfig = { scrollPositionRestoration: 'top', }) ), - provideFastSVG({ - url: (name: string) => `assets/svg-icons/${name}.svg`, - }), // global actions RxActionFactory, /** @@ -49,30 +46,7 @@ const appConfig: ApplicationConfig = { * * Fetch data visible in viewport on app bootstrap instead of component initialization. */ - withGobalStateInitializer(), - /** - * **🚀 Perf Tip for TBT:** - * - * Chunk app bootstrap over APP_INITIALIZER. - */ - { - provide: APP_INITIALIZER, - useFactory: () => (): Promise => - new Promise((resolve) => { - setTimeout(() => resolve()); - }), - deps: [], - multi: true, - }, - /** - * **🚀 Perf Tip for TBT, LCP, CLS:** - * - * Configure RxAngular to get maximum performance. - */ - { - provide: RX_RENDER_STRATEGIES_CONFIG, - useValue: {patchZone: false}, - }, + withGlobalStateInitializer() ], }; diff --git a/projects/movies/src/app/app.config.ts b/projects/movies/src/app/app.config.ts index 6b798475f..e24228cec 100644 --- a/projects/movies/src/app/app.config.ts +++ b/projects/movies/src/app/app.config.ts @@ -1,4 +1,8 @@ -import {ApplicationConfig} from '@angular/core'; +import { + APP_INITIALIZER, + ApplicationConfig, + ɵInitialRenderPendingTasks as InitialRenderPendingTasks +} from '@angular/core'; import {mergeBaseConfig} from './app.base.config'; import {provideFastSVG} from '@push-based/ngx-fast-svg'; import {provideHttpClient, withInterceptors} from '@angular/common/http'; @@ -8,15 +12,16 @@ import {tmdbContentTypeInterceptor} from './data-access/api/tmdbContentTypeInter import {tmdbReadAccessInterceptor} from './auth/tmdb-http-interceptor.feature'; import {provideServiceWorker} from '@angular/service-worker'; import {environment} from '../environments/environment'; -import {ApplicationRendered} from "./shared/cdk/application-rendered/applicationRenderdToken"; import {waitForElementTiming} from "./shared/cdk/element-timing/wait-for-element-timing"; -import {provideNgZoneZoneless} from "./shared/zone-less/provideNgZone"; +import {provideNgZoneZoneless} from "./shared/zone-less/provide-ngZone"; +InitialRenderPendingTasks; +APP_INITIALIZER; const browserConfig: ApplicationConfig = { providers: [ provideClientHydration(), provideHttpClient( - withInterceptors([tmdbContentTypeInterceptor, tmdbReadAccessInterceptor]) + withInterceptors([tmdbContentTypeInterceptor, tmdbReadAccessInterceptor]) ), provideFastSVG({ url: (name: string) => `assets/svg-icons/${name}.svg`, @@ -30,11 +35,35 @@ const browserConfig: ApplicationConfig = { provide: RX_RENDER_STRATEGIES_CONFIG, useValue: {patchZone: false}, }, - { - provide: ApplicationRendered, - useFactory: () => () => waitForElementTiming(['header-main', 'tile-img']) + /**/{ + provide: APP_INITIALIZER, + useFactory: (initialRenderPendingTasks: InitialRenderPendingTasks) => + // () => Observable | Promise | void + (): void => { + const taskId = initialRenderPendingTasks.add(); + waitForElementTiming(['header-main', 'tile-img']).finally(() => { + console.log(' initialRenderPendingTasks.remove(taskId)', taskId); + initialRenderPendingTasks.remove(taskId) + }); + }, + deps: [InitialRenderPendingTasks], + multi: true }, provideNgZoneZoneless(), + /** + * **🚀 Perf Tip for TBT:** + * + * Chunk app bootstrap over APP_INITIALIZER. + + { + provide: APP_INITIALIZER, + useFactory: () => (): Promise => + new Promise((resolve) => { + setTimeout(() => resolve()); + }), + deps: [], + multi: true, + },*/ provideServiceWorker('ngsw-worker.js', { enabled: environment.production, // Register the ServiceWorker as soon as the app is stable diff --git a/projects/movies/src/app/shared/cdk/application-rendered/application-renderd-token.ts b/projects/movies/src/app/shared/cdk/application-rendered/application-renderd-token.ts deleted file mode 100644 index 1739a9ba7..000000000 --- a/projects/movies/src/app/shared/cdk/application-rendered/application-renderd-token.ts +++ /dev/null @@ -1,3 +0,0 @@ -import {InjectionToken} from '@angular/core'; - -export const ApplicationRendered = new InjectionToken<() => Promise>(''); diff --git a/projects/movies/src/app/shared/cdk/element-timing/wait-for-element-timing.ts b/projects/movies/src/app/shared/cdk/element-timing/wait-for-element-timing.ts index d3622e9a0..e068ffec7 100644 --- a/projects/movies/src/app/shared/cdk/element-timing/wait-for-element-timing.ts +++ b/projects/movies/src/app/shared/cdk/element-timing/wait-for-element-timing.ts @@ -1,9 +1,18 @@ declare const ngDevMode: boolean; export function waitForElementTiming( - identifiers: string[] + identifiers: string[], + config: { + timeout: number + } = {timeout: 5000} ): Promise { return new Promise((resolve, reject) => { + const {timeout} = config; + const timeoutId = setTimeout(() => { + if (ngDevMode) + console.log(`waitForElementTiming timed out after ${timeout}ms`); + resolve(); + }, timeout); try { const performanceObserver = new PerformanceObserver((l) => { if (identifiers.length > 0 && l.getEntries().length) { @@ -18,6 +27,8 @@ export function waitForElementTiming( if (identifiers.length === 0) { if (ngDevMode) console.log("All elements observed"); + // cleanup + clearTimeout(timeoutId); performanceObserver.disconnect(); resolve(); } diff --git a/projects/movies/src/app/shared/zone-less/provide-ngZone.ts b/projects/movies/src/app/shared/zone-less/provide-ngZone.ts index 1a792ad4c..7ee169f2f 100644 --- a/projects/movies/src/app/shared/zone-less/provide-ngZone.ts +++ b/projects/movies/src/app/shared/zone-less/provide-ngZone.ts @@ -1,19 +1,19 @@ -import {waitForElementTiming} from "../cdk/element-timing/wait-for-element-timing"; -import {EventEmitter, inject, Injectable, NgZone} from "@angular/core"; -import {ApplicationRendered} from "../cdk/application-rendered/applicationRenderdToken"; -import {BehaviorSubject} from "rxjs"; +import { + EventEmitter, + inject, + Injectable, + NgZone, + ɵInitialRenderPendingTasks as InitialRenderPendingTasks, +} from "@angular/core"; +import {BehaviorSubject, skip, startWith, tap} from "rxjs"; declare const ngDevMode: boolean; export function provideNgZoneZoneless() { - return [{ - provide: ApplicationRendered, - useFactory: () => () => waitForElementTiming(['header-main', 'tile-img']) - }, - { - provide: NgZone, - useClass: AppRenderedNgZoneZoneless, - }] + return { + provide: NgZone, + useClass: AppRenderedNgZoneZoneless, + } } /** @@ -24,7 +24,7 @@ export function provideNgZoneZoneless() { providedIn: "root", }) export class AppRenderedNgZoneZoneless { - applicationRenderDone = inject(ApplicationRendered); + private initialRenderPendingTasks = inject(InitialRenderPendingTasks) hasPendingMicrotasks = true; hasPendingMacrotasks = true; @@ -40,22 +40,32 @@ export class AppRenderedNgZoneZoneless { onError = new EventEmitter(); constructor() { + const timeToken = "onStableTrigger" + Math.random(); if (ngDevMode) - console.time("onStableTrigger"); + console.time(timeToken); /** * Notice: * This is a hack to delay the emission of isStable for a micro task * This helps HttpTransferCache to get its values first from the cache */ - this.applicationRenderDone() - .then(() => { - if (ngDevMode) - console.timeEnd('onStableTrigger'); - - this.hasPendingMicrotasks = false; - this.hasPendingMacrotasks = false; - this.onStable.next(true); + + this.initialRenderPendingTasks.hasPendingTasks + .pipe( + skip(1), + startWith(true), + tap(console.log) + ) + .subscribe((isPending: boolean) => { + if (!isPending) { + + if (ngDevMode) + console.timeEnd(timeToken); + + this.hasPendingMicrotasks = false; + this.hasPendingMacrotasks = false; + this.onStable.next(true); + } }); } diff --git a/projects/movies/src/app/state/state-app-initializer.provider.ts b/projects/movies/src/app/state/state-app-initializer.provider.ts index c49a75d7f..8a5ce04ba 100644 --- a/projects/movies/src/app/state/state-app-initializer.provider.ts +++ b/projects/movies/src/app/state/state-app-initializer.provider.ts @@ -1,8 +1,8 @@ -import { APP_INITIALIZER } from '@angular/core'; -import { GenreResource } from '../data-access/api/resources/genre.resource'; -import { MovieState } from './movie.state'; -import { RouterState } from '../shared/router/router.state'; -import { take } from 'rxjs'; +import {APP_INITIALIZER} from '@angular/core'; +import {GenreResource} from '../data-access/api/resources/genre.resource'; +import {MovieState} from './movie.state'; +import {RouterState} from '../shared/router/router.state'; +import {take} from 'rxjs'; /** * **🚀 Perf Tip for LCP, TTI:** @@ -10,7 +10,7 @@ import { take } from 'rxjs'; * Use `APP_INITIALIZER` and an init method in data services to run data fetching * on app bootstrap instead of component initialization. */ -export function withGobalStateInitializer() { +export function withGlobalStateInitializer() { return [ { provide: APP_INITIALIZER, diff --git a/projects/movies/src/index.ts b/projects/movies/src/index.ts index 9d37d9008..c84f77193 100644 --- a/projects/movies/src/index.ts +++ b/projects/movies/src/index.ts @@ -11,4 +11,3 @@ export default bootstrap; export {environment} from "./environments/environment"; export {tmdbContentTypeInterceptor} from "./app/data-access/api/tmdbContentTypeInterceptor"; export {tmdbReadAccessInterceptor} from "./app/auth/tmdb-http-interceptor.feature"; -export {ApplicationRendered} from "./app/shared/cdk/application-rendered/application-renderd-token"; diff --git a/projects/ng-universal-express/.eslintrc.json b/projects/ng-universal-express/.eslintrc.json index c81046fd6..653d578a3 100644 --- a/projects/ng-universal-express/.eslintrc.json +++ b/projects/ng-universal-express/.eslintrc.json @@ -14,7 +14,6 @@ "*.jsx" ], "extends": [ - "plugin:unicorn/recommended" ], "rules": {} }, diff --git a/projects/ng-universal-express/project.json b/projects/ng-universal-express/project.json index b643cba94..efadb2a80 100644 --- a/projects/ng-universal-express/project.json +++ b/projects/ng-universal-express/project.json @@ -26,6 +26,13 @@ } ] }, + "development": { + "buildOptimizer": false, + "optimization": false, + "sourceMap": true, + "extractLicenses": false, + "vendorChunk": true + }, "serve-production": { "sourceMap": false, "fileReplacements": [ @@ -37,13 +44,6 @@ "main": "projects/ng-universal-express/src/main.ts", "tsConfig": "projects/ng-universal-express/tsconfig.serve.json" }, - "development": { - "buildOptimizer": false, - "optimization": false, - "sourceMap": true, - "extractLicenses": false, - "vendorChunk": true - }, "serve-development": { "buildOptimizer": false, "optimization": false, @@ -119,7 +119,7 @@ }, "configurations": { "production": { - "browserTarget": "movies:build:production", + "browserTarget": "movies:build:stats", "serverTarget": "ng-universal-express:build:production" } }, diff --git a/projects/ng-universal-express/src/app/app.config.ts b/projects/ng-universal-express/src/app/app.config.ts index 295b3c8b1..c3199b94a 100644 --- a/projects/ng-universal-express/src/app/app.config.ts +++ b/projects/ng-universal-express/src/app/app.config.ts @@ -5,7 +5,6 @@ import {RX_RENDER_STRATEGIES_CONFIG} from '@rx-angular/cdk/render-strategies'; import {provideHttpClient, withInterceptors} from '@angular/common/http'; import {IconLoadStrategySsr} from './icon-load.ssr.strategy'; import {tmdbContentTypeInterceptor, tmdbReadAccessInterceptor} from "angular-movies"; -import {provideNgZone} from "./provide-ngZone"; const serverConfig: ApplicationConfig = { providers: [ @@ -18,7 +17,6 @@ const serverConfig: ApplicationConfig = { `dist/projects/movies/browser/assets/svg-icons/${name}.svg`, svgLoadStrategy: IconLoadStrategySsr, }), - provideNgZone(), { provide: RX_RENDER_STRATEGIES_CONFIG, useValue: {primaryStrategy: 'native'}, diff --git a/projects/ng-universal-express/src/app/app.ts b/projects/ng-universal-express/src/app/app.ts new file mode 100644 index 000000000..dd197cf8a --- /dev/null +++ b/projects/ng-universal-express/src/app/app.ts @@ -0,0 +1,65 @@ +import 'zone.js/dist/zone-node'; +import express from 'express'; +import {existsSync} from 'node:fs'; +import {join} from 'node:path'; +import {ngExpressEngine} from '@nguniversal/express-engine'; +import bootstrap from './bootstrap'; +import {useCompression, useTiming} from './utils'; +import {APP_BASE_HREF} from "@angular/common"; + +// The Express app is exported so that it can be used by serverless Functions. +export function app(): express.Express { + const server = express(); + + // use gzip + useCompression(server); + // use server-timing + useTiming(server); + + const distributionFolder = join( + process.cwd(), + 'dist/projects/movies/browser' + ); + + const indexHtml = existsSync(join(distributionFolder, 'index.html')) + ? 'index.html' + : 'index'; + + // Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine) + server.engine('html', ngExpressEngine({bootstrap: () => bootstrap()})); + + server.set('view engine', 'html'); + server.set('views', distributionFolder); + + // Serve static files from /browser + server.get( + '*.*', + express.static(distributionFolder, { + maxAge: '1y', + // missing assets results in 404 instead of continuing to next route handler (and rendering route) + fallthrough: false, + }) + ); + + server.get( + '*', + (request, response) => { + // return rendered HTML including Angular generated DOM + console.log('SSR for route', request.url); + response.startTime('SSR', 'Total SSR Time'); + response.render( + indexHtml, + { + req: request, providers: [ + {provide: APP_BASE_HREF, useValue: request.baseUrl} + ] + }, + (_: any, html: string) => { + response.endTime('SSR'); + response.send(html); + } + ); + } + ); + return server; +} diff --git a/projects/ng-universal-express/src/app/bootstrap.ts b/projects/ng-universal-express/src/app/bootstrap.ts index 707e18f24..0205560e5 100644 --- a/projects/ng-universal-express/src/app/bootstrap.ts +++ b/projects/ng-universal-express/src/app/bootstrap.ts @@ -1,5 +1,4 @@ import bootstrapMovies from 'angular-movies'; -import 'zone.js'; import {appConfig} from './app.config'; const bootstrap = () => bootstrapMovies(appConfig); diff --git a/projects/ng-universal-express/src/index.ts b/projects/ng-universal-express/src/index.ts index c84ed9d1e..f4a42dcbf 100644 --- a/projects/ng-universal-express/src/index.ts +++ b/projects/ng-universal-express/src/index.ts @@ -1,72 +1,2 @@ -import 'zone.js'; -import 'zone.js/dist/zone-node'; -// The Express app is exported so that it can be used by serverless Functions. -import express from 'express'; -import {existsSync} from 'node:fs'; -import {join} from 'node:path'; -import {ngExpressEngine} from '@nguniversal/express-engine'; -import bootstrap from './app/bootstrap'; -import {useCompression, useTiming} from './app/utils'; -import {APP_BASE_HREF} from "@angular/common"; // bootstrap needs to get exported for the pre-render task - -// The Express app is exported so that it can be used by serverless Functions. -export function app(): express.Express { - const server = express(); - - const distributionFolder = join( - process.cwd(), - 'dist/projects/movies/browser' - ); - - const indexHtml = existsSync(join(distributionFolder, 'index.html')) - ? 'index.html' - : 'index'; - - // use gzip - useCompression(server); - // use server-timing - useTiming(server); - - // Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine) - server.engine('html', ngExpressEngine({bootstrap})); - - server.set('view engine', 'html'); - server.set('views', distributionFolder); - - // Example Express Rest API endpoints - // server.get('/api/**', (req, res) => { }); - // Serve static files from /browser - server.get( - '*.*', - express.static(distributionFolder, { - maxAge: '1y', - - // missing assets results in 404 instead of continuing to next route handler (and rendering route) - fallthrough: false, - }) - ); - - server.get( - '*', - - // Version with server timings for SSR - (request, response) => { - // return rendered HTML including Angular generated DOM - console.log('SSR for route', request.url); - response.startTime('SSR', 'Total SSR Time'); - response.render( - indexHtml, - {req: request, providers: [{provide: APP_BASE_HREF, useValue: request.baseUrl}]}, - (_, html) => { - response.endTime('SSR') - response.send(html); - } - ); - } - ); - - return server; -} - -export {default} from './app/bootstrap'; +export {app} from './app/app'; diff --git a/projects/ng-universal-express/src/main.ts b/projects/ng-universal-express/src/main.ts index 615b5b899..f7794653a 100644 --- a/projects/ng-universal-express/src/main.ts +++ b/projects/ng-universal-express/src/main.ts @@ -1,4 +1,4 @@ -import {app} from './index'; +import {app} from './app/app'; const port = process.env.PORT || 4000; app().listen(port, () => { From ce4ea432bb47e869d9e3053af9605a0accff5d79 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 1 Aug 2023 18:39:51 +0300 Subject: [PATCH 03/13] refactor: clean prerender task and deps --- CONTRIBUTING.md | 8 ++- .../.user-flowrc.action-hack.json | 15 ----- projects/cloudflare-worker/.user-flowrc.json | 1 + projects/firebase-function/Readme.md | 16 +++-- projects/firebase-function/project.json | 28 ++++++-- projects/firebase-function/src/index.ts | 13 ++-- .../tooling/generate-package-json/bin.ts | 13 ---- .../generate-package-json.impl.ts | 33 ---------- .../firebase-function/tsconfig.function.json | 11 +++- projects/movies/src/app/app.config.ts | 8 +-- projects/movies/src/app/auth/auth.effects.ts | 16 ++--- .../not-found-page.component.ts | 22 +++---- projects/movies/src/index.original.html | 6 +- projects/movies/src/index.ts | 1 - projects/movies/src/ngsw-config.json | 2 +- projects/ng-universal-express/README.md | 4 ++ projects/ng-universal-express/project.json | 65 ++++++++++++------- .../src/app/provide-ngZone.ts | 50 +++++++++----- .../ng-universal-express/src/prerender.ts | 2 + .../tsconfig.prerender.json | 8 +++ 20 files changed, 163 insertions(+), 159 deletions(-) delete mode 100644 projects/cloudflare-worker/.user-flowrc.action-hack.json create mode 100644 projects/cloudflare-worker/.user-flowrc.json delete mode 100644 projects/firebase-function/tooling/generate-package-json/bin.ts delete mode 100644 projects/firebase-function/tooling/generate-package-json/generate-package-json.impl.ts create mode 100644 projects/ng-universal-express/src/prerender.ts create mode 100644 projects/ng-universal-express/tsconfig.prerender.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6f205a293..4f17bbd17 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -221,18 +221,20 @@ The CI has different actions: ## Docs -- **`docs-hosting-m.yml`** - runs on `m` +- **`docs-hosting-pr+m.yml`** - runs on `m` ## Test and Build - **`ci.yml`** - runs on `pr` and `m` --affected build,test,lint,prerender -### Firebase Hosting +### Firebase + +#### Hosting - **`firebase-hosting-m.yml`** - runs on `m` firebase-function:deploy + movies:user-flow:production, - **`firebase-hosting-pr.yml`** - runs on `pr` ng-universal-express:deploy + ng-universal-express:user-flow:preview -### Firebase Function +#### Function As the build will not break because of require usage we need to test against function emulator diff --git a/projects/cloudflare-worker/.user-flowrc.action-hack.json b/projects/cloudflare-worker/.user-flowrc.action-hack.json deleted file mode 100644 index 6752ccd20..000000000 --- a/projects/cloudflare-worker/.user-flowrc.action-hack.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "collect": { - "url": "http://127.0.0.1:4501/", - "ufPath": "./projects/cloudflare-worker/user-flows", - "outPath": "./dist/user-flow/cloudflare-worker", - "serveCommand": "nx run cloudflare-worker:emulate", - "awaitServeStdout": "[mf:inf] Ready on" - }, - "persist": { - "outPath": "./dist/user-flow/cloudflare-worker", - "format": [ - "md" - ] - } -} diff --git a/projects/cloudflare-worker/.user-flowrc.json b/projects/cloudflare-worker/.user-flowrc.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/projects/cloudflare-worker/.user-flowrc.json @@ -0,0 +1 @@ +{} diff --git a/projects/firebase-function/Readme.md b/projects/firebase-function/Readme.md index e2eb4a328..e721488b9 100644 --- a/projects/firebase-function/Readme.md +++ b/projects/firebase-function/Readme.md @@ -11,17 +11,19 @@ It is also fragile and does not allow save changes of the code. How does it currently work: - The `ng-universal-express` application is built in independently before the `firebase-function` build is executed -- The firebase build is a custom `tsc` execution and fragile not integrated well. - - The source code of the `ng-universal-express` application is used over a `require` statement. - This is needed to avoid consume the `main.js` bundle built the Angular CLI over webpack. +- The firebase build is a custom `tsc` execution and fragile + not integrated well. + - The source code of the `ng-universal-express` application is used over a `require` statement. () + This is needed to avoid consume the `main.js` bundle built by the Angular CLI over webpack. - The deployment of the `firebase-function` is entangled with the static hosting folder structure of the `ng-universal-express` application and the `movies` application. - This is needed as the express server needs to spin up the Angular application and render it in case already rendered - version is available. + This is needed as the express server needs to spin up the Angular application and render it in case a already rendered + version is not available. - The files `firebase.json` and `.firebaserc.json` define the project root. -- Firebase executes npm ci on the server to run the deployment. +- Firebase executes `npm ci` on the server to run the deployment. (no errors allowed no --force allowed) - The closest `package.json` is used to execute the function (defined under the `main` property) - This also is used as root in the functions and so in the angular app and affects the assets paths etc. +- For the targets `build`, `perender`, `serve` there are a couple of different requirements given. + ATM all of them live in the same file and are concurrent. It only works because of hacks. ## @@ -70,3 +72,5 @@ const t = { } } ``` + + diff --git a/projects/firebase-function/project.json b/projects/firebase-function/project.json index 1176f5271..34b958e78 100644 --- a/projects/firebase-function/project.json +++ b/projects/firebase-function/project.json @@ -7,15 +7,33 @@ "targets": { "build": { "executor": "@nx/webpack:webpack", - "outputs": ["{options.outputPath}"], + "outputs": [ + "{options.outputPath}" + ], "options": { - "target": "node", - "compiler": "tsc", "deleteOutputPath": false, + "sourceMap": false, + "namedChunks": true, + "compiler": "tsc", + "target": "node", "outputPath": "./dist/projects/movies/server", - "outputFileName": "index.js", + "tsConfig": "./projects/firebase-function/tsconfig.function.json", "main": "projects/firebase-function/src/index.ts", - "tsConfig": "./projects/firebase-function/tsconfig.function.json" + "outputFileName": "index.js" + }, + "configurations": { + "production": { + "optimization": true, + "sourceMap": true + }, + "development": { + "optimization": false, + "sourceMap": false + }, + "stats": { + "optimization": true, + "statsJson": true + } } }, "serve": { diff --git a/projects/firebase-function/src/index.ts b/projects/firebase-function/src/index.ts index 9eae3e91f..e35484bb5 100644 --- a/projects/firebase-function/src/index.ts +++ b/projects/firebase-function/src/index.ts @@ -1,20 +1,19 @@ import * as functions from 'firebase-functions'; -/* Hack start */ +/* HACK START */ declare const __non_webpack_require__: NodeRequire; // Webpack will replace 'require' with '__webpack_require__' // '__non_webpack_require__' is a proxy to Node 'require' +const ssrApp = __non_webpack_require__('./main').app(); // NOTE: leave this as require() since this file is built dynamically by Angular Universal CLI webpack +/* HACK END */ -// tslint:disable-next-line:no-require-imports no-var-requires -// eslint-disable-next-line unicorn/prefer-module -const ssrApp = __non_webpack_require__('./main').app(); // NOTE: leave this as require() since this file is built dynamically by Angular CLI webpack -/* Hack end */ - +// "ssr" is the name in firebase.function.json export const ssr = functions .runWith({ timeoutSeconds: 5, memory: - '4GB' /* 4096MB memory function will run at currently fastest - 4.8 GHz CPU ( https://cloud.google.com/functions/pricing) */, + // 4096MB memory function will run at currently fastest - 4.8 GHz CPU ( https://cloud.google.com/functions/pricing) + '4GB' }) .https.onRequest(ssrApp); diff --git a/projects/firebase-function/tooling/generate-package-json/bin.ts b/projects/firebase-function/tooling/generate-package-json/bin.ts deleted file mode 100644 index 24577bc14..000000000 --- a/projects/firebase-function/tooling/generate-package-json/bin.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {getArgv} from "../utils"; -import {run} from "./generate-routes.impl"; - -// collect params -const verbose = !getArgv('verbose'); -const noMutation = !getArgv('no-mutation'); -const targetFile = getArgv('target-file'); -const sourceFile = getArgv('source-file'); - -// execute command -run({targetFile, sourceFile, verbose, noMutation}); - - diff --git a/projects/firebase-function/tooling/generate-package-json/generate-package-json.impl.ts b/projects/firebase-function/tooling/generate-package-json/generate-package-json.impl.ts deleted file mode 100644 index 11a5add38..000000000 --- a/projects/firebase-function/tooling/generate-package-json/generate-package-json.impl.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {readFileSync,} from 'node:fs'; -import {join, relative} from 'node:path'; -import {getLog} from "../utils"; - -export function run(parameters: { targetFolder: string, sourceFile?: string, verbose?: boolean }): Promise { - - // setup - let {targetFolder, sourceFile, verbose} = parameters; - - const log = getLog(verbose); - - // validation - if (!targetFolder) { - throw new Error('CLI param --targetFolder is required'); - } - if (!sourceFile) { - throw new Error('CLI param --sourceFile is required'); - } - - const targetFile = join(targetFolder, 'package.json'); - const sourcePackageJson = JSON.parse(readFileSync(sourceFile).toString()); - log(relative(sourceFile, targetFile)); - - // writeFileSyncRecursive(); - const targetPackageJson = adoptPackageJson(sourcePackageJson, {main: relative(sourceFile, targetFile)}); - log(targetPackageJson); - return Promise.resolve(); - -} - -function adoptPackageJson>(json: T, overrides: T): T { - return {...json, ...overrides}; -} diff --git a/projects/firebase-function/tsconfig.function.json b/projects/firebase-function/tsconfig.function.json index e05f73cda..403bccd1c 100644 --- a/projects/firebase-function/tsconfig.function.json +++ b/projects/firebase-function/tsconfig.function.json @@ -1,14 +1,19 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "lib": ["es6", "dom", "es2015", "es2017"], + "lib": [ + "es6", + "dom", + "es2015", + "es2017" + ], "module": "commonjs", "noImplicitReturns": true, "outDir": "../../dist/projects/movies/server", "sourceMap": false, "target": "es2019", - "allowJs": true, - "noImplicitAny": false, + "allowJs": false, + "noImplicitAny": true, "strict": true, "esModuleInterop": true }, diff --git a/projects/movies/src/app/app.config.ts b/projects/movies/src/app/app.config.ts index e24228cec..b3966989a 100644 --- a/projects/movies/src/app/app.config.ts +++ b/projects/movies/src/app/app.config.ts @@ -15,8 +15,6 @@ import {environment} from '../environments/environment'; import {waitForElementTiming} from "./shared/cdk/element-timing/wait-for-element-timing"; import {provideNgZoneZoneless} from "./shared/zone-less/provide-ngZone"; -InitialRenderPendingTasks; -APP_INITIALIZER; const browserConfig: ApplicationConfig = { providers: [ provideClientHydration(), @@ -54,8 +52,8 @@ const browserConfig: ApplicationConfig = { * **🚀 Perf Tip for TBT:** * * Chunk app bootstrap over APP_INITIALIZER. - - { + */ + { provide: APP_INITIALIZER, useFactory: () => (): Promise => new Promise((resolve) => { @@ -63,7 +61,7 @@ const browserConfig: ApplicationConfig = { }), deps: [], multi: true, - },*/ + }, provideServiceWorker('ngsw-worker.js', { enabled: environment.production, // Register the ServiceWorker as soon as the app is stable diff --git a/projects/movies/src/app/auth/auth.effects.ts b/projects/movies/src/app/auth/auth.effects.ts index 7d96ad296..151b1c2f0 100644 --- a/projects/movies/src/app/auth/auth.effects.ts +++ b/projects/movies/src/app/auth/auth.effects.ts @@ -1,12 +1,8 @@ -import { DOCUMENT, isPlatformBrowser } from '@angular/common'; -import { inject, Injectable, PLATFORM_ID } from '@angular/core'; -import { - AccessTokenResponse, - Authv4Resource, - RequestTokenResponse, -} from '../data-access/api/resources/authv4.resource'; -import { AccessTokenFacade } from './access-token-facade.service'; -import { AccountState } from '../state/account.state'; +import {DOCUMENT, isPlatformBrowser} from '@angular/common'; +import {inject, Injectable, PLATFORM_ID} from '@angular/core'; +import {AccessTokenResponse, Authv4Resource, RequestTokenResponse,} from '../data-access/api/resources/authv4.resource'; +import {AccessTokenFacade} from './access-token-facade.service'; +import {AccountState} from '../state/account.state'; @Injectable({ providedIn: 'root', @@ -21,7 +17,7 @@ export class AuthEffects { constructor() { if (isPlatformBrowser(this.platformId)) { - // should we finish the signIn ? + // @TODO should we finish the signIn? const requestToken = window.localStorage.getItem('requestToken'); requestToken && this.signInFinish(requestToken); } diff --git a/projects/movies/src/app/pages/not-found-page/not-found-page.component.ts b/projects/movies/src/app/pages/not-found-page/not-found-page.component.ts index 2a331337e..c59ac8db0 100644 --- a/projects/movies/src/app/pages/not-found-page/not-found-page.component.ts +++ b/projects/movies/src/app/pages/not-found-page/not-found-page.component.ts @@ -1,19 +1,17 @@ -import { - ChangeDetectionStrategy, - Component, - ViewEncapsulation, -} from '@angular/core'; -import { FastSvgComponent } from '@push-based/ngx-fast-svg'; +import {ChangeDetectionStrategy, Component, ViewEncapsulation,} from '@angular/core'; +import {FastSvgComponent} from '@push-based/ngx-fast-svg'; +import {RouterLink} from "@angular/router"; @Component({ standalone: true, - imports: [FastSvgComponent], + imports: [FastSvgComponent, RouterLink, FastSvgComponent], selector: 'ct-not-found', - template: `
- -

Sorry, page not found

- See popular -
`, + template: ` +
+ +

Sorry, page not found

+ See popular +
`, styleUrls: ['./not-found-page.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, diff --git a/projects/movies/src/index.original.html b/projects/movies/src/index.original.html index 680c00a0d..96ed22e76 100644 --- a/projects/movies/src/index.original.html +++ b/projects/movies/src/index.original.html @@ -11,9 +11,9 @@ + **🚀 Perf Tip for LCP:** + pre-connect urls used in the bootstrap phase or important urls used later in time. + --> diff --git a/projects/movies/src/index.ts b/projects/movies/src/index.ts index c84f77193..c4bd1805a 100644 --- a/projects/movies/src/index.ts +++ b/projects/movies/src/index.ts @@ -6,7 +6,6 @@ import {mergeBaseConfig} from './app/app.base.config'; const bootstrap = (config: ApplicationConfig = {providers: []}) => bootstrapApplication(AppStandaloneComponent, mergeBaseConfig(config)); - export default bootstrap; export {environment} from "./environments/environment"; export {tmdbContentTypeInterceptor} from "./app/data-access/api/tmdbContentTypeInterceptor"; diff --git a/projects/movies/src/ngsw-config.json b/projects/movies/src/ngsw-config.json index f91ed8fa0..528b8c99a 100644 --- a/projects/movies/src/ngsw-config.json +++ b/projects/movies/src/ngsw-config.json @@ -1,5 +1,5 @@ { - "$schema": "../node_modules/@angular/service-worker/config/schema.json", + "$schema": "../../../node_modules/@angular/service-worker/config/schema.json", "index": "/index.html", "assetGroups": [ { diff --git a/projects/ng-universal-express/README.md b/projects/ng-universal-express/README.md index cb3b7f43b..54429f0ef 100644 --- a/projects/ng-universal-express/README.md +++ b/projects/ng-universal-express/README.md @@ -1 +1,5 @@ # Ng Universal Express + +## TODO + +- elaborate `serve`, `build` and `prerender` setup in `project.json` and `tsconfig.json` diff --git a/projects/ng-universal-express/project.json b/projects/ng-universal-express/project.json index efadb2a80..4296e3853 100644 --- a/projects/ng-universal-express/project.json +++ b/projects/ng-universal-express/project.json @@ -9,8 +9,13 @@ "executor": "@angular-devkit/build-angular:server", "options": { "deleteOutputPath": false, - "outputHashing": "media", + "namedChunks": true, "sourceMap": true, + "optimization": false, + "buildOptimizer": false, + "vendorChunk": true, + "extractLicenses": false, + "outputHashing": "media", "outputPath": "dist/projects/movies/server", "main": "projects/ng-universal-express/src/index.ts", "tsConfig": "projects/ng-universal-express/tsconfig.app.json", @@ -18,7 +23,9 @@ }, "configurations": { "production": { - "sourceMap": false, + "optimization": true, + "buildOptimizer": true, + "extractLicenses": true, "fileReplacements": [ { "replace": "projects/movies/src/environments/environment.ts", @@ -27,11 +34,20 @@ ] }, "development": { - "buildOptimizer": false, - "optimization": false, - "sourceMap": true, - "extractLicenses": false, - "vendorChunk": true + "sourceMap": true + }, + "stats": { + "statsJson": true, + "sourceMap": false, + "optimization": true, + "buildOptimizer": true, + "extractLicenses": true, + "fileReplacements": [ + { + "replace": "projects/movies/src/environments/environment.ts", + "with": "projects/movies/src/environments/environment.production.ts" + } + ] }, "serve-production": { "sourceMap": false, @@ -45,13 +61,24 @@ "tsConfig": "projects/ng-universal-express/tsconfig.serve.json" }, "serve-development": { - "buildOptimizer": false, - "optimization": false, "sourceMap": true, "extractLicenses": false, - "vendorChunk": true, "main": "projects/ng-universal-express/src/main.ts", "tsConfig": "projects/ng-universal-express/tsconfig.serve.json" + }, + "prerender": { + "sourceMap": true, + "optimization": true, + "buildOptimizer": true, + "extractLicenses": true, + "fileReplacements": [ + { + "replace": "projects/movies/src/environments/environment.ts", + "with": "projects/movies/src/environments/environment.production.ts" + } + ], + "main": "projects/ng-universal-express/src/prerender.ts", + "tsConfig": "projects/ng-universal-express/tsconfig.prerender.json" } }, "defaultConfiguration": "production" @@ -66,11 +93,6 @@ "browserTarget": "movies:build:development", "serverTarget": "ng-universal-express:build:serve-development" }, - "development-prerender": { - "port": 4310, - "browserTarget": "movies:build:development", - "serverTarget": "ng-universal-express:build:serve-development" - }, "production": { "port": 4301, "browserTarget": "movies:build:production", @@ -115,15 +137,10 @@ "outputs": ["dist/projects/movies/browser"], "executor": "@nguniversal/builders:prerender", "options": { - "routesFile": "dist/tmp/routes.txt" - }, - "configurations": { - "production": { - "browserTarget": "movies:build:stats", - "serverTarget": "ng-universal-express:build:production" - } - }, - "defaultConfiguration": "production" + "routesFile": "dist/tmp/routes.txt", + "browserTarget": "movies:build:stats", + "serverTarget": "ng-universal-express:build:prerender" + } }, "emulate-firebase": { "executor": "nx:run-commands", diff --git a/projects/ng-universal-express/src/app/provide-ngZone.ts b/projects/ng-universal-express/src/app/provide-ngZone.ts index 73f2ef61a..27831b19d 100644 --- a/projects/ng-universal-express/src/app/provide-ngZone.ts +++ b/projects/ng-universal-express/src/app/provide-ngZone.ts @@ -1,39 +1,53 @@ -import {EventEmitter, inject, Injectable, NgZone} from "@angular/core"; -import {ApplicationRendered} from "../../../movies/src/app/shared/cdk/application-rendered/application-renderd-token"; +import { + EventEmitter, + inject, + Injectable, + NgZone, + ɵInitialRenderPendingTasks as InitialRenderPendingTasks +} from "@angular/core"; +import {tap} from "rxjs"; -declare const ngDevelopmentMode: boolean; +// eslint +declare const ngDevMode: boolean; export function provideNgZone() { return [{ - provide: ApplicationRendered, - useFactory: () => () => Promise.resolve() - }, - { - provide: NgZone, - useClass: AppRenderedNgZone, - }] + provide: NgZone, + useClass: AppRenderedNgZone, + }] } /** * Provides a noop like implementation of `NgZone` which does nothing and provides a way to customize behavior. - * This zone requires explicit calls to framework to perform rendering. + * This zone implements the ApplicationRendered token and fires `onStable` */ @Injectable({ providedIn: "root", }) export class AppRenderedNgZone extends NgZone { - applicationRenderDone = inject(ApplicationRendered); - // eslint-disable-next-line unicorn/prefer-event-target + private initialRenderPendingTasks = inject(InitialRenderPendingTasks) onStable = new EventEmitter(); constructor() { super({}); + const timeToken = "onStableTrigger" + Math.random(); - this.applicationRenderDone() - .then(() => { - if (ngDevelopmentMode) - console.timeEnd('onStableTrigger'); - }) + if (ngDevMode) + console.log(timeToken); + console.time(timeToken); + + this.initialRenderPendingTasks.hasPendingTasks + .pipe( + tap(v => console.log('this.initialRenderPendingTasks', v)) + ) + .subscribe((isPending: boolean) => { + if (!isPending) { + if (ngDevMode) + console.timeEnd(timeToken); + + this.onStable.emit(); + } + }); } } diff --git a/projects/ng-universal-express/src/prerender.ts b/projects/ng-universal-express/src/prerender.ts new file mode 100644 index 000000000..834df9981 --- /dev/null +++ b/projects/ng-universal-express/src/prerender.ts @@ -0,0 +1,2 @@ +// bootstrap needs to get exported as default for the pre-render task of @nguniversal +export {default} from './app/bootstrap'; diff --git a/projects/ng-universal-express/tsconfig.prerender.json b/projects/ng-universal-express/tsconfig.prerender.json new file mode 100644 index 000000000..abf7a0139 --- /dev/null +++ b/projects/ng-universal-express/tsconfig.prerender.json @@ -0,0 +1,8 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.app.json", + "compilerOptions": {}, + "files": [ + "src/prerender.ts" + ] +} From 0190b850dc2f19d576a82734ec9127e1a6b9541b Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 1 Aug 2023 19:04:17 +0300 Subject: [PATCH 04/13] refactor: polish universal files --- .../{ => angular}/icon-load.ssr.strategy.ts | 10 +++----- .../src/app/{ => angular}/provide-ngZone.ts | 10 +++----- .../src/app/app.config.ts | 2 +- projects/ng-universal-express/src/app/app.ts | 10 +++++--- .../ng-universal-express/src/app/utils.ts | 25 ------------------- .../src/app/utils/express-compression.ts | 7 ++++++ .../src/app/utils/express-timing.ts | 7 ++++++ 7 files changed, 28 insertions(+), 43 deletions(-) rename projects/ng-universal-express/src/app/{ => angular}/icon-load.ssr.strategy.ts (55%) rename projects/ng-universal-express/src/app/{ => angular}/provide-ngZone.ts (84%) delete mode 100644 projects/ng-universal-express/src/app/utils.ts create mode 100644 projects/ng-universal-express/src/app/utils/express-compression.ts create mode 100644 projects/ng-universal-express/src/app/utils/express-timing.ts diff --git a/projects/ng-universal-express/src/app/icon-load.ssr.strategy.ts b/projects/ng-universal-express/src/app/angular/icon-load.ssr.strategy.ts similarity index 55% rename from projects/ng-universal-express/src/app/icon-load.ssr.strategy.ts rename to projects/ng-universal-express/src/app/angular/icon-load.ssr.strategy.ts index fb273f8f8..f38bf087b 100644 --- a/projects/ng-universal-express/src/app/icon-load.ssr.strategy.ts +++ b/projects/ng-universal-express/src/app/angular/icon-load.ssr.strategy.ts @@ -1,14 +1,12 @@ +import {readFileSync} from 'node:fs'; +import {of} from "rxjs"; import {Injectable} from '@angular/core'; import {SvgLoadStrategy} from '@push-based/ngx-fast-svg'; -import {readFileSync} from 'node:fs'; -import {join} from 'node:path'; -import {Observable, of} from 'rxjs'; @Injectable() export class IconLoadStrategySsr implements SvgLoadStrategy { - load(url: string): Observable { - const iconPath = join(url); - const iconSVG = readFileSync(iconPath, 'utf8'); + load(url: string) { + const iconSVG = readFileSync(url, 'utf8'); return of(iconSVG); } } diff --git a/projects/ng-universal-express/src/app/provide-ngZone.ts b/projects/ng-universal-express/src/app/angular/provide-ngZone.ts similarity index 84% rename from projects/ng-universal-express/src/app/provide-ngZone.ts rename to projects/ng-universal-express/src/app/angular/provide-ngZone.ts index 27831b19d..f7fd251ea 100644 --- a/projects/ng-universal-express/src/app/provide-ngZone.ts +++ b/projects/ng-universal-express/src/app/angular/provide-ngZone.ts @@ -5,9 +5,7 @@ import { NgZone, ɵInitialRenderPendingTasks as InitialRenderPendingTasks } from "@angular/core"; -import {tap} from "rxjs"; -// eslint declare const ngDevMode: boolean; export function provideNgZone() { @@ -33,14 +31,12 @@ export class AppRenderedNgZone extends NgZone { const timeToken = "onStableTrigger" + Math.random(); if (ngDevMode) - console.log(timeToken); - console.time(timeToken); + console.time(timeToken); this.initialRenderPendingTasks.hasPendingTasks - .pipe( - tap(v => console.log('this.initialRenderPendingTasks', v)) - ) .subscribe((isPending: boolean) => { + if (ngDevMode) + console.log('initialRenderPendingTasks', isPending); if (!isPending) { if (ngDevMode) console.timeEnd(timeToken); diff --git a/projects/ng-universal-express/src/app/app.config.ts b/projects/ng-universal-express/src/app/app.config.ts index c3199b94a..f4c275f12 100644 --- a/projects/ng-universal-express/src/app/app.config.ts +++ b/projects/ng-universal-express/src/app/app.config.ts @@ -3,7 +3,7 @@ import {provideServerRendering} from '@angular/platform-server'; import {provideFastSVG} from '@push-based/ngx-fast-svg'; import {RX_RENDER_STRATEGIES_CONFIG} from '@rx-angular/cdk/render-strategies'; import {provideHttpClient, withInterceptors} from '@angular/common/http'; -import {IconLoadStrategySsr} from './icon-load.ssr.strategy'; +import {IconLoadStrategySsr} from './angular/icon-load.ssr.strategy'; import {tmdbContentTypeInterceptor, tmdbReadAccessInterceptor} from "angular-movies"; const serverConfig: ApplicationConfig = { diff --git a/projects/ng-universal-express/src/app/app.ts b/projects/ng-universal-express/src/app/app.ts index dd197cf8a..4a31c274e 100644 --- a/projects/ng-universal-express/src/app/app.ts +++ b/projects/ng-universal-express/src/app/app.ts @@ -1,19 +1,21 @@ import 'zone.js/dist/zone-node'; + import express from 'express'; import {existsSync} from 'node:fs'; import {join} from 'node:path'; import {ngExpressEngine} from '@nguniversal/express-engine'; -import bootstrap from './bootstrap'; -import {useCompression, useTiming} from './utils'; import {APP_BASE_HREF} from "@angular/common"; +import bootstrap from './bootstrap'; +import {useCompression} from "./utils/express-compression"; +import {useTiming} from "./utils/express-timing"; // The Express app is exported so that it can be used by serverless Functions. export function app(): express.Express { const server = express(); - // use gzip + // Serve gzip for faster load useCompression(server); - // use server-timing + // Use server-timing fo better debugging useTiming(server); const distributionFolder = join( diff --git a/projects/ng-universal-express/src/app/utils.ts b/projects/ng-universal-express/src/app/utils.ts deleted file mode 100644 index a400c7bd9..000000000 --- a/projects/ng-universal-express/src/app/utils.ts +++ /dev/null @@ -1,25 +0,0 @@ -import compressionModule from 'compression'; -import {Express} from 'express'; -import serverTiming from 'server-timing'; - -export function addServerTiming( - function_: (request: any, response: any, next: any) => Promise, - cfg: { name: string; label?: string } -) { - return async (request: any, response: any, next: any) => { - response.startTime(cfg.name, cfg.label || cfg.name); - const result = await function_(request, response, next); - response.endTime(cfg.name); - return result; - }; -} - -export function useCompression(server: Express) { - // **🚀 Perf Tip:** - // Serve gzip for faster load - server.use(compressionModule()); -} - -export function useTiming(server: Express) { - server.use(serverTiming()); -} diff --git a/projects/ng-universal-express/src/app/utils/express-compression.ts b/projects/ng-universal-express/src/app/utils/express-compression.ts new file mode 100644 index 000000000..069360066 --- /dev/null +++ b/projects/ng-universal-express/src/app/utils/express-compression.ts @@ -0,0 +1,7 @@ +// @ts-ignore +import compressionModule from 'compression'; +import {Express} from 'express'; + +export function useCompression(server: Express) { + server.use(compressionModule()); +} diff --git a/projects/ng-universal-express/src/app/utils/express-timing.ts b/projects/ng-universal-express/src/app/utils/express-timing.ts new file mode 100644 index 000000000..ed0b395ba --- /dev/null +++ b/projects/ng-universal-express/src/app/utils/express-timing.ts @@ -0,0 +1,7 @@ +// @ts-ignore +import {Express} from 'express'; +import serverTiming from 'server-timing'; + +export function useTiming(server: Express) { + server.use(serverTiming()); +} From e5ff1d5d0fd4e33ccd948e7c0b89d9c12539dc97 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 1 Aug 2023 21:29:36 +0200 Subject: [PATCH 05/13] refactor: polish 1 --- projects/firebase-function/project.json | 2 +- projects/movies-user-flows/src/fixtures/tmdb.fixtures.ts | 3 +-- projects/ng-universal-express/src/index.ts | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/projects/firebase-function/project.json b/projects/firebase-function/project.json index 34b958e78..503bcea69 100644 --- a/projects/firebase-function/project.json +++ b/projects/firebase-function/project.json @@ -40,7 +40,7 @@ "executor": "nx:run-commands", "options": { "commands": [ - "nx build firebase-function", + "nx run firebase-function:build", "firebase emulators:start --project angular-movies" ], "parallel": false diff --git a/projects/movies-user-flows/src/fixtures/tmdb.fixtures.ts b/projects/movies-user-flows/src/fixtures/tmdb.fixtures.ts index 8affd4032..ee8f9be2d 100644 --- a/projects/movies-user-flows/src/fixtures/tmdb.fixtures.ts +++ b/projects/movies-user-flows/src/fixtures/tmdb.fixtures.ts @@ -2,8 +2,7 @@ export const TmdbAuthUrl = 'www.themoviedb.org/auth/access'; export const TmdbLoginUrl = 'www.themoviedb.org/login'; export const TmdbApproveBtn = 'input[value="Approve"]'; export const TmdbCookieRejectAllBtn = '#onetrust-reject-all-handler'; -export const TmdbLoginBtn = - '#main > section > div > div > div:nth-child(2) > a'; +export const TmdbLoginBtn = '#main > section > div > div > div:nth-child(2) > a'; export const TmdbUsernameInput = '#username'; export const TmdbPasswordInput = '#password'; export const TmdbUser = 'angular-movies'; diff --git a/projects/ng-universal-express/src/index.ts b/projects/ng-universal-express/src/index.ts index f4a42dcbf..e7d42d887 100644 --- a/projects/ng-universal-express/src/index.ts +++ b/projects/ng-universal-express/src/index.ts @@ -1,2 +1 @@ -// bootstrap needs to get exported for the pre-render task export {app} from './app/app'; From 5bc7a4dff8a8339604d23e93d4349231a50a9d57 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 1 Aug 2023 22:28:15 +0200 Subject: [PATCH 06/13] refactor: polish 2 --- projects/movies/project.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/projects/movies/project.json b/projects/movies/project.json index 90bb03268..61e3d9809 100644 --- a/projects/movies/project.json +++ b/projects/movies/project.json @@ -45,11 +45,6 @@ "stats": { "statsJson": true, "namedChunks": true, - "sourceMap": true, - "extractLicenses": false, - "buildOptimizer": false, - "optimization": false, - "serviceWorker": false, "budgets": [] }, "production": { From 1dc7380849b96d71f89e6ee663a1381d549999af Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 1 Aug 2023 22:30:34 +0200 Subject: [PATCH 07/13] refactor: polish 3 --- projects/movies/src/app/app.base.config.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/projects/movies/src/app/app.base.config.ts b/projects/movies/src/app/app.base.config.ts index 2a400805b..0557a04b2 100644 --- a/projects/movies/src/app/app.base.config.ts +++ b/projects/movies/src/app/app.base.config.ts @@ -1,5 +1,5 @@ import {provideHttpClient, withInterceptors} from '@angular/common/http'; -import {APP_INITIALIZER, ApplicationConfig, mergeApplicationConfig,} from '@angular/core'; +import {ApplicationConfig, mergeApplicationConfig,} from '@angular/core'; import {provideClientHydration} from '@angular/platform-browser'; import {provideRouter, withInMemoryScrolling,} from '@angular/router'; import {RxActionFactory} from '@rx-angular/state/actions'; @@ -9,8 +9,6 @@ import {tmdbContentTypeInterceptor} from './data-access/api/tmdbContentTypeInter import {provideTmdbImageLoader} from './data-access/images/image-loader'; import {withGlobalStateInitializer} from "./state/state-app-initializer.provider"; -APP_INITIALIZER; - const appConfig: ApplicationConfig = { providers: [ provideClientHydration(), From de21d19ad10fdc39ef844644b3d6c8fe8ba0d2f3 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 1 Aug 2023 22:39:21 +0200 Subject: [PATCH 08/13] refactor: polish 4 --- projects/movies/src/app/app.base.config.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/movies/src/app/app.base.config.ts b/projects/movies/src/app/app.base.config.ts index 0557a04b2..497187dba 100644 --- a/projects/movies/src/app/app.base.config.ts +++ b/projects/movies/src/app/app.base.config.ts @@ -1,7 +1,7 @@ import {provideHttpClient, withInterceptors} from '@angular/common/http'; import {ApplicationConfig, mergeApplicationConfig,} from '@angular/core'; import {provideClientHydration} from '@angular/platform-browser'; -import {provideRouter, withInMemoryScrolling,} from '@angular/router'; +import {provideRouter, withDisabledInitialNavigation, withInMemoryScrolling,} from '@angular/router'; import {RxActionFactory} from '@rx-angular/state/actions'; import {ROUTES} from './routes'; import {tmdbReadAccessInterceptor} from './auth/tmdb-http-interceptor.feature'; @@ -24,7 +24,7 @@ const appConfig: ApplicationConfig = { * * Disable initial sync navigation in router config and schedule it in router-outlet container component */ - // withDisabledInitialNavigation(), + withDisabledInitialNavigation(), withInMemoryScrolling({ /** * **💡 UX Tip for InfiniteScroll:** @@ -37,7 +37,7 @@ const appConfig: ApplicationConfig = { scrollPositionRestoration: 'top', }) ), - // global actions + // Global actions RxActionFactory, /** * **🚀 Perf Tip for LCP, TTI:** From 9475b5e8f21eaee04075966696915746f32207ac Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 1 Aug 2023 22:41:57 +0200 Subject: [PATCH 09/13] refactor: adopt bundle size --- projects/movies/project.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/movies/project.json b/projects/movies/project.json index 61e3d9809..5a0f91664 100644 --- a/projects/movies/project.json +++ b/projects/movies/project.json @@ -59,8 +59,8 @@ "budgets": [ { "type": "initial", - "maximumWarning": "349.9kb", - "maximumError": "350KB" + "maximumWarning": "350.8kb", + "maximumError": "351KB" }, { "type": "anyComponentStyle", From e38ab225a70f3e4fb0b03f21012d9a0eca83a878 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 1 Aug 2023 22:49:00 +0200 Subject: [PATCH 10/13] refactor: cleanup comments --- .../tooling/generate-routes/generate-routes.impl.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/projects/ng-universal-express/tooling/generate-routes/generate-routes.impl.ts b/projects/ng-universal-express/tooling/generate-routes/generate-routes.impl.ts index 3f4962ff8..888be2638 100644 --- a/projects/ng-universal-express/tooling/generate-routes/generate-routes.impl.ts +++ b/projects/ng-universal-express/tooling/generate-routes/generate-routes.impl.ts @@ -42,7 +42,6 @@ export function run(parameters: { targetFile: string, sourceFile?: string, verbo const movieGenresRoutes = _fetch<{ genres: GenresResponse }>(movieGenresURL, { headers: getTmdbHeaders(), }) - // eslint-disable-next-line unicorn/prefer-top-level-await .then(({genres}) => { return genres.map(({id}) => genresListURL(id)) }) @@ -81,7 +80,6 @@ export function run(parameters: { targetFile: string, sourceFile?: string, verbo content ); }) - // eslint-disable-next-line unicorn/prefer-top-level-await .catch((error) => console.error(error)); } @@ -89,7 +87,6 @@ function readApi(url: string): string { return `${environment.tmdbBaseUrl}/${environment.apiV3}/${url}`; } -// eslint-disable-next-line unicorn/no-object-as-default-parameter function writeFileSyncRecursive( filename: string, content: string, From c23d1e493ab8add34c5ef5addb3ce3150ea090f1 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 1 Aug 2023 22:50:10 +0200 Subject: [PATCH 11/13] refactor: cleanup action --- .github/workflows/cloudflare-hosting-pr.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/cloudflare-hosting-pr.yml b/.github/workflows/cloudflare-hosting-pr.yml index 3a18381c8..fcc53b40b 100644 --- a/.github/workflows/cloudflare-hosting-pr.yml +++ b/.github/workflows/cloudflare-hosting-pr.yml @@ -29,4 +29,3 @@ jobs: commentId: cloudflare-preview-pages-hosting onlyComments: on outPath: ./dist/user-flow/cloudflare-worker - rcPath: projects/cloudflare-worker/.user-flowrc.action-hack.json From ae792213544c0c1f65f115eb35ee19d5b6259852 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 1 Aug 2023 22:59:18 +0200 Subject: [PATCH 12/13] refactor: switch target config fo prerender --- projects/ng-universal-express/project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/ng-universal-express/project.json b/projects/ng-universal-express/project.json index 4296e3853..5bc43c7e3 100644 --- a/projects/ng-universal-express/project.json +++ b/projects/ng-universal-express/project.json @@ -138,7 +138,7 @@ "executor": "@nguniversal/builders:prerender", "options": { "routesFile": "dist/tmp/routes.txt", - "browserTarget": "movies:build:stats", + "browserTarget": "movies:build:production", "serverTarget": "ng-universal-express:build:prerender" } }, From a0ce38e020638342caebfa7ec6981789a3cf8a78 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 1 Aug 2023 23:11:04 +0200 Subject: [PATCH 13/13] refactor: remove comments --- .../tooling/generate-routes/generate-routes.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/ng-universal-express/tooling/generate-routes/generate-routes.spec.ts b/projects/ng-universal-express/tooling/generate-routes/generate-routes.spec.ts index ca2b37d19..e4d57a71b 100644 --- a/projects/ng-universal-express/tooling/generate-routes/generate-routes.spec.ts +++ b/projects/ng-universal-express/tooling/generate-routes/generate-routes.spec.ts @@ -26,7 +26,6 @@ describe('generate-routes', () => { it('should throw if params are not given', () => { const errorMessage = 'CLI param --targetFile is required'; - // eslint-disable-next-line unicorn/prefer-module expect(() => run({} as any)).toThrow(errorMessage); expect(() => run({verbose: true, targetFile: ''})).toThrow(errorMessage); })