W3docs

Polyfill e Transpiler in JavaScript

Scopri come JavaScript moderno gira su motori più vecchi usando transpiler come Babel e polyfill come core-js per aggiungere metodi mancanti.

JavaScript acquisisce nuove funzionalità ogni anno. Ogni rilascio annuale — ES2015 (ES6), ES2020, ES2022 e così via — aggiunge nuova sintassi e nuovi metodi integrati. Il problema è che il tuo codice non gira su un motore fisso: gira nel browser o nel runtime che i tuoi visitatori si trovano a utilizzare, e alcuni di questi sono datati di anni. Codice che funziona perfettamente nell'ultimo Chrome potrebbe sollevare un SyntaxError in una versione più vecchia, o fallire silenziosamente perché un metodo come Array.prototype.includes semplicemente non esiste.

Ci sono due lacune distinte da colmare, e richiedono due strumenti diversi. La nuova sintassi che un motore vecchio non riesce nemmeno ad analizzare ha bisogno di un transpiler. Le nuove API integrate che un motore vecchio non ha mai incluso hanno bisogno di un polyfill. Questo capitolo spiega entrambi, mostra come si differenziano e descrive come si inseriscono in una moderna toolchain di build.

Le Due Lacune: Sintassi vs. API

Prima di cercare uno strumento, è utile capire perché uno solo non basta.

  • La sintassi è la grammatica del linguaggio — arrow function, class, optional chaining (?.), l'operatore di coalescenza nulla (??), i template literal. Se un motore non comprende la grammatica, lo script non viene analizzato e nulla viene eseguito. Non è possibile rimediare a runtime, perché il file viene rifiutato prima che qualsiasi codice venga eseguito.
  • Le API sono le funzioni e gli oggetti integrati disponibili durante l'esecuzione del codice — Promise, Array.prototype.includes, String.prototype.padStart, Object.fromEntries, fetch. Questi sono semplicemente valori che risiedono su oggetti globali e prototipi. Se uno manca, è possibile aggiungerlo prima che il codice lo utilizzi.

Questa distinzione è tutto: riscrivi la grammatica in anticipo, oppure fornisci i valori mancanti a runtime.

Transpiler: Da Nuova Sintassi a Vecchia Sintassi

Un transpiler (detto anche compilatore sorgente-a-sorgente) legge il tuo JavaScript moderno e lo riscrive in un JavaScript equivalente più vecchio, comprensibile da più motori. Il transpiler più noto è Babel. Questo avviene al momento del build — prima che il tuo codice venga mai distribuito — quindi il browser riceve soltanto sintassi che riesce ad analizzare.

Ecco un piccolo esempio prima e dopo. Scrivi una moderna arrow function:

// Source — modern syntax
const double = x => x * 2;

Un transpiler che punta a motori più vecchi la riscrive in una classica espressione di funzione:

// Output — down-leveled to ES5
var double = function (x) {
  return x * 2;
};

Il comportamento è identico; è cambiata solo la grammatica. Babel fa lo stesso per le dichiarazioni class, la destrutturazione, i parametri predefiniti, l'optional chaining e molto altro. Ad esempio, user?.address?.city diventa una serie di controlli && che i motori più vecchi gestiscono senza problemi.

@babel/preset-env e browserslist

Raramente si configura ogni funzionalità manualmente. Si utilizza invece @babel/preset-env, un preset che decide quali trasformazioni applicare in base agli ambienti che si desidera supportare. Questi ambienti vengono dichiarati con una query browserslist — un modo breve e condiviso per descrivere i browser di destinazione:

{
  "browserslist": [
    "> 0.5%",
    "last 2 versions",
    "not dead"
  ]
}

Con questo elenco, @babel/preset-env esegue il transpiling solo di ciò che quei browser effettivamente non supportano. Restringi l'elenco ai browser moderni e quasi nulla viene ridotto; amplialo ai browser antichi e avvengono molte più trasformazioni. L'idea chiave: un transpiler è un passaggio di build, e browserslist gli dice quanto lavorare.

Polyfill: Fornire API Mancanti a Runtime

Un polyfill è un pezzo di codice che aggiunge un built-in mancante, in modo che un motore vecchio acquisisca l'API a runtime. Un transpiler può riscrivere la sintassi ?., ma non può far apparire un oggetto Promise che il motore non ha mai incluso — quello è compito di un polyfill. La libreria di polyfill più utilizzata è core-js, che fornisce implementazioni per Promise, Array.from, Object.fromEntries, String.prototype.padStart e centinaia di altri.

È anche possibile scrivere un piccolo polyfill a mano. Il pattern essenziale è un controllo di rilevamento delle funzionalità — un controllo if che installa la tua versione solo quando quella nativa è assente:

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 riga if (!String.prototype.padStart) è la parte importante. Senza di essa, si sovrascrive l'implementazione nativa del motore ogni volta — sostituendo codice integrato veloce e ben testato con il tuo. Il controllo dice "intervieni solo quando la funzionalità è genuinamente assente", in modo che i motori moderni mantengano la loro versione ottimizzata e solo i motori vecchi ricorrano alla tua.

L'esempio seguente rileva e utilizza padStart nello stesso modo in cui farebbe un polyfill. In un browser moderno viene eseguito il metodo nativo; in uno antico, il fallback protetto sopra lo avrebbe fornito in precedenza.

javascript— editable
Attenzione

Modificare i prototipi integrati (come String.prototype) è qualcosa che solo i polyfill dovrebbero fare, e solo dietro un controllo di rilevamento delle funzionalità. Nel codice della tua applicazione, evita di aggiungere metodi ai prototipi nativi — può creare conflitti con altre librerie e con le future funzionalità del linguaggio.

Transpiler vs. Polyfill a Colpo d'Occhio

I due strumenti si confondono facilmente perché entrambi esistono per supportare motori più vecchi. Questo confronto li distingue chiaramente:

AspettoTranspiler (es. Babel)Polyfill (es. core-js)
CorreggeSintassi nuova che il motore non riesce ad analizzareAPI integrate mancanti
Quando viene eseguitoMomento del build (prima della distribuzione)Runtime (nel browser)
Esempio di input?., ??, arrow function, classPromise, fetch, Array.prototype.includes
Come funzionaRiscrive il codice in una grammatica più vecchiaAggiunge la funzione/oggetto mancante
Può colmare l'altra lacuna?No — non può aggiungere API mancantiNo — non può correggere sintassi non analizzabile

Una semplice regola pratica: se un motore non riesce a leggere il tuo codice, hai bisogno di un transpiler; se riesce a leggerlo ma una funzione è undefined, hai bisogno di un polyfill. La maggior parte dei progetti reali usa entrambi contemporaneamente.

Come Si Inserisce in una Moderna Toolchain

In pratica non esegui questi strumenti manualmente. Un bundler o uno strumento di build — come Vite, webpack o esbuild — li gestisce per te. Una configurazione tipica funziona così:

  1. Dichiari i tuoi ambienti di destinazione una volta, in browserslist.
  2. Lo strumento di build esegue Babel con @babel/preset-env, che riduce solo la sintassi che i tuoi target non supportano.
  3. La stessa configurazione inietta i polyfill di core-js per le API che quei target non hanno — e, con useBuiltIns: 'usage', solo quelli a cui il tuo codice fa effettivamente riferimento.

Il risultato è un bundle calibrato sul tuo pubblico reale: nulla viene trasformato o polyfillato quando i browser dei tuoi visitatori lo supportano già.

Informazione

I moderni browser evergreen di oggi — Chrome, Edge, Firefox e Safari — si aggiornano automaticamente e supportano già la grande maggioranza delle funzionalità ES6 e successive. Il transpiling massiccio e il polyfilling esteso sono molto meno necessari di quanto non fossero un tempo. Imposta un target browserslist realistico per il tuo pubblico e lascia che la toolchain riduca e polyfilli solo ciò che è genuinamente necessario.

C'è un costo reale nell'ignorare questo consiglio. Il polyfilling eccessivo gonfia il bundle con codice che ogni visitatore moderno scarica, analizza e poi non utilizza. Anche ridurre la sintassi in modo troppo aggressivo produce output più grande e lento. L'obiettivo non è "supportare tutto" ma "supportare ciò che i tuoi utenti effettivamente usano". Per ragionare su quali funzionalità un determinato browser supporta, il capitolo sulla compatibilità DOM e browser è un utile complemento.

Metti alla Prova le Tue Conoscenze

Pratica
Quale strumento riscrive la moderna sintassi JavaScript in una sintassi più vecchia equivalente al momento del build?
Quale strumento riscrive la moderna sintassi JavaScript in una sintassi più vecchia equivalente al momento del build?
Pratica
Perché un polyfill scritto a mano inizia con un controllo come 'if (!String.prototype.padStart)'?
Perché un polyfill scritto a mano inizia con un controllo come 'if (!String.prototype.padStart)'?
Pratica
Quale lacuna può colmare un polyfill, ma non un transpiler?
Quale lacuna può colmare un polyfill, ma non un transpiler?
Was this page helpful?