Lecture : 12 minutes · Niveau : intermédiaire · Mise à jour : avril 2026
Migrer un projet JavaScript existant vers TypeScript ne doit pas être un big bang. Mal préparée, la migration provoque des semaines de blocage et un découragement de l’équipe. Bien planifiée, elle se fait en arrière-plan sans interrompre les livraisons et apporte une valeur croissante au fil des semaines. Ce guide trace une stratégie pragmatique éprouvée sur des projets de production.
Voir aussi → TypeScript et JavaScript moderne : guide pratique.
Sommaire
- Évaluer si la migration en vaut la peine
- Stratégie progressive vs big bang
- Setup initial sans bloquer
- Première vague : ajouter les types sans renommer
- Deuxième vague : renommer .js en .ts
- Troisième vague : durcir progressivement
- Gérer les bibliothèques externes
- Outils et automatisation
- Communiquer avec l’équipe
- FAQ
1. Évaluer si la migration en vaut la peine
Avant de migrer, vérifier que ça vaut le coût.
Bons candidats à la migration : projet actif avec développement régulier, équipe de 2+ développeurs, base de code 5k+ lignes, bugs récurrents liés aux types (passage de mauvais objet, propriété inexistante, undefined non géré), refactoring fréquents, intégration avec des bibliothèques bien typées.
Migration discutable : projet en mode maintenance pure (très peu de changements), équipe très petite et stable, taille modeste, deadline serrée à court terme qui rendrait la migration dangereuse.
Migration probablement inutile : script ad-hoc qui tourne une fois, projet en fin de vie qui sera remplacé bientôt.
Un signal pratique : si l’équipe consulte régulièrement la console au runtime pour découvrir la forme exacte d’un objet manipulé, TypeScript apporterait une valeur immédiate. Si l’équipe a déjà une excellente discipline JSDoc et de tests, le gain sera plus marginal.
2. Stratégie progressive vs big bang
Deux écoles existent, mais la pratique a tranché : la migration progressive gagne.
Big bang : tout convertir en une seule branche, puis merger. Avantages : pas de mode mixte, code 100% TypeScript d’un coup. Inconvénients : énorme PR difficile à reviewer, conflit avec toutes les PRs en cours pendant la migration, risque élevé de bugs subtils introduits, démotivation si la migration prend des semaines bloquantes.
Progressive : configurer TypeScript pour accepter à la fois des fichiers .ts et .js, convertir fichier par fichier en parallèle des autres tâches. Avantages : pas de blocage, livraisons continues, valeur ajoutée incrémentale (chaque fichier converti est un gain). Inconvénients : période de cohabitation plus longue, discipline nécessaire pour ne pas l’oublier en route.
Pour 95% des projets, l’approche progressive est la bonne.
3. Setup initial sans bloquer
L’objectif du setup est d’ajouter TypeScript sans casser ce qui marche.
npm install -D typescript @types/node tsx
npx tsc --init
tsconfig.json permissif au démarrage :
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"allowJs": true,
"checkJs": false,
"strict": false,
"noImplicitAny": false,
"strictNullChecks": false,
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Points clés :
– allowJs: true permet à TypeScript de gérer les .js aux côtés des .ts
– checkJs: false ne vérifie pas les .js (sinon trop d’erreurs immédiates)
– strict: false au début, on durcira plus tard
Ajouter aussi un script de check dans package.json :
{
"scripts": {
"type-check": "tsc --noEmit"
}
}
Cela permet de vérifier les types sans produire de fichiers — utile en CI.
À ce stade, le projet compile et tourne exactement comme avant. La seule différence : on peut maintenant créer des fichiers .ts à côté des .js existants.
4. Première vague : ajouter les types sans renommer
Avant même de renommer, on peut typer du code JavaScript via des commentaires JSDoc. TypeScript les comprend.
/**
* @param {string} email
* @returns {boolean}
*/
function valider(email) {
return /^[^@]+@[^@]+\.[^@]+$/.test(email);
}
/**
* @typedef {Object} Client
* @property {number} id
* @property {string} nom
* @property {string} email
* @property {string} [telephone]
*/
/** @type {Client} */
const c = { id: 1, nom: "Acme", email: "contact@acme.test" };
Activer checkJs: true (au moins pour quelques fichiers via // @ts-check en tête) commence à faire vérifier ces annotations. Bénéfice : on commence à attraper des bugs sans avoir renommé un seul fichier.
Cette étape est optionnelle mais utile pour des équipes qui veulent voir le bénéfice de TypeScript avant de s’engager dans le renommage massif.
5. Deuxième vague : renommer .js en .ts
Maintenant on commence le vrai travail. Stratégie : renommer un fichier à la fois, par PR séparée si possible.
Par où commencer
L’ordre optimal :
- Fichiers feuilles : modules sans dépendance interne (utils, helpers, constants)
- Modules avec peu de dépendances : modèles de données, validateurs
- Logique métier : services, contrôleurs
- Points d’entrée :
main.ts,index.ts
Cet ordre limite les erreurs en cascade. Chaque fichier renommé bénéficie déjà des types des dépendances précédemment converties.
Que faire à la conversion
Pour chaque fichier renommé .js → .ts :
- Renommer
- Lancer
npm run type-check - Corriger les erreurs apparues, par ordre de gravité
- Si une erreur est trop coûteuse à corriger immédiatement :
// @ts-expect-erroravec un commentaire explicatif et un TODO. À retirer plus tard. - Commiter
Quelques transformations courantes :
// JS
function additionner(a, b) {
return a + b;
}
// TS
function additionner(a: number, b: number): number {
return a + b;
}
// JS
const config = {};
config.port = process.env.PORT || 3000;
// TS
const config: { port: number | string } = {};
config.port = process.env.PORT || 3000;
// ou mieux : interface
interface Config { port: number | string; }
const config: Config = { port: process.env.PORT || 3000 };
Quand any est acceptable temporairement
Pendant la migration, any peut servir de marqueur. Plutôt que de bloquer 2 heures à typer correctement une fonction complexe :
// TODO: type properly
function process(data: any): any {
// ...
}
Mieux vaut un fichier converti avec quelques any qu’un fichier qui reste en .js. Garder une liste des any à éliminer plus tard, traités lors de sprints dédiés ou en accompagnement de modifications futures.
6. Troisième vague : durcir progressivement
Une fois la majorité (~80%) du code en TypeScript, durcir la configuration.
Activer ces options dans cet ordre, en corrigeant les erreurs entre chaque :
noImplicitAny: truestrictNullChecks: true(souvent l’étape qui révèle le plus de bugs)strictFunctionTypes: truestrict: true(active toutes les options de la famille strict)noUnusedLocalsetnoUnusedParametersnoUncheckedIndexedAccess: true(peut faire beaucoup de bruit)- Désactiver
allowJsune fois 100% migré
Chaque palier demande typiquement quelques heures à quelques jours de travail selon la taille de la base. Faire ces durcissements pendant des sprints calmes, pas pendant des urgences business.
Détail des options dans TypeScript tsconfig strict en pratique.
7. Gérer les bibliothèques externes
Pour chaque dépendance utilisée :
Cas 1 : la lib expose ses types nativement
La majorité des bibliothèques modernes (React, Express, Prisma, etc.) sont écrites en TypeScript ou exposent des typings officiels. Aucune action.
Cas 2 : un paquet @types/... existe sur DefinitelyTyped
npm install -D @types/lodash @types/express
Ces paquets communautaires couvrent la plupart des bibliothèques populaires.
Cas 3 : aucun typing existant
Rare en 2026, mais possible pour des libs de niche. Solutions :
// Option A : déclaration minimaliste dans un .d.ts
declare module "ma-lib-non-typee"; // implicit any partout
// Option B : déclaration partielle
declare module "ma-lib-non-typee" {
export function init(config: { apiKey: string }): void;
export function envoyer(message: string): Promise<boolean>;
}
Option A est rapide mais perd la sécurité. Option B demande quelques heures mais apporte de la valeur durable. Pour une dépendance critique : envisager de contribuer le typing au repo upstream pour bénéficier à la communauté.
8. Outils et automatisation
Conversion semi-automatique
ts-migrate (Airbnb), dts-gen, et autres outils peuvent automatiser une partie du travail. À utiliser avec prudence : ils ajoutent souvent beaucoup de any qu’il faut nettoyer ensuite. La conversion manuelle reste plus propre.
Lint pour pousser à la rigueur
ESLint avec le plugin typescript-eslint permet d’imposer des règles :
@typescript-eslint/no-explicit-any: warn ou error sur lesanyexplicites@typescript-eslint/no-unsafe-*: warn sur les opérations non-sûres@typescript-eslint/strict-boolean-expressions: force les conditions explicites
Ces règles sont à activer progressivement pendant la migration, pas en bloc.
CI bloquant
Une fois la base raisonnablement typée, ajouter un step CI qui fait tsc --noEmit et fait échouer le build si erreurs. Cela protège contre les régressions.
# Dans GitHub Actions
- name: Type check
run: npm run type-check
Voir GitHub Actions tutoriel.
9. Communiquer avec l’équipe
Une migration longue (souvent 2-4 mois pour un projet moyen, plus pour un gros) nécessite de l’alignement.
Points à clarifier
- Pourquoi on migre : exposer le bénéfice attendu (moins de bugs, meilleure auto-complétion), pas juste « parce que c’est moderne »
- Combien de temps : estimation honnête, sachant que ça déborde toujours un peu
- Qui fait quoi : un référent qui pilote, mais idéalement toute l’équipe contribue à son rythme
- Règle de PR pendant la migration : on n’écrit plus de nouveau code en
.js; toute nouvelle fonctionnalité va directement en.ts - Quand durcit-on : annoncer en avance les durcissements de tsconfig pour que personne soit surpris
Documenter le statut
Tenir un compteur quelque part (un README dédié, un wiki) :
Migration TypeScript : 142 / 287 fichiers (49%)
- src/utils/* : 100%
- src/services/* : 78%
- src/components/* : 35%
- src/pages/* : 12%
Cela donne de la visibilité et motive l’équipe.
10. FAQ
Combien de temps pour migrer un projet de 50k lignes ?
Variable selon discipline et taille d’équipe : typiquement 2 à 6 mois en parallèle des autres tâches, avec 1-2 personnes qui poussent activement. Plus si la base est dense en logique typée complexe (formulaires, états redux, etc.).
Faut-il refactorer pendant la migration ?
Non, séparer les deux. Migrer un fichier sans changer sa logique, dans une PR dédiée. Refactorer dans une autre PR si nécessaire. Mélanger les deux brouille les revues et augmente le risque de bugs.
Mon projet utilise du Redux/Vuex/Pinia mal typé, comment migrer ?
Commencer par typer les états et actions/mutations dans des fichiers dédiés. Les frameworks d’état modernes (Pinia, Zustand, Redux Toolkit avec TypeScript) sont conçus pour TypeScript. Pour du legacy Redux non-typé, c’est l’une des parties les plus coûteuses de la migration.
Faut-il convertir les fichiers de test aussi ?
Oui, en même temps que les fichiers qu’ils testent. Avoir des tests typés assure que les types sont effectivement testés et utilisés correctement.
Et si une partie du code reste en .js définitivement ?
Acceptable s’il s’agit de scripts d’admin ponctuels, code legacy qui sera supprimé bientôt, ou contraintes externes. Mais pour le code de production actif, viser 100% TypeScript apporte le plein bénéfice. Mode hybride durable est un compromis qui finit par coûter cher en mental load.
Comment justifier la migration au business ?
Mesurer les bugs liés aux types avant et après sur 3 mois. Un projet typique voit une réduction significative de cette catégorie de bugs après TypeScript bien fait. Les développeurs sont plus rapides en feature ajoutée à code existant. Ces gains se voient en vélocité d’équipe et qualité.
as casting partout, c’est mauvais ?
Oui généralement. Chaque as est une affirmation non vérifiée — vous prenez la responsabilité de dire au compilateur que vous savez mieux. Parfois nécessaire, mais à minimiser. Préférer narrowing (if (typeof x === ...)), type guards (function isClient(x: unknown): x is Client), validation à l’entrée (Zod, Yup) pour avoir des types validés plutôt que castés.
Articles liés (cluster TypeScript et JavaScript moderne)
- 👉 TypeScript et JavaScript moderne : guide pratique (pillar)
- 👉 TypeScript tsconfig strict en pratique
- 👉 JavaScript moderne : patterns pratiques
Article mis à jour le 25 avril 2026. Pour signaler une erreur ou suggérer une amélioration, écrivez-nous.