Paquete inteligente: cómo entregar código heredado solo a navegadores heredados
- El costo de admitir navegadores heredados
- Una guía práctica para enviar código eficiente
- Herramientas que necesitaremos
- 1. Definición de navegadores modernos y heredados
- 2. Transpilación y polillenado de ES6+
- 3. Características de la plataforma web Polyfilling
- 4. Prefijo CSS
- Poniendolo todo junto
- Ofreciendo el paquete adecuado a los usuarios
- Conclusión: ¿Vale la pena?
- Paquete diferencial que sirve en producción
Si bien la agrupación efectiva de recursos en la web ha recibido mucha atención en los últimos tiempos, la forma en que enviamos recursos de front-end a nuestros usuarios se ha mantenido prácticamente igual. El peso promedio de JavaScript y los recursos de estilo que incluye un sitio web está aumentando, aunque las herramientas de creación para optimizar el sitio web nunca han sido mejores. Con la participación de mercado de los navegadores tradicionales aumentando rápidamente y los navegadores lanzando soporte para nuevas funciones al mismo tiempo, ¿es hora de repensar la entrega de activos para la web moderna?
Hoy en día, un sitio web recibe una gran parte de su tráfico de navegadores permanentes, la mayoría de los cuales tienen buen soporte para ES6+, nuevos estándares de JavaScript, nuevas API de plataforma web y atributos CSS. Sin embargo, los navegadores heredados aún deben ser compatibles en el futuro cercano: su uso compartido es lo suficientemente grande como para no ser ignorado, dependiendo de su base de usuarios.
Un vistazo rápido a la tabla de uso de caniuse.com revela que los navegadores tradicionales ocupan la mayor parte del mercado de navegadores: más del 75%. A pesar de esto, la norma es prefijar CSS, transpilar todo nuestro JavaScript a ES5 e incluir polyfills para admitir a todos los usuarios que nos interesan.
Si bien esto es comprensible desde un contexto histórico (la Web siempre se ha centrado en la mejora progresiva), la pregunta sigue siendo: ¿estamos ralentizando la Web para la mayoría de nuestros usuarios para admitir un conjunto cada vez menor de navegadores heredados?
Intentemos comprender cómo los diferentes pasos en una canalización de compilación típica pueden agregar peso a nuestros recursos de front-end:
Transpilando a ES5
Para estimar cuánto peso puede agregar la transpilación a un paquete de JavaScript, tomé algunas bibliotecas de JavaScript populares escritas originalmente en ES6+ y comparé los tamaños de sus paquetes antes y después de la transpilación:
| Biblioteca | Tamaño (ES6 minificado) |
Tamaño (ES5 minificado) |
Diferencia |
|---|---|---|---|
| TodoMVC | 8,4 KB | 11 KB | 24,5% |
| Arrastrable | 53,5KB | 77,9KB | 31,3% |
| Luxón | 75,4KB | 100,3KB | 24,8% |
| Vídeo.js | 237,2KB | 335,8KB | 29,4% |
| PixiJS | 370,8KB | 452KB | 18% |
En promedio, los paquetes no transpilados son aproximadamente un 25 % más pequeños que los que se han transpilado a ES5. Esto no es sorprendente dado que ES6+ proporciona una forma más compacta y expresiva de representar la lógica equivalente y que la transpilación de algunas de estas características a ES5 puede requerir mucho código.
ES6+ Polirellenos
Si bien Babel hace un buen trabajo al aplicar transformaciones sintácticas a nuestro código ES6+, las características integradas introducidas en ES6+, como Promise, Mapy Set, y nuevos métodos de matriz y cadena, aún deben completarse. Ingresarlo babel-polyfilltal como está puede agregar cerca de 90 KB a su paquete minimizado.
Plataforma web Polyfills
El desarrollo de aplicaciones web modernas se ha simplificado gracias a la disponibilidad de una gran cantidad de nuevas API de navegador. Los más utilizados son fetch, para solicitar recursos, IntersectionObserverpara observar eficientemente la visibilidad de los elementos y la URLespecificación, lo que facilita la lectura y manipulación de URL en la web.
Agregar un polyfill que cumpla con las especificaciones para cada una de estas características puede tener un impacto notable en el tamaño del paquete.
Prefijo CSS
Por último, veamos el impacto del prefijo CSS. Si bien los prefijos no agregarán tanto peso muerto a los paquetes como lo hacen otras transformaciones de compilación, especialmente porque se comprimen bien cuando se hacen con Gzip, todavía se pueden lograr algunos ahorros aquí.
| Biblioteca | Tamaño (minimizado, con prefijo para las últimas 5 versiones del navegador) |
Tamaño (minimizado, con el prefijo de la última versión del navegador) |
Diferencia |
|---|---|---|---|
| Oreja | 159KB | 132KB | 17% |
| Bulma | 184KB | 164KB | 10,9% |
| Base | 139KB | 118KB | 15,1% |
| IU semántica | 622KB | 569 KB | 8,5% |
Una guía práctica para enviar código eficiente
Probablemente sea evidente hacia dónde quiero llegar con esto. Si aprovechamos los canales de compilación existentes para enviar estas capas de compatibilidad solo a los navegadores que las requieran, podemos ofrecer una experiencia más ligera al resto de nuestros usuarios (aquellos que forman una mayoría creciente) mientras mantenemos la compatibilidad para los navegadores más antiguos.
Esta idea no es del todo nueva. Servicios como Polyfill.io son intentos de rellenar dinámicamente entornos de navegador en tiempo de ejecución. Pero enfoques como este adolecen de algunas deficiencias:
- La selección de polyfills se limita a los enumerados en el servicio, a menos que usted mismo aloje y mantenga el servicio.
- Debido a que el polyfilling ocurre en tiempo de ejecución y es una operación de bloqueo, el tiempo de carga de la página puede ser significativamente mayor para los usuarios de navegadores antiguos.
- Servir un archivo polyfill personalizado a cada usuario introduce entropía en el sistema, lo que dificulta la resolución de problemas cuando algo sale mal.
Además, esto no resuelve el problema del peso agregado por la transpilación del código de la aplicación, que a veces puede ser mayor que los propios polyfills.
Veamos cómo podemos solucionar todas las fuentes de hinchazón que hemos identificado hasta ahora.
Herramientas que necesitaremos
- Webpack
Esta será nuestra herramienta de compilación, aunque el proceso seguirá siendo similar al de otras herramientas de compilación, como Parcel y Rollup. - Lista de navegadores
Con esto, administraremos y definiremos los navegadores que nos gustaría admitir. - Y usaremos algunos complementos de soporte de Browserslist .
Primero, queremos dejar claro qué queremos decir con navegadores "modernos" y "heredados". Para facilitar el mantenimiento y las pruebas, es útil dividir los navegadores en dos grupos discretos: agregar navegadores que requieren poco o ningún polyfilling o transpilación a nuestra lista moderna y colocar el resto en nuestra lista heredada.
Una configuración de Browserslist en la raíz de su proyecto puede almacenar esta información. Las subsecciones "Entorno" se pueden utilizar para documentar los dos grupos de navegadores, así:
[modern]Firefox = 53Edge = 15Chrome = 58iOS = 10.1[legacy] 1%
La lista que se proporciona aquí es sólo un ejemplo y se puede personalizar y actualizar según los requisitos de su sitio web y el tiempo disponible. Esta configuración actuará como fuente de verdad para los dos conjuntos de paquetes de interfaz de usuario que crearemos a continuación: uno para los navegadores modernos y otro para todos los demás usuarios.
2. Transpilación y polillenado de ES6+
Para transpilar nuestro JavaScript de manera respetuosa con el medio ambiente, usaremos babel-preset-env.
Inicialicemos un .babelrcarchivo en la raíz de nuestro proyecto con esto:
{ "presets": [ ["env", { "useBuiltIns": "entry"}] ]}
Habilitar la useBuiltInsbandera permite a Babel rellenar selectivamente las funciones integradas que se introdujeron como parte de ES6+. Debido a que filtra los polirellenos para incluir solo los requeridos por el medio ambiente, mitigamos el costo de envío babel-polyfillen su totalidad.
Para que esta bandera funcione, también necesitaremos importar babel-polyfillen nuestro punto de entrada.
// Inimport "babel-polyfill";
Al hacerlo, se reemplazará la babel-polyfillimportación grande con importaciones granulares, filtradas por el entorno del navegador al que nos dirigimos.
// Transformed outputimport "core-js/modules/es7.string.pad-start";import "core-js/modules/es7.string.pad-end";import "core-js/modules/web.timers";…
3. Características de la plataforma web Polyfilling
Para enviar polyfills para funciones de plataforma web a nuestros usuarios, necesitaremos crear dos puntos de entrada para ambos entornos:
require('whatwg-fetch');require('es6-promise').polyfill();// … other polyfills
Y esto:
// polyfills for modern browsers (if any)require('intersection-observer');
Este es el único paso de nuestro flujo que requiere cierto grado de mantenimiento manual. Podemos hacer que este proceso sea menos propenso a errores agregando eslint-plugin-compat al proyecto. Este complemento nos advierte cuando utilizamos una función del navegador que aún no se ha completado.
4. Prefijo CSS
Finalmente, veamos cómo podemos reducir los prefijos CSS para navegadores que no lo requieren. Debido a que autoprefixerfue una de las primeras herramientas en el ecosistema que admitió la lectura de un browserslistarchivo de configuración, no tenemos mucho que hacer aquí.
Debería ser suficiente crear un archivo de configuración PostCSS simple en la raíz del proyecto:
module.exports = { plugins: [ require('autoprefixer') ],}
Poniendolo todo junto
Ahora que hemos definido todas las configuraciones de complementos requeridas, podemos armar una configuración de paquete web que las lea y genere dos compilaciones dist/moderny dist/legacycarpetas separadas.
const MiniCssExtractPlugin = require('mini-css-extract-plugin')const isModern = process.env.BROWSERSLIST_ENV === 'modern'const buildRoot = path.resolve(__dirname, "dist")module.exports = { entry: [ isModern ? './polyfills.modern.js' : './polyfills.legacy.js', "./main.js" ], output: { path: path.join(buildRoot, isModern ? 'modern' : 'legacy'), filename: 'bundle.[hash].js', }, module: { rules: [ { test: /.jsx?$/, use: "babel-loader" }, { test: /.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'] } ]}, plugins: { new MiniCssExtractPlugin(), new HtmlWebpackPlugin({ template: 'index.hbs', filename: 'index.html', }), },};
Para terminar, crearemos algunos comandos de compilación en nuestro package.jsonarchivo:
"scripts": { "build": "yarn build:legacy yarn build:modern", "build:legacy": "BROWSERSLIST_ENV=legacy webpack -p --config webpack.config.js", "build:modern": "BROWSERSLIST_ENV=modern webpack -p --config webpack.config.js"}
Eso es todo. La ejecución yarn buildahora debería darnos dos compilaciones, que son equivalentes en funcionalidad.
Ofreciendo el paquete adecuado a los usuarios
Crear compilaciones separadas nos ayuda a lograr solo la primera mitad de nuestro objetivo. Todavía necesitamos identificar y ofrecer el paquete adecuado a los usuarios.
¿Recuerda la configuración de Browserslist que definimos anteriormente? ¿No sería bueno si pudiéramos usar la misma configuración para determinar en qué categoría cae el usuario?
Ingrese browserslist-useragent . Como sugiere el nombre, browserslist-useragentpuede leer nuestra browserslistconfiguración y luego hacer coincidir un agente de usuario con el entorno relevante. El siguiente ejemplo demuestra esto con un servidor Koa:
const Koa = require('koa')const app = new Koa()const send = require('koa-send')const { matchesUA } = require('browserslist-useragent')var router = new Router()app.use(router.routes())router.get('/', async (ctx, next) = { const useragent = ctx.get('User-Agent') const isModernUser = matchesUA(useragent, { env: 'modern', allowHigherVersions: true, }) const index = isModernUser ? 'dist/modern/index.html', 'dist/legacy/index.html' await send(ctx, index);});
Aquí, establecer la allowHigherVersionsbandera garantiza que si se lanzan versiones más nuevas de un navegador (aquellas que aún no forman parte de la base de datos de Can I Use), seguirán reportándose como veraces para los navegadores modernos.
Una de browserslist-useragentlas funciones de es garantizar que se tengan en cuenta las peculiaridades de la plataforma al hacer coincidir los agentes de usuario. Por ejemplo, todos los navegadores en iOS (incluido Chrome) usan WebKit como motor subyacente y se relacionarán con la consulta de lista de navegadores específica de Safari respectiva.
Puede que no sea prudente confiar únicamente en la exactitud del análisis del agente de usuario en producción. Al recurrir al paquete heredado para navegadores que no están definidos en la lista moderna o que tienen cadenas de agentes de usuario desconocidas o no analizables, nos aseguramos de que nuestro sitio web siga funcionando.
Conclusión: ¿Vale la pena?
Hemos logrado cubrir un flujo de extremo a extremo para enviar paquetes sin hinchazón a nuestros clientes. Pero es razonable preguntarse si los gastos de mantenimiento que esto añade a un proyecto valen sus beneficios. Evaluemos los pros y los contras de este enfoque:
1. Mantenimiento y pruebas
Se requiere uno para mantener solo una única configuración de Browserslist que impulse todas las herramientas en este proceso. La actualización de las definiciones de los navegadores modernos y heredados se puede realizar en cualquier momento en el futuro sin tener que refactorizar las configuraciones o el código de soporte. Yo diría que esto hace que los gastos generales de mantenimiento sean casi insignificantes.
Sin embargo, existe un pequeño riesgo teórico asociado con confiar en Babel para producir dos paquetes de código diferentes, cada uno de los cuales debe funcionar bien en su entorno respectivo.
Si bien los errores debidos a diferencias en los paquetes pueden ser poco frecuentes, monitorear estas variantes en busca de errores debería ayudar a identificar y mitigar eficazmente cualquier problema.
2. Tiempo de construcción versus tiempo de ejecución
A diferencia de otras técnicas que prevalecen en la actualidad, todas estas optimizaciones ocurren en el momento de la compilación y son invisibles para el cliente.
3. Velocidad progresivamente mejorada
La experiencia de los usuarios de los navegadores modernos se vuelve significativamente más rápida, mientras que los usuarios de los navegadores antiguos siguen recibiendo el mismo paquete que antes, sin consecuencias negativas.
A menudo evitamos el uso de nuevas funciones del navegador debido al tamaño de los polyfills necesarios para utilizarlas. A veces, incluso elegimos polyfills más pequeños que no cumplen con las especificaciones para ahorrar tamaño. Este nuevo enfoque nos permite utilizar polyfills que cumplan con las especificaciones sin preocuparnos mucho de afectar a todos los usuarios.
Paquete diferencial que sirve en producción
Dadas las importantes ventajas, adoptamos este proceso de construcción al crear una nueva experiencia de pago móvil para los clientes de Urban Ladder, uno de los minoristas de muebles y decoración más grandes de la India.
En nuestro paquete ya optimizado, pudimos obtener ahorros de aproximadamente el 20% en los recursos CSS y JavaScript con Gzip enviados por cable a los usuarios móviles modernos. Debido a que más del 80% de nuestros visitantes diarios utilizaban estos navegadores permanentes, el esfuerzo realizado valió la pena.
Recursos adicionales
- “ Cargar Polyfills sólo cuando sea necesario ”, Philip Walton
@babel/preset-env
Un ajuste preestablecido de Babel inteligente- Browserslist “Herramientas”
Ecosistema de complementos creados para Browserslist - ¿Puedo utilizar
la tabla de cuota de mercado actual del navegador?
(dm, ra, yk, il, al)Explora más en
- Navegadores
- Actuación
- javascript
- Herramientas

Deja un comentario