Leçon 8/10 10 min

Compilation et runtime

Les types sont effacés à l'exécution : ce que tsconfig compile, ce que « as » promet, et pourquoi une API peut quand même te mentir.

« C'est typé, donc c'est sûr » : et puis le crash

L'équipe est confiante. Le code passe la compilation : zéro erreur, TypeScript est content. L'API partenaire renvoie un objet utilisateur depuis des semaines, tout roule. Et puis un matin, l'API change un champ silencieusement, et la prod plante sur Cannot read properties of undefined, reading 'toFixed'. Du code 100 % vert au compile, 100 % cassé à l'exécution.

Que s'est-il passé ? Les types avaient disparu au moment du crash. Le compilateur les avait vérifiés hier, sur le code d'hier, puis il les avait effacés. La donnée qui est arrivée ce matin, lui, personne ne l'avait vérifiée.

C'est la leçon la plus importante du cours : TypeScript ne protège que ce qu'il peut voir. Ce qui arrive au runtime (réponses d'API, JSON.parse, localStorage, formulaires) est une inconnue. Et si tu affirmes son type avec as, le compilateur te croit sur parole.

Prédis avant de lire

Ce code passe le type-check avec ✓ 0 erreur. Que se passe-t-il quand on l'exécute, et pourquoi le compilateur n'a-t-il rien vu ?

const reponse = '{"nom":"Sam"}';
const data = JSON.parse(reponse) as { nom: string; age: number };
console.log('Age : ' + data.age.toFixed(0));
Voir la réponse

Crash : TypeError: Cannot read properties of undefined, reading 'toFixed'. L'objet parsé n'a pas de champ age : il vaut undefined, et undefined.toFixed n'existe pas. Le compilateur n'a rien vu parce que as lui a ordonné de croire que age existait bien. Et au runtime, les types sont effacés : personne ne vérifie plus rien.

Ce que le compilateur efface, et ce qu'il ne voit pas

Rappel de la leçon 1 : entre ton code et le navigateur, le compilateur installe un filtre. Il vérifie chaque contrat, puis efface toutes les annotations. Le navigateur exécute du JavaScript nu : aucun type ne subsiste, aucun garde n'est posté. Ce que le compilateur a vu, il l'a jugé. Ce qu'il n'a pas vu, il ne peut pas le juger.

Or, les données qui arrivent au runtime (réponses d'API, JSON.parse, localStorage, valeurs de formulaire) le compilateur ne les voit jamais. Il ne sait pas ce que l'API va envoyer ce soir. Il fait confiance à ce que toi tu lui déclares.

C'est là qu'entre en scène as. Quand tu écris JSON.parse(reponse) as { nom: string; age: number }, tu ne convertis rien. Tu promets au compilateur que c'est bien ce type-là. Il te croit sur parole, range son crayon rouge, et au runtime il ne reste plus que le JavaScript nu, sans la moindre vérification.

as X n'est pas une conversion ni une validation. C'est une promesse non vérifiée. Si la donnée réelle ne correspond pas au type affirmé, le compilateur ne le saura jamais, et le crash attendra le runtime pour se produire. Utilise as uniquement quand tu as une certitude externe, et jamais pour « faire taire » une erreur.

La même logique s'applique à tsconfig.json : les options strict, target, noEmitOnError contrôlent ce que le compilateur vérifie et produit. Mais elles ne changent pas la nature de l'effacement : peu importe la rigueur de ta config, au runtime il n'y a plus de types.

À toi : le labo inversé

Ce labo est inversé : commence par Vérifier les types ; tu obtiens ✓ 0 erreur. Puis clique Exécuter. Et là, le crash. C'est le piège de la leçon.

🧐 Labo TypeScript · le compilateur juge ton code (mode strict)
Bloqué sur le fix ? Voir la correction

Marque age comme incertain (age?: number) et vérifie avant d'utiliser avec un typeof : ce garde existe dans le JavaScript exécuté, lui. Le typeof est une vraie vérification au runtime, contrairement à as.

const reponse = '{"nom":"Sam"}';

const data = JSON.parse(reponse) as { nom: string; age?: number };

if (typeof data.age === 'number') {
  console.log('Age : ' + data.age.toFixed(0));
} else {
  console.log('Age inconnu');
}

Résultat : check ✓ et Exécuter affiche « Age inconnu » : plus de crash.

Les bons réflexes à la frontière

La règle est simple : ton code interne, TypeScript le surveille de bout en bout. Les données qui arrivent de l'extérieur, c'est à toi de les valider avant de les laisser entrer.

Compile-time pour TON code ; validation runtime pour LEURS données. Vérifie les champs critiques avant usage : typeof, in, narrowing (leçon 4) ; ces gardes existent dans le JavaScript exécuté et constituent de vraies vérifications.

Des bibliothèques comme Zod font cette validation runtime à grande échelle : tu décris le schéma attendu une seule fois, Zod vérifie chaque objet entrant et renvoie soit la donnée validée, soit une erreur propre. On les rencontrera dans la vraie vie ; la leçon 9 aborde unknown, le type de départ idéal pour ces flux non vérifiés.

Et côté tsconfig : active strict: true (c'est le mode de tout ce cours), noEmitOnError: true pour ne jamais produire de JavaScript si le compilateur n'est pas satisfait, et choisis un target adapté à tes navigateurs cibles. Ce sont tes filets de sécurité côté compile-time, mais ils ne remplacent pas la validation à la frontière.

La frontière compile-time / runtime : à gauche la zone TON CODE (verte, compilateur actif, contrats vérifiés) ; au centre une frontière en pointillés marquée « runtime : types effacés » ; à droite la zone LEURS DONNÉES (API, JSON, formulaires, orange) avec un point d'interrogation. Une flèche rouge étiquetée « as » saute la frontière sans contrôle. Une flèche verte étiquetée « typeof / narrowing » passe par un poste de contrôle. TON CODE compilateur actif contrats vérifiés COMPILE-TIME runtime : types effacés LEURS DONNÉES API · JSON.parse · localStorage formulaires ? as : promesse non vérifiée poste contrôle typeof / narrowing : validée
La frontière : as saute sans contrôle ; typeof et le narrowing constituent de vraies vérifications au runtime.

"It's typed, so it's safe" — and then the crash

The team is confident. The code compiles with zero errors, TypeScript is happy. The partner API has been returning a user object for weeks, everything's fine. Then one morning the API silently changes a field, and prod crashes on Cannot read properties of undefined, reading 'toFixed'. Code that was 100% green at compile time, 100% broken at runtime.

What happened? The types had disappeared at the time of the crash. The compiler had checked them yesterday, on yesterday's code — then erased them. The data that arrived this morning, nobody had validated it.

This is the most important lesson of the course: TypeScript only protects what it can see. What arrives at runtime (API responses, JSON.parse, localStorage, form values) is unknown. And if you assert its type with as, the compiler takes your word for it.

Predict before reading on

This code passes the type check with ✓ 0 errors. What happens when you run it, and why did the compiler see nothing?

const response = '{"name":"Sam"}';
const data = JSON.parse(response) as { name: string; age: number };
console.log('Age: ' + data.age.toFixed(0));
Show the answer

Crash: TypeError: Cannot read properties of undefined, reading 'toFixed'. The parsed object has no age field — it's undefined, and undefined.toFixed doesn't exist. The compiler saw nothing because as ordered it to trust that age was there. And at runtime, types are erased: nobody checks anything anymore.

What the compiler erases — and what it never sees

Lesson 1 recap: between your code and the browser, the compiler installs a filter. It checks every contract, then erases all annotations. The browser runs bare JavaScript — no type survives, no guard is posted. What the compiler saw, it judged. What it never saw, it cannot judge.

But the data that arrives at runtime — API responses, JSON.parse, localStorage, form values — the compiler never sees. It doesn't know what the API will send tonight. It trusts what you declare to it.

That's where as comes in. When you write JSON.parse(response) as { name: string; age: number }, you're not converting anything. You're promising the compiler that it's that type. It takes your word, puts down its red pen, and at runtime only bare JavaScript remains — no check whatsoever.

Beware: as X is not a conversion or a validation. It's an unverified promise. If the actual data doesn't match the asserted type, the compiler will never know, and the crash will wait for runtime. Use as only when you have external certainty — never to silence an error.

The same logic applies to tsconfig.json: options like strict, target, and noEmitOnError control what the compiler checks and outputs. But they don't change the nature of type erasure: no matter how strict your config, at runtime there are no types left.

Your turn: the inverted lab

This lab is inverted: start by clicking Check the types — you'll get ✓ 0 errors. Then click Run. And there, the crash. That's the trap of this lesson.

🧐 TypeScript lab · the compiler judges your code (strict mode)
Stuck on the fix? Show it

Mark age as uncertain (age?: number) and check before using with a typeof guard — that guard exists in the executed JavaScript, unlike as.

const response = '{"name":"Sam"}';

const data = JSON.parse(response) as { name: string; age?: number };

if (typeof data.age === 'number') {
  console.log('Age: ' + data.age.toFixed(0));
} else {
  console.log('Age unknown');
}

Result: check ✓ and Run prints "Age unknown" — no crash.

The right reflexes at the boundary

The rule is simple: your internal code, TypeScript watches end to end. Data that comes from outside, you validate before letting it in.

Compile-time for YOUR code; runtime validation for THEIR data. Check critical fields before use: typeof, in, narrowing (lesson 4) — these guards exist in the executed JavaScript and are real checks.

Libraries like Zod do this runtime validation at scale: you describe the expected schema once, Zod checks each incoming object and returns either the validated data or a clean error. We'll encounter them in real-world projects — lesson 9 covers unknown, the ideal starting type for these unvalidated flows.

On the tsconfig side: enable strict: true (this is the mode used throughout this course), noEmitOnError: true to never produce JavaScript if the compiler isn't satisfied, and pick a target matching your browser support. These are your compile-time safety nets — but they don't replace boundary validation.

The compile-time / runtime boundary: on the left, YOUR CODE zone (green, active compiler, verified contracts); in the centre, a dashed boundary labelled "runtime: types erased"; on the right, THEIR DATA zone (API, JSON, forms, orange) with a question mark. A red arrow labelled "as" leaps the boundary unchecked. A green arrow labelled "typeof / narrowing" passes through a checkpoint. YOUR CODE compiler active contracts verified COMPILE TIME runtime: types erased THEIR DATA API · JSON.parse · localStorage form values ? as — unverified promise check point typeof / narrowing — validated
The boundary: as leaps it unchecked; typeof and narrowing are real runtime checks.

🎯 Pratique

S'entraîner (clique pour ouvrir) :

💬 Ré-explique sans regarder
Ré-explique sans regarder

Explique pourquoi du code 100 % typé peut crasher en prod, avec l'exemple de l'API.

Une bonne explication dit : les types sont vérifiés puis effacés à la compilation ; au runtime il ne reste que du JS nu. Quand tu utilises as sur une réponse d'API, le compilateur te croit sur parole : il ne voit pas ce que l'API va vraiment envoyer. Si le champ manque au runtime, plus personne ne le vérifie et le crash survient. La règle : valider à la frontière (typeof, narrowing) les données qui arrivent de l'extérieur.
🧠 Rappel libre
Rappel libre

Sans remonter : quelle est la différence fondamentale entre as X et un garde typeof ?

as X n'existe que pour le compilateur : c'est une promesse qui disparaît à la compilation, zéro vérification au runtime. Un garde typeof (ou in, ou narrowing) existe dans le JavaScript exécuté : c'est une vraie vérification qui se produit au moment où la donnée est là. L'un convainc le compilateur ; l'autre protège réellement le runtime.
⚖️ Juge le code de l'IA
Accepter ou rejeter le code de l'IA

Le code plante au runtime sur un champ API manquant. Tu demandes à l'IA de corriger. Elle répond : « Corrigé ! J'ai remplacé le cast par as any et ajouté data?.age?.toFixed?.(0) partout. » Tu acceptes, ou tu rejettes ?

À rejeter : double aveu d'impuissance. as any éteint le compilateur (plus aucun contrat vérifié sur cet objet). La cascade de ?. masque l'absence de donnée au lieu de la gérer : le client voit undefined affiché tel quel. Le bon fix : modéliser l'incertitude (age?: number) et vérifier explicitement à la frontière (if (typeof data.age === 'number')). Quand l'IA neutralise le compilateur ET cache les erreurs, c'est un double faux correctif.
Que reste-t-il de tes types quand le code tourne dans le navigateur ?
Que fait réellement as { age: number } ?
Pourquoi le labo passe-t-il le check mais crashe à l'exécution ?
La règle des frontières, c'est quoi ?
Prochaine étape

Tu as vu la frontière compile-time / runtime et le danger de « as ». Jusqu'ici, tout tournait dans le navigateur. Leçon 9 : installer TypeScript pour de vrai, tsconfig, et le workflow pro (qui exécute ton code, qui vérifie tes types).

Leçon 9 : Installer TypeScript →
Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement