Skip to content

Commit c204c08

Browse files
authored
feat: support angular@20 (#292)
* test: upgrade demo to 20-next * chore(demo): ensure min supported node version * chore: manual migration of app.config.server.ts * chore: bump * chore: bump to rc.0 * chore: bump to newest rc * test: add 20-rc fixtures * fix: add angular-20 server.ts signature * fix: handle case of outputPath not being explicitly defined * chore: remove outputPath from demo's angular.json to follow newly scaffolded projects setup * fix: nx handling * test: install new fixtures deps * chore: skip angular@20 on not supported node versions * chore: bump node18 version a bit to match node18 compatible angular min node version * chore: add node22 to test matrix * chore: don't suggest common engine for Angular 20 * docs: refresh readme for Angular 20 * chore: bump min to 20.19 * fix: add link to repo readme for more details * chore: upgrade demo and recreate angular-20 fixtures using most recent versions * chore: sync lock files
1 parent 4828c39 commit c204c08

Some content is hidden

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

70 files changed

+22781
-7553
lines changed

.github/workflows/test.yml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@ jobs:
1212
strategy:
1313
matrix:
1414
os: [ubuntu-latest, macOS-latest, windows-latest]
15-
node-version: [18.19.0, 20.13.1]
15+
node-version: [18.19.1, 20.19.1, 22]
1616
exclude:
1717
- os: macOS-latest
18-
node-version: 18.19.0
18+
node-version: 18.19.1
1919
- os: windows-latest
20-
node-version: 18.19.0
20+
node-version: 18.19.1
21+
- os: macOS-latest
22+
node-version: 20.19.1
23+
- os: windows-latest
24+
node-version: 20.19.1
2125
fail-fast: false
2226

2327
steps:
@@ -31,6 +35,6 @@ jobs:
3135
name: NPM Install
3236
- name: Linting
3337
run: npm run format:ci
34-
if: "${{ matrix.node-version == '20.13.1' }}"
38+
if: "${{ matrix.node-version == '20.19.1' }}"
3539
- name: Run tests
3640
run: npm run test

README.md

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ Netlify automatically detects Angular projects and sets up the latest version of
3333

3434
There's no further configuration needed from Netlify users.
3535

36-
### For Angular 19
36+
### For Angular 19 and Angular 20
3737

3838
If you are using Server-Side Rendering you will need to install Angular Runtime in your Angular project to be able to import required utilities to successfully deploy request handler to Netlify. See [Manual Installation](#manual-installation) for installations details. See [Request handling](#request-handling) for more information about request handler.
3939

4040
### Manual Installation
4141

42-
If you need to pin this plugin to a specific version or if you are using Server-Side Rendering with Angular 19, you will need to install the plugin manually.
42+
If you need to pin this plugin to a specific version or if you are using Server-Side Rendering with Angular 19 or Angular 20, you will need to install the plugin manually.
4343

4444
Install it via your package manager:
4545

@@ -77,9 +77,10 @@ To test this in local development, run your Angular project using `netlify serve
7777
```sh
7878
netlify serve
7979
```
80-
### App Engine Developer Preview usage with Angular@19
8180

82-
If you opt into the App Engine Developer Preview accessing `Request` and `Context` objects is streamlined. Instead of custom Netlify prefixed providers, you should use the standardized injection tokens for those provided by `@angular/core` instead:
81+
### App Engine usage
82+
83+
With App Engine accessing `Request` and `Context` objects is streamlined. Instead of custom Netlify prefixed providers, you should use the standardized injection tokens for those provided by `@angular/core` instead:
8384

8485
```diff
8586
+import { REQUEST, REQUEST_CONTEXT } from '@angular/core'
@@ -101,6 +102,8 @@ export class FooComponent {
101102
}
102103
```
103104

105+
Note that App Engine in Angular 19 is in Developer Preview and requires explicit opt-in.
106+
104107
## Request handling
105108

106109
Starting with Angular@19. The build plugin makes use of the `server.ts` file to handle requests. The default Angular scaffolding generates incompatible code for Netlify so the build plugin will swap it for compatible `server.ts` file automatically if it detects default version being used.
@@ -111,52 +114,52 @@ Make sure you have `@netlify/angular-runtime` version 2.2.0 or later installed i
111114

112115
If you need to customize the request handling, you can do so by copying one of code snippets below to your `server.ts` file.
113116

114-
If you did not opt into the App Engine Developer Preview:
117+
If you are using Angular 20 or Angular 19 with App Engine Developer Preview:
115118

116119
```ts
117-
import { CommonEngine } from '@angular/ssr/node'
118-
import { render } from '@netlify/angular-runtime/common-engine.mjs'
120+
import { AngularAppEngine, createRequestHandler } from '@angular/ssr'
121+
import { getContext } from '@netlify/angular-runtime/context.mjs'
119122

120-
const commonEngine = new CommonEngine()
123+
const angularAppEngine = new AngularAppEngine()
124+
125+
export async function netlifyAppEngineHandler(request: Request): Promise<Response> {
126+
const context = getContext()
121127

122-
export async function netlifyCommonEngineHandler(request: Request, context: any): Promise<Response> {
123128
// Example API endpoints can be defined here.
124129
// Uncomment and define endpoints as necessary.
125130
// const pathname = new URL(request.url).pathname;
126131
// if (pathname === '/api/hello') {
127132
// return Response.json({ message: 'Hello from the API' });
128133
// }
129134

130-
return await render(commonEngine)
135+
const result = await angularAppEngine.handle(request, context)
136+
return result || new Response('Not found', { status: 404 })
131137
}
138+
139+
/**
140+
* The request handler used by the Angular CLI (dev-server and during build).
141+
*/
142+
export const reqHandler = createRequestHandler(netlifyAppEngineHandler)
132143
```
133144

134-
If you opted into the App Engine Developer Preview:
145+
If you are using Angular 19 and did not opt into the App Engine Developer Preview:
135146

136147
```ts
137-
import { AngularAppEngine, createRequestHandler } from '@angular/ssr'
138-
import { getContext } from '@netlify/angular-runtime/context.mjs'
139-
140-
const angularAppEngine = new AngularAppEngine()
148+
import { CommonEngine } from '@angular/ssr/node'
149+
import { render } from '@netlify/angular-runtime/common-engine.mjs'
141150

142-
export async function netlifyAppEngineHandler(request: Request): Promise<Response> {
143-
const context = getContext()
151+
const commonEngine = new CommonEngine()
144152

153+
export async function netlifyCommonEngineHandler(request: Request, context: any): Promise<Response> {
145154
// Example API endpoints can be defined here.
146155
// Uncomment and define endpoints as necessary.
147156
// const pathname = new URL(request.url).pathname;
148157
// if (pathname === '/api/hello') {
149158
// return Response.json({ message: 'Hello from the API' });
150159
// }
151160

152-
const result = await angularAppEngine.handle(request, context)
153-
return result || new Response('Not found', { status: 404 })
161+
return await render(commonEngine)
154162
}
155-
156-
/**
157-
* The request handler used by the Angular CLI (dev-server and during build).
158-
*/
159-
export const reqHandler = createRequestHandler(netlifyAppEngineHandler)
160163
```
161164

162165
### Limitations

demo.test.mjs

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
11
import assert from 'node:assert'
2+
import { versions } from 'node:process'
23
import { test } from 'node:test'
34

4-
test('edge function config', async () => {
5-
const { config } = await import('./demo/.netlify/edge-functions/angular-ssr/angular-ssr.mjs')
5+
import { satisfies } from 'semver'
66

7-
const excludedPathsWithMaskedHashes = config.excludedPath.map((path) => path.replace(/-[A-Z\d]{8}\./, '-HASHHASH.'))
7+
test(
8+
'edge function config',
9+
{
10+
skip: !satisfies(versions.node, '>=20.11'),
11+
},
12+
async () => {
13+
const { config } = await import('./demo/.netlify/edge-functions/angular-ssr/angular-ssr.mjs')
814

9-
assert.deepEqual(excludedPathsWithMaskedHashes, [
10-
'/.netlify/*',
11-
'/favicon.ico',
12-
'/heroes/index.html',
13-
'/index.csr.html',
14-
'/main-HASHHASH.js',
15-
'/polyfills-HASHHASH.js',
16-
'/styles-HASHHASH.css',
17-
'/heroes',
18-
])
19-
})
15+
const excludedPathsWithMaskedHashes = config.excludedPath.map((path) => path.replace(/-[A-Z\d]{8}\./, '-HASHHASH.'))
16+
17+
assert.deepEqual(excludedPathsWithMaskedHashes, [
18+
'/.netlify/*',
19+
'/favicon.ico',
20+
'/heroes/index.html',
21+
'/index.csr.html',
22+
'/main-HASHHASH.js',
23+
'/polyfills-HASHHASH.js',
24+
'/styles-HASHHASH.css',
25+
'/heroes',
26+
])
27+
},
28+
)

demo/angular.json

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@
1111
"prefix": "app",
1212
"architect": {
1313
"build": {
14-
"builder": "@angular-devkit/build-angular:application",
14+
"builder": "@angular/build:application",
1515
"options": {
16-
"outputPath": "dist/demo",
1716
"index": "src/index.html",
1817
"browser": "src/main.ts",
1918
"polyfills": [
@@ -59,7 +58,7 @@
5958
"defaultConfiguration": "production"
6059
},
6160
"serve": {
62-
"builder": "@angular-devkit/build-angular:dev-server",
61+
"builder": "@angular/build:dev-server",
6362
"configurations": {
6463
"production": {
6564
"buildTarget": "demo:build:production"
@@ -71,13 +70,13 @@
7170
"defaultConfiguration": "development"
7271
},
7372
"extract-i18n": {
74-
"builder": "@angular-devkit/build-angular:extract-i18n",
73+
"builder": "@angular/build:extract-i18n",
7574
"options": {
7675
"buildTarget": "demo:build"
7776
}
7877
},
7978
"test": {
80-
"builder": "@angular-devkit/build-angular:karma",
79+
"builder": "@angular/build:karma",
8180
"options": {
8281
"polyfills": [
8382
"zone.js",
@@ -99,5 +98,31 @@
9998
},
10099
"cli": {
101100
"analytics": false
101+
},
102+
"schematics": {
103+
"@schematics/angular:component": {
104+
"type": "component"
105+
},
106+
"@schematics/angular:directive": {
107+
"type": "directive"
108+
},
109+
"@schematics/angular:service": {
110+
"type": "service"
111+
},
112+
"@schematics/angular:guard": {
113+
"typeSeparator": "."
114+
},
115+
"@schematics/angular:interceptor": {
116+
"typeSeparator": "."
117+
},
118+
"@schematics/angular:module": {
119+
"typeSeparator": "."
120+
},
121+
"@schematics/angular:pipe": {
122+
"typeSeparator": "."
123+
},
124+
"@schematics/angular:resolver": {
125+
"typeSeparator": "."
126+
}
102127
}
103128
}

0 commit comments

Comments
 (0)