@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,521 @@
|
|
|
1
|
+
# Guide Migration Angular 19→20 avec Exemples
|
|
2
|
+
|
|
3
|
+
## 🔴 RÈGLES OBLIGATOIRES (Breaking Changes)
|
|
4
|
+
|
|
5
|
+
### 1. Environnement & Dépendances
|
|
6
|
+
```bash
|
|
7
|
+
# ❌ Avant: TypeScript 5.5, Node.js 18.x
|
|
8
|
+
# ✅ Après:
|
|
9
|
+
npm install typescript@~5.8.0
|
|
10
|
+
# Node.js 20.11.1+ requis (PAS v18, ni v22.0-22.10)
|
|
11
|
+
# Vérifier: node --version >= 20.11.1
|
|
12
|
+
ng update @angular/core@20 @angular/cli@20
|
|
13
|
+
ng update @angular/material@20 # Si Material utilisé
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### 2. Renommages d'APIs critiques
|
|
17
|
+
```typescript
|
|
18
|
+
// ❌ Avant - Angular 19
|
|
19
|
+
afterRender(() => { /* code */ });
|
|
20
|
+
provideExperimentalCheckNoChangesForDebug();
|
|
21
|
+
provideExperimentalZonelessChangeDetection();
|
|
22
|
+
|
|
23
|
+
// ✅ Après - Angular 20
|
|
24
|
+
afterEveryRender(() => { /* code */ });
|
|
25
|
+
provideCheckNoChangesConfig(); // Note: useNgZoneOnStable supprimé
|
|
26
|
+
provideZonelessChangeDetection();
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 3. Resource API - Changements de propriétés
|
|
30
|
+
```typescript
|
|
31
|
+
// ❌ Avant
|
|
32
|
+
const userResource = resource({
|
|
33
|
+
request: () => ({id: userId()}),
|
|
34
|
+
loader: ({request}) => fetch(`/users/${request.id}`)
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const rxResource = rxResource({
|
|
38
|
+
loader: () => httpClient.get('/data')
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// ✅ Après
|
|
42
|
+
const userResource = resource({
|
|
43
|
+
params: () => ({id: userId()}), // request → params
|
|
44
|
+
loader: ({request}) => fetch(`/users/${request.id}`)
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const rxResource = rxResource({
|
|
48
|
+
stream: () => httpClient.get('/data') // loader → stream
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 4. ResourceStatus n'est plus un enum
|
|
53
|
+
```typescript
|
|
54
|
+
// ❌ Avant
|
|
55
|
+
import { ResourceStatus } from '@angular/core';
|
|
56
|
+
if (resource.status() === ResourceStatus.Loading) { }
|
|
57
|
+
|
|
58
|
+
// ✅ Après - Utiliser les strings
|
|
59
|
+
if (resource.status() === 'loading') { }
|
|
60
|
+
// Valeurs: 'idle' | 'loading' | 'resolved' | 'error' | 'reloading' | 'local'
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 5. Opérateurs JS dans templates
|
|
64
|
+
```typescript
|
|
65
|
+
// ❌ Avant - 'in' et 'void' comme propriétés
|
|
66
|
+
@Component({
|
|
67
|
+
template: `{{ in }} {{ void }}`
|
|
68
|
+
})
|
|
69
|
+
class MyComponent {
|
|
70
|
+
in = 'value';
|
|
71
|
+
void = 'data';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ✅ Après - Utiliser this pour éviter conflit
|
|
75
|
+
@Component({
|
|
76
|
+
template: `{{ this.in }} {{ this.void }}`
|
|
77
|
+
})
|
|
78
|
+
// OU renommer les propriétés
|
|
79
|
+
class MyComponent {
|
|
80
|
+
inValue = 'value';
|
|
81
|
+
voidData = 'data';
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 6. TestBed.flushEffects() supprimé
|
|
86
|
+
```typescript
|
|
87
|
+
// ❌ Avant
|
|
88
|
+
it('should test effects', () => {
|
|
89
|
+
TestBed.flushEffects();
|
|
90
|
+
expect(component.value).toBe('updated');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// ✅ Après
|
|
94
|
+
it('should test effects', () => {
|
|
95
|
+
TestBed.tick(); // Plus proche équivalent
|
|
96
|
+
expect(component.value).toBe('updated');
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 7. ng-reflect attributes supprimés
|
|
101
|
+
```typescript
|
|
102
|
+
// ❌ Avant - Dépendance sur ng-reflect-*
|
|
103
|
+
const value = element.getAttribute('ng-reflect-disabled');
|
|
104
|
+
|
|
105
|
+
// ✅ Après - Migration temporaire si nécessaire
|
|
106
|
+
bootstrapApplication(App, {
|
|
107
|
+
providers: [
|
|
108
|
+
provideNgReflectAttributes() // Dev mode uniquement
|
|
109
|
+
]
|
|
110
|
+
});
|
|
111
|
+
// Mais refactoriser le code pour ne pas en dépendre
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 8. Guards avec RedirectFn asynchrones
|
|
115
|
+
```typescript
|
|
116
|
+
// ❌ Avant
|
|
117
|
+
const redirectFn: RedirectFn = () => '/login';
|
|
118
|
+
|
|
119
|
+
// ✅ Après - Peut retourner Observable/Promise
|
|
120
|
+
const redirectFn: RedirectFn = () => {
|
|
121
|
+
// Peut retourner:
|
|
122
|
+
return '/login'; // string
|
|
123
|
+
return of('/login'); // Observable
|
|
124
|
+
return Promise.resolve('/login'); // Promise
|
|
125
|
+
};
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 9. Route Guards - Suppression du type 'any'
|
|
129
|
+
```typescript
|
|
130
|
+
// ❌ Avant
|
|
131
|
+
const routes: Routes = [{
|
|
132
|
+
canActivate: ['authGuard'] // string deprecated
|
|
133
|
+
}];
|
|
134
|
+
|
|
135
|
+
// ✅ Après
|
|
136
|
+
const routes: Routes = [{
|
|
137
|
+
canActivate: [AuthGuard] // ProviderToken<T> ou fonction
|
|
138
|
+
}];
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 10. Parenthèses respectées dans templates
|
|
142
|
+
```typescript
|
|
143
|
+
// ❌ Avant - Fonctionnait par accident
|
|
144
|
+
template: `{{ (foo?.bar).baz }}` // Ne crashait pas si foo null
|
|
145
|
+
|
|
146
|
+
// ✅ Après - Comportement JS natif
|
|
147
|
+
template: `{{ foo?.bar?.baz }}` // Utiliser optional chaining correctement
|
|
148
|
+
// (foo?.bar).baz lancera une erreur si foo est null
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### 11. TestBed.get() complètement supprimé
|
|
152
|
+
```typescript
|
|
153
|
+
// ❌ Avant
|
|
154
|
+
const service = TestBed.get(MyService);
|
|
155
|
+
|
|
156
|
+
// ✅ Après
|
|
157
|
+
const service = TestBed.inject(MyService);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 12. InjectFlags enum supprimé
|
|
161
|
+
```typescript
|
|
162
|
+
// ❌ Avant
|
|
163
|
+
import { InjectFlags } from '@angular/core';
|
|
164
|
+
inject(MyService, InjectFlags.Optional);
|
|
165
|
+
|
|
166
|
+
// ✅ Après
|
|
167
|
+
inject(MyService, { optional: true });
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 13. PendingTasks.run changements
|
|
171
|
+
```typescript
|
|
172
|
+
// ❌ Avant - Retour utilisé
|
|
173
|
+
const result = await pendingTasks.run(() => promise);
|
|
174
|
+
|
|
175
|
+
// ✅ Après - Utiliser add + gérer manuellement
|
|
176
|
+
const taskId = pendingTasks.add();
|
|
177
|
+
try {
|
|
178
|
+
const result = await promise;
|
|
179
|
+
// Gérer result
|
|
180
|
+
} finally {
|
|
181
|
+
pendingTasks.remove(taskId);
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### 14. Erreurs AsyncPipe vers ErrorHandler
|
|
186
|
+
```typescript
|
|
187
|
+
// ❌ Avant - Erreurs silencieuses dans tests
|
|
188
|
+
template: `{{ observableWithError$ | async }}`
|
|
189
|
+
|
|
190
|
+
// ✅ Après - Erreurs reportées à ErrorHandler
|
|
191
|
+
// Dans les tests:
|
|
192
|
+
TestBed.configureTestingModule({
|
|
193
|
+
providers: [{
|
|
194
|
+
provide: ErrorHandler,
|
|
195
|
+
useValue: { handleError: jasmine.createSpy('handleError') }
|
|
196
|
+
}]
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 🟡 RÈGLES RECOMMANDÉES
|
|
203
|
+
|
|
204
|
+
### 1. Dépréciation des directives structurelles
|
|
205
|
+
```typescript
|
|
206
|
+
// ⚠️ DÉPRÉCIÉ - Retrait prévu en v22
|
|
207
|
+
<div *ngIf="condition">Content</div>
|
|
208
|
+
<div *ngFor="let item of items">{{ item }}</div>
|
|
209
|
+
<div [ngSwitch]="value">
|
|
210
|
+
<div *ngSwitchCase="'a'">A</div>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
// ✅ Migration vers Control Flow
|
|
214
|
+
@if (condition) {
|
|
215
|
+
<div>Content</div>
|
|
216
|
+
}
|
|
217
|
+
@for (item of items; track item.id) {
|
|
218
|
+
<div>{{ item }}</div>
|
|
219
|
+
}
|
|
220
|
+
@switch (value) {
|
|
221
|
+
@case ('a') { <div>A</div> }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Migration automatique
|
|
225
|
+
ng generate @angular/core:control-flow
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### 2. APIs Signals stabilisées
|
|
229
|
+
```typescript
|
|
230
|
+
// ✅ Maintenant stables (plus de 'experimental')
|
|
231
|
+
effect(() => {
|
|
232
|
+
console.log('Value changed:', mySignal());
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const linked = linkedSignal({
|
|
236
|
+
source: sourceSignal,
|
|
237
|
+
computation: () => 'default'
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const signalFromObs = toSignal(observable$, {
|
|
241
|
+
initialValue: 'default'
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### 3. Hydratation incrémentale stable
|
|
246
|
+
```typescript
|
|
247
|
+
// ✅ API stable
|
|
248
|
+
bootstrapApplication(App, {
|
|
249
|
+
providers: [
|
|
250
|
+
provideClientHydration(withIncrementalHydration())
|
|
251
|
+
]
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Dans les templates
|
|
255
|
+
@defer (hydrate on viewport) {
|
|
256
|
+
<heavy-component />
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### 4. Configuration rendu par route stable
|
|
261
|
+
```typescript
|
|
262
|
+
// app.routes.server.ts - ✅ API stable
|
|
263
|
+
export const serverRoutes: ServerRoute[] = [
|
|
264
|
+
{ path: '/', renderMode: RenderMode.Prerender },
|
|
265
|
+
{ path: '/api/**', renderMode: RenderMode.Server },
|
|
266
|
+
{ path: '/admin/**', renderMode: RenderMode.Client },
|
|
267
|
+
{
|
|
268
|
+
path: '/product/:id',
|
|
269
|
+
renderMode: RenderMode.Prerender,
|
|
270
|
+
async getPrerenderParams() {
|
|
271
|
+
const ids = await getProductIds();
|
|
272
|
+
return ids.map(id => ({ id }));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
];
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### 5. Zoneless avec gestion d'erreurs
|
|
279
|
+
```typescript
|
|
280
|
+
// ✅ Preview développeur avec error handling
|
|
281
|
+
bootstrapApplication(App, {
|
|
282
|
+
providers: [
|
|
283
|
+
provideZonelessChangeDetection(),
|
|
284
|
+
provideBrowserGlobalErrorListeners() // Nouveau
|
|
285
|
+
]
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Suppression zone.js de angular.json
|
|
289
|
+
"polyfills": [
|
|
290
|
+
// "zone.js" ← Supprimer
|
|
291
|
+
]
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## 🟢 RÈGLES OPTIONNELLES
|
|
297
|
+
|
|
298
|
+
### 1. Nouvelles APIs Resource expérimentales
|
|
299
|
+
```typescript
|
|
300
|
+
// httpResource pour requêtes HTTP réactives
|
|
301
|
+
const userResource = httpResource<User>(() =>
|
|
302
|
+
`https://api.example.com/users/${userId()}`
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// Resource streaming
|
|
306
|
+
const streamResource = resource({
|
|
307
|
+
stream: () => new Promise<Signal<ResourceStreamItem<Data>>>((resolve) => {
|
|
308
|
+
const result = signal<{ value: Data }>({ value: initialData });
|
|
309
|
+
|
|
310
|
+
websocket.onmessage = (event) => {
|
|
311
|
+
result.update(current => ({
|
|
312
|
+
value: [...current.value, event.data]
|
|
313
|
+
}));
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
resolve(result);
|
|
317
|
+
})
|
|
318
|
+
});
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### 2. createComponent avec bindings
|
|
322
|
+
```typescript
|
|
323
|
+
// Nouveau: directives et bindings dynamiques
|
|
324
|
+
createComponent(MyDialog, {
|
|
325
|
+
bindings: [
|
|
326
|
+
inputBinding('title', titleSignal),
|
|
327
|
+
outputBinding('onClose', (result) => console.log(result)),
|
|
328
|
+
twoWayBinding('value', valueSignal)
|
|
329
|
+
],
|
|
330
|
+
directives: [
|
|
331
|
+
FocusTrap,
|
|
332
|
+
{
|
|
333
|
+
type: HasColor,
|
|
334
|
+
bindings: [inputBinding('color', () => 'primary')]
|
|
335
|
+
}
|
|
336
|
+
]
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### 3. Support Vitest expérimental
|
|
341
|
+
```bash
|
|
342
|
+
npm i vitest jsdom --save-dev
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
```json
|
|
346
|
+
// angular.json
|
|
347
|
+
"test": {
|
|
348
|
+
"builder": "@angular/build:unit-test",
|
|
349
|
+
"options": {
|
|
350
|
+
"runner": "vitest"
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### 4. Guide de style simplifié
|
|
356
|
+
```bash
|
|
357
|
+
# Plus de suffixes par défaut dans v20
|
|
358
|
+
ng generate component user
|
|
359
|
+
# Crée: user.ts (pas user.component.ts)
|
|
360
|
+
|
|
361
|
+
# Pour garder les suffixes (projets existants)
|
|
362
|
+
# ng update ajoutera automatiquement dans angular.json:
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
```json
|
|
366
|
+
{
|
|
367
|
+
"schematics": {
|
|
368
|
+
"@schematics/angular:component": { "type": "component" }
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### 5. Host bindings avec type checking
|
|
374
|
+
```typescript
|
|
375
|
+
// tsconfig.json
|
|
376
|
+
{
|
|
377
|
+
"angularCompilerOptions": {
|
|
378
|
+
"typeCheckHostBindings": true // Activé par défaut en v21
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Maintenant avec vérification de type
|
|
383
|
+
@Component({
|
|
384
|
+
host: {
|
|
385
|
+
'[class.active]': 'isActive()', // Vérifié
|
|
386
|
+
'(click)': 'onClick($event)' // Types vérifiés
|
|
387
|
+
}
|
|
388
|
+
})
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### 6. DatePipe avec avertissements
|
|
392
|
+
```typescript
|
|
393
|
+
// ⚠️ Avertissement si Y sans w
|
|
394
|
+
{{ date | date:'Y' }} // Suspect: année sans semaine
|
|
395
|
+
|
|
396
|
+
// ✅ Correct
|
|
397
|
+
{{ date | date:'y' }} // Année standard
|
|
398
|
+
{{ date | date:'Y w' }} // Année et semaine ensemble
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## 📊 Checklist Migration Complète
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
interface MigrationStatus20 {
|
|
407
|
+
obligatoire: {
|
|
408
|
+
nodeVersion: boolean; // 20.11.1+
|
|
409
|
+
typescript58: boolean; // v5.8+
|
|
410
|
+
apiRenames: boolean; // afterEveryRender, etc.
|
|
411
|
+
resourceParams: boolean; // request→params, loader→stream
|
|
412
|
+
resourceStatus: boolean; // enum→string
|
|
413
|
+
templateOperators: boolean; // this.in, this.void
|
|
414
|
+
testBedFlush: boolean; // flushEffects→tick
|
|
415
|
+
ngReflect: boolean; // Supprimer dépendances
|
|
416
|
+
redirectAsync: boolean; // Support Observable/Promise
|
|
417
|
+
routeGuards: boolean; // Pas de strings
|
|
418
|
+
parentheses: boolean; // Respect JS natif
|
|
419
|
+
testBedGet: boolean; // get→inject
|
|
420
|
+
injectFlags: boolean; // enum→options
|
|
421
|
+
pendingTasks: boolean; // run→add
|
|
422
|
+
asyncPipeErrors: boolean; // ErrorHandler
|
|
423
|
+
};
|
|
424
|
+
recommandee: {
|
|
425
|
+
controlFlow: boolean; // Migration *ngIf/*ngFor
|
|
426
|
+
signalsStable: boolean; // effect, linkedSignal, toSignal
|
|
427
|
+
hydratationStable: boolean; // Incremental hydration
|
|
428
|
+
renderModes: boolean; // Server routes config
|
|
429
|
+
zoneless: boolean; // Preview avec error handling
|
|
430
|
+
};
|
|
431
|
+
optionnelle: {
|
|
432
|
+
httpResource?: boolean; // Nouvelle API
|
|
433
|
+
createComponent?: boolean; // Bindings dynamiques
|
|
434
|
+
vitest?: boolean; // Test runner
|
|
435
|
+
styleGuide?: boolean; // Sans suffixes
|
|
436
|
+
hostBindings?: boolean; // Type checking
|
|
437
|
+
datePipe?: boolean; // Warnings Y/w
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
## 🚀 Script Migration Automatisé
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
#!/bin/bash
|
|
446
|
+
# migration-v20.sh
|
|
447
|
+
|
|
448
|
+
echo "🔄 Angular 19 -> 20 Migration Starting..."
|
|
449
|
+
|
|
450
|
+
# 1. Vérification Node.js
|
|
451
|
+
NODE_VERSION=$(node -v | cut -d'v' -f2)
|
|
452
|
+
if [[ ! "$NODE_VERSION" > "20.11.0" ]]; then
|
|
453
|
+
echo "❌ Node.js 20.11.1+ required"
|
|
454
|
+
exit 1
|
|
455
|
+
fi
|
|
456
|
+
|
|
457
|
+
# 2. Backup
|
|
458
|
+
git checkout -b migration-angular-20
|
|
459
|
+
git add . && git commit -m "Pre-migration v20 backup"
|
|
460
|
+
|
|
461
|
+
# 3. Core updates
|
|
462
|
+
echo "📦 Updating to Angular 20..."
|
|
463
|
+
npm install typescript@~5.8.0
|
|
464
|
+
ng update @angular/core@20 @angular/cli@20 --verbose
|
|
465
|
+
ng update @angular/material@20
|
|
466
|
+
|
|
467
|
+
# 4. Renommages automatiques
|
|
468
|
+
echo "🔧 Applying renames..."
|
|
469
|
+
find . -name "*.ts" -not -path "*/node_modules/*" | while read file; do
|
|
470
|
+
# API renames
|
|
471
|
+
sed -i 's/afterRender(/afterEveryRender(/g' "$file"
|
|
472
|
+
sed -i 's/provideExperimentalCheckNoChangesForDebug/provideCheckNoChangesConfig/g' "$file"
|
|
473
|
+
sed -i 's/provideExperimentalZonelessChangeDetection/provideZonelessChangeDetection/g' "$file"
|
|
474
|
+
sed -i 's/ExperimentalPendingTasks/PendingTasks/g' "$file"
|
|
475
|
+
|
|
476
|
+
# TestBed migrations
|
|
477
|
+
sed -i 's/TestBed\.flushEffects()/TestBed.tick()/g' "$file"
|
|
478
|
+
sed -i 's/TestBed\.get(/TestBed.inject(/g' "$file"
|
|
479
|
+
|
|
480
|
+
# Resource API
|
|
481
|
+
sed -i 's/request:/params:/g' "$file"
|
|
482
|
+
sed -i 's/loader:/stream:/g' "$file"
|
|
483
|
+
|
|
484
|
+
# ResourceStatus enum to strings
|
|
485
|
+
sed -i 's/ResourceStatus\.Loading/'\''loading'\''/g' "$file"
|
|
486
|
+
sed -i 's/ResourceStatus\.Idle/'\''idle'\''/g' "$file"
|
|
487
|
+
sed -i 's/ResourceStatus\.Resolved/'\''resolved'\''/g' "$file"
|
|
488
|
+
sed -i 's/ResourceStatus\.Error/'\''error'\''/g' "$file"
|
|
489
|
+
done
|
|
490
|
+
|
|
491
|
+
# 5. Migration Control Flow (recommandée)
|
|
492
|
+
echo "🎯 Migrating to Control Flow..."
|
|
493
|
+
ng generate @angular/core:control-flow
|
|
494
|
+
|
|
495
|
+
# 6. Tests
|
|
496
|
+
echo "🧪 Running validation..."
|
|
497
|
+
ng test --no-watch --code-coverage
|
|
498
|
+
ng build --configuration production
|
|
499
|
+
|
|
500
|
+
echo "✅ Migration complete! Check for TODOs:"
|
|
501
|
+
grep -r "TODO" src/ | grep -v node_modules
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
## ⚠️ Points d'Attention
|
|
505
|
+
|
|
506
|
+
| Catégorie | Risque | Solution | Vérification |
|
|
507
|
+
|-----------|--------|----------|--------------|
|
|
508
|
+
| Node.js | Version incompatible | v20.11.1+ obligatoire | `node -v` |
|
|
509
|
+
| Parenthèses | Nullish coalescing crash | Corriger chaînage | Tests unitaires |
|
|
510
|
+
| AsyncPipe | Erreurs cachées | Mock ErrorHandler | Tests passent |
|
|
511
|
+
| Control Flow | Dépréciation v22 | Migrer maintenant | 0 *ngIf/*ngFor |
|
|
512
|
+
| ng-reflect | Tests cassés | Refactorer ou provider | Pas de dépendance |
|
|
513
|
+
| Zoneless | SSR errors | Error handlers | Tests SSR |
|
|
514
|
+
|
|
515
|
+
## 📈 Métriques Cibles
|
|
516
|
+
|
|
517
|
+
- **Performance**: +35% avec zoneless (cf YouTube)
|
|
518
|
+
- **Bundle**: -20% sans Zone.js (~50KB)
|
|
519
|
+
- **Tests**: +40% vitesse avec Vitest
|
|
520
|
+
- **Hydratation**: -30% JavaScript initial
|
|
521
|
+
- **DevTools**: Intégration Chrome native
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"key": "split_large_components",
|
|
4
|
+
"summary": "Réorganiser composants/directives/pipes > 400 lignes en unités plus petites",
|
|
5
|
+
"description": "Les fichiers TypeScript contenant des composants, directives ou pipes de plus de 400 lignes doivent être réorganisés en unités plus petites et modulaires. Cette refactorisation améliore la maintenabilité, la testabilité et la réutilisabilité du code. Décomposer en sous-composants, extraire la logique métier dans des services, et séparer les préoccupations selon le principe de responsabilité unique.",
|
|
6
|
+
"estimated_time_per_occurrence": 150,
|
|
7
|
+
"onFile": null,
|
|
8
|
+
"fileTypes": [
|
|
9
|
+
"*.ts"
|
|
10
|
+
],
|
|
11
|
+
"regex": "@(Component|Directive|Pipe)\\s*\\(",
|
|
12
|
+
"astPattern": {
|
|
13
|
+
"nodeType": "Decorator",
|
|
14
|
+
"name": [
|
|
15
|
+
"Component",
|
|
16
|
+
"Directive",
|
|
17
|
+
"Pipe"
|
|
18
|
+
],
|
|
19
|
+
"excludeContext": [
|
|
20
|
+
"Comment",
|
|
21
|
+
"StringLiteral"
|
|
22
|
+
],
|
|
23
|
+
"fileMatches": {
|
|
24
|
+
"minLines": 400
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"category": "architecture",
|
|
28
|
+
"auto_fixable": false,
|
|
29
|
+
"migration_command": null,
|
|
30
|
+
"risk_level": "medium",
|
|
31
|
+
"code_description": "// Avant: composant monolithique (> 400 lignes)\n@Component({\n selector: 'app-user-dashboard',\n templateUrl: './user-dashboard.component.html'\n})\nexport class UserDashboardComponent {\n // 400+ lignes de logique métier, UI, gestion état...\n}\n\n// Après: décomposition en sous-composants\n@Component({\n selector: 'app-user-dashboard',\n template: `\n <app-user-profile [user]=\"user\"></app-user-profile>\n <app-user-stats [stats]=\"stats\"></app-user-stats>\n <app-user-actions [user]=\"user\"></app-user-actions>\n `\n})\nexport class UserDashboardComponent {\n // Orchestration uniquement (~50 lignes)\n user = inject(UserService).currentUser;\n stats = inject(UserStatsService).stats;\n}\n\n// + Extraire logique métier dans services\n// + Créer sous-composants spécialisés\n// + Séparer concerns (présentation, logique, état)",
|
|
32
|
+
"doc_url": "https://angular.dev/style-guide#single-responsibility"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"key": "split_large_functions",
|
|
36
|
+
"summary": "Refactoriser fonctions > 75 lignes pour réduire la complexité",
|
|
37
|
+
"description": "Les fonctions de plus de 75 lignes indiquent généralement une complexité excessive et violent le principe de responsabilité unique. Ces fonctions doivent être décomposées en sous-fonctions plus petites, plus lisibles et plus testables. Extraire les blocs logiques en fonctions distinctes, réduire le niveau d'imbrication et améliorer la séparation des préoccupations.",
|
|
38
|
+
"estimated_time_per_occurrence": 45,
|
|
39
|
+
"onFile": null,
|
|
40
|
+
"fileTypes": [
|
|
41
|
+
"*.ts"
|
|
42
|
+
],
|
|
43
|
+
"regex": "(function\\s+\\w+|\\w+\\s*\\([^)]*\\)\\s*\\{|=>\\s*\\{)",
|
|
44
|
+
"astPattern": {
|
|
45
|
+
"nodeType": [
|
|
46
|
+
"FunctionDeclaration",
|
|
47
|
+
"MethodDeclaration",
|
|
48
|
+
"ArrowFunction",
|
|
49
|
+
"FunctionExpression"
|
|
50
|
+
],
|
|
51
|
+
"excludeContext": [
|
|
52
|
+
"Comment",
|
|
53
|
+
"StringLiteral"
|
|
54
|
+
],
|
|
55
|
+
"body": {
|
|
56
|
+
"minLines": 75
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"category": "architecture",
|
|
60
|
+
"auto_fixable": false,
|
|
61
|
+
"migration_command": null,
|
|
62
|
+
"risk_level": "medium",
|
|
63
|
+
"code_description": "// Avant: fonction complexe (> 75 lignes)\nexport class UserService {\n processUserData(user: User): ProcessedUser {\n // 75+ lignes de logique\n // - Validation\n // - Transformation\n // - Calculs\n // - Formatage\n // - Gestion erreurs\n // ...\n }\n}\n\n// Après: décomposition en fonctions plus petites\nexport class UserService {\n processUserData(user: User): ProcessedUser {\n const validated = this.validateUser(user);\n const transformed = this.transformUserData(validated);\n const calculated = this.calculateMetrics(transformed);\n return this.formatOutput(calculated);\n }\n\n private validateUser(user: User): ValidatedUser {\n // Validation uniquement (~10 lignes)\n }\n\n private transformUserData(user: ValidatedUser): TransformedUser {\n // Transformation uniquement (~15 lignes)\n }\n\n private calculateMetrics(user: TransformedUser): CalculatedUser {\n // Calculs uniquement (~20 lignes)\n }\n\n private formatOutput(user: CalculatedUser): ProcessedUser {\n // Formatage uniquement (~10 lignes)\n }\n}\n\n// Bénéfices:\n// - Chaque fonction a une responsabilité unique\n// - Code plus lisible et maintenable\n// - Tests unitaires plus faciles\n// - Réutilisation possible des sous-fonctions",
|
|
64
|
+
"doc_url": "https://angular.dev/style-guide#small-functions"
|
|
65
|
+
}
|
|
66
|
+
]
|