Skip to content

Commit deb1d89

Browse files
committed
Upgrades
1 parent 975fc28 commit deb1d89

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+14615
-12428
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ jobs:
3030
${{ runner.os }}-firebase-emulators-
3131
3232
- name: Set up pnpm
33-
uses: pnpm/action-setup@v3
33+
uses: pnpm/action-setup@v4
3434
with:
35-
version: 8
35+
version: 9
3636

3737
- name: Set up Node.js
3838
uses: actions/setup-node@v4

ARCHITECTURE.md

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,6 @@ app/src
206206
└─ website.routes.ts
207207
└─ app.component.ts
208208
└─ app.routes.ts
209-
└─ loader-shell.component.ts
210209
└─ assets
211210
└─ {images, icons (incl PWA icons), fonts, etc.}
212211
└─ environments
@@ -253,13 +252,13 @@ As things grow you may need to adapt and tweak this structure (e.g. to add anoth
253252

254253
| **:brain: Design decision** |
255254
| :-- |
256-
| Out of the box, we _don't_ use server-side rendering (SSR). We _do_ use prerendering for certain pages (configured explicitly), and everything else is fully dynamic (i.e. client-side only, with a minimal loader shell). |
255+
| Out of the box, we _don't_ use server-side rendering (SSR). We _do_ use prerendering for certain pages (configured explicitly), and everything else is fully dynamic (i.e. client-side only). |
257256

258257
Whilst Angular has very good [support for server-side rendering (SSR)](https://angular.dev/guide/ssr) we don't make use of this in the _deployed app_ as we want to be able to run the app wholly from static assets (i.e. no dynamic server required to render any pages).
259258

260-
Instead, we do make use of [build-time prerendering](https://angular.dev/guide/prerendering) for routes we explicitly specify in [`app/prerendered-routes.txt`](./app/prerendered-routes.txt) (currently the website home and about pages) — a static HTML file is built and served for each path specified there (with some additional Firebase Hosting and PWA configuration).
259+
Instead, we do make use of [build-time prerendering](https://angular.dev/guide/prerendering) for routes we explicitly specify in [`app/prerendered-routes.txt`](./app/prerendered-routes.txt) file (currently the website home and about pages) — a static HTML file is built and served for _each_ path specified there (with some additional Firebase Hosting and PWA configuration required to support these).
261260

262-
And then everything else in the app is fully dynamic (i.e. rendered on the client) — a special empty "loader" HTML file (prerendered from the [`LoaderShellComponent`](./app/src/app/loader-shell.component.ts)) is served for all these routes, and we've configured Firebase Hosting and the PWA set-up to serve this loader file for these routes (more details below).
261+
And then everything else in the app is fully dynamic (i.e. rendered on the client) — the special `index.csr.html` generated by the Angular build is used in the Firebase Hosting config and the PWA set-up as the file to serve for all non-prerendered routes (more details below).
263262

264263
> [!NOTE]
265264
>
@@ -273,13 +272,10 @@ For the build-time prerendering of pages, we:
273272

274273
- Configure the `prerender` option in `angular.json` to prerender all paths defined in the [`app/prerendered-routes.txt`](./app/prerendered-routes.txt) file.
275274
- We also set `"discoverRoutes": false` so only the routes we explicitly specify are prerendered.
276-
- Specify all static paths we want prerendered, in the `prerendered-routes.txt` file.
275+
- Specify all static paths we want prerendered, in the aforementioned `prerendered-routes.txt` file.
277276
- Out of the box, we have the website home page (`/`) and the about page (`/about`).
278-
- Specify the `/loader` path in the `prerendered-routes.txt` file, so that the loader shell is prerendered too.
279-
- The `/loader` route serves an empty shell of the app (using the [`LoaderShellComponent`](./app/src/app/loader-shell.component.ts)), which then loads the full app on the client-side. This route is defined in the [`app.routes.ts`](./app/src/app/app.routes.ts) file.
280-
- This is used as the default HTML file to serve for all fully dynamic parts of the app.
281277

282-
So, when we run the production build (`pnpm build`) Angular will output static HTML files for the prerendered routes (including an HTML file for the loader shell) together with the usual JavaScript, CSS, etc. assets.
278+
So, when we run the production build (`pnpm build`) Angular will output separate static HTML files for the prerendered routes.
283279

284280
> [!NOTE]
285281
>
@@ -293,7 +289,7 @@ Given we have a mix of prerendered static and fully dynamic pages, we have to co
293289
>
294290
> In a typical single-page app (SPA) without any server side rendering or static page generation, you'd serve a static `index.html` file for all paths requested. This file would usually contain very little UI, and then bootstrap the app and handle routing, data fetching, templating, etc. on the client-side (all handled by your framework).
295291
>
296-
> However, in our case, the `index.html` file is now the static (prerendered) website home page (which still bootstraps the Angular app when it loads client-side), which we wouldn't want to serve for all routes in our app as it contains content for the home page. And we have a mix of static pages and fully dynamic pages that need to work regardless of whether they are requested directly (from the "server" — a static host, Firebase Hosting, in our case) or within the single-page app (client-side). Hence the need for the loader shell and the Firebase Hosting and PWA set-up.
292+
> However, in our case, the `index.html` file is now the static (prerendered) website home page (which still bootstraps the Angular app when it loads client-side), which we wouldn't want to serve for all routes in our app as it contains content for the home page. And we have a mix of static pages and fully dynamic pages that need to work regardless of whether they are requested directly (from Firebase Hosting) or within the single-page app (client-side). As part of the build, Angular outputs a special `index.csr.html` file which we make use of for all routes not covered by the prerendered pages.
297293
298294
For the static pages (prerendered), we:
299295

@@ -305,10 +301,11 @@ For the static pages (prerendered), we:
305301

306302
Then, for the rest of the fully dynamic pages, we:
307303

308-
- Add an entry in the [`firebase/firebase.json`](./firebase/firebase.json) file (under the `hosting.rewrites` key) to serve the `/loader` path (the default loader shell, mentioned previously) for all paths that aren't explicitly covered by the static pages.
304+
- Add an entry in the [`firebase/firebase.json`](./firebase/firebase.json) file (under the `hosting.rewrites` key) to serve the special `/index.csr.html` file for all paths that aren't explicitly covered by the static (aka prerendered) pages.
309305
- This is known as a "catch-all" rule, and MUST be the last item in the list of rewrite rules.
310-
- Configure the PWA service worker (in the [`app/ngsw-config.json`](./app/ngsw-config.json) file) to use the `"/loader/index.html"` path as the default "index" file to serve for all paths not covered by those defined in the `navigationUrls` key.
311-
- In this same file, we also add `"/loader/index.html"` to the list of prefetched URLs so it can be cached by the service worker.
306+
- Configure the PWA service worker (in the [`app/ngsw-config.json`](./app/ngsw-config.json) file) to use the same `"/index.csr.html"` path as the default "index" file to serve for all paths not covered by those defined in the `navigationUrls` key.
307+
- I.e. this will be used by the service worker for all dynamic pages.
308+
- In this same file, we also add `"/index.csr.html"` to the list of prefetched URLs so it can be cached by the service worker.
312309

313310
Note also: in the [`firebase/firebase.json`](./firebase/firebase.json) file (under the `hosting` key) we set `"cleanUrls": true` and `"trailingSlash": false` to normalize the behavior and ensure our static paths are served correctly.
314311

@@ -414,7 +411,7 @@ To try out the login flow run the app locally and click on the "Login" button.
414411
>
415412
> Firebase Authentication does not provide server-side sessions, which is not a problem for us as we don't use server-side rendering (SSR), and for any server-side functionality we use Firebase Functions (which has access to the auth token in each request). All authentication is carried out and managed client-side using the Firebase JavaScript SDK.
416413
>
417-
> This does mean that in the auth guard we need a check to see if we're running server-side (currently only applicable to local development and running the build process), where we then "redirect" to the special `/loader` route (covered in a previous section). Note that this isn't a proper redirect, just something that happens purely server-side to determine what content gets rendered for that route (so it doesn’t actually change the path the user is requesting). Once the page loads in the browser then the usual client-side auth check takes over when the Angular app hydrates (i.e. fully loads up).
414+
> This does mean that in the auth guard we need a check to see if we're running server-side and then short-circuit the logic and return `false`. Note that, currently, this is only applicable to local development, since we don't use SSR in production. Once the page loads in the browser then the usual client-side auth check takes over when the Angular app hydrates (i.e. fully loads up). You may see the error `ERROR RuntimeError: NG04002: Cannot match any routes.` in the dev process output — you can safely ignore this as it will only happen in local development.
418415
419416
## [`app`] Logging
420417

@@ -454,9 +451,9 @@ Most of the components, services, etc. provided in the base template have corres
454451

455452
| **:brain: Design decision** |
456453
| :-- |
457-
| We use [ESLint](https://eslint.org/) for linting and [Prettier](https://prettier.io/) for code formatting.<br><br>Running `pnpm lint` performs the linting, and all Prettier formatting is carried out within VS Code (e.g. when you save a file). |
454+
| We use [ESLint](https://eslint.org/) for linting and [Prettier](https://prettier.io/) for code formatting.<br><br>Running `pnpm lint` performs the linting only, and all Prettier formatting is carried out within VS Code (e.g. when you save a file). |
458455

459-
The config for linting is in [`app/.eslintrc.json`](./app/.eslintrc.json) and for formatting in [`app/.prettierrc`](./app/.prettierrc).
456+
The config for linting is in [`app/eslint.config.js`](./app/eslint.config.js) and for formatting in [`app/.prettierrc`](./app/.prettierrc).
460457

461458
We also integrate [`prettier-plugin-tailwindcss`](https://www.npmjs.com/package/prettier-plugin-tailwindcss) to format Tailwind CSS classes in your HTML and JavaScript files.
462459

@@ -551,9 +548,9 @@ See the files within the [`firebase/test`](./firebase/test/) folder for the secu
551548

552549
| **:brain: Design decision** |
553550
| :-- |
554-
| We use [ESLint](https://eslint.org/) for linting and [Prettier](https://prettier.io/) for code formatting.<br><br>Running `pnpm lint` performs the linting, and all Prettier formatting is carried out within VS Code (e.g. when you save a file). |
551+
| We use [ESLint](https://eslint.org/) for linting and [Prettier](https://prettier.io/) for code formatting.<br><br>Running `pnpm lint` performs the linting only, and all Prettier formatting is carried out within VS Code (e.g. when you save a file). |
555552

556-
The config for linting is in [`firebase/.eslintrc.js`](./firebase/.eslintrc.js) and for formatting in [`firebase/.prettierrc`](./firebase/.prettierrc).
553+
The config for linting is in [`firebase/eslint.config.js`](./firebase/eslint.config.js) and for formatting in [`firebase/.prettierrc`](./firebase/.prettierrc).
557554

558555
## Continuous integration (CI) using GitHub Actions
559556

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ For more details see the [Architecture and design decisions](./ARCHITECTURE.md)
4141
4242
- [Node.js](https://nodejs.org/en/) v20.x
4343
- [TypeScript](https://www.typescriptlang.org/) v5.4
44-
- [Angular](https://angular.dev/) v17.3
45-
- [Angular Material](https://material.angular.io/) v17.3
44+
- [Angular](https://angular.dev/) v18.0
45+
- [Angular Material](https://material.angular.io/) v18.0
4646
- [Tailwind CSS](https://tailwindcss.com/) v3.4
47-
- [NgRx Signals](https://ngrx.io/guide/signals) v17.2
47+
- [NgRx Signals](https://ngrx.io/guide/signals) v18.0
4848
- [RxFire](https://github.com/FirebaseExtended/rxfire) v6
4949
- [Firebase](https://firebase.google.com/)
5050
- [Hosting](https://firebase.google.com/products/hosting)

app/.eslintrc.json

Lines changed: 0 additions & 57 deletions
This file was deleted.

app/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,6 @@ testem.log
4040
# System files
4141
.DS_Store
4242
Thumbs.db
43+
44+
# Nx cache etc.
45+
.nx

app/angular.json

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,13 @@
4646
"polyfills": ["zone.js"],
4747
"tsConfig": "tsconfig.app.json",
4848
"inlineStyleLanguage": "scss",
49-
"assets": ["src/favicon.ico", "src/assets", "src/manifest.webmanifest"],
49+
"assets": [
50+
{
51+
"glob": "**/*",
52+
"input": "public"
53+
},
54+
"src/manifest.webmanifest"
55+
],
5056
"styles": ["src/styles.scss"],
5157
"stylePreprocessorOptions": {
5258
"includePaths": ["src/styles"]
@@ -66,13 +72,13 @@
6672
"budgets": [
6773
{
6874
"type": "initial",
69-
"maximumWarning": "1mb",
70-
"maximumError": "1.5mb"
75+
"maximumWarning": "1MB",
76+
"maximumError": "1.5MB"
7177
},
7278
{
7379
"type": "anyComponentStyle",
74-
"maximumWarning": "2kb",
75-
"maximumError": "4kb"
80+
"maximumWarning": "2kB",
81+
"maximumError": "4kB"
7682
}
7783
],
7884
"outputHashing": "all",
@@ -116,7 +122,13 @@
116122
"polyfills": ["zone.js", "zone.js/testing"],
117123
"tsConfig": "tsconfig.spec.json",
118124
"inlineStyleLanguage": "scss",
119-
"assets": ["src/favicon.ico", "src/assets", "src/manifest.webmanifest"],
125+
"assets": [
126+
{
127+
"glob": "**/*",
128+
"input": "public"
129+
},
130+
"src/manifest.webmanifest"
131+
],
120132
"styles": ["src/styles.scss"],
121133
"stylePreprocessorOptions": {
122134
"includePaths": ["src/styles"]

app/eslint.config.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// @ts-check
2+
const eslint = require("@eslint/js");
3+
const tseslint = require("typescript-eslint");
4+
const angular = require("angular-eslint");
5+
const eslintConfigPrettier = require("eslint-config-prettier");
6+
7+
module.exports = tseslint.config(
8+
{
9+
files: ["**/*.ts"],
10+
extends: [
11+
eslint.configs.recommended,
12+
...tseslint.configs.strictTypeChecked,
13+
...tseslint.configs.stylisticTypeChecked,
14+
...angular.configs.tsRecommended,
15+
eslintConfigPrettier,
16+
],
17+
languageOptions: {
18+
parserOptions: {
19+
project: true,
20+
tsconfigRootDir: __dirname,
21+
},
22+
},
23+
processor: angular.processInlineTemplates,
24+
rules: {
25+
"@angular-eslint/component-selector": [
26+
"error",
27+
{
28+
type: "element",
29+
prefix: "app",
30+
style: "kebab-case",
31+
},
32+
],
33+
"@angular-eslint/directive-selector": [
34+
"error",
35+
{
36+
type: "attribute",
37+
prefix: "app",
38+
style: "camelCase",
39+
},
40+
],
41+
"@angular-eslint/prefer-standalone": "error",
42+
"@typescript-eslint/consistent-type-definitions": "off",
43+
"@typescript-eslint/method-signature-style": ["error", "property"],
44+
"@typescript-eslint/no-confusing-void-expression": [
45+
"error",
46+
{
47+
ignoreArrowShorthand: true,
48+
},
49+
],
50+
"@typescript-eslint/no-extraneous-class": [
51+
"error",
52+
{
53+
allowConstructorOnly: true,
54+
allowEmpty: true,
55+
},
56+
],
57+
"@typescript-eslint/no-unused-vars": [
58+
"error",
59+
{
60+
ignoreRestSiblings: true,
61+
},
62+
],
63+
"@typescript-eslint/restrict-template-expressions": [
64+
"error",
65+
{
66+
allowNumber: true,
67+
allowBoolean: true,
68+
},
69+
],
70+
"@typescript-eslint/unbound-method": [
71+
"error",
72+
{
73+
ignoreStatic: true,
74+
},
75+
],
76+
"no-console": ["error"],
77+
},
78+
},
79+
{
80+
files: ["**/*.html"],
81+
extends: [...angular.configs.templateRecommended, ...angular.configs.templateAccessibility],
82+
rules: {
83+
"@angular-eslint/template/prefer-control-flow": ["error"],
84+
"@angular-eslint/template/prefer-self-closing-tags": ["error"],
85+
},
86+
},
87+
);

app/ngsw-config.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
{
22
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
3-
"index": "/loader/index.html",
3+
"index": "/index.csr.html",
44
"assetGroups": [
55
{
66
"name": "app",
77
"installMode": "prefetch",
88
"resources": {
9-
"files": ["/favicon.ico", "/loader/index.html", "/manifest.webmanifest", "/*.css", "/*.js"]
9+
"files": ["/favicon.ico", "/index.csr.html", "/manifest.webmanifest", "/*.css", "/*.js"]
1010
}
1111
},
1212
{

0 commit comments

Comments
 (0)