Cómo instalar y configurar Bootstrap con Symfony Webpack Encore

Symfony + WebpackEncore

En este artículo quiero contaros los pasos que he seguido para hacer funcionar Bootstrap 4 junto con Webpack Encore y Symfony 4.

De primeras puede que su instalación parezca muy sencilla pero durante el proceso he encontrado alguna peculiaridad que me ha inspirado para hacer este pequeño artículo por si os topáis con lo mismo.

Así que, como siempre, vamos a ello!

Paso 1. Instalar y configurar Webpack Encore

Para empezar, instalaremos y configuraremos Webpack Encore siguiendo la magnífica documentación que posee este bundle:

Una de las cosas que más me gustan de Symfony es la extensa documentación que tiene de cada bundle / funcionalidad. Agiliza mucho el proceso de aprendizaje y siempre que me preguntan algún libro sobre este framework les remito a ella porque no creo que exista nada mejor. Un diez por quienes la mantienen!

Además, instalaremos Bootstrap mediante yarn de la forma habitual:

yarn add bootstrap

lo cual nos dejará sus archivos en la carpeta node_modules .

A partir de aquí empiezan los problemas.

Paso 2. Problemas

Supongamos que necesitáis usar alguna librería de Bootstrap (como por ejemplo collapse.js ).

Puesto que no vamos a importar el archivo bootstrap.js entero (si no, para que queremos webpackencore ^^) lo que haremos será crear dentro de la carpeta assets/js un archivo similar al siguiente:

// assets/js/collapse-menu.js
var $ = require(‘jquery’);
require(‘../../../node_modules/bootstrap/js/src/util.js’);
require(‘../../../node_modules/bootstrap/js/src/collapse.js’);

El problema es que si ahora tratáis de generar el build mediante Webpack Encore mediante el comando:

yarn run encore dev

es probable que os salte el siguiente fallo:

error in ./node_modules/bootstrap/js/src/collapse.js
Module parse failed: Unexpected token (278:8)
You may need an appropriate loader to handle this file type.
| _getConfig(config) {
|   config = {
|     ...Default,
|     ...config
|   }

Provocado por el uso del spread operator de javascript que apareció en la especificación ES6 de Javascript.

Es aquí donde la librería Babel acude al rescate, permitiéndonos transpilar el código Javascript de nuestros archivos a código compatible con los motores empleados por los navegadores (que suele ser habitual que se demoren en integrar las novedades de las nuevas especificaciones de Javascript).

Para configurarlo (Webpack Encore lo emplea por defecto como loader de los archivos javascript) podemos crear un archivo .babelrc en la raíz de nuestro proyecto o bien escribir las instrucciones de configuración directamente en nuestro archivo webpack.config.js . Personalmente prefiero la primera opción por evitar mezclar en un mismo archivo la configuración de varias librerías pero esto ya va al gusto de cada cual.

Para este caso concreto necesitaremos añadir dos preset de Babel, es-2015 y stage3 .


Nota. Con la llegada de la versión 7 de Babel se recomienda usar el siguiente preset:

@babel/preset-env

En vez de recurrir a los preset asociados a versiones de ES.

El cual podéis configurar de la siguiente forma:

{
“presets”: [
[
@babel/preset-env”,
{
“targets”: {
“chrome”: “58”,
“ie”: “11”
},
“useBuiltIns”: “usage”
}
]
]
}

stage3 añade la posibilidad de usar el spread operator en nuestros archivos mientras que es-2015 permitirá usar las novedades surgidas con la especificación aparecida en ES6. Los instalaremos mediante yarn:

yarn add babel-preset-es2015
yarn add babel-preset-stage-3

A continuación, en el archivo .babelrc añadimos lo siguiente:

{
“presets”: [
  [“es2015”, {
    “loose”: true,
    “modules”: false,
    “useBuiltIns”: true
  }],
  “stage-3”
]
}

Ahora con cualquiera de estas dos configuraciones en teoría ya debería funcionar la compilación correctamente, por lo que nuevamente lanzamos el comando:

yarn run encore dev

Pero… ¡sorpresa! Nos vuelve a suceder el mismo fallo.

Paso 3. Solución. Método getWebpackConfig

Rebuscando por Internet no parece haber mucha información acerca de este problema por lo que me tocó bajar un poco al barro hasta dar con la causa de ese error:

Webpack Encore configura Babel para que no pase por node_modules

Concretamente en la línea 136 del archivo:

@symfony/webpack-encore/lib/config-generator.js

Esto tiene sentido, pues así se ahorra un tiempo considerable de compilación. Sin embargo, no todas las librerías que instalamos vienen con una versión compatible con ES6 que nos permita importar tan solo algunos de sus módulos (el caso de Bootstrap que nos ocupa por ejemplo), por lo que es necesario que podamos compilar sus sources de cara a poder trabajar con ellas de la forma en que queremos (es decir, importando tan solo aquellos archivos que realmente necesitamos).

Para solucionar esto, es necesario que sobrescribamos dicha configuración para permitir que Babel pase por la carpeta:

node_modules/bootstrap

Y para ello editaremos el archivo webpack.config.js añadiendo esto al final:

const webpackConfig = Encore.getWebpackConfig();
if (webpackConfig.module && webpackConfig.module.rules) {
  for (const rule of webpackConfig.module.rules) {
    if (rule.use) {
      for (const loader of rule.use) {
        if (loader.loader === ‘babel-loader’) {
          rule.exclude = /node_modules\/(?!bootstrap\/).*/;
        }
      }
   }
  }
}
module.exports = webpackConfig;

Seguramente no sea la solución más limpia pero hasta el momento de escribir este artículo no se me ha ocurrido otra que permita instalar Bootstrap mediante yarn y ser capaz de compilar las librerías javascript que emplean operadores de la especificación ES6.

Una alternativa sería usar las librerías compiladas directamente de Bootstrap (disponibles en la carpeta dist del proyecto) pero dado que éstas no realizan un export al final, los plugins como collapse que dependen de la clase Utils.js de Bootstrap dan el fallo undefined al tratar de usar ésta última.

En cualquier caso, con esta solución ya podremos compilar sin problemas y seguir trabajando con Bootstrap y Webpack Encore (o con cualquier otra librería que nos diese el mismo fallo).