03-comment-je-travaille/guides/frontend-stack.md

Stack Frontend — Telaria

Ce document dĂ©crit ce qui tourne rĂ©ellement en production, pas les alternatives Ă©cartĂ©es. Choix dĂ©libĂ©rĂ©ment sobre : pas de framework JS Ă  gouverner — la qualitĂ© d'implĂ©mentation comme diffĂ©renciateur.


Vue d'ensemble

Couche Outil Version
Gestion assets Symfony AssetMapper (Symfony 7)
Modules JS Import Maps (natif navigateur) —
Composants UI Bootstrap 5.3.8
IcĂŽnes Bootstrap Icons 1.13.1
Navigation SPA-like Turbo Drive + Turbo Frames 7.3.0
Comportements JS Stimulus 3.2.2
Graphiques Chart.js 3.9.1
Templates Twig (Symfony 7)
CSS CSS natif (BEM partiel) + Bootstrap utilities —

Pas de build step (webpack, vite, esbuild). AssetMapper + importmap = assets servis directement, versionnés par hash.


AssetMapper + importmap

Symfony AssetMapper remplace Webpack Encore. Les modules sont déclarés dans importmap.php et résolus par le navigateur via <script type="importmap">.

// importmap.php (extrait)
return [
    'app' => ['path' => './assets/app.js', 'entrypoint' => true],
    '@hotwired/turbo'     => ['version' => '7.3.0'],
    '@hotwired/stimulus'  => ['version' => '3.2.2'],
    'bootstrap'           => ['version' => '5.3.8'],
    'bootstrap/dist/css/bootstrap.min.css' => ['version' => '5.3.8', 'type' => 'css'],
    'bootstrap-icons/font/bootstrap-icons.min.css' => ['version' => '1.13.1', 'type' => 'css'],
    'chart.js'            => ['version' => '3.9.1'],
    'canvas-confetti'     => ['version' => '1.9.4'],
];

Ajout d'une dĂ©pendance : php bin/console importmap:require nom-du-package — met Ă  jour importmap.php automatiquement.

Point d'entrée : assets/app.js (seul entrypoint). Tous les autres modules sont importés depuis ce fichier ou via Stimulus.


Turbo Drive + Turbo Frames

Turbo Drive

Navigation SPA-like sans JS cĂŽtĂ© serveur. Le clic sur un lien charge la rĂ©ponse en AJAX et remplace le <body> — pas de rechargement complet. ActivĂ© globalement via l'import dans app.js :

import '@hotwired/turbo'; // démarre Turbo Drive globalement

Turbo Frames — navigation partielle

Les frames chargent et mettent à jour une portion de page indépendamment. Usage en production :

  • <turbo-frame id="doc"> — arborescence de documentation (navigation gauche/contenu)
  • <turbo-frame id="config"> — hub de configuration BO (panneau gauche/panneau droit)

Navigation pilotée explicitement via app.js (l'observer de clic Turbo Drive n'intercepte pas les data-turbo-frame dans cette version) :

document.addEventListener('click', (event) => {
    const link = event.target.closest('a[href]');
    if (link?.dataset.turboFrame) {
        const frame = document.getElementById(link.dataset.turboFrame);
        if (frame && 'TURBO-FRAME' === frame.tagName) {
            event.preventDefault();
            frame.src = link.href;
            // Synchronise l'URL du navigateur pour /docs et /admin/config (F5/partage)
            if (link.dataset.turboFrame in FRAME_NAV_PREFIXES) {
                window.history.pushState({}, '', link.href);
            }
        }
    }
});

Popstate (window.history) synchronise le cadre avec le bouton Précédent.

Auto-refresh conditionnel

Le cadre #config se recharge toutes les 10 s tant qu'une ré-indexation RAG est en cours (marqueur data-config-reindex-running dans le DOM) :

setInterval(() => {
    if (!document.querySelector('[data-config-reindex-running]')) return;
    document.getElementById('config')?.reload();
}, 10000);

Stimulus — comportements JS ciblĂ©s

Stimulus ajoute des comportements sans framework global. Chaque controller est un ES module chargé à la demande.

Chargement

// assets/stimulus_bootstrap.js (généré par Symfony)
// assets/controllers.json — controllers UX Symfony (chart, turbo)
{
    "controllers": {
        "@symfony/ux-chartjs": { "chart": { "enabled": true, "fetch": "eager" } },
        "@symfony/ux-turbo":   { "turbo-core": { "enabled": true } }
    }
}

CSRF Protection (Stimulus controller)

// assets/controllers/csrf_protection_controller.js
// GénÚre et double-soumet un token CSRF (champ formulaire + cookie)
// Compatible Turbo : écoute turbo:submit-start / turbo:submit-end
export function generateCsrfToken(formElement) { /* ... */ }
export function generateCsrfHeaders(formElement) { /* ... */ }

/* stimulusFetch: 'lazy' */ — chargĂ© seulement quand le contrĂŽleur est prĂ©sent dans le DOM.

Sélection groupée (sources de veille)

Gestion de la sélection de masse via délégation globale d'événements (pas de Stimulus controller dédié ici) :

// délégation sur 'click' et 'change' pour les checkboxes .src-check
// opérant que la liste soit en pleine page ou dans le cadre #config

Bootstrap 5.3 + Bootstrap Icons

Bootstrap est importé comme module ES (pas en CDN) :

import 'bootstrap';                              // JS (composants : modal, dropdown, collapse
)
import 'bootstrap/dist/css/bootstrap.min.css';  // CSS
import 'bootstrap-icons/font/bootstrap-icons.min.css'; // IcĂŽnes (font)

IcÎnes référencées dans le code PHP (interface ConfigSectionInterface::icon()) :

public function icon(): string { return 'bi-rss'; }  // VeilleSourcesSection
public function icon(): string { return 'bi-chat-dots'; }

Les icÎnes Bootstrap Icons sont utilisées directement dans les templates Twig via <i class="bi bi-rss">.


Organisation CSS

assets/styles/
├── app.css       ← rĂšgles globales : chrome CMS Ă©ditable, skip link, utilitaires
├── admin.css     ← styles back-office (chargĂ© conditionnellement si utilisateur connectĂ©)
└── front.css     ← styles surface publique

Chargement conditionnel

import 'bootstrap/dist/css/bootstrap.min.css';
import './styles/app.css';

if (isLoggedIn()) {
    import('./styles/admin.css');  // dynamic import — pas chargĂ© pour les visiteurs
}

export function isLoggedIn() {
    return window.app?.user !== null;
}

Chrome CMS éditable (app.css)

Les blocs nav/footer sont édités en Markdown au BO et rendus en HTML en front. Le CSS neutralise les marges et stylise les liens pour qu'un [Lien](/url) ressemble au chrome natif :

.cms-chrome > :last-child { margin-bottom: 0; }
.cms-chrome p { margin: 0; }
.cms-chrome ul { list-style: none; display: flex; flex-wrap: wrap; gap: .75rem; }
.cms-chrome-nav a { text-decoration: none; }

Skip link (accessibilité)

.skip-link {
    position: absolute;
    top: -1px;
    transform: translate(-50%, -200%);  /* hors-écran par défaut */
    transition: all 0.1s;
    &:focus { transform: translate(-50%, 0%); }  /* visible au focus clavier */
}

Organisation des templates Twig

templates/
├── admin/
│   ├── body/nav.html.twig      ← navigation BO globale
│   ├── cms/                    ← CRUD CMS (sites, contenus, mĂ©dias)
│   ├── config/                 ← hub de configuration (index + sections par feature)
│   ├── dashboard.html.twig     ← tableau de bord avec partials _metrics/_rag/_veille
│   ├── metrics/                ← alertes, graphiques consommation IA
│   ├── veille/                 ← proposals (validation), sources, stats
│   └── users/
├── cms/
│   ├── index.html.twig         ← layout CMS (Ă©tendu par les sites multisite)
│   └── show.html.twig          ← rendu d'une page CmsContent (Markdown → HTML)
├── components/
│   ├── _pagination.html.twig
│   └── _source_tree.html.twig
├── contact/
├── bundles/TwigBundle/Exception/
│   ├── error.html.twig
│   ├── error403.html.twig
│   └── error404.html.twig
└── (docs/, reader/, assistant/, 
)

Convention : les partials commencent par _ (ex. _list.html.twig, _metrics.html.twig).

Multisite — hĂ©ritage de layout

Chaque Site pointe vers son layout_template (ex. cms/index.html.twig). Les pages CMS d'un site vitrine étendent ce layout et n'ont accÚs qu'à leur propre chrome éditorial (blocs nav/footer scopés au site).


PWA — Web App Manifest

// assets/favicon/site.webmanifest
// Fournit les métadonnées pour "Ajouter à l'écran d'accueil"
// Icînes : 192×192 + 512×512 (android-chrome-*.png)
// Apple touch icon : 180×180

Pas de Service Worker actif en production — manifest seul, pas de mode offline.


Voir aussi

Assistant documentaire

Posez une question sur la documentation. Les rĂ©ponses citent leurs sources — un clic ouvre le document Ă  gauche.

Loading…
Loading the web debug toolbar…
Attempt #