Polyfills et transpileurs JavaScript
Découvrez comment JavaScript moderne s'exécute sur d'anciens moteurs grâce aux transpileurs comme Babel et aux polyfills comme core-js.
JavaScript s'enrichit de nouvelles fonctionnalités chaque année. Chaque version annuelle — ES2015 (ES6), ES2020, ES2022, et ainsi de suite — apporte une nouvelle syntaxe et de nouvelles méthodes intégrées. Le problème, c'est que votre code ne s'exécute pas dans un moteur fixe : il s'exécute dans le navigateur ou l'environnement d'exécution qu'utilisent vos visiteurs, et certains d'entre eux ont plusieurs années de retard. Un code qui fonctionne parfaitement dans la dernière version de Chrome peut générer une SyntaxError dans une version plus ancienne, ou échouer silencieusement parce qu'une méthode comme Array.prototype.includes n'existe tout simplement pas.
Il y a deux lacunes distinctes à combler, et elles nécessitent deux outils différents. La nouvelle syntaxe qu'un ancien moteur ne peut même pas analyser nécessite un transpileur. Les nouvelles API intégrées qu'un ancien moteur n'a jamais embarquées nécessitent un polyfill. Ce chapitre explique les deux, montre en quoi ils diffèrent, et décrit comment ils s'intègrent dans une chaîne de compilation moderne.
Les deux lacunes : syntaxe et API
Avant de choisir un outil, il est utile de comprendre pourquoi un seul outil ne suffit pas.
- La syntaxe est la grammaire du langage — les fonctions fléchées,
class, le chaînage optionnel (?.), l'opérateur de coalescence nulle (??), les littéraux de gabarit. Si un moteur ne comprend pas la grammaire, le script échoue à l'analyse et rien ne s'exécute. Il est impossible de corriger cela à l'exécution, car le fichier est rejeté avant qu'aucun code ne s'exécute. - Les API sont les fonctions et objets intégrés disponibles pendant l'exécution de votre code —
Promise,Array.prototype.includes,String.prototype.padStart,Object.fromEntries,fetch. Ce sont simplement des valeurs qui résident sur des objets globaux et des prototypes. Si l'une d'elles est absente, vous pouvez l'ajouter vous-même avant que votre code ne l'utilise.
Voilà toute la logique : réécrire la grammaire au préalable, ou fournir les valeurs manquantes à l'exécution.
Transpileurs : transformer la nouvelle syntaxe en ancienne syntaxe
Un transpileur (également appelé compilateur source à source) lit votre JavaScript moderne et le réécrit en JavaScript plus ancien, compréhensible par davantage de moteurs. Le transpileur le plus connu est Babel. Cette transformation se produit au moment de la compilation — avant que votre code ne soit livré — de sorte que le navigateur ne reçoit jamais qu'une syntaxe qu'il peut analyser.
Voici un avant-après concret. Vous écrivez une fonction fléchée moderne :
// Source — modern syntax
const double = x => x * 2;Un transpileur ciblant d'anciens moteurs la réécrit en une expression de fonction classique :
// Output — down-leveled to ES5
var double = function (x) {
return x * 2;
};Le comportement est identique ; seule la grammaire a changé. Babel fait de même pour les déclarations class, la déstructuration, les paramètres par défaut, le chaînage optionnel, et bien plus encore. Par exemple, user?.address?.city devient une série de vérifications && que les anciens moteurs gèrent sans problème.
@babel/preset-env et browserslist
Il est rare de configurer chaque fonctionnalité manuellement. À la place, on utilise @babel/preset-env, un preset qui décide quelles transformations appliquer en fonction des environnements que vous souhaitez prendre en charge. Ces environnements sont déclarés avec une requête browserslist — une façon concise et partagée de décrire vos navigateurs cibles :
{
"browserslist": [
"> 0.5%",
"last 2 versions",
"not dead"
]
}Avec cette liste, @babel/preset-env ne transpile que ce dont ces navigateurs manquent réellement. Restreignez la liste aux navigateurs modernes et presque rien ne sera rétrogradé ; élargissez-la aux anciens navigateurs et bien plus de transformations s'appliqueront. L'idée clé : un transpileur est une étape de compilation, et browserslist lui indique l'effort à fournir.
Polyfills : fournir les API manquantes à l'exécution
Un polyfill est un morceau de code qui ajoute une fonctionnalité intégrée manquante afin qu'un ancien moteur dispose de l'API à l'exécution. Un transpileur peut réécrire la syntaxe ?., mais il ne peut pas créer un objet Promise que le moteur n'a jamais fourni — c'est le rôle d'un polyfill. La bibliothèque de polyfills la plus utilisée est core-js, qui fournit des implémentations pour Promise, Array.from, Object.fromEntries, String.prototype.padStart, et des centaines d'autres.
Vous pouvez également écrire un petit polyfill à la main. Le schéma essentiel est une vérification de détection de fonctionnalité — une condition if qui n'installe votre version que lorsque la version native est absente :
if (!String.prototype.padStart) {
String.prototype.padStart = function (targetLength, padString) {
targetLength = Math.floor(targetLength) || 0;
if (targetLength < this.length) {
return String(this);
}
padString = padString ? String(padString) : ' ';
let pad = '';
const len = targetLength - this.length;
let i = 0;
while (pad.length < len) {
if (!padString[i]) {
i = 0;
}
pad += padString[i];
i++;
}
return pad + String(this).slice(0);
};
}La ligne if (!String.prototype.padStart) est la partie importante. Sans elle, vous écraseriez l'implémentation native du moteur à chaque fois — en remplaçant un code intégré rapide et bien testé par le vôtre. La vérification dit « n'intervenir que lorsque la fonctionnalité est vraiment absente », de sorte que les moteurs modernes conservent leur version optimisée et que seuls les anciens moteurs utilisent la vôtre en secours.
L'exemple ci-dessous détecte et utilise padStart de la même manière qu'un polyfill le ferait. Dans un navigateur moderne, la méthode native s'exécute ; dans un navigateur ancien, le recours gardé ci-dessus l'aurait fournie au préalable.
Modifier les prototypes natifs (comme String.prototype) est une chose que seuls les polyfills devraient faire, et uniquement derrière une vérification de détection de fonctionnalité. Dans votre propre code applicatif, évitez d'ajouter des méthodes aux prototypes natifs — cela peut entrer en conflit avec d'autres bibliothèques et de futures fonctionnalités du langage.
Transpileur vs. polyfill en un coup d'œil
Les deux outils sont faciles à confondre car tous deux existent pour prendre en charge d'anciens moteurs. Ce tableau permet de les distinguer clairement :
| Aspect | Transpileur (ex. Babel) | Polyfill (ex. core-js) |
|---|---|---|
| Corrige | Nouvelle syntaxe que le moteur ne peut pas analyser | API intégrées manquantes |
| Quand il s'exécute | Au moment de la compilation (avant livraison) | À l'exécution (dans le navigateur) |
| Exemple d'entrée | ?., ??, fonctions fléchées, class | Promise, fetch, Array.prototype.includes |
| Comment ça fonctionne | Réécrit le code en grammaire plus ancienne | Ajoute la fonction/l'objet manquant |
| Peut-il combler l'autre lacune ? | Non — ne peut pas ajouter des API manquantes | Non — ne peut pas corriger une syntaxe illisible |
Une règle simple : si un moteur ne peut pas lire votre code, vous avez besoin d'un transpileur ; s'il peut le lire mais qu'une fonction est undefined, vous avez besoin d'un polyfill. La plupart des projets réels utilisent les deux simultanément.
Comment cela s'intègre dans une chaîne de compilation moderne
En pratique, vous n'exécutez pas ces outils manuellement. Un bundler ou outil de compilation — comme Vite, Webpack, ou esbuild — les pilote pour vous. Une configuration typique fonctionne ainsi :
- Vous déclarez vos environnements cibles une seule fois, dans
browserslist. - L'outil de compilation exécute Babel avec
@babel/preset-env, qui ne rétrograde que la syntaxe dont vos cibles manquent. - La même configuration injecte les polyfills
core-jspour les API manquantes dans ces cibles — et, avecuseBuiltIns: 'usage', uniquement celles que votre code référence réellement.
Le résultat est un bundle adapté à votre audience réelle : rien n'est transformé ou polyfillé si les navigateurs de vos visiteurs le prennent déjà en charge.
Les navigateurs evergreen d'aujourd'hui — Chrome, Edge, Firefox et Safari — se mettent à jour automatiquement et prennent déjà en charge la grande majorité des fonctionnalités modernes d'ES6 et ultérieures. La transpilation intensive et le polyfillage étendu sont bien moins nécessaires qu'auparavant. Définissez une cible browserslist réaliste pour votre audience et laissez la chaîne de compilation rétrograder et polyfiller uniquement ce qui est vraiment nécessaire.
Il y a un vrai coût à ignorer ce conseil. Sur-polyfiller alourdit votre bundle avec du code que chaque visiteur moderne télécharge, analyse et jette sans l'utiliser. Rétrograder trop agressivement produit également des sorties plus volumineuses et plus lentes. L'objectif n'est pas « prendre en charge tout » mais « prendre en charge ce que vos utilisateurs utilisent réellement ». Pour raisonner sur les fonctionnalités prises en charge par un navigateur donné, le chapitre sur la compatibilité DOM et navigateur est un complément utile.