@silvestv/migration-planificator 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +96 -0
- package/README.fr.md +359 -0
- package/README.md +360 -0
- package/SECURITY.md +187 -0
- package/dist/client.bundle.js +357 -0
- package/dist/src/core/app-analyzer.js +134 -0
- package/dist/src/core/ast/matchers/html/html-attribute-matcher.js +86 -0
- package/dist/src/core/ast/matchers/html/html-component-matcher.js +40 -0
- package/dist/src/core/ast/matchers/html/html-element-matcher.js +54 -0
- package/dist/src/core/ast/matchers/html/html-parser.js +58 -0
- package/dist/src/core/ast/matchers/html/html-pipe-matcher.js +95 -0
- package/dist/src/core/ast/matchers/html/html-text-matcher.js +53 -0
- package/dist/src/core/ast/matchers/html/index.js +118 -0
- package/dist/src/core/ast/matchers/index.js +377 -0
- package/dist/src/core/ast/matchers/ts/collection-matcher.js +51 -0
- package/dist/src/core/ast/matchers/ts/context-matcher.js +275 -0
- package/dist/src/core/ast/matchers/ts/decorator-matcher.js +465 -0
- package/dist/src/core/ast/matchers/ts/expression-matcher.js +237 -0
- package/dist/src/core/ast/matchers/ts/file-matcher.js +97 -0
- package/dist/src/core/ast/matchers/ts/hierarchy-matcher.js +172 -0
- package/dist/src/core/ast/matchers/ts/import-matcher.js +39 -0
- package/dist/src/core/ast/matchers/ts/index.js +53 -0
- package/dist/src/core/ast/matchers/ts/node-matcher.js +156 -0
- package/dist/src/core/ast/matchers/ts/symbol-matcher.js +281 -0
- package/dist/src/core/ast/matchers/ts/type-matcher.js +207 -0
- package/dist/src/core/ast/matchers/utils/matcher-helpers.js +37 -0
- package/dist/src/core/ast/scanner-ast.js +444 -0
- package/dist/src/core/project-detector.js +196 -0
- package/dist/src/core/project-strategy/index.js +9 -0
- package/dist/src/core/project-strategy/nx-strategy.js +130 -0
- package/dist/src/core/project-strategy/project-strategy.interface.js +2 -0
- package/dist/src/core/project-strategy/standalone-strategy.js +74 -0
- package/dist/src/core/project-strategy/strategy-factory.js +15 -0
- package/dist/src/core/rules-loader.js +89 -0
- package/dist/src/core/scan-reporter.js +316 -0
- package/dist/src/core/scanner-delta.js +339 -0
- package/dist/src/core/scanner-orchestrator.js +266 -0
- package/dist/src/core/scanner-regex.js +298 -0
- package/dist/src/core/workload/calculator.js +82 -0
- package/dist/src/core/workload/constants.js +15 -0
- package/dist/src/core/workload/grouping.js +18 -0
- package/dist/src/core/workload/hierarchy-calculator.js +127 -0
- package/dist/src/core/workload/index.js +11 -0
- package/dist/src/core/workload/metadata.js +20 -0
- package/dist/src/core/workload/special-workload.js +101 -0
- package/dist/src/core/workload/target-resolver.js +34 -0
- package/dist/src/data/angular-migration-rules.json +2337 -0
- package/dist/src/data/markdown/angular-migration-17-18.md +408 -0
- package/dist/src/data/markdown/angular-migration-18-19.md +600 -0
- package/dist/src/data/markdown/angular-migration-19-20.md +521 -0
- package/dist/src/data/rules/rearchitecture/rearchitecture-rules.json +66 -0
- package/dist/src/data/rules/to18/rules-18-obligatoire.json +374 -0
- package/dist/src/data/rules/to18/rules-18-optionnelle.json +188 -0
- package/dist/src/data/rules/to18/rules-18-recommande.json +218 -0
- package/dist/src/data/rules/to19/rules-19-obligatoire.json +348 -0
- package/dist/src/data/rules/to19/rules-19-optionnelle.json +223 -0
- package/dist/src/data/rules/to19/rules-19-recommande.json +200 -0
- package/dist/src/data/rules/to20/rules-20-obligatoire.json +556 -0
- package/dist/src/data/rules/to20/rules-20-optionnelle.json +190 -0
- package/dist/src/data/rules/to20/rules-20-recommande.json +151 -0
- package/dist/src/index.js +161 -0
- package/dist/src/models/chip-config.js +45 -0
- package/dist/src/models/interfaces/app-details.interface.js +2 -0
- package/dist/src/models/interfaces/ast-interfaces.js +5 -0
- package/dist/src/models/interfaces/ast-pattern.interface.js +2 -0
- package/dist/src/models/interfaces/client-interfaces.js +6 -0
- package/dist/src/models/interfaces/detection-stats.interface.js +2 -0
- package/dist/src/models/interfaces/html-match.interface.js +2 -0
- package/dist/src/models/interfaces/html-report-data.interface.js +2 -0
- package/dist/src/models/interfaces/lib-details.interface.js +2 -0
- package/dist/src/models/interfaces/migration-rules.interface.js +2 -0
- package/dist/src/models/interfaces/parsed-args.interface.js +2 -0
- package/dist/src/models/interfaces/project-info.interface.js +2 -0
- package/dist/src/models/interfaces/project-overview-data.interface.js +2 -0
- package/dist/src/models/interfaces/rule-match.interface.js +2 -0
- package/dist/src/models/interfaces/rule.interface.js +2 -0
- package/dist/src/models/interfaces/rules-by-priority.interface.js +2 -0
- package/dist/src/models/interfaces/scanner-comparison.interface.js +2 -0
- package/dist/src/models/interfaces/special-workload.interface.js +2 -0
- package/dist/src/models/interfaces/workload-report.interface.js +2 -0
- package/dist/src/models/types/build-block-blob.type.js +2 -0
- package/dist/src/models/types/migration-version.type.js +2 -0
- package/dist/src/models/types/project-type.type.js +2 -0
- package/dist/src/models/types/risk-level.type.js +2 -0
- package/dist/src/models/types/rule-category.type.js +2 -0
- package/dist/src/models/types/rule-priority.type.js +2 -0
- package/dist/src/models/types/rule-workload-type.type.js +2 -0
- package/dist/src/templates/landing/applications-analyzed.template.js +18 -0
- package/dist/src/templates/landing/card-app-info.template.js +63 -0
- package/dist/src/templates/landing/card-lib-info.template.js +67 -0
- package/dist/src/templates/landing/libs-analyzed.template.js +22 -0
- package/dist/src/templates/landing/nx-summary.template.js +115 -0
- package/dist/src/templates/landing/project-overview.template.js +27 -0
- package/dist/src/templates/page/index-page.template.js +95 -0
- package/dist/src/templates/page/main.template.js +83 -0
- package/dist/src/templates/page/migration-guide.template.js +175 -0
- package/dist/src/templates/page/workload-report.template.js +53 -0
- package/dist/src/templates/workload/dashboard.template.js +184 -0
- package/dist/src/templates/workload/filters-panel.template.js +215 -0
- package/dist/src/templates/workload/guide-rule-card.template.js +107 -0
- package/dist/src/templates/workload/hierarchy-nx.template.js +104 -0
- package/dist/src/templates/workload/hierarchy-shared.js +163 -0
- package/dist/src/templates/workload/hierarchy-standalone.template.js +36 -0
- package/dist/src/templates/workload/hierarchy.template.js +35 -0
- package/dist/src/templates/workload/rule-modal.template.js +280 -0
- package/dist/src/utils/core/args-parser.js +123 -0
- package/dist/src/utils/core/array-helpers.js +18 -0
- package/dist/src/utils/core/ast-helpers.js +99 -0
- package/dist/src/utils/core/file-helpers.js +109 -0
- package/dist/src/utils/core/html-helpers.js +36 -0
- package/dist/src/utils/core/index.js +28 -0
- package/dist/src/utils/core/logger.js +38 -0
- package/dist/src/utils/core/rule-helpers.js +15 -0
- package/dist/src/utils/core/workload-formatter.js +6 -0
- package/dist/src/utils/shared/array-helpers.js +25 -0
- package/dist/src/utils/shared/date-helpers.js +109 -0
- package/dist/src/utils/shared/html-helpers.js +37 -0
- package/dist/src/utils/shared/index.js +25 -0
- package/dist/src/utils/shared/rule-helpers.js +20 -0
- package/dist/src/utils/shared/time-formatters.js +76 -0
- package/dist/styles.css +2 -0
- package/package.json +107 -0
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
# Guide Migration Angular 18→19 avec Exemples
|
|
2
|
+
|
|
3
|
+
## 🔴 RÈGLES OBLIGATOIRES (Breaking Changes)
|
|
4
|
+
|
|
5
|
+
### 1. Environnement
|
|
6
|
+
```bash
|
|
7
|
+
# ❌ Avant: TypeScript 5.4
|
|
8
|
+
# ✅ Après:
|
|
9
|
+
npm install typescript@~5.5.0 # Minimum 5.5, 5.4 n'est plus supporté
|
|
10
|
+
ng update @angular/core@19 @angular/cli@19
|
|
11
|
+
ng update @angular/material@19 # Si Material est utilisé
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### 2. Standalone par défaut & Migration
|
|
15
|
+
```typescript
|
|
16
|
+
// ⚠️ BREAKING CHANGE: standalone est maintenant true par défaut
|
|
17
|
+
|
|
18
|
+
// Pour les projets existants, 2 options:
|
|
19
|
+
|
|
20
|
+
// Option 1: Quick fix - Ajouter standalone: false partout
|
|
21
|
+
@Component({
|
|
22
|
+
selector: 'app-legacy',
|
|
23
|
+
standalone: false, // Ajout obligatoire pour composants non-standalone
|
|
24
|
+
template: `...`
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// Option 2: Migration complète (recommandée)
|
|
28
|
+
// Exécuter les 3 commandes dans l'ordre:
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# 1. Convertir en standalone
|
|
33
|
+
ng generate @angular/core:standalone
|
|
34
|
+
# → "Convert all components, directives and pipes to standalone"
|
|
35
|
+
|
|
36
|
+
# 2. Supprimer NgModules inutiles
|
|
37
|
+
ng generate @angular/core:standalone
|
|
38
|
+
# → "Remove unnecessary NgModule classes"
|
|
39
|
+
|
|
40
|
+
# 3. Bootstrap standalone
|
|
41
|
+
ng generate @angular/core:standalone
|
|
42
|
+
# → "Bootstrap the project using standalone APIs"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 3. Template Reference Variables
|
|
46
|
+
```typescript
|
|
47
|
+
// ❌ Avant
|
|
48
|
+
@Component({
|
|
49
|
+
template: `
|
|
50
|
+
<div #myDiv></div>
|
|
51
|
+
<button (click)="onClick(this.myDiv)">{{ this.myDiv.textContent }}</button>
|
|
52
|
+
`
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// ✅ Après
|
|
56
|
+
@Component({
|
|
57
|
+
template: `
|
|
58
|
+
<div #myDiv></div>
|
|
59
|
+
<button (click)="onClick(myDiv)">{{ myDiv.textContent }}</button>
|
|
60
|
+
`
|
|
61
|
+
})
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 4. BrowserModule.withServerTransition
|
|
65
|
+
```typescript
|
|
66
|
+
// ❌ Avant
|
|
67
|
+
@NgModule({
|
|
68
|
+
imports: [
|
|
69
|
+
BrowserModule.withServerTransition({ appId: 'my-app' })
|
|
70
|
+
]
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// ✅ Après
|
|
74
|
+
import { APP_ID } from '@angular/core';
|
|
75
|
+
|
|
76
|
+
bootstrapApplication(AppComponent, {
|
|
77
|
+
providers: [
|
|
78
|
+
{ provide: APP_ID, useValue: 'my-app' }
|
|
79
|
+
]
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 5. KeyValueDiffers factories
|
|
84
|
+
```typescript
|
|
85
|
+
// ❌ Avant
|
|
86
|
+
const differs = inject(KeyValueDiffers);
|
|
87
|
+
const factories = differs.factories; // Propriété supprimée
|
|
88
|
+
|
|
89
|
+
// ✅ Après
|
|
90
|
+
const differs = inject(KeyValueDiffers);
|
|
91
|
+
// Utiliser directement les méthodes find() et create()
|
|
92
|
+
const differ = differs.find({}).create();
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 6. Angular.json Localize
|
|
96
|
+
```json
|
|
97
|
+
// ❌ Avant
|
|
98
|
+
{
|
|
99
|
+
"builder": "@angular/localize:extract",
|
|
100
|
+
"options": {
|
|
101
|
+
"name": "my-project"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ✅ Après
|
|
106
|
+
{
|
|
107
|
+
"builder": "@angular/localize:extract",
|
|
108
|
+
"options": {
|
|
109
|
+
"project": "my-project" // "name" → "project"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 7. PendingTasks (ExperimentalPendingTasks)
|
|
115
|
+
```typescript
|
|
116
|
+
// ❌ Avant
|
|
117
|
+
import { ExperimentalPendingTasks } from '@angular/core';
|
|
118
|
+
|
|
119
|
+
@Injectable()
|
|
120
|
+
export class MyService {
|
|
121
|
+
tasks = inject(ExperimentalPendingTasks);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ✅ Après
|
|
125
|
+
import { PendingTasks } from '@angular/core';
|
|
126
|
+
|
|
127
|
+
@Injectable()
|
|
128
|
+
export class MyService {
|
|
129
|
+
tasks = inject(PendingTasks);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 8. Router.errorHandler Migration
|
|
134
|
+
```typescript
|
|
135
|
+
// ❌ Avant
|
|
136
|
+
@Injectable()
|
|
137
|
+
export class MyErrorHandler {
|
|
138
|
+
constructor(private router: Router) {
|
|
139
|
+
this.router.errorHandler = (error) => {
|
|
140
|
+
console.error('Navigation error:', error);
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ✅ Après
|
|
146
|
+
bootstrapApplication(AppComponent, {
|
|
147
|
+
providers: [
|
|
148
|
+
provideRouter(routes,
|
|
149
|
+
withNavigationErrorHandler((error) => {
|
|
150
|
+
console.error('Navigation error:', error);
|
|
151
|
+
})
|
|
152
|
+
)
|
|
153
|
+
]
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 9. Resolve Interface avec RedirectCommand
|
|
158
|
+
```typescript
|
|
159
|
+
// ❌ Avant
|
|
160
|
+
class UserResolver implements Resolve<User> {
|
|
161
|
+
resolve(): Observable<User> | Promise<User> | User {
|
|
162
|
+
return this.userService.getUser();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ✅ Après
|
|
167
|
+
import { RedirectCommand } from '@angular/router';
|
|
168
|
+
|
|
169
|
+
class UserResolver implements Resolve<User | RedirectCommand> {
|
|
170
|
+
resolve(): Observable<User | RedirectCommand> | Promise<User | RedirectCommand> | User | RedirectCommand {
|
|
171
|
+
return this.userService.getUser().pipe(
|
|
172
|
+
catchError(() => of(new RedirectCommand('/error')))
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### 10. Tests Effects Timing
|
|
179
|
+
```typescript
|
|
180
|
+
// ❌ Avant - Tests avec effects
|
|
181
|
+
it('should update on effect', () => {
|
|
182
|
+
component.triggerEffect();
|
|
183
|
+
expect(component.value).toBe('updated'); // Peut échouer
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// ✅ Après
|
|
187
|
+
it('should update on effect', async () => {
|
|
188
|
+
component.triggerEffect();
|
|
189
|
+
await fixture.whenStable(); // Attendre les effects
|
|
190
|
+
// OU
|
|
191
|
+
fixture.detectChanges(); // Déclencher manuellement
|
|
192
|
+
expect(component.value).toBe('updated');
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 11. FakeAsync Flush par défaut
|
|
197
|
+
```typescript
|
|
198
|
+
// ❌ Avant
|
|
199
|
+
it('should work', fakeAsync(() => {
|
|
200
|
+
setTimeout(() => value = true, 100);
|
|
201
|
+
tick(100);
|
|
202
|
+
// Les timers ne sont pas automatiquement flush
|
|
203
|
+
}));
|
|
204
|
+
|
|
205
|
+
// ✅ Après
|
|
206
|
+
it('should work', fakeAsync(() => {
|
|
207
|
+
setTimeout(() => value = true, 100);
|
|
208
|
+
tick(100);
|
|
209
|
+
// Les timers sont automatiquement flush
|
|
210
|
+
}));
|
|
211
|
+
|
|
212
|
+
// Pour l'ancien comportement
|
|
213
|
+
it('should work', fakeAsync(() => {
|
|
214
|
+
// Code...
|
|
215
|
+
}, {flush: false})); // Désactiver le flush automatique
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### 12. CreateComponent et ng-content
|
|
219
|
+
```typescript
|
|
220
|
+
// ❌ Avant
|
|
221
|
+
const componentRef = createComponent(MyComponent, {
|
|
222
|
+
// Pas de contenu projeté → affiche le fallback par défaut
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// ✅ Après
|
|
226
|
+
const componentRef = createComponent(MyComponent, {
|
|
227
|
+
projectableNodes: [[document.createTextNode('')]] // Évite le fallback
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## 🟡 RÈGLES RECOMMANDÉES
|
|
234
|
+
|
|
235
|
+
### 1. Migrations vers Signals (Séparées)
|
|
236
|
+
```typescript
|
|
237
|
+
// Migration @Input vers input()
|
|
238
|
+
ng generate @angular/core:signal-input-migration
|
|
239
|
+
|
|
240
|
+
// ❌ Avant
|
|
241
|
+
@Input() name: string;
|
|
242
|
+
@Input() required age: number;
|
|
243
|
+
|
|
244
|
+
// ✅ Après
|
|
245
|
+
readonly name = input<string>();
|
|
246
|
+
readonly age = input.required<number>();
|
|
247
|
+
|
|
248
|
+
// Migration @Output vers output()
|
|
249
|
+
ng generate @angular/core:output-migration
|
|
250
|
+
|
|
251
|
+
// ❌ Avant
|
|
252
|
+
@Output() userChange = new EventEmitter<User>();
|
|
253
|
+
|
|
254
|
+
// ✅ Après
|
|
255
|
+
readonly userChange = output<User>();
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### 2. LinkedSignal pour état réactif
|
|
259
|
+
```typescript
|
|
260
|
+
// ❌ Avant - Gestion manuelle de dépendances
|
|
261
|
+
export class FilterComponent {
|
|
262
|
+
category = signal('all');
|
|
263
|
+
itemsPerPage = signal(10);
|
|
264
|
+
|
|
265
|
+
ngOnInit() {
|
|
266
|
+
effect(() => {
|
|
267
|
+
if (this.category() !== 'all') {
|
|
268
|
+
this.itemsPerPage.set(10); // Reset manuel
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ✅ Après avec linkedSignal
|
|
275
|
+
export class FilterComponent {
|
|
276
|
+
category = signal('all');
|
|
277
|
+
itemsPerPage = linkedSignal({
|
|
278
|
+
source: this.category,
|
|
279
|
+
computation: () => 10 // Reset automatique
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### 4. Nouveaux Initialisateurs
|
|
285
|
+
```typescript
|
|
286
|
+
// ❌ Avant
|
|
287
|
+
export const appConfig = {
|
|
288
|
+
providers: [
|
|
289
|
+
{
|
|
290
|
+
provide: APP_INITIALIZER,
|
|
291
|
+
useFactory: () => () => inject(ConfigService).load(),
|
|
292
|
+
multi: true,
|
|
293
|
+
deps: [ConfigService]
|
|
294
|
+
}
|
|
295
|
+
]
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// ✅ Après
|
|
299
|
+
import { provideAppInitializer } from '@angular/core';
|
|
300
|
+
|
|
301
|
+
export const appConfig = {
|
|
302
|
+
providers: [
|
|
303
|
+
provideAppInitializer(() => {
|
|
304
|
+
const configService = inject(ConfigService);
|
|
305
|
+
return configService.load();
|
|
306
|
+
})
|
|
307
|
+
]
|
|
308
|
+
};
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### 5. Partage via RouterOutlet
|
|
312
|
+
```typescript
|
|
313
|
+
// ❌ Avant - Props drilling ou services
|
|
314
|
+
@Component({
|
|
315
|
+
template: `<router-outlet></router-outlet>`
|
|
316
|
+
})
|
|
317
|
+
export class ParentComponent {
|
|
318
|
+
// Données passées via services ou inputs complexes
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ✅ Après avec routerOutletData
|
|
322
|
+
@Component({
|
|
323
|
+
template: `
|
|
324
|
+
<router-outlet [routerOutletData]="sharedData"></router-outlet>
|
|
325
|
+
`
|
|
326
|
+
})
|
|
327
|
+
export class ParentComponent {
|
|
328
|
+
sharedData = signal({ theme: 'dark', user: currentUser });
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Dans le composant enfant
|
|
332
|
+
export class ChildComponent {
|
|
333
|
+
data = inject(ROUTER_OUTLET_DATA); // Signal réactif
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## 🟢 RÈGLES OPTIONNELLES
|
|
340
|
+
|
|
341
|
+
### 1. API Resource pour HTTP
|
|
342
|
+
```typescript
|
|
343
|
+
// ✅ Nouvelle approche optionnelle
|
|
344
|
+
import { resource, ResourceStatus } from '@angular/core';
|
|
345
|
+
|
|
346
|
+
@Component({
|
|
347
|
+
template: `
|
|
348
|
+
@switch (userResource.status()) {
|
|
349
|
+
@case (ResourceStatus.Loading) { <p>Chargement...</p> }
|
|
350
|
+
@case (ResourceStatus.Error) { <p>Erreur: {{ userResource.error() }}</p> }
|
|
351
|
+
@case (ResourceStatus.Resolved) {
|
|
352
|
+
<p>{{ userResource.value().name }}</p>
|
|
353
|
+
<button (click)="userResource.reload()">Recharger</button>
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
`
|
|
357
|
+
})
|
|
358
|
+
export class UserComponent {
|
|
359
|
+
userResource = resource({
|
|
360
|
+
loader: () => fetch('/api/user').then(r => r.json())
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Avec paramètres réactifs
|
|
365
|
+
export class SearchComponent {
|
|
366
|
+
searchTerm = signal('');
|
|
367
|
+
results = resource({
|
|
368
|
+
request: this.searchTerm,
|
|
369
|
+
loader: ({ request }) =>
|
|
370
|
+
fetch(`/api/search?q=${request}`).then(r => r.json())
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### 2. Hydratation Incrémentale
|
|
376
|
+
```typescript
|
|
377
|
+
// ✅ Nouveau - Hydratation différée
|
|
378
|
+
@Component({
|
|
379
|
+
template: `
|
|
380
|
+
@defer (render on server; hydrate on interaction) {
|
|
381
|
+
<heavy-component />
|
|
382
|
+
} @placeholder {
|
|
383
|
+
<loading-skeleton />
|
|
384
|
+
}
|
|
385
|
+
`
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
// main.ts
|
|
389
|
+
bootstrapApplication(App, {
|
|
390
|
+
providers: [
|
|
391
|
+
provideClientHydration(withIncrementalHydration())
|
|
392
|
+
]
|
|
393
|
+
});
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### 2. Service Worker Amélioré
|
|
397
|
+
```json
|
|
398
|
+
// ngsw-config.json
|
|
399
|
+
{
|
|
400
|
+
"applicationMaxAge": "7d", // Nouveau
|
|
401
|
+
"dataGroups": [{
|
|
402
|
+
"name": "api-cache",
|
|
403
|
+
"urls": ["/api/**"],
|
|
404
|
+
"cacheConfig": {
|
|
405
|
+
"maxAge": "1h",
|
|
406
|
+
"refreshAhead": "10m" // Nouveau
|
|
407
|
+
}
|
|
408
|
+
}]
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### 3. Rendu Hybride
|
|
413
|
+
```typescript
|
|
414
|
+
// app.routes.server.ts (nouveau fichier)
|
|
415
|
+
import { RenderMode } from '@angular/ssr';
|
|
416
|
+
|
|
417
|
+
export const serverRoutes = [
|
|
418
|
+
{ path: '', renderMode: RenderMode.Prerender },
|
|
419
|
+
{ path: 'blog/**', renderMode: RenderMode.Server },
|
|
420
|
+
{ path: 'admin/**', renderMode: RenderMode.Client }
|
|
421
|
+
];
|
|
422
|
+
|
|
423
|
+
// Installation
|
|
424
|
+
// ng add @angular/ssr --server-routing
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### 4. Control Flow Migration
|
|
428
|
+
```typescript
|
|
429
|
+
// ❌ Avant - Directives
|
|
430
|
+
<div *ngIf="show">Content</div>
|
|
431
|
+
<div *ngFor="let item of items">{{ item }}</div>
|
|
432
|
+
|
|
433
|
+
// ✅ Après - Control Flow
|
|
434
|
+
@if (show) {
|
|
435
|
+
<div>Content</div>
|
|
436
|
+
}
|
|
437
|
+
@for (item of items; track item.id) {
|
|
438
|
+
<div>{{ item }}</div>
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Migration automatique
|
|
442
|
+
// ng generate @angular/core:control-flow
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### 6. Lazy Loading Routes Migration
|
|
446
|
+
```bash
|
|
447
|
+
# Migration automatique vers lazy loading
|
|
448
|
+
ng generate @angular/core:route-lazy-loading
|
|
449
|
+
|
|
450
|
+
# Pour un dossier spécifique
|
|
451
|
+
ng generate @angular/core:route-lazy-loading --path src/app/features
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
// ❌ Avant - Eager loading
|
|
456
|
+
const routes: Routes = [
|
|
457
|
+
{
|
|
458
|
+
path: 'users',
|
|
459
|
+
component: UsersComponent // Composant standalone chargé eagerly
|
|
460
|
+
}
|
|
461
|
+
];
|
|
462
|
+
|
|
463
|
+
// ✅ Après - Lazy loading automatique
|
|
464
|
+
const routes: Routes = [
|
|
465
|
+
{
|
|
466
|
+
path: 'users',
|
|
467
|
+
loadComponent: () => import('./users/users.component').then(m => m.UsersComponent)
|
|
468
|
+
}
|
|
469
|
+
];
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## 📊 Checklist Migration Complète
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
interface MigrationStatus19 {
|
|
478
|
+
obligatoire: {
|
|
479
|
+
typescript55: boolean; // v5.5+
|
|
480
|
+
standaloneDefault: boolean; // standalone: false explicite
|
|
481
|
+
templateRefs: boolean; // Supprimer this.
|
|
482
|
+
browserModule: boolean; // APP_ID token
|
|
483
|
+
keyValueDiffers: boolean; // Supprimer .factories
|
|
484
|
+
localizeName: boolean; // name → project
|
|
485
|
+
pendingTasks: boolean; // Experimental → stable
|
|
486
|
+
routerErrorHandler: boolean; // withNavigationErrorHandler
|
|
487
|
+
resolveRedirect: boolean; // RedirectCommand dans Resolve
|
|
488
|
+
effectsTiming: boolean; // whenStable() pour tests
|
|
489
|
+
fakeAsyncFlush: boolean; // {flush: false} si nécessaire
|
|
490
|
+
createComponent: boolean; // projectableNodes vide
|
|
491
|
+
};
|
|
492
|
+
recommandee: {
|
|
493
|
+
signalsMigration?: boolean; // @Input → input()
|
|
494
|
+
resourceAPI?: boolean; // Gestion état HTTP
|
|
495
|
+
linkedSignal?: boolean; // Signaux liés
|
|
496
|
+
newInitializers?: boolean; // provideAppInitializer
|
|
497
|
+
routerOutletData?: boolean; // Partage parent-enfant
|
|
498
|
+
};
|
|
499
|
+
optionnelle: {
|
|
500
|
+
incrementalHydration?: boolean; // @defer hydrate
|
|
501
|
+
serviceWorker?: boolean; // refreshAhead, maxAge
|
|
502
|
+
hybridRendering?: boolean; // Rendu par route
|
|
503
|
+
controlFlow?: boolean; // @if, @for, @switch
|
|
504
|
+
injectFunction?: boolean; // inject() vs constructor
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
## 🚀 Script Migration Automatisé
|
|
510
|
+
|
|
511
|
+
```bash
|
|
512
|
+
#!/bin/bash
|
|
513
|
+
# migration-v19.sh
|
|
514
|
+
|
|
515
|
+
echo "🔄 Angular 18 -> 19 Migration Starting..."
|
|
516
|
+
|
|
517
|
+
# 1. Backup
|
|
518
|
+
git checkout -b migration-angular-19
|
|
519
|
+
git add . && git commit -m "Pre-migration v19 backup"
|
|
520
|
+
|
|
521
|
+
# 2. Core updates
|
|
522
|
+
echo "📦 Updating to Angular 19..."
|
|
523
|
+
npm install typescript@~5.5.0
|
|
524
|
+
ng update @angular/core@19 @angular/cli@19 --verbose
|
|
525
|
+
ng update @angular/material@19 # Si utilisé
|
|
526
|
+
|
|
527
|
+
# 3. Fix automatiques obligatoires
|
|
528
|
+
echo "🔧 Applying mandatory fixes..."
|
|
529
|
+
find . -name "*.ts" -not -path "*/node_modules/*" | while read file; do
|
|
530
|
+
# Remove this. prefix from template refs
|
|
531
|
+
sed -i 's/this\.\([a-zA-Z_][a-zA-Z0-9_]*\)/\1/g' "$file"
|
|
532
|
+
|
|
533
|
+
# Rename ExperimentalPendingTasks
|
|
534
|
+
sed -i 's/ExperimentalPendingTasks/PendingTasks/g' "$file"
|
|
535
|
+
done
|
|
536
|
+
|
|
537
|
+
# 4. Angular.json updates
|
|
538
|
+
sed -i 's/"name":/"project":/g' angular.json
|
|
539
|
+
|
|
540
|
+
# 5. Migrations recommandées (interactives)
|
|
541
|
+
echo "🎯 Running recommended migrations..."
|
|
542
|
+
|
|
543
|
+
# Migration Standalone (3 étapes)
|
|
544
|
+
echo "📦 Step 1/3: Converting to standalone..."
|
|
545
|
+
ng generate @angular/core:standalone # Choisir option 1
|
|
546
|
+
|
|
547
|
+
echo "📦 Step 2/3: Removing unnecessary modules..."
|
|
548
|
+
ng generate @angular/core:standalone # Choisir option 2
|
|
549
|
+
|
|
550
|
+
echo "📦 Step 3/3: Bootstrap with standalone API..."
|
|
551
|
+
ng generate @angular/core:standalone # Choisir option 3
|
|
552
|
+
|
|
553
|
+
# Migrations des signals (séparées)
|
|
554
|
+
echo "🎯 Migrating inputs to signals..."
|
|
555
|
+
ng generate @angular/core:signal-input-migration --best-effort-mode
|
|
556
|
+
|
|
557
|
+
echo "🎯 Migrating outputs to signals..."
|
|
558
|
+
ng generate @angular/core:output-migration
|
|
559
|
+
|
|
560
|
+
echo "🎯 Migrating queries to signals..."
|
|
561
|
+
ng generate @angular/core:signal-queries-migration
|
|
562
|
+
|
|
563
|
+
# 6. Migrations optionnelles
|
|
564
|
+
read -p "Apply optional migrations? (y/n) " -n 1 -r
|
|
565
|
+
echo
|
|
566
|
+
if [[ $REPLY =~ ^[Yy]$ ]]
|
|
567
|
+
then
|
|
568
|
+
echo "🔄 Applying optional migrations..."
|
|
569
|
+
ng generate @angular/core:control-flow
|
|
570
|
+
ng generate @angular/core:inject
|
|
571
|
+
ng generate @angular/core:route-lazy-loading
|
|
572
|
+
fi
|
|
573
|
+
|
|
574
|
+
# 7. Tests
|
|
575
|
+
echo "🧪 Running validation..."
|
|
576
|
+
ng test --no-watch --code-coverage
|
|
577
|
+
ng build --configuration production
|
|
578
|
+
|
|
579
|
+
echo "✅ Migration complete! Review changes with: git diff"
|
|
580
|
+
echo "📝 Check for TODOs: grep -r 'TODO' src/"
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
## ⚠️ Points d'Attention & Validation
|
|
584
|
+
|
|
585
|
+
| Catégorie | Risque | Solution | Vérification |
|
|
586
|
+
|-----------|--------|----------|--------------|
|
|
587
|
+
| Standalone | Composants legacy cassés | Ajouter `standalone: false` | Build sans erreurs |
|
|
588
|
+
| Effects | Tests timing incorrects | Utiliser `whenStable()` | Tests passent |
|
|
589
|
+
| FakeAsync | Comportement modifié | Option `{flush: false}` | Tests timing OK |
|
|
590
|
+
| Signals | Migration incomplète | Mode `--best-effort` | Vérifier TODOs |
|
|
591
|
+
| Hydratation | SSR performance | Activer incremental | Mesurer LCP |
|
|
592
|
+
| Router | ErrorHandler obsolète | Migration provider | Gestion erreurs OK |
|
|
593
|
+
|
|
594
|
+
## 📈 Métriques Cibles
|
|
595
|
+
|
|
596
|
+
- **Bundle**: -15% avec signals et lazy loading
|
|
597
|
+
- **Hydratation**: -30% TTI avec incremental
|
|
598
|
+
- **Build**: -20% temps avec esbuild
|
|
599
|
+
- **Tests**: -40% durée avec esbuild runner
|
|
600
|
+
- **Coverage**: Maintenir >80%
|