Trocear los bundles con Webpack: code splitting

  • Por
Cómo trocear los bundles con Webpack: hacer paquetes de código Javascript más reducidos, incorporando únicamente el código que necesitas para cada vista de la aplicación.

En aplicaciones web frontend modernas es habitual que el código Javascript ocupe una gran cantidad de KB. Fácilmente una aplicación frontend puede tener varios cientos de Kilobytes, algo que es normal, si pensamos en todas las funciones que Javascript realizará dentro de la aplicación. Quizás no sea algo tan grave, si tenemos en cuenta en la velocidad de las redes actuales, pero ya se sabe que todo lo que se pueda hacer para mejorar la experiencia de usuario es más que relevante.

Además, en una aplicación con decenas de vistas o secciones, es importante pensar que un usuario quizás no va a necesitar nunca muchas partes del código Javascript, pertenecientes a secciones que quizás no llegue a entrar nunca. Es por ello que resulta muy importante la posibilidad de trocear los bundles, realizando la tarea conocida por "code splitting".

Con el code splitting conseguimos crear diversos paquetes con el código de Javascript de cada parte de la aplicación por separado. Generalmente tendremos uno o varios bundles globales, que necesitas de manera común en todo el sitio y un paquete particular para cada punto de entrada. Así, cuando por ejemplo un usuario entra en la portada del sitio, se descargará el código global y únicamente el código particular de la portada, dejando para más adelante la carga del código específico de cada una de las secciones interiores. Por supuesto, cuando se acceda como punto de entrada a una sección interna de la aplicación, se requerirá el bundle global y además el bundle específico de esa sección, dejando aparte el código de la portada y de las otras secciones de la aplicación.

Este code splitting se realiza de manera automática con el CLI de muchos de los frameworks y librerías frontend. Pero si nosotros configuramos Webpack a mano tendremos que hacer esta personalización también de manera manual. Es lo que vamos a abordar en este artículo del Manual de Webpack.

Construir una aplicación para permitir el code splitting

Primero hay que advertir, para conseguir partir en trocitos tu código Javascript, es que tienes que haber construido tu aplicación correctamente. Si has colocado todo el código a partir de tu index.js sería imposible, porque Webpack analizará ese archivo y colocará todo en un mismo bundle.

Lo que tienes que hacer para conseguir que Webpack pueda producir los bundles es básicamente tener varios archivos índice, uno para cada sección. Basicamente tendremos un archivo llamado algo como "index-portada.js" con el código necesario en la portada. Un "index-dashboard.js", con el código del dashboard, un "index-view_x.js" con el código de la vista "x". En cada uno de esos index generalmente lo que tendrás será una serie de imports a cada uno de los submódulos necesarios para cada una de las funcionalidades o componentes que estás programando para esa parte de tu aplicación.

Así, con el código separado por puntos de entrada, será sencillo que Webpack pueda analizar el código y crear los bundles correctamente configurados.

Para concretar un poco, aquí dejo dos códigos de dos supuestos puntos de entrada.

Página de artículos (article.js)

import './index';
import './components/vote/dw-vote';
import './components/article/dw-article-vote';

Página de FAQ (faq.js)

import './index';
import './components/vote/dw-vote';
import './components/faq/dw-faq-vote';
import './components/faq/dw-faq-form';
import './components/faq/dw-answer-vote';

Solo tienes que fijarte en el detalle de que cada uno de estos puntos de entrada, el de los artículos y el de las FAQ, tiene una serie de imports. Siendo comunes algunos de ellos. Obviamente algunos de estos imports pueden ser dependencias desarrolladas por nosotros y otras dependencias de terceros. Además, en cada uno de estos imports puedes tener otros imports dentro. Webpack será capaz de analizar el código, revisando las dependencias, y las dependencias de las dependencias para crear los bundles correctamente. Nosotros simplemente le tenemos que dejar claro el código de cada uno de nuestros posibles puntos de entrada en la aplicación.

Configurar puntos de entrada y salida en Webpack

Ya dentro de la configuración de Webpack,el primer paso para conseguir partir el código Javascript en trocitos es configurar las propiedades de input y output. Esta parte es bastante sencilla.

Simplemente tenemos que configurar cada punto de entrada con su index particular. Para nuestro ejemplo simplificado teníamos dos puntos de entrada, el de artículos y el de FAQ. Cada uno de ellos los colocamos como entry points.

entry: {
  faq: './src/faq.js',
  article: './src/article.js',
}

Luego también tenemos que configurar el output de modo que cada bundle le asigne un nombre distinto. Para construir personalizar el nombrado del archivo usaremos el nombre de la propiedad de cada punto de entrada, de esta manera.

output: {
  path: path.resolve(__dirname, '../../../public/js/bundles'),
  filename: '[name].bundle.js',
}

El problema que nos podemos encontrar en esta configuración, si lo dejamos tal cual, es que Webpack repita el código común en cada uno de los bundles. Es decir, tenemos que hacer un par de pasos adicionales para que el sistema de análisis del código de Webpack sea capaz de quitar las partes comunes de cada punto de entrada y llevarlas a otros bundles comunes.

Usando SplitChunksPlugin

Este es el plugin que previene la duplicación del código entre los bundles de la aplicación. Para usarlo simplemente tenemos que configurar el archivo webpack.config.js.

Nota: En principio el plugin SplitChunksPlugin ya viene instalado cuando instalas Webpack, por lo que es solamente usarlo a través de la configuración.

En este caso la novedad dentro del archivo de configuración de Webpack es la configuración "optimization", donde usamos el objeto "splitChunks", con una personalización básica: chunks: 'all'.

module.exports = {
  entry: {
    faq: './src/faq.js',
    article: './src/article.js',
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, '../../../public/js/bundles/'),
  },
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  },
}

La configuración realizada para SplitChunksPlugin en este caso puede ser ideal para diversos tipos de proyecto, pues hace diversos bundles con un criterio bastante estándar. Básicamente estamos indicando que trocee todo lo que pueda, creando por una parte los bundles con nuestro propio código dividido por secciones o puntos de entrada, y por otro los bundles de terceros (los vendors).

Nota: Colocar los vendors en un bundle por separado nos ofrece una ventaja interesante en tiempo de desarrollo, ya que nos permite ahorrar mucho tiempo a la hora de producir los bundles de la aplicación, ya que no tiene que volver a recompilar cada vez todo el código de las librerías de terceros (que no suelen cambiar habitualmente entre compilación y compilación). También en tiempo de ejecución, ya que igualmente no suelen cambiar los vendors entre builds, por lo que el navegador los puede mantener cacheados y cada vez que hagamos un deploy, nuestros usuarios no tendrán que volver a descargar todo el código de la aplicación.

Al compilar la aplicación, Webpack nos ofrece esta salida detallando los bundles que ha creado, así como su peso, nombres de archivos, etc.

Además puedes fijarte que para cada punto de entrada de tu aplicación te indica cuáles son los bundles que deberías cargar, lo que aclara cómo debería ser la colocación de las etiquetas SCRIPT y sus src.

Otras configuraciones para el troceado del código

Webpack nos permite una configuración bastante minuciosa para indicarle cómo queremos que construya los bundles. Dependiendo de nuestra aplicación podemos tener diversas alternativas de configuraciones posibles. Aquí sería escoger la que más sentido tenga para nosotros.

Para facilitarnos la tarea, en la documentación del plugin SplitChunksPlugin encontramos diversos ejemplos que nos sirven para orientarnos a la hora de crear nuestro archivo de configuración.

A mi me ha resultado particularmente interesante esta configuración:

optimization: {
  splitChunks: {
    cacheGroups: {
      commons: {
        name: 'commons',
        chunks: 'initial',
        minChunks: 2
      }
    }
  }
},

Gracias a ella conseguimos separar todo el código común de la aplicación a un bundle y el de cada uno de los puntos de entrada en otros bundles particulares. De esta manera, tendremos en cada sección el bundle común más el bundle particular de esa página. Es una configuración bastante sencilla de entender y de usar en la aplicación, ya que reduce el número de bundles y queda muy intuitiva la selección necesaria para cada punto de entrada.

La salida que produce Webpack al producir los bundles es ahora la siguiente:

Para cada punto de entrada tendremos que cargar el archivo commons.bundle.js y su archivo con el código particular de esa sección.

Nota: Como advierten en la documentación del plugin SplitChunksPlugin, la desventaja de esta alternativa radica en producir un bundle "commons" que tiende a ser demasiado grande, pues agrupa cualquier cosa que sea común a todo el proyecto. Por decirlo de alguna manera, es como si tuviéramos un bundle con todo el código de la aplicación, en el que hemos podido aligerar el código de todo lo que sólo se usa en una (y sólo una) sección en concreto. No obstante, ya resulta una ventaja importante con respecto a la alternativa de no hacer ningún troceado de código.

Conclusión

Hemos podido conocer una de las alternativas más importantes que nos ofrece Webpack para la optimización del código de las aplicaciones. Nos permite usar un conjunto de bundles que contienen el código de cada sección por separado, aligerando mucho el peso que necesitamos descargar y la cantidad de código a procesar, en el primer renderizado de la aplicación. Gracias al troceado del código Javascript podrás mejorar la experiencia de usuario.

Obviamente, cuando accedemos a nuestra página, tenemos que cargar solamente los bundles que esa determinada sección usada como punto de entrada necesita. Retrasando la carga de los scripts y componentes que no se van a usar hasta más adelante. Todos los bundles que se necesiten a continuación se tendrán que incluir por carga perezosa (lazy load) y para ello usarás las prácticas habituales de Javascript nativo, o bien de tu framework o librería si es que te ofrecen una manera específica de realizarlo.

Para traerte por lazy load los bundles necesarios para las nuevas vistas o secciones a las que acceda el usuario generalmente usarás el estándar de los "dinamic imports" (imports dinámicos de ES6), que es algo que ya está disponible en la mayoría de los navegadores comunes (y cuando no esté, tendrás que usar los correspondientes polyfills).

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