W3docs

Polyfills e Transpilers em JavaScript

Aprenda como o JavaScript moderno funciona em engines mais antigas usando transpilers como Babel e polyfills como core-js para métodos ausentes.

O JavaScript ganha novos recursos a cada ano. Cada versão anual — ES2015 (ES6), ES2020, ES2022, e assim por diante — adiciona nova sintaxe e novos métodos integrados. O problema é que o seu código não roda em uma engine fixa: ele roda no navegador ou runtime que os seus visitantes utilizam, e alguns deles estão anos desatualizados. Um código que funciona perfeitamente no Chrome mais recente pode lançar um SyntaxError em uma versão mais antiga, ou falhar silenciosamente porque um método como Array.prototype.includes simplesmente não existe.

Há duas lacunas distintas a preencher, e elas exigem duas ferramentas diferentes. A nova sintaxe que uma engine antiga não consegue nem analisar precisa de um transpiler. As novas APIs integradas que uma engine antiga nunca implementou precisam de um polyfill. Este capítulo explica os dois, mostra como eles diferem e descreve como se encaixam em uma toolchain de build moderna.

As Duas Lacunas: Sintaxe vs. APIs

Antes de buscar uma ferramenta, vale entender por que uma única ferramenta não é suficiente.

  • Sintaxe é a gramática da linguagem — arrow functions, class, encadeamento opcional (?.), o operador de coalescência nula (??), template literals. Se uma engine não entende a gramática, o script falha ao ser analisado e nada é executado. Isso não pode ser corrigido em tempo de execução, porque o arquivo é rejeitado antes de qualquer código ser executado.
  • APIs são as funções e objetos integrados disponíveis enquanto o seu código roda — Promise, Array.prototype.includes, String.prototype.padStart, Object.fromEntries, fetch. Esses são apenas valores que existem em objetos globais e protótipos. Se algum estiver ausente, você pode adicioná-lo antes que o seu código o utilize.

Essa divisão resume tudo: reescreva a gramática antecipadamente, ou forneça os valores ausentes em tempo de execução.

Transpilers: Nova Sintaxe para Sintaxe Antiga

Um transpiler (também chamado de compilador fonte-a-fonte) lê o seu JavaScript moderno e o reescreve em um JavaScript mais antigo equivalente, que mais engines conseguem entender. O transpiler mais conhecido é o Babel. Isso acontece em tempo de build — antes que o seu código seja enviado — portanto o navegador só recebe a sintaxe que consegue analisar.

Veja um pequeno exemplo antes e depois. Você escreve uma arrow function moderna:

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

Um transpiler voltado para engines mais antigas a reescreve em uma expressão de função clássica:

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

O comportamento é idêntico; apenas a gramática mudou. O Babel faz o mesmo para declarações class, desestruturação, parâmetros padrão, encadeamento opcional e muito mais. Por exemplo, user?.address?.city se torna uma série de verificações com && que engines mais antigas tratam sem problemas.

@babel/preset-env e browserslist

Você raramente configura cada recurso manualmente. Em vez disso, usa o @babel/preset-env, um preset que decide quais transformações aplicar com base nos ambientes que você deseja suportar. Esses ambientes são declarados com uma consulta browserslist — uma forma curta e compartilhada de descrever os navegadores alvo:

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

Com essa lista, o @babel/preset-env transpila apenas o que esses navegadores realmente não suportam. Restrinja a lista a navegadores modernos e quase nada será convertido; amplie-a para navegadores antigos e muito mais transformações ocorrerão. A ideia principal: um transpiler é uma etapa de build, e o browserslist diz o quanto ele precisa trabalhar.

Polyfills: Fornecendo APIs Ausentes em Tempo de Execução

Um polyfill é um trecho de código que adiciona um built-in ausente para que uma engine antiga ganhe a API em tempo de execução. Um transpiler pode reescrever a sintaxe ?., mas não consegue criar um objeto Promise que a engine nunca implementou — esse é o trabalho de um polyfill. A biblioteca de polyfills mais utilizada é a core-js, que fornece implementações de Promise, Array.from, Object.fromEntries, String.prototype.padStart e centenas de outros.

Você também pode escrever um pequeno polyfill manualmente. O padrão essencial é um guard de detecção de recurso — uma verificação if que só instala a sua versão quando a nativa está ausente:

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);
  };
}

A linha if (!String.prototype.padStart) é a parte importante. Sem ela, você sobrescreveria a implementação nativa da engine toda vez — substituindo código built-in rápido e bem testado pelo seu próprio. O guard diz "intervenha apenas quando o recurso estiver genuinamente ausente", então engines modernas mantêm sua versão otimizada e apenas engines antigas recorrem à sua.

O exemplo abaixo detecta e usa padStart da mesma forma que um polyfill faria. Em um navegador moderno, o método nativo é executado; em um antigo, o fallback com guard acima já o teria fornecido.

javascript— editable
Aviso

Modificar protótipos integrados (como String.prototype) é algo que apenas polyfills devem fazer, e somente com um guard de detecção de recurso. No código da sua aplicação, evite adicionar métodos a protótipos nativos — isso pode entrar em conflito com outras bibliotecas e futuros recursos da linguagem.

Transpiler vs. Polyfill: Visão Geral

As duas ferramentas são fáceis de confundir porque ambas existem para suportar engines mais antigas. Este contraste ajuda a diferenciá-las:

AspectoTranspiler (ex.: Babel)Polyfill (ex.: core-js)
CorrigeSintaxe nova que a engine não consegue analisarAPIs integradas ausentes
Quando é executadoTempo de build (antes do envio)Tempo de execução (no navegador)
Exemplo de entrada?., ??, arrow functions, classPromise, fetch, Array.prototype.includes
Como funcionaReescreve o código em gramática mais antigaAdiciona a função/objeto ausente
Consegue resolver a outra lacuna?Não — não consegue adicionar APIs ausentesNão — não consegue corrigir sintaxe inanalisável

Uma regra simples: se uma engine não consegue ler o seu código, você precisa de um transpiler; se ela consegue ler, mas uma função está indefinida, você precisa de um polyfill. A maioria dos projetos reais usa ambos ao mesmo tempo.

Como Isso Se Encaixa em uma Toolchain Moderna

Na prática, você não executa essas ferramentas manualmente. Um bundler ou ferramenta de build — como Vite, webpack ou esbuild — as gerencia para você. Uma configuração típica funciona assim:

  1. Você declara os ambientes alvo uma vez, no browserslist.
  2. A ferramenta de build executa o Babel com @babel/preset-env, que converte apenas a sintaxe que seus alvos não suportam.
  3. A mesma configuração injeta polyfills do core-js para as APIs que esses alvos não possuem — e, com useBuiltIns: 'usage', apenas os que o seu código realmente referencia.

O resultado é um bundle ajustado para o seu público real: nada é transformado ou polyfillado que os navegadores dos seus visitantes já suportem.

Informação

Os navegadores evergreen de hoje — Chrome, Edge, Firefox e Safari — se atualizam automaticamente e já suportam a vasta maioria dos recursos modernos do ES6 e posteriores. Transpilação pesada e polyfilling amplo são muito menos necessários do que antes. Defina um alvo browserslist realista para o seu público e deixe a toolchain converter e polyfilllar apenas o que for genuinamente necessário.

Há um custo real em ignorar esse conselho. Excesso de polyfills infla o seu bundle com código que todo visitante moderno baixa, analisa e descarta sem usar. Converter para versões mais antigas de forma muito agressiva também produz uma saída maior e mais lenta. O objetivo não é "suportar tudo", mas "suportar o que os seus usuários realmente usam". Para entender quais recursos um determinado navegador suporta, o capítulo sobre compatibilidade de DOM e navegadores é um complemento útil.

Teste Seus Conhecimentos

Prática
Qual ferramenta reescreve a sintaxe moderna de JavaScript em uma sintaxe mais antiga equivalente em tempo de build?
Qual ferramenta reescreve a sintaxe moderna de JavaScript em uma sintaxe mais antiga equivalente em tempo de build?
Prática
Por que um polyfill escrito manualmente começa com uma verificação como 'if (!String.prototype.padStart)'?
Por que um polyfill escrito manualmente começa com uma verificação como 'if (!String.prototype.padStart)'?
Prática
Qual lacuna um polyfill consegue preencher, mas um transpiler não consegue?
Qual lacuna um polyfill consegue preencher, mas um transpiler não consegue?
Was this page helpful?