Gestione del front-end con Webpack su Symfony

Symfony logo e webpack logo

In questo esempio ti mostrerò come installare e configurare Webpack per la gestione del front-end di un applicazione Symfony.

PREREQUISITI: In questo articolo darò per scontato che sia stato creato un nuovo progetto utilizzando la Symfony CLI. Se non sai come fare puoi seguire questo tutorial.


Cosa è Webpack?

A bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows for loading parts of the application on demand.

– pagina GitHub di Webpack

Webpack è un assmblatore di bundle. Ciò significa che il compito principale di Webpack è quello di elaborare un insieme di asset composto da file JS/CSS/Immagini combinandoli in dei pacchetti chiamati bundle.
Ad esempio può essere usato per creare due bundle a partire dai file Javascript di un’applicazione: uno per i file specifici della webapp e uno contenente i file delle librerie installate.

Webpack prende in ingresso un set di file JS/CSS/JPG e li combina in bundle
Webpack prende in ingresso un set di file JS/CSS/JPG e li combina in bundle

Webpack non si limita a impacchettare le risorse front-end del progetto ma può essere utilizzato per ottimizzare al massimo le performance di un’applicazione. Questa possibilità però viene fornita ad un costo: Webpack è molto complesso. Nonostante gli sviluppatori abbiano cercato di semplificare sempre più le impostazioni versione dopo versione, ad oggi rimane abbastanza difficile impostare Webpack al meglio e capire cosa stia accadendo dietro le quinte. Proprio per questo motivo gli sviluppatori di Symfony hanno pensato di semplificarne l’utilizzo creando un wrapper chiamato webpack-encore.

Aggiungere una nuova pagina

La prima cosa da fare per iniziare la tua avventura nel mondo frontend è creare una nuova pagina web. Per farlo è necessario seguire due passaggi:

  • Creare una rotta (route): una rotta rappresenta l’URL della pagina (es /about) che verrà mappato su di un’azione del controller,
  • Creare un controller: un controller è una classe PHP che prende in ingresso la richiesta del browser e restituisce in uscita la risposta alla richiesta.

Ai fini di questo tutorial immaginiamo di creare l’home page del sito che risponderà alla rotta /. Per farlo la prima cosa da fare è creare una nuova classe PHP chiamata IndexController.php all’interno della cartella della cartella src/Controller/.

<?php
declare(strict_types=1);

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class IndexController extends AbstractController
{
    /**
     * @Route(path="/")
     */
    public function index(): Response
    {
        return $this->render(
            'index.html.twig'
        );
    }
}

Il metodo index() appena creato non fa altro che accettare una richiesta GET alla rotta / (definita tramite l’annotazione @Route) e rispondere renderizzando il file index.html.twig che andrai a definire ora.

NOTA: Twig è il template engine predefinito usato da Symfony.

All’interno della cartella src\templates crea un nuovo file e chiamalo index.html.twig ed inserisci il seguente contenuto:

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

{% block body %}
  <h1>Hello world!</h1>
{% endblock %}

A questo punto, se tutto ha funzionato correttamente, navigando sul sito all’indirizzo http://127.0.0.1:8000 dovresti essere visualizzare la nuova pagina appena creata con il messaggio “Hello world”.

La prima pagina web creata con Symfony e Twig
La prima pagina web creata con Symfony e Twig

Installare Webpack

Arrivato a questo punto hai realizzato la prima pagina web dell’applicazione utilizzando esclusivamente l’HTML ma come ogni sviluppatore web sa, per creare una pagina accattivante è necessario adornarla con del buon stile CSS e renderla interattiva aggiungendo del Javascript.
Ed è qui che entra in gioco Webpack per la creazione dei bundle CSS e JS con il codice dell’applicazione.

NOTA: Come accennavo nell’introduzione, gli sviluppatori di Symfony hanno realizzato un wrapper per Webpack che ne semplifica di gran lunga la configurazione chiamato webpack-encore. In questo tutorial farò sempre riferimento a quello ma è possibile adattare tutti i concetti riportati anche alla configurazione di Webpack “standard”.

La prima cosa da fare è ovviamente quella di installare sia il bundle PHP (tramite composer) che la libreria Javascript (tramite yarn) utilizzando i seguenti comandi:

 composer require symfony/webpack-encore-bundle
 yarn add @symfony/webpack-encore --dev

Al termine dell’installazione verrà creato automaticamente il file di configurazione di Webpack chiamato webpack.config.js e una cartella assets che andrà a contenere tutti i file CSS e JS del progetto.

La configurazione di default di Webpack

Analizziamo ora la configurazione di default webpack.config.js appena generata:

var Encore = require('@symfony/webpack-encore');

// Manually configure the runtime environment if not already configured yet by the "encore" command.
// It's useful when you use tools that rely on webpack.config.js file.
if (!Encore.isRuntimeEnvironmentConfigured()) {
    Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
}

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 sub-directory deploy
    //.setManifestKeyPrefix('build/')

    /*
     * ENTRY CONFIG
     *
     * Add 1 entry for each "page" of your app
     * (including one that's included on every page - e.g. "app")
     *
     * 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/js/app.js')
    //.addEntry('page1', './assets/js/page1.js')
    //.addEntry('page2', './assets/js/page2.js')

    // When enabled, Webpack "splits" your files into smaller pieces for greater optimization.
    .splitEntryChunks()

    // will require an extra script tag for runtime.js
    // but, you probably want this, unless you're building a single-page app
    .enableSingleRuntimeChunk()

    /*
     * FEATURE CONFIG
     *
     * Enable & configure other features below. For a full
     * list of features, see:
     * https://symfony.com/doc/current/frontend.html#adding-more-features
     */
    .cleanupOutputBeforeBuild()
    .enableBuildNotifications()
    .enableSourceMaps(!Encore.isProduction())
    // enables hashed filenames (e.g. app.abc123.css)
    .enableVersioning(Encore.isProduction())

    // enables @babel/preset-env polyfills
    .configureBabel(() => {}, {
        useBuiltIns: 'usage',
        corejs: 3
    })

    // enables Sass/SCSS support
    //.enableSassLoader()

    // uncomment if you use TypeScript
    //.enableTypeScriptLoader()

    // uncomment to get integrity="..." attributes on your script & link tags
    // requires WebpackEncoreBundle 1.4 or higher
    //.enableIntegrityHashes(Encore.isProduction())

    // uncomment if you're having problems with a jQuery plugin
    //.autoProvidejQuery()

    // uncomment if you use API Platform Admin (composer req api-admin)
    //.enableReactPreset()
    //.addEntry('admin', './assets/js/admin.js')
;

module.exports = Encore.getWebpackConfig();

Vediamo nel dettaglio cosa vanno ad impostare le istruzioni contenute in questo file di configurazione:

  • La prima cosa che Webpack va a controllare (righe 5-6) è l’ambiente da utilizzare per costruire i bundle. Nel caso in cui nessun ambiente sia stato impostato viene assunto di default che la build sia stata lanciata in modalità dev. Questo controllo viene fatto perché a seconda dell’ambiente utilizzato per lanciare la costruzione dei bundle vengono eseguite delle ottimizzazioni diverse (ad esempio il codice Javascript dell’ambiente di produzione sarà minificato mentre quello in sviluppo no).
  • Alle righe 11 e 13 viene indicato a Webpack quali sono le cartelle da utilizzare come output del suo processo di pacchettizzazione.
  • Alla riga 26 inizia il vero utilizzo di Webpack. In particolare con il comando addEntry() viene indicato a Webpack di generare un nuovo pacchetto a partire dal file indicato come argomento della funzione.
  • Alla riga 31 abbiamo la prima ottimizzazione che Webpack andrà ad eseguire sul nostro codice: splitEntryChunks() (vedi sezione dedicata)
  • Alla riga 35 abbiamo ancora un’altra ottimizzazione: enableSingleRuntimeChunk() (vedi sezione dedicata).
  • Alle righe 44 e 45 abbiamo ancora delle configurazioni di Webpack per le quali chiediamo esplicitamente di pulire la cartella di output ogni volta prima della build (cleanupOutputBeforeBuild())e di utilizzare le notifiche del sistema per informarci sullo stato di avanzamento della build (enableBuildNotifications()).
  • Le righe 46 e 48 indicano a Webpack di creare e servire i file map quando l’ambiente è in development e di aggiungere un numero di versione ai bundle generati quando invece l’ambiente è in produzione.
  • Infine alla riga 51 viene aggiunta una configurazione specifica per Babel. In particolare viene indicato a Babel di importare esclusivamente le polyfill che sono necessarie per il corretto funzionamento del codice Javascript scritto (useBuiltIns: 'usage‘) e che la versione di corejs installata è la 3 (corejs: 3).

All’interno della configurazione di default di Webpack sono abilitate le opzioni che comunemente vengono utilizzate nello sviluppo di un applicazione web con Symfony. Tuttavia già all’interno del file sono presenti altre righe di codice commentate che illustrano come sia possibile espandere il file di configurazione di default per aggiungere funzionalità extra (come la traspilazione Typescript o dei file Sass)

Ottimizzazione: SingleRuntimeChunk

Di default i moduli importati dagli script sono inizializzati una volta per ogni entry-point.

Questo vuol dire che se all’interno della stessa pagina vengono importati i due file scriptA.js e scriptB.js ed entrambi importano jQuery allora di quest’ultimo verranno inizializzate due istanze separate per i due script. Di conseguenza, se il primo script emette un evento all’interno della propria istanza jQuery allora questo non verrà intercettato dal secondo script (e viceversa).

Abilitando il SingleRuntimeChunk con l’istruzione enableSingleRuntimeChunk() viene creato un asset aggiuntivo chiamato runtime.js che, come suggerisce il nome, contiene il codice necessario ad inizializzare il runtime di Webpack.
Poiché in questo modo tutti i moduli vengono inizializzati da questo script allora tutti gli script importati all’interno della stessa pagina web condivideranno le stesse istanze dei moduli.
Quindi, tornando all’esempio con scriptA.js e scriptB.js all’interno della stessa pagina questa volta entrambi gli script si riferiranno alla medesima istanza di jQuery.

ATTENZIONE: abilitare o disabilitare il SingleRuntimeChunk è una scelta progettuale molto importante. In generale può essere una buna idea lasciarlo abilitato in modo che se in una pagina sono presenti più script questi condividano le stesse istanze dei moduli in comune. Tuttavia se stai realizzando una single-page application allora avere un runtime condiviso può essere un overhead non necessario.

NOTA PERSONALE: se nella tua applicazione utilizzi dei modali il cui contenuto viene caricato tramite AJAX e che al loro interno importano degli script allora potrebbe venire istanziato un nuovo runtime! Questo potrebbe portare all’impossibilità di comunicare tra il modale e il contenuto della pagina sottostante.

Per saperne di più sul single runtime chunk e per approfondire le opzioni di configurazione puoi consultare la pagina con la documentazione ufficiale.

Ottimizzazione: SplitEntryChunks

Abilitando splitEntryChunks() Webpack si occuperà di frammentare gli script in chunk di dimensioni minori ottimizzando le risorse necessarie al browser per valutare ed interpretare gli script.
Di default Webpack applicherà le seguenti regole per spezzare gli script in chunk:

  • I nuovi chunk devono essere condivisi tra più script oppure provengono dalla cartella node_modules;
  • Il nuovo chunk ha una dimensione maggiore di 30Kb (prima della compressione);
  • Il numero di richieste parallele da fare per scaricare i chunk on-demand è minore o uguale di 5;
  • Il numero di richieste parallele da fare per scaricare tutti i chunk necessari al rendering iniziale della pagina è minore o uguale di 3.

Per saperne di più sullo splitting dei chunks puoi consultare la pagina della documentazione ufficiale.

Creazione dei bundle

Una volta che Webpack è installato puoi iniziare a creare i bundle a partire dai tuoi script con il seguente comando da terminale:

yarn encore dev

Una volta che il comando sarà terminato, all’interno della cartella /public/build troverai i bundle appena realizzati.

Aggiunta di uno script

Arrivati a questo punto dovresti avere Webpack configurato e pronto per l’uso. Per verificare che tutto funzioni correttamente ti mostrerò come aggiungere uno script Javascript che al click di un bottone apra un alert del browser.

Come prima cosa aggiungi un bottone all’interno della pagina creata allo step precedente:

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

{% block body %}
  <h1>Hello world!</h1>
  <div>
    <button id="clickMeButton">Click me</button>
  </div>
{% endblock %}

Vai ora ad aggiungere un nuovo file Javascript chiamato homepage.js all’interno della cartella assets/js come mostrato di seguito:

const clickMeButton = document.getElementById('clickMeButton');

clickMeButton.addEventListener('click', function () {
  alert('Bottone clickato!');
});

Aggiungiamo il file appena creato a quelli che Webpack andrà ad analizzare per l’impacchettamento all’interno del bundle

.addEntry('homepage', './assets/js/homepage.js')

Aggiungi il nuovo script alla pagina appena creata

{% block javascripts %}
    {{ encore_entry_script_tags('homepage') }}
{% endblock %}

A questo punto ricompilando gli asset con Webpack (comando: yarn encore dev) e ricaricando la pagina, al click del bottone verrà mostrato l’alert appena creato.

Aggiunta di Bootstrap

Per dare un po’ di stile alla pagina aggiungi un framework CSS come Bootstrap.

 yarn add bootstrap

Aggiungi a Webpack una nuova entry:

.addStyleEntry('bootstrap', './node_modules/bootstrap/dist/css/bootstrap.css')

importa nella pagina il nuovo asset e aggiungi un po’ di stile

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

{% block body %}
  <div class="container">
    <h1>Hello world!</h1>
    <div class="row">
      <div class="col">
        <button id="clickMeButton" class="btn btn-primary">Click me</button>
      </div>
    </div>
  </div>

{% endblock %}

{% block stylesheets %}
  {{ encore_entry_link_tags('bootstrap') }}
{% endblock %}


{% block javascripts %}
  {{ encore_entry_script_tags('homepage') }}
{% endblock %}

Conclusioni

Sviluppare un applicazione web moderna e accattivante richiede l’integrazione di diverse conoscenze e di diversi strumenti di sviluppo che permettano di trarre il meglio dal mondo back-end e da quello front-end. Proprio per questo gli sviluppatori di Symfony hanno realizzato uno strumento come webpack-encore in grado di semplificare al massimo la gestione degli asset javascript e CSS.

Puoi scaricare il codice sorgente del progetto direttamente da GitHub:

https://github.com/lmillucci/symfony-webpack-example


Se questo post ti è stato utile puoi farmelo sapere con un commento qui sotto oppure scrivendomi direttamente a t.me/lorenzomillucci.
Inoltre ti invito ad iscriverti al mio canale Telegram o a seguirmi su Twitter per non perderti nemmeno un post del mio blog.

Come creare un’applicazione Symfony da Ubuntu

installazione di Symfony

Cos’è Symfony?

Symfony è un framework per la creazione di applicazioni web in PHP creato e sponsorizzato da SensioLabs.
Grazie alla moltitudine di componenti pronti all’uso e alle sue elevate performance Symfony è uno dei framework più diffusi per la creazione di progetti di medio/grandi dimensioni.
Inoltre la sua modularità consente di utilizzare questo framework per realizzare senza particolari problemi qualsiasi tipologia di applicazione come ad esempio: API, microservizi e applicazioni monolitiche.

Inoltre, a parer mio, la documentazione del framework è una delle migliori in circolazione. E questo è un fattore da non sottovalutare in particolar modo per chi è alle prime armi con un nuovo progetto Symfony.

Indice

Installazione di Symfony

Dopo questa brevissima introduzione a Symfony inizio subito a spiegarti come installare il framework su Ubuntu tramite la command line interface (CLI).

NOTA: questa procedura funziona anche utilizzando il sottosistema Linux all’interno di Windows 10 (WSL). Per cui puoi seguire gli step riguardanti l’installazione all’interno del sottosistema Linux e poi utilizzare Windows per scrivere il codice e visualizzare l’applicazione web.

1 – Installazione di PHP

Il requisito base per poter utilizzare un framework PHP, naturalmente, è quello di avere un installazione del linguaggio PHP all’interno del tuo computer.

Per poter utilizzare Symfony è richiesta almeno la versione 7.1.3 di PHP e che siano installate alcune estensioni del linguaggio come: Ctype, iconv, JSON, PCRE, Session, SimpleXML e Tokenizer. (Qui puoi trovare l’elenco delle dipendenze richieste)

Per installare tutte le dipendenze con un solo comando apri una nuova sessione del terminale e digita:

sudo apt install php libapache2-mod-php php-mbstring php-xmlrpc php-soap php-gd php-xml php-cli php-zip php-mysql php-curl

2 – Installazione di Composer

Un altro requisito fondamentale per poter creare una nuova applicazione Symfony è il gestore di pacchetti per il linguaggio PHP chiamato Composer. Tramite questo software è infatti possibile aggiungere, aggiornare e rimuovere le dipendenze esterne al progetto.

NOTA: Se in passato ti è capitato di lavorare con un framework Javascript allora ti sarai sicuramente imbattuto in npm o yarn per gestire le dipendenze. Composer è esattamente la stessa cosa, ma per il mondo PHP.

Per installare l’ultima versione di Composer digita i seguenti comandi nel terminale:

curl -sS https://getcomposer.org/installer -o composer-setup.php
sudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer

Per verificare che l’installazione sia andata a buon fine devi aprire e richiudere il terminale e provare a lanciare il programma appena installato utilizzando il comando composer. Se digitando il comando composer ricevi un messaggio del tipo:

unable to find composer, get it at https://getcomposer.org/download/: exec: "composer": executable file not found in $PATH

Allora è possibile che tu non abbia riavviato il terminale oppure che qualcosa sia andato storto e che tu debba riprovare ad installare Composer nuovamente.

3 – Installazione di Git

Questo passaggio non è strettamente necessario ma è fortemente consigliato. Infatti, quando arriverà il momento di creare il progetto dell’applicazione Symfony tramite la CLI, se git non fosse installato e configurato, riceverai un messaggio d’errore.

Per installare git il comando da digitare è:

sudo apt install git-core

Per poter utilizzare git è necessario configurarlo inserendo nome utente ed indirizzo email con i seguenti comandi da terminale:

git config --global user.email "you@example.com"
git config --global user.name "Your Name" 

4 – Installazione della Symfony CLI

Una volta installate tutte le dipendenze necessarie, sei finalmente pronto per installare la CLI (Command Line Interface) di Symfony.

La prima cosa da fare è scaricare l’installer con il seguente comando:

wget https://get.symfony.com/cli/installer -O - | bash
Download della CLI di Symfony

A questo punto è possibile scegliere se installare la CLI livello globale (disponibile per tutti gli utenti del sistema) oppure a livello locale (limitando l’uso al solo utente corrente). Vediamo come fare in entrambi i casi:

4.1a – Installazione globale

Per installare la CLI globalmente rendendola disponibile a tutti gli utenti del sistema il comando da eseguire è:

mv $HOME/.symfony/bin/symfony /usr/local/bin/symfony

4.1b – Installazione locale

Per installare la CLI per il solo utente corrente devi aggiungere Symfony alle tue variabili d’ambiente. Per farlo puoi modificare il file .bashrc utilizzando i seguenti comandi

touch ~/.bashrc
echo 'export PATH="$HOME/.symfony/bin:$PATH"' >> ~/.bashrc 

4.2 – Verifica dell’installazione

Per verificare che l’installazione della CLI sia andata a buon fine è necessario riavviare il terminale (ti basta chiudere e riaprire) e digitare il comando symfony.
Se riceverai un messaggio simile a quello riportato nell’immagine qui sotto allora l’installazione sarà stata completata con successo e potrai finalmente iniziare a creare un nuovo progetto Symfony. 🎉

Symfony CLI installata correttamente

Bonus: configurare l’ambiente di Symfony con Docker

Se non vuoi perdere tempo ad installare PHP e la Symfony CLI all’interno del tuo PC e/o vuoi un ambiente di sviluppo che sia sempre lo stesso anche quando cambi il computer che utilizzi puoi utilizzare creare il progetto all’interno dei container Docker che ho preparato. (Se non sai come installare Docker su Ubuntu leggi qui)

Per usare Symfony all’interno di Docker ti basta clonare il repository linkato qui sotto, avviare i container con il comando docker-compose up -d --build e connetterti al container con docker exec -it symfony-environment bash

https://github.com/lmillucci/symfony-docker-environment

A questo punto configura git come descritto poco più sopra e continua a seguire i passaggi descritti di seguito per creare un nuovo progetto e per avviare il server.

Creazione di un nuovo progetto

Se hai seguito correttamente tutti i passaggi fino a questo punto allora sei finalmente pronto per creare il tuo primo progetto Symfony tramite la CLI. Senza perdere altro tempo, il comando da eseguire per creare un nuovo progetto tramite la CLI è il seguente:

symfony new --full my_symfony_app

NOTA: con il comando --full verranno installate automaticamente all’interno del progetto anche tutti i pacchetti che di solito sono necessari per creare un applicazione web completa. Omettendo il --full invece verrà creato un progetto con una configurazione minimale che è particolarmente indicato per creare microservizi o API.

NOTA2: con questo comando verrà creato un nuovo progetto chiamato my_symfony_app. Naturalmente puoi chiamare il progetto con un nome differente sostituendo my_symfony_app con il nome che vuoi utilizzare.

Avvio di Symfony

Arrivato a questo punto non resta che avviare l’applicazione Symfony appena creata. Normalmente sarebbe necessario configurare un server Apache o NGINX per poter servire un applicazione PHP ma grazie alla CLI di Symfony potrai avviare immediatamente il progetto appena creato utilizzando il server integrato (chiamato: Symfony Local Web Server).

Per avviare l’applicazione Symfony appena creata ti basta spostarti nella cartella del progetto e avviare il server con i seguenti comandi:

cd my_symfony_app
symfony serve

Una volta che il server si sarà avviato, visitando l’indirizzo http://127.0.0.1:8000/, potrai visualizzare la pagina di benvenuto di Symfony.

Benvenuto nella tua prima applicazione Symfony

Da questo punto in poi potrai modificare il codice PHP all’interno del progetto per iniziare a realizzare l’applicazione che desideri.

Bisogno di aiuto?

Se vuoi rimanere sempre aggiornato su ciò che posto in questo blog iscriviti al mio canale Telegram oppure, se hai bisogno di aiuto nel seguire questa guida o non ti è chiaro qualche passaggio puoi lasciarmi un commento qui sotto oppure scrivermi direttamente a t.me/lorenzomillucci.

Per sapere come gestire gli asset front-end tramite Webpack puoi consultare quest’altro articolo.

Fonte