Utiliser Tailwind CSS 2, PurgeCSS avec Symfony et Webpack Encore

Introduction

J’ai suivi une formation Backend, et le Front n’est pas du tout ma spécialité, mais au quotidien, j’utilise généralement Bootstrap, et je dois dire que jusqu’à présent ce Framework me suffit largement. Ceci dit, de plus en plus de monde disent tout le bien qu’ils pensent de Tailwind CSS, et j’avais envie de tester par moi-même !

Histoire de faire les choses dans les règles de l’art, je vous propose d’utiliser Tailwind dans un projet Symfony, en utilisant Webpack Encore et PurgeCSS (qui est utilisé par Tailwind pour réduire la taille du fichier CSS, nous le verrons plus tard).

Mise en place d’un projet Symfony

Première étape, mettre en place un nouveau projet Symfony que l’on va appeler “tailwind”. Pour cela on utilise la commande classique de la CLI Symfony.

symfony new tailwind --full

On lance le serveur de Symfony pour vérifier que tout est OK.

symfony server:start -d

Et on vérifie dans un navigateur internet que tout est OK. Dans mon cas à l’adresse http://127.0.0.1:8000.

Installation de Webpack Encore

Deuxième étape de notre découverte, l’installation de Webpack Encore.

composer require symfony/webpack-encore-bundle

Une fois que Webpack Encore est en place, demandons à npm de nous télécharger les dépendances de base de Webpack Encore. (Nous aurions pu également utiliser yarn.)

npm install

Notre projet Symfony est désormais capable d’utiliser Webpack pour compiler du JS, et dans le cas qui nous concerne du CSS.

Installation de Tailwind CSS et PurgeCSS

Il est temps d’installer Tailwind et PurgeCSS. Comme nous l’utilisons dans un contexte “Webpack”, nous ajoutons et modifions un peu les commandes suggérées dans la doc de Tailwind.

npm install -D tailwindcss postcss-loader purgecss-webpack-plugin glob-all path autoprefixer

Qu’avons-nous installé avec cette commande ?

Fichier de configurations

Pour que notre mise en place de Tailwind fonctionne, nous devons modifier quelques fichiers.

Commençons par mettre en place un fichier postcss.config.js (à la racine de notre projet).

module.exports = {
    plugins: [
        require('tailwindcss'),
    ],
};

Modifiions ensuite notre fichier webpack.config.js, en y ajoutant en début de fichier les références aux librairies que nous allons utiliser.

var Encore = require('@symfony/webpack-encore');
const PurgeCssPlugin = require('purgecss-webpack-plugin');
const glob = require('glob-all');
const path = require('path')

Toujours dans le fichier webpack.config.js, activons l’utilisation du loader PostCSS tel que décrit dans la documentation de Webpack Encore.

    .enablePostCssLoader()

Il nous faut également importer les feuilles de style dans notre fichier CSS, c’est quand même l’objectif lorsque l’on utilise un framework CSS ;-). Pour cela ajoutons ces quelques lignes à notre fichier assets/styles/app.css.

@import "tailwindcss/base";

@import "tailwindcss/components";

@import "tailwindcss/utilities";

Notre premier build

À cette étape nous devrions être en mesure de lancer un premier build pour vérifier que notre configuration fonctionne.

npm run build

Suivant les cas, vous pouvez obtenir le message “Error: PostCSS plugin tailwindcss requires PostCSS 8.”. Pour corriger le problème, Tailwind vous propose la solution dans la documentation officielle : https://tailwindcss.com/docs/installation#post-css-7-compatibility-build

Une page de test

Pour valider le bon fonctionnement de notre installation, nous allons mettre en place une page de test qui utilisera les possibilités du Tailwind CSS.

symfony console make:controller Demo

Modifions le fichier “templates/base.html.twig” pour utiliser nos fichiers compilés par Webpack Encore.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}Welcome!{% endblock %}</title>
        {% block stylesheets %}{{ encore_entry_link_tags('app') }}{% endblock %}
    </head>
    <body>
        {% block body %}{% endblock %}
        {% block javascripts %}{{ encore_entry_script_tags('app') }}{% endblock %}
    </body>
</html>

Récupérons un exemple de code utilisant Tailwind sur tailwindui.com et ajoutons-le dans notre fichier “templates/demo/index.html.twig“.

{% extends 'base.html.twig' %}

{% block title %}Hello DemoController!{% endblock %}

{% block body %}
<!-- This example requires Tailwind CSS v2.0+ -->
<div class="relative bg-white overflow-hidden">
  <div class="max-w-7xl mx-auto">
    <div class="relative z-10 pb-8 bg-white sm:pb-16 md:pb-20 lg:max-w-2xl lg:w-full lg:pb-28 xl:pb-32">
      <svg class="hidden lg:block absolute right-0 inset-y-0 h-full w-48 text-white transform translate-x-1/2" fill="currentColor" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true">
        <polygon points="50,0 100,0 50,100 0,100" />
      </svg>

      <div class="relative pt-6 px-4 sm:px-6 lg:px-8">
        <nav class="relative flex items-center justify-between sm:h-10 lg:justify-start" aria-label="Global">
          <div class="flex items-center flex-grow flex-shrink-0 lg:flex-grow-0">
            <div class="flex items-center justify-between w-full md:w-auto">
              <a href="#">
                <span class="sr-only">Workflow</span>
                <img class="h-8 w-auto sm:h-10" src="https://tailwindui.com/img/logos/workflow-mark-indigo-600.svg">
              </a>
              <div class="-mr-2 flex items-center md:hidden">
                <button type="button" class="bg-white rounded-md p-2 inline-flex items-center justify-center text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500" id="main-menu" aria-haspopup="true">
                  <span class="sr-only">Open main menu</span>
                  <!-- Heroicon name: menu -->
                  <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
                  </svg>
                </button>
              </div>
            </div>
          </div>
          <div class="hidden md:block md:ml-10 md:pr-4 md:space-x-8">
            <a href="#" class="font-medium text-gray-500 hover:text-gray-900">Product</a>

            <a href="#" class="font-medium text-gray-500 hover:text-gray-900">Features</a>

            <a href="#" class="font-medium text-gray-500 hover:text-gray-900">Marketplace</a>

            <a href="#" class="font-medium text-gray-500 hover:text-gray-900">Company</a>

            <a href="#" class="font-medium text-indigo-600 hover:text-indigo-500">Log in</a>
          </div>
        </nav>
      </div>

      <!--
        Mobile menu, show/hide based on menu open state.

        Entering: "duration-150 ease-out"
          From: "opacity-0 scale-95"
          To: "opacity-100 scale-100"
        Leaving: "duration-100 ease-in"
          From: "opacity-100 scale-100"
          To: "opacity-0 scale-95"
      -->
      <div class="absolute top-0 inset-x-0 p-2 transition transform origin-top-right md:hidden">
        <div class="rounded-lg shadow-md bg-white ring-1 ring-black ring-opacity-5 overflow-hidden">
          <div class="px-5 pt-4 flex items-center justify-between">
            <div>
              <img class="h-8 w-auto" src="https://tailwindui.com/img/logos/workflow-mark-indigo-600.svg" alt="">
            </div>
            <div class="-mr-2">
              <button type="button" class="bg-white rounded-md p-2 inline-flex items-center justify-center text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500">
                <span class="sr-only">Close main menu</span>
                <!-- Heroicon name: x -->
                <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
                </svg>
              </button>
            </div>
          </div>
          <div role="menu" aria-orientation="vertical" aria-labelledby="main-menu">
            <div class="px-2 pt-2 pb-3 space-y-1" role="none">
              <a href="#" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50" role="menuitem">Product</a>

              <a href="#" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50" role="menuitem">Features</a>

              <a href="#" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50" role="menuitem">Marketplace</a>

              <a href="#" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50" role="menuitem">Company</a>
            </div>
            <div role="none">
              <a href="#" class="block w-full px-5 py-3 text-center font-medium text-indigo-600 bg-gray-50 hover:bg-gray-100" role="menuitem">
                Log in
              </a>
            </div>
          </div>
        </div>
      </div>

      <main class="mt-10 mx-auto max-w-7xl px-4 sm:mt-12 sm:px-6 md:mt-16 lg:mt-20 lg:px-8 xl:mt-28">
        <div class="sm:text-center lg:text-left">
          <h1 class="text-4xl tracking-tight font-extrabold text-gray-900 sm:text-5xl md:text-6xl">
            <span class="block xl:inline">Data to enrich your</span>
            <span class="block text-indigo-600 xl:inline">online business</span>
          </h1>
          <p class="mt-3 text-base text-gray-500 sm:mt-5 sm:text-lg sm:max-w-xl sm:mx-auto md:mt-5 md:text-xl lg:mx-0">
            Anim aute id magna aliqua ad ad non deserunt sunt. Qui irure qui lorem cupidatat commodo. Elit sunt amet fugiat veniam occaecat fugiat aliqua.
          </p>
          <div class="mt-5 sm:mt-8 sm:flex sm:justify-center lg:justify-start">
            <div class="rounded-md shadow">
              <a href="#" class="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 md:py-4 md:text-lg md:px-10">
                Get started
              </a>
            </div>
            <div class="mt-3 sm:mt-0 sm:ml-3">
              <a href="#" class="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-indigo-700 bg-indigo-100 hover:bg-indigo-200 md:py-4 md:text-lg md:px-10">
                Live demo
              </a>
            </div>
          </div>
        </div>
      </main>
    </div>
  </div>
  <div class="lg:absolute lg:inset-y-0 lg:right-0 lg:w-1/2">
    <img class="h-56 w-full object-cover sm:h-72 md:h-96 lg:w-full lg:h-full" src="https://images.unsplash.com/photo-1551434678-e076c223a692?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2850&q=80" alt="">
  </div>
</div>

{% endblock %}

Ouvrons dans un navigateur internet, la route de notre page de démo (chez moi https://127.0.0.1:8000/demo) et constatons le bon fonctionnement. Cool non ?!

Optimisations du fichier CSS avec PurgeCSS

Si vous êtes curieux, vous avez peut-être remarqué que le fichier CSS compilé par Webpack Encore pèse lourd, très lourd même ! Sur mon poste, le fichier pèse 2.8 Mo !

Pas d’inquiétudes ! À ce stade c’est tout à fait logique, puisque nous importons l’ensemble de Tailwind CSS, sans le nettoyer de tout ce que nous n’utilisons pas.

C’est le moment de faire entrer PurgeCSS, dont la mission va consister à supprimer tout le CSS inutile, c’est-à-dire non-utiliser dans nos fichiers twig. On retrouve pas mal de documentation sur la mise en place de PurgeCSS avec Webpack Encore, mais le meilleur bout de code que j’ai trouvé permet en plus de ne réaliser la purge du CSS que lors d’un build de prod, génial non !?

Ajoutons donc, en fin du fichier webpack.config.js ces quelques lignes.

if (Encore.isProduction()) {
    Encore.addPlugin(new PurgeCssPlugin({
          paths: glob.sync([
              path.join(__dirname, 'templates/**/*.html.twig')
          ]),
          defaultExtractor: (content) => {
              return content.match(/[\w-/:]+(?<!:)/g) || [];
          }
      }));
  }
;

Relançons un build (de prod).

npm run build

Et nous pouvons constater que la taille du fichier CSS à considérablement été réduite. Elle est passée de 2.8 Mo à 9.3 Ko !

Conclusions et dépôt GitLab

Nous venons de voir à quel point il est simple de mettre en place Tailwind CSS 2.0 et PurgeCSS avec Symfony et Webpack Encore ! Il ne reste finalement plus qu’à découvrir le fonctionnement de Tailwind pour construire de belles interfaces front !

Les sources du projet sont disponibles dans ce dépôt GitLab.

5 réflexions sur “Utiliser Tailwind CSS 2, PurgeCSS avec Symfony et Webpack Encore”

  1. Ping : Bootstrap 5 avec Symfony 5 et Webpack Encore – YoanDev

  2. Super ! merci cela a fonctionné chez moi.
    Petite précision le bout de code pour PurgeCSS, doit bien être placé avant “module.exports = Encore.getWebpackConfig();” 😉 perso j’avais pas fait gaffe et la purge ne se faisait pas.

    Merci pour ton taff en tout cas

  3. Je suis tous les tutos que tu publies.
    J’aime beaucoup ta pédagogie.
    Je suis admiratif de la masse de travail que tu effectues.
    Merci
    Luc

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.