Distribuir Javascript transpilado o no (ES5 o ES6) según navegador con Webpack

  • Por
Configurar Webpack para entrega de código transpilado o no, dependiendo de si el navegador es compatible solo con ES5 o si soporta ES2015 (ES6).

En la actualidad la mayoría de los navegadores soportan ES6. Sin embargo, gran cantidad de desarrolladores continúan distribuyendo el código Javascript transpilado, en ES5, para asegurar la compatibilidad con todos los navegadores.

Pero debemos preguntarnos ¿es esa la mejor estrategia? La verdad es que no. El transpilado es un mal necesario en muchos casos, pero podemos evitarlo para los navegadores que están preparados, acelerando la descarga y el procesamiento del código, lo que permitrá mejorar el rendimiento de los sitios y aplicaciones web.

En este artículo queremos explicar la configuración de Webpack para que puedas distribuir en tu aplicación frontend la versión del código que más convenga a cada navegador. Los que sean compatibles con ES6, recibirán el código original y aquellos browsers antiguos, que solo soportan ES5, recibirán el código transpilado.

Por qué no es buena idea transpilar para todos los navegadores

Cuando se transpila de ES2015 (ES6) a ES5, el código se transforma, para que sea compatible con todos los navegadores. En esa transformación ocurre que el código aumenta de tamaño, ya que se tienen que emular características disponibles ahora de manera nativa por los navegadores.

Ese aumento de tamaño es sensible. No son unas pocas KB, sino que en ocasiones puede representar casi el doble de tamaño. Por ejemplo, en un sitio web de tamaño minúsculo, un bundle Javascript escrito en ES6 puede pesar en torno de 180 Kb. Una vez transpilado puede acabar pesando 334 Kb. Con el pequeño proyecto que he construido para ilustrar el artículo vamos a demostrarlo. En esta imagen puedes ver los tamaños de los bundles, con el mismo código transpilado y sin transpilar.

Ahora imagina en términos de aplicaciones grandes, donde podemos tener varios cientos de Kb de diferencia. Te darás cuenta de lo importante que es ofrecer para cada navegador un código optimizado según sus capacidades. Pero además, piensa que el tiempo de descarga no es único precio a pagar por el transpilado. Si el navegador debe procesar el doble de código Javascript para hacer lo mismo, el rendimiento también se verá afectado.

Cómo configurar Webpack para que genere dos bundles con configuraciones distintas de Babel

Bien, vamos entonces a abordar la práctica, para conseguir primero los dos bundles que queremos como respuesta al proceso de producción del código.

  • Bundle con el código original (sin transpilar, en ES2015)
  • Bundle con el código transpilado (ES5)

Esta tarea la realizaremos con Webpack. Explicaremos el procedimiento a continuación, pero es importante que conozcas de antemano las bases de esta herramienta, para lo que te recomendamos la lectura del Manual de Webpack de DesarrolloWeb.com.

Para conseguirlo además vamos a usar Babel, que es quien realiza el transpilado realmente. Debemos producir por tanto dos configuraciones para Babel, una con transpilado y otra sin transpilado.

Nota: En nuestro ejemplo tendremos una configuración con transpilado y otra sin transpilado, pero en muchos casos podríamos tener realmente dos configuraciones de babel con transpilado, pues podríamos usar construcciones como async/await, que están disponibles solamente en ES7, y por tanto fuera del alcance actual de los navegadores. Así pues, podríamos tener un código transpilado a ES5 para navegadores antiguos y otro código transpilado a ES6 para los navegadores modernos. En cualquier caso, lo hagas como lo hagas, siguen siendo dos configuraciones diferentes de Babel.

Webpack no permite implementar en el mismo archivo de configuración (webpack.config.js) dos configuraciones distintas para Babel, por lo que tenemos que hacer el trabajo en archivos de configuración diferentes.

webpack.config.js para configuración con transpilado

Comenzemos echando un vistazo al archivo de configuración de Babel, para el código transpilado a ES5. Podría parecerse a algo como esto:

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  module: {
    rules: [
      { 
        test: /\.js$/, 
        //exclude: /node_modules/, 
        loader: "babel-loader" 
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({  
      template: 'src/index.html',
    })
  ]
}
Nota: recuerda que para hacer el transpilado correctamente, requieres instalar dependencias diversas de Babel, además de hacer el .babelrc y otros detalles que hemos explicado en el artículo de Transpilar con Webpack y Babel. También en este manual hemos explicado otras cosas usadas en el anterior ejemplo, como el HTML Webpack Plugin.

Webpack.config-es6.js para el bundle sin transpilado

Por otra parte tendremos un archivo de configuración especial, que no realiza transpilado. Este archivo es bastante similar, con la diferencia que no configuramos Babel en él.

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  output: {
    filename: 'main-es6.js'
  },
  plugins: [
    new HtmlWebpackPlugin({  
      template: 'dist/index.html',
    }),
  ]
}
Nota: En tu proyecto podría ser una configuración que transpila de ES7 a ES6, si fuera necesario según tu código Javascript.

Fíjate aquí en un detalle importante: El template de este segundo script, en la instanciación de HTML Webpack Plugin, se toma desde dist/index.html, en vez de src/index.html. Es para que al inyectar el código de este segundo bundle, se coloque el script sobre el archivo HTML ya generado en la configuración de webpack.config.js anterior.

La secuencia de ejecución y la creación del template será:

1) Se ejecuta primero Webpack con la configuración webpack.config.js
    1.1) Se toma como template src/index.html y se inyecta script del primer bundle.
2) Se ejecuta Webpack con la configuración webpack.config-es6.js.
    2.2) Se toma como template el archivo generado HTML en el paso anterior (1.1) y se inyecta el bundle

Scripts de npm para invocar las configuraciones

Estas dos configuraciones se tienen que ejecutar en llamadas diferentes a Webpack, la primera y la segunda, por ese orden. Para resumir esta tarea podemos crear unos scripts npm, en el package.json, como estos:

"scripts": {
    "build:es5": "webpack --mode production",
    "build:es6": "webpack --mode production --config webpack.config-es6.js",
    "build": "npm run build:es5 && npm run build:es6",
},
  • El primer script "build:es5" ejecuta Webpack con la configuración predeterminada que tenemos en webpack.config.js
  • El segundo script "build:es6" ejecuta Webpack con la configuración sin transpilado, que tenemos en el archivo webpack.config-es6.js.
  • El tercer script "build" tiene las llamadas a ambos scripts vistos anteriormente, de modo que se consiguen ejecutar ambos con un único comando.

Module y nomodule

Este punto es importante, puesto que una vez tenemos dos archivos con código orientado a navegadores que soportan ES6 y a los que solamente llegan a ES5, debemos aleccionar a cada cliente web a obtener el script más adecuado para él.

Conseguirlo es muy sencillo gracias a la etiqueta script, en la que podemos agregar atributos específicos para decirle al navegador cuál es su archivo de script para cargar. El atributo type="module" indicará a los navegadores que ese código está escrito con ES6 (ES2015). Mientras que el atributo "nomodule" indicará que ese código sólo está indicado para los navegadores que no entienden ES6.

Nota: Si deseas obtener una información más detallada de esta práctica para entregar el código adecuado de Javascript dependiendo de la compatibilidad de los navegadores, te recomendamos leer el artículo de los Módulos ES6.

El problema que se nos puede presentar en este punto, derivado del uso de Webpack, es que los códigos de los scripts se inyectan en la página mediante el uso de HTML Webpack Plugin. Este plugin, de casa, no está preparado para poder colocarle atributos específicos a las etiquetas SCRIPT, por lo que tenemos que usar un complemento del que no hemos hablado todavía.

Uso de Script Extension for HTML Webpack Plugin

El paquete de npm "script-ext-html-webpack-plugin" es un plugin de Webpack (aunque más específicamente deberíamos decir que Script Extension for HTML Webpack Plugin es un plugin de HTML Webpack plugin. Parece un trabalenguas!). Sirve para conseguir agregar de manera arbitraria cualquier tipo de atributo en la etiqueta SCRIPT colocada por el HTML Webpack Plugin.

Para poder usar este plugin primero tenemos que instalarlo.

npm install -D script-ext-html-webpack-plugin

Una vez instalado su uso es bastante similar al conocido HTML Webpack Plugin. Tenemos primero que hacer el require y a continuación instanciar el plugin en el array "plugins" de la configuración Webpack.

Veamos entonces cómo nos quedarían los dos archivos de configuración usados para este ejemplo de Webpack 4, una vez integramos el uso de Script Extension for HTML Webpack Plugin.

Archivo config.webpack.js

Te puedes fijar en el require, segunda línea de código, junto con el "new ScriptExtHtmlWebpackPlugin". Todo lo demás es exactamente igual.

const HtmlWebpackPlugin = require('html-webpack-plugin');
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');

module.exports = {
  module: {
    rules: [
      { 
        test: /\.js$/, 
        //exclude: /node_modules/, 
        loader: "babel-loader" 
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({  
      filename: 'index.html',
      template: 'src/index.html',
      hash: true,
      templateParameters: {
        titulo: 'Manual de Webpack en Desarrolloweb',
        encabezamiento: 'Aprendo Webpack en DesarrolloWeb.com',
      }
    }),
    new ScriptExtHtmlWebpackPlugin({
      custom: [
        {
          test: /\.js$/,
          attribute: 'nomodule',
        },
      ]
    })
  ]
}

Recuerda que con el anterior script de configuración estamos definiendo el código para los navegadores que solo tienen soporte a ES5. Por ello, se insertará en el atributo "nomodule" en la etiqueta del script.

Archivo webpack.config-es6.js

Este el script de configuración para producir el código ES6, para navegadores modernos.

const HtmlWebpackPlugin = require('html-webpack-plugin');
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');

module.exports = {
  output: {
    filename: 'main-es6.js'
  },
  plugins: [
    new HtmlWebpackPlugin({  
      filename: 'index.html',
      template: 'dist/index.html',
      hash: true
    }),
    new ScriptExtHtmlWebpackPlugin({
      module: 'js'
    })
  ]
}

En este caso usaremos una configuración específica de ScriptExtHtmlWebpackPlugin, en la que estamos indicando que coloque el atributo type="module".

Nota: Recuerda leer la documentación completa de Script Extension for HTML Webpack Plugin en su propia página de Github.

Inyección producida por esta configuración Webpack

Con estos cambios, conseguiremos unas etiquetas de script como las siguientes. Podrás observar la colocación de los atributos para module y nomodule.

<script type="text/javascript" src="main.js?e4db83476e1db5d39150" nomodule></script>
<script type="module" src="main-es6.js?3fbcd35a8d7f213030e7"></script>

Conclusión

Hemos visto cómo generar código de script con diversos sabores, adaptado o no a las últimas características de Javascript, ES2016 y ES5. Además hemos visto cómo inyectar esos scripts de modo que cada navegador descargue y use el más apropiado según su compatibilidad.

Ha sido una práctica un poco más compleja de las que habíamos visto anteriormente en el Manual de Webpack, pero que resultará muy útil para la optimización de sitios y aplicaciones modernas. Sin duda será fundamental en aplicaciones web complejas, con gran cantidad de Javascript adaptado para aprovechar las últimas ventajas del lenguaje.

Autor

Miguel Angel Alvarez

Miguel es fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Comenzó en el mundo del desarrollo web en el año 1997, transformando su hobby en su trabajo.

Compartir