Apprendre Symfony 6 : Bundler ses Assets avec Webpack Encore

Webpack Encore est un bundle Symfony intégrant Webpack, un module JS permettant d'optimiser son workflow frontend (compilation, minification, rétrocompatibilité…).

Icône de calendrier
Intermédiaire
13 chapitres

Comprendre Webpack

Avant webpack : les task runners

Avant, webpack il y avait Grunt ou encore Gulp. Il s’agit de « task runners » permettant d’effectuer des tâches utiles sur nos fichiers de travail SCSS, JavaScript…

Ces tâches se caractérisent entre autres par :

  • De la minification
  • De la rétrocompatibilité (PostCSS, Babel…)
  • De la compilation / transpilation (par exemple SCSS vers CSS)
  • Du tree shaking
  • Du formatage et du linting de code

En revanche, la configuration de ces tâches via des task runners est assez fastidieuse.

Webpack : un bundler de modules

Webpack est un module Open Source dont le rôle est de « bundler » des fichiers JavaScript. Il vient importer une couche d’abstraction aux task runners afin d’en faciliter la configuration et l’exploitation.

Concrètement, Webpack va :

  1. Importer les fichiers d’entrée (SCSS, JavaScript, PNG…) en tant que modules. On les appelle « assets » ou « entrypoints ».
  2. Analyser et appliquer un traitement sur ces fichiers (compilation, minification, rétrocompatibilité…).
  3. Les regrouper en un (ou plusieurs) fichier de sortie appelé « output ». C’est eux qui seront inclus dans les pages HTML via les balises <link> et <script>.

Par défaut, Webpack ne comprend que les fichiers JavaScript et JSON. Il n’est donc initialement pas capable de lire un fichier .scss ou encore .ts. Pour traiter d’autres types de fichiers, il faudra alors utiliser un loader approprié, en charge d’effectuer ce « preprocessing ».

Alors que les loaders sont utilisés pour transformer et traiter certains types de modules, les plugins leurs confèrent des fonctionnalités supplémentaires telles que l’optimisation des bundles, la compression d’image, l’autopréfixe, etc.

Installation de Webpack Encore

Webpack Encore est un bundle intégrant le bundler Webpack au sein d’une architecture Symfony.

Installer Node JS

Afin que Webpack soit en mesure de bundler nos assets, il est important d’installer l’environnement d’exécution JavaScript Node.js.

Installer le bundle Webpack Encore

Ajoutons cette nouvelle dépendance à notre projet avec composer :

copié !
composer require symfony/webpack-encore-bundle

Cette ligne de commande va effectuer certaines modifications dans votre projet Symfony :

Ajouter les dépendances
  • Création du fichier 📄 package.json et ajout de la dépendance Webpack
  • Ajout de la dépendance Webpack Encore dans 📄 composer.json
Ajouter les fichiers de configuration
  • Création du fichier 📄 webpack.config.js
  • Création du fichier 📄 config/packages/webpack_encore.yaml
Enregistrer le bundle Webpack Encore

Enregistrement du bundle dans le fichier 📄 config/bundles.php

Créer le dossier 📁 assets

Ce dossier, présent à la racine du projet, est destiné à contenir les fichiers sources/d’entrée (CSS, SASS, JS…) buildés avec Webpack. Ces fichiers de développement seront buildés vers le répertoire de sortie 📁 public.

Vous constaterez que votre dossier 📁 assets contient les fichiers suivants :

  • 📄 app.js : Il s’agit du fichier principal qui importe tous les fichiers sources du projet afin de les bundler vers un fichier de sortie 📄 app.js (et 📄 app.css).
  • 📄 styles/app.css : Il s’agit d’un fichier de style créé par défaut et importé dans 📄 app.js. C’est ici que vous pouvez écrire votre CSS.
  • 📄 bootstrap.js, 📄 controllers.json, 📄 controllers/hello_controller.js : Il s’agit de fichiers utilisés pour la configuration de Stimulus. Stimulus est un micro-framework JS pour ajouter de la réactivité aux applications Symfony, sans passer par des solutions comme Vue, React ou encore Angular, parfois trop « riches » pour un projet (virtual DOM, data-binding, système de templating…).

Ensuite, installer les paquets avec votre gestionnaire de paquet favori. Dans mon cas je travaillerai avec npm, mais vous pouvez bien entendu taper les lignes de commande équivalentes pour yarn.

copié !
npm install

Le répertoire 📁 node_modules apparaît désormais à la racine du projet et vous voyez qu’une section dédiée a été ajoutée dans le fichier 📄 .gitignore.

Configurer Webpack Encore

La configuration de Webpack Encore est gérée depuis le fichier 📄 webpack.config.js.

Ce fichier contient déjà une configuration de base :

webpack.config.js
copié !
// ...

Encore
	// directory where compiled assets will be stored
	.setOutputPath('public/build/')
	// public path used by the web server to access the output path
	.setPublicPath('/build')
	// only needed for CDN's or subdirectory deploy
	//.setManifestKeyPrefix('build/')

	/*
	 * ENTRY CONFIG
	 *
	 * Each entry will result in one JavaScript file (e.g. app.js)
	 * and one CSS file (e.g. app.css) if your JavaScript imports CSS.
	 */
	.addEntry('app', './assets/app.js')

	// enables the Symfony UX Stimulus bridge (used in assets/bootstrap.js)
	.enableStimulusBridge('./assets/controllers.json')

	// ...

module.exports = Encore.getWebpackConfig();

Ce fichier indique notamment :

  • setOutputPath() : où doivent être stockées les assets compilées.
  • setPublicPath() : le répertoire public du serveur web.
  • addEntry() : indique de charger le fichier 📄 assets/app.js. En fonction des imports qu’il contient, tout sera buildé dans un unique fichier de sortie 📄 public/build/app.js (et 📄 public/build/app.css si une feuille de style est importée).
  • enableStimulusBridge() : active le microframework Stimulus

Ajouter une seconde méthode addEntry() permettrait de builder un autre fichier. Cela s’avèrerait intéressant pour des raisons de performance si le fichier en question n’est utilisé que sur une page spécifique du site web.

webpack.config.js
copié !
.addEntry('checkout', './assets/checkout.js')

Build des assets

Webpack Encore propose plusieurs lignes de commandes pour builder ses assets. Les alias de ces dernières sont visibles dans le fichier 📄 package.json.

Build (développement)

La commande npm run dev permet de builder vos assets vers le répertoire 📁 public/build.

copié !
npm run dev

La commande npm run watch permet de surveiller vos fichiers sources. À chaque fois que l’un d’entre eux va subir des modifications, alors le build sera automatiquement regénéré.

copié !
npm run watch

Nous privilégierons cette commande.

Vous constaterez désormais la présence de 📄 app.js et 📄 app.css dans votre dossier 📁 public/build. D’autres fichiers y sont aussi présents - ils sont utiles pour Webpack mais nous ne nous occuperons pas d’eux.

Build (production)

En production, les exigences ne sont pas les mêmes qu’en développement. Il est alors d’usage de :

  • Minifier les ressources pour des questions de performances.
  • Ne pas générer de fichier source map.

Cela sera automatiquement géré par la ligne de commande npm run build.

copié !
npm run build

Inclusion des ressources via Twig

Pour inclure les scripts et styles buildés dans 📁 public/build, Twig nous offre deux fonctions encore_entry_link_tags() et encore_entry_script_tags().

base.html.twig
copié !
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>{% block title %}Welcome!{% endblock %}</title>
		<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text></svg>">
		{# Run `composer require symfony/webpack-encore-bundle` to start using Symfony UX #}
		{% block stylesheets %}
			{{ encore_entry_link_tags('app') }}
		{% endblock %}

		{% block javascripts %}
			{{ encore_entry_script_tags('app') }}
		{% endblock %}
	</head>
	<body>
		{% block body %}{% endblock %}
	</body>
</html>

Ici, l’argument app transmis à ces fonctions correspond au premier argument de la méthode addEntry('app', './assets/app.js') du fichier de configuration 📄 webpack.config.js.

Fonctionnalités utiles

Module sur-mesure

Webpack Encore permet de bundler des modules. Cela signifie que vous pouvez créer et importer vos propres modules JavaScript.

Création du module

Créons par exemple un module nommé 📄 assets/scripts/random.js contenant une fonction générant un nombre entier aléatoire contenu dans un intervalle.

random.js
copié !
export default function(min, max) {
	return Math.floor(Math.random() * (max - min + 1)) + min;
}

Import du module

Pour importer ce module, on l’ajoute au build avec import dans 📄 assets/app.js.

app.js
copié !
import random from './scripts/random';

Vous pouvez désormais utiliser la fonction exportée par 📄 random.js.

copié !
console.log(random(3, 6));

Désormais, ce console.log() fera partie de l’output 📄 public/build/app.js et s’exécutera donc sur vos pages web.

Utiliser un préprocesseur CSS (Sass, Less…)

Sass, Less ou encore Stylus sont les préprocesseurs CSS les plus couramment utilisés.

Ils offrent une surcouche permettant de dynamiser le langage CSS, via des fonctionnalités comme :

  • L’import de fichiers
  • L’utilisation de variables
  • L’imbrication (nesting)
  • Les mixins
  • Les fonctions
  • L’héritage
  • Les boucles
  • Les conditions
  • Etc.
Activer le loader

Pour commencer, il faut activer dans le fichier 📄 webpack.config.js le loader propre au préprocesseur à utiliser.

  • Pour Sass, il s’agit de la méthode .enableSassLoader().
  • Pour Less, il s’agit de la méthode .enableLessLoader().
  • Pour Stylus, il s’agit de la méthode .enableStylusLoader().
Installer les dépendances nécessaires

En fonction du loader que vous avez activé, en relançant votre build avec npm run watch, le terminal vous invitera à installer les paquets nécessaires :

  • Le préprocesseur (ex : sass)
  • Son loader (ex : sass-loader)

Taper la ligne de commande d’installation spécifiée dans le terminal.

Les dépendances seront ajoutées en tant que dépendances de développement.

Vous pouvez désormais par exemple créer un fichier app.scss dans 📁 assets/scss

copié !
$colorPrimary: #645ff2;

nav {
	a {
		text-decoration: none;
		&.active {
			color: $colorPrimary;
		}
	}
}

L’ajouter au build avec import dans 📄 assets/app.js

app.js
copié !
import './scss/app.scss';

Lancer le build :

copié !
npm run watch

Autoprefixing avec PostCSS

PostCSS est un postprocesseur CSS. Son rôle est de modifier le code CSS, après qu’il ait été transpilé.

Un préprocesseur comme Sass va transformer un fichier .scss en .css.

Un postprocesseur comme PostCSS va analyser le code CSS généré et y effectuer un traitement en fonction, comme :

  • L’autoprefixing : assurant la compatibilité de notre code sur un maximum de navigateurs (« cross browsers »). Il ajoute automatiquement les préfixe -moz-, -webkit-, -o- et -ms- aux propriétés non prises en charge dans leur version initiale par tous les navigateurs.
  • Le linting : un outil pour vous alerter d’erreurs potentielles ou du non-respect de conventions CSS.
  • Etc.
Activer le loader

Pour commencer, il faut activer le loader de PostCSS. Cela se fait avec la méthode .enablePostCssLoader() dans le fichier 📄 webpack.config.js.

Installer les dépendances nécessaires

En relançant votre build avec npm run watch, le terminal vous invitera à installer postcss-loader, le paquet du loader de PostCSS. Taper la ligne de commande d’installation spécifiée dans le terminal.

La dépendance sera ajoutée en tant que dépendance de développement.

Un fichier de configuration

Pour configurer notre postprocesseur, on créera un fichier 📄 postcss.config.js à la racine de notre projet.

postcss.config.js
copié !
module.exports = {
		
}
Installer les plugins souhaitées

Une fois le loader installé, il est possible d’ajouter à Webpack les plugins souhaités via notre package manager favori.

Si nous souhaitons préfixer automatiquement nos propriétés CSS pour une comptabilité cross-browsers, on ajoutera par exemple autoprefixer :

copié !
npm install autoprefixer --save-dev

Ensuite, dans le fichier de configuration 📄 postcss.config.js, on ajoute le plugin dans notre configuration.

postcss.config.js
copié !
module.exports = {
	plugins: {
		autoprefixer: {}
	}
}

Enfin, il ne reste plus qu’à préciser quel(s) navigateur(s) supporter et dans quelle(s) version(s). Pour cela, on ajoute une clé browserslist à notre fichier 📄 package.json, en y spécifiant à l’intérieur un tableau de critères :

package.json
copié !
{
	// ...
	"browserslist": [
		"defaults"
	]
}

default est une valeur recommandée dans la plupart des cas. Elle correspond à l’extrait suivant :

package.json
copié !
{
	// ...
	"browserslist": [
		"> 0.5%",
		"last 2 versions",
		"Firefox ESR",
		"not dead"
	]
}

Pour constater le fonctionnement de l’autoprefix, ajoutez par exemple quelque part dans votre style la règle CSS suivante :

copié !
clip-path: path(
	"M0.5,1 C0.5,1,0,0.7,0,0.3 A0.25,0.25,1,1,1,0.5,0.3 A0.25,0.25,1,1,1,1,0.3 C1,0.7,0.5,1,0.5,1 Z"
);

La propriété clip-path n’ayant pas un support homogène, vous constaterez l’ajout d’une propriété préfixée par -webkit-, au niveau du fichier de sortie :

copié !
	-webkit-clip-path: path("M0.5,1 C0.5,1,0,0.7,0,0.3 A0.25,0.25,1,1,1,0.5,0.3 A0.25,0.25,1,1,1,1,0.3 C1,0.7,0.5,1,0.5,1 Z");
	clip-path: path("M0.5,1 C0.5,1,0,0.7,0,0.3 A0.25,0.25,1,1,1,0.5,0.3 A0.25,0.25,1,1,1,1,0.3 C1,0.7,0.5,1,0.5,1 Z");

Aller plus loin : Babel, Vue, React…

Webpack Encore est capable de nombreuses autres choses comme assurer la rétrocompatibilité du code via Babel ou encore exploiter les frameworks Vue et React avec des loaders dédiés.