Polyfills y transpiladores en JavaScript
Aprende cómo el JavaScript moderno se ejecuta en motores antiguos usando transpiladores como Babel y polyfills como core-js para añadir métodos faltantes.
JavaScript incorpora nuevas funcionalidades cada año. Cada versión anual — ES2015 (ES6), ES2020, ES2022, y así sucesivamente — añade nueva sintaxis y nuevos métodos integrados. El problema es que tu código no se ejecuta en un único motor fijo: se ejecuta en el navegador o entorno de ejecución que usen tus visitantes, y algunos de ellos están años desactualizados. Un código que funciona perfectamente en la última versión de Chrome puede lanzar un SyntaxError en una versión más antigua, o fallar silenciosamente porque un método como Array.prototype.includes simplemente no existe.
Hay dos brechas distintas que cerrar, y cada una requiere una herramienta diferente. La nueva sintaxis que un motor antiguo ni siquiera puede analizar necesita un transpilador. Las nuevas API integradas que un motor antiguo nunca incorporó necesitan un polyfill. Este capítulo explica ambas herramientas, muestra en qué se diferencian y describe cómo encajan en una cadena de herramientas de compilación moderna.
Las dos brechas: sintaxis vs. API
Antes de recurrir a una herramienta, conviene entender por qué una sola no es suficiente.
- La sintaxis es la gramática del lenguaje — funciones flecha,
class, encadenamiento opcional (?.), el operador de coalescencia nula (??), las plantillas literales. Si un motor no entiende la gramática, el script no puede analizarse y nada se ejecuta. No puedes corregir esto en tiempo de ejecución, porque el archivo es rechazado antes de que se ejecute ningún código. - Las API son las funciones y objetos integrados disponibles mientras se ejecuta tu código —
Promise,Array.prototype.includes,String.prototype.padStart,Object.fromEntries,fetch. Estos son simplemente valores que viven en objetos globales y prototipos. Si falta alguno, puedes añadirlo tú mismo antes de que tu código lo utilice.
Esa distinción lo resume todo: reescribir la gramática con antelación, o proporcionar los valores faltantes en tiempo de ejecución.
Transpiladores: nueva sintaxis a sintaxis antigua
Un transpilador (también llamado compilador fuente a fuente) lee tu JavaScript moderno y lo reescribe en JavaScript más antiguo equivalente que más motores pueden entender. El transpilador más conocido es Babel. Esto ocurre en el momento de compilación — antes de que tu código se publique — por lo que el navegador solo recibe sintaxis que puede analizar.
Aquí hay un pequeño ejemplo antes y después. Tú escribes una función flecha moderna:
// Source — modern syntax
const double = x => x * 2;Un transpilador dirigido a motores más antiguos la reescribe como una expresión de función clásica:
// Output — down-leveled to ES5
var double = function (x) {
return x * 2;
};El comportamiento es idéntico; solo cambió la gramática. Babel hace lo mismo con las declaraciones class, la desestructuración, los parámetros por defecto, el encadenamiento opcional y mucho más. Por ejemplo, user?.address?.city se convierte en una serie de comprobaciones con && que los motores más antiguos manejan sin problemas.
@babel/preset-env y browserslist
Raramente se configura cada característica a mano. En su lugar, se utiliza @babel/preset-env, un preset que decide qué transformaciones aplicar en función de los entornos que deseas soportar. Esos entornos se declaran con una consulta de browserslist — una forma corta y compartida de describir los navegadores objetivo:
{
"browserslist": [
"> 0.5%",
"last 2 versions",
"not dead"
]
}Con esta lista, @babel/preset-env solo transpila lo que esos navegadores realmente no soportan. Si reduces la lista a navegadores modernos, casi nada se convierte a versiones inferiores; si la amplías a navegadores muy antiguos, se producen muchas más transformaciones. La idea clave: un transpilador es un paso de compilación, y browserslist le indica cuánto trabajo tiene que hacer.
Polyfills: suministrar API faltantes en tiempo de ejecución
Un polyfill es un fragmento de código que añade un elemento integrado faltante para que un motor antiguo obtenga la API en tiempo de ejecución. Un transpilador puede reescribir la sintaxis ?., pero no puede conjurar un objeto Promise que el motor nunca incorporó — eso es trabajo para un polyfill. La biblioteca de polyfills más utilizada es core-js, que proporciona implementaciones para Promise, Array.from, Object.fromEntries, String.prototype.padStart y cientos más.
También puedes escribir un pequeño polyfill a mano. El patrón esencial es una comprobación de detección de características — una condición if que solo instala tu versión cuando la 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);
};
}La línea if (!String.prototype.padStart) es la parte importante. Sin ella, sobreescribirías la implementación nativa del motor cada vez — reemplazando el código integrado rápido y bien probado con el tuyo propio. La comprobación dice "solo intervengo cuando la característica realmente falta", por lo que los motores modernos mantienen su versión optimizada y solo los motores antiguos recurren a la tuya.
El ejemplo a continuación detecta y usa padStart de la misma manera que lo haría un polyfill. En un navegador moderno se ejecuta el método nativo; en uno muy antiguo, el mecanismo de reserva protegido de arriba lo habría suministrado antes.
Modificar prototipos integrados (como String.prototype) es algo que solo deben hacer los polyfills, y únicamente tras una comprobación de detección de características. En el código de tu propia aplicación, evita añadir métodos a prototipos nativos — puede entrar en conflicto con otras bibliotecas y con futuras características del lenguaje.
Transpilador vs. polyfill de un vistazo
Las dos herramientas son fáciles de confundir porque ambas existen para soportar motores más antiguos. Esta comparación las diferencia claramente:
| Aspecto | Transpilador (p. ej. Babel) | Polyfill (p. ej. core-js) |
|---|---|---|
| Corrige | Nueva sintaxis que el motor no puede analizar | API integradas faltantes |
| Cuándo se ejecuta | Momento de compilación (antes de publicar) | Tiempo de ejecución (en el navegador) |
| Ejemplo de entrada | ?., ??, funciones flecha, class | Promise, fetch, Array.prototype.includes |
| Cómo funciona | Reescribe el código en gramática más antigua | Añade la función/objeto faltante |
| ¿Puede cerrar la otra brecha? | No — no puede añadir API faltantes | No — no puede corregir sintaxis no analizable |
Una regla sencilla: si un motor no puede leer tu código, necesitas un transpilador; si puede leerlo pero una función es undefined, necesitas un polyfill. La mayoría de los proyectos reales usan ambos a la vez.
Cómo encaja esto en una cadena de herramientas moderna
En la práctica no se ejecutan estas herramientas a mano. Un empaquetador o herramienta de compilación — como Vite, Webpack o esbuild — las gestiona por ti. Una configuración típica funciona así:
- Declaras tus entornos objetivo una sola vez, en
browserslist. - La herramienta de compilación ejecuta Babel con
@babel/preset-env, que convierte a versiones inferiores solo la sintaxis que tus objetivos no soportan. - La misma configuración inyecta polyfills de
core-jspara las API que esos objetivos no tienen — y, conuseBuiltIns: 'usage', solo aquellas a las que tu código hace referencia.
El resultado es un paquete ajustado a tu audiencia real: nada se transforma o rellena con polyfills si los navegadores de tus visitantes ya lo soportan.
Los navegadores evergreen de hoy — Chrome, Edge, Firefox y Safari — se actualizan automáticamente y ya soportan la gran mayoría de las características modernas de ES6 y posteriores. La transpilación intensiva y el uso generalizado de polyfills son mucho menos necesarios que antes. Establece un objetivo de browserslist realista para tu audiencia y deja que la cadena de herramientas convierta a versiones inferiores y añada polyfills solo donde sea genuinamente necesario.
Ignorar este consejo tiene un coste real. El exceso de polyfills infla tu paquete con código que todos los visitantes modernos descargan, analizan y desechan sin usar. Convertir a versiones inferiores de forma demasiado agresiva también produce una salida más grande y lenta. El objetivo no es "soportar todo", sino "soportar lo que tus usuarios realmente ejecutan". Para razonar sobre qué características soporta un navegador dado, el capítulo sobre compatibilidad del DOM y del navegador es un complemento útil.