Aller au contenu

Assets (CSS/JS)

Drupal utilise un système de librairies pour gérer les assets CSS et JavaScript. Contrairement à l’inclusion classique de fichiers dans le <head>, ce système offre un contrôle granulaire et des optimisations automatiques.

Problèmes de l’approche classique :

<!-- ❌ Mauvais : Chargement non optimisé #}
<link rel="stylesheet" href="/style.css">
<script src="/script.js"></script>

Avantages du système Drupal :

  • Chargement conditionnel : Ne charger que ce qui est nécessaire
  • Gestion des dépendances : Ordre de chargement automatique
  • Agrégation : Combine plusieurs fichiers en un seul
  • Minification : Réduit la taille des fichiers en production
  • Cache optimisé : Cache navigateur avec busting automatique
  • Prévention des doublons : Un fichier n’est chargé qu’une seule fois
1. Déclaration dans .libraries.yml
2. Attachement (global, template, PHP)
3. Résolution des dépendances
4. Agrégation (si activée)
5. Minification (si activée)
6. Injection dans le HTML (<head> ou avant </body>)
7. Cache navigateur avec version
FichierRôleExemple
*.libraries.ymlDéclare les librairiestailstore.libraries.yml
*.info.ymlAttache globalementlibraries: - tailstore/global
*.themeAttache en PHP$variables['#attached']['library'][]
Templates TwigAttache dans un template{{ attach_library('tailstore/slider') }}
# tailstore.libraries.yml
global:
version: 1.0
css:
# Catégories (ordre de chargement)
base:
css/base/reset.css: {}
css/base/typography.css: {}
layout:
css/layout/grid.css: {}
css/layout/regions.css: {}
component:
css/components/buttons.css: {}
css/components/cards.css: {}
css/components/forms.css: {}
theme:
css/tailstore.css: {}
CatégoriePoidsUsage
base-200Reset, normalisation
layout-100Grilles, mise en page
component0Composants
state100États (hover, active)
theme200Styles finaux, surcharges
css:
theme:
css/style.css:
weight: 10 # Poids relatif
media: screen # Media query
preprocess: true # Agrégation
minified: false # Déjà minifié
attributes:
crossorigin: anonymous
global:
js:
js/tailstore.js: {}
dependencies:
- core/drupal
- core/once
cart:
js:
js/cart.js:
attributes:
defer: true
dependencies:
- core/drupalSettings
- tailstore/global
js:
js/script.js:
weight: -10 # Chargement prioritaire
minified: true # Fichier déjà minifié
preprocess: true # Inclure dans l'agrégation
attributes:
defer: true # Attribut defer
async: true # Attribut async
type: module # ES modules
DépendanceDescription
core/drupalObjet Drupal, behaviors
core/onceHelper pour exécuter une seule fois
core/jqueryjQuery (éviter si possible)
core/drupalSettingsVariables PHP → JS

Dans tailstore.info.yml :

libraries:
- tailstore/global
{{ attach_library('tailstore/slider') }}
<div class="slider">
{# Contenu du slider #}
</div>
function tailstore_preprocess_node(&$variables) {
if ($variables['node']->bundle() === 'product') {
$variables['#attached']['library'][] = 'tailstore/product';
}
}
public function build() {
return [
'#markup' => '<div class="my-component"></div>',
'#attached' => [
'library' => ['tailstore/component'],
'drupalSettings' => [
'tailstore' => [
'apiUrl' => '/api/endpoint',
],
],
],
];
}
swiper:
version: 11.0
remote: https://swiperjs.com/
license:
name: MIT
url: https://github.com/nolimits4web/swiper/blob/master/LICENSE
gpl-compatible: true
css:
theme:
https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css:
type: external
minified: true
js:
https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js:
type: external
minified: true

Dans tailstore.info.yml :

libraries-override:
# Désactiver complètement une librairie
core/normalize: false
# Remplacer un fichier CSS
core/drupal.vertical-tabs:
css:
component:
misc/vertical-tabs.css: css/components/vertical-tabs.css
# Remplacer par une librairie CDN
core/jquery: tailstore/jquery-cdn

Ajouter des assets à une librairie existante :

libraries-extend:
core/ckeditor5:
- tailstore/ckeditor-custom
// js/tailstore.js
(function (Drupal, once) {
'use strict';
/**
* Product gallery behavior.
*/
Drupal.behaviors.productGallery = {
attach: function (context, settings) {
// once() s'assure que le code ne s'exécute qu'une fois par élément
once('product-gallery', '.product__gallery', context).forEach(function (gallery) {
const thumbnails = gallery.querySelectorAll('.product__thumbnail');
const mainImage = gallery.querySelector('.product__main-image img');
thumbnails.forEach(function (thumb) {
thumb.addEventListener('click', function () {
const img = this.querySelector('img');
mainImage.src = img.src;
mainImage.alt = img.alt;
// Active state
thumbnails.forEach(t => t.classList.remove('active'));
this.classList.add('active');
});
});
});
},
detach: function (context, settings, trigger) {
// Nettoyage si nécessaire (avant suppression AJAX)
if (trigger === 'unload') {
// Cleanup code
}
}
};
/**
* Add to cart behavior.
*/
Drupal.behaviors.addToCart = {
attach: function (context, settings) {
once('add-to-cart', '.product__add-to-cart', context).forEach(function (button) {
button.addEventListener('click', function () {
const productId = this.dataset.productId;
const quantity = document.getElementById('quantity')?.value || 1;
// Récupérer les options
const size = document.querySelector('.size-btn.active')?.dataset.size;
const color = document.querySelector('.color-btn.active')?.dataset.color;
// Appel API ou action
Drupal.tailstore.addToCart(productId, quantity, { size, color });
});
});
}
};
/**
* Namespace pour les fonctions TailStore.
*/
Drupal.tailstore = {
addToCart: function (productId, quantity, options) {
console.log('Adding to cart:', productId, quantity, options);
// Exemple avec fetch
fetch('/api/cart/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
product_id: productId,
quantity: quantity,
options: options,
}),
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Mettre à jour l'icône panier
document.dispatchEvent(new CustomEvent('cart-updated', {
detail: data.cart
}));
// Notification
Drupal.announce(Drupal.t('Product added to cart'));
}
});
}
};
})(Drupal, once);

PHP (dans .theme ou module) :

function tailstore_preprocess_page(&$variables) {
$variables['#attached']['drupalSettings']['tailstore'] = [
'cartApiUrl' => '/api/cart',
'currency' => '€',
'locale' => 'fr-FR',
];
}

JavaScript :

(function (Drupal, drupalSettings) {
const config = drupalSettings.tailstore;
console.log('API URL:', config.cartApiUrl);
console.log('Currency:', config.currency);
})(Drupal, drupalSettings);
/* Variables boutons */
:root {
--btn-padding-x: 1rem;
--btn-padding-y: 0.5rem;
--btn-border-radius: 0.375rem;
--btn-font-weight: 600;
--btn-transition: all 0.2s ease;
}
/* Base button */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: var(--btn-padding-y) var(--btn-padding-x);
font-weight: var(--btn-font-weight);
text-align: center;
text-decoration: none;
border: 1px solid transparent;
border-radius: var(--btn-border-radius);
cursor: pointer;
transition: var(--btn-transition);
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* Variantes */
.btn--primary {
background-color: var(--color-primary);
color: white;
}
.btn--primary:hover:not(:disabled) {
background-color: var(--color-primary-dark);
}
.btn--secondary {
background-color: var(--color-secondary);
color: white;
}
.btn--outline {
background-color: transparent;
border-color: currentColor;
}
.btn--outline:hover:not(:disabled) {
background-color: var(--color-primary);
border-color: var(--color-primary);
color: white;
}
/* Tailles */
.btn--sm {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
.btn--lg {
padding: 0.75rem 1.5rem;
font-size: 1.125rem;
}
.btn--full {
width: 100%;
}
/* Product Card */
.product-card {
position: relative;
display: flex;
flex-direction: column;
background: white;
border-radius: 0.5rem;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
}
.product-card:hover {
transform: translateY(-4px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.product-card__badges {
position: absolute;
top: 0.5rem;
left: 0.5rem;
z-index: 10;
display: flex;
gap: 0.25rem;
}
.product-card__image-link {
display: block;
aspect-ratio: 1;
overflow: hidden;
}
.product-card__image-link img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s;
}
.product-card:hover .product-card__image-link img {
transform: scale(1.05);
}
.product-card__quick-actions {
position: absolute;
top: 0.5rem;
right: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.25rem;
opacity: 0;
transform: translateX(10px);
transition: opacity 0.2s, transform 0.2s;
}
.product-card:hover .product-card__quick-actions {
opacity: 1;
transform: translateX(0);
}
.product-card__info {
padding: 1rem;
flex-grow: 1;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.product-card__brand {
font-size: 0.75rem;
color: var(--color-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.product-card__title {
font-size: 1rem;
font-weight: 600;
line-height: 1.3;
}
.product-card__title a {
color: inherit;
text-decoration: none;
}
.product-card__title a:hover {
color: var(--color-primary);
}
.product-card__price {
margin-top: auto;
padding-top: 0.5rem;
}
.price--current {
font-size: 1.25rem;
font-weight: 700;
color: var(--color-dark);
}
.price--sale {
color: var(--color-danger);
}
.price--old {
font-size: 0.875rem;
color: var(--color-secondary);
text-decoration: line-through;
margin-right: 0.5rem;
}
.product-card__colors {
display: flex;
gap: 0.25rem;
margin-top: 0.5rem;
}
.color-dot {
width: 1rem;
height: 1rem;
border-radius: 50%;
border: 1px solid rgba(0, 0, 0, 0.1);
}
.product-card__add-to-cart {
margin: 1rem;
margin-top: 0;
}

Objectif : Créer et attacher une librairie CSS simple pour personnaliser les boutons.

  1. Créez le fichier CSS :

    Fenêtre de terminal
    mkdir -p themes/custom/tailstore/css/components
    touch themes/custom/tailstore/css/components/buttons.css
  2. Ajoutez des styles de test :

    /* css/components/buttons.css */
    .btn-test {
    background-color: #ff6b6b;
    color: white;
    padding: 10px 20px;
    border-radius: 5px;
    border: none;
    }
  3. Déclarez la librairie dans tailstore.libraries.yml :

    buttons:
    version: 1.0.0
    css:
    component:
    css/components/buttons.css: {}
  4. Attachez-la globalement dans tailstore.info.yml :

    libraries:
    - tailstore/global
    - tailstore/buttons
  5. Videz le cache :

    Fenêtre de terminal
    drush cr
  6. Testez en ajoutant un bouton quelque part :

    <button class="btn-test">Test Button</button>

Validation : ✅ Le bouton doit avoir un fond rouge avec le style appliqué


Exercice 2 : Librairie JavaScript avec Drupal.behaviors

Section intitulée « Exercice 2 : Librairie JavaScript avec Drupal.behaviors »

Objectif : Créer un script qui affiche une alerte au clic sur un bouton.

  1. Créez le fichier JS :

    Fenêtre de terminal
    mkdir -p themes/custom/tailstore/js
    touch themes/custom/tailstore/js/alert.js
  2. Ajoutez le code :

    (function (Drupal, once) {
    'use strict';
    Drupal.behaviors.testAlert = {
    attach: function (context, settings) {
    once('test-alert', '.btn-alert', context).forEach(function (button) {
    button.addEventListener('click', function () {
    alert('Hello from Drupal!');
    });
    });
    }
    };
    })(Drupal, once);
  3. Déclarez dans tailstore.libraries.yml :

    alert:
    version: 1.0.0
    js:
    js/alert.js: {}
    dependencies:
    - core/drupal
    - core/once
  4. Attachez dans un template ou globalement

  5. Testez avec :

    <button class="btn-alert">Click me!</button>

Validation : ✅ Un clic sur le bouton doit afficher “Hello from Drupal!”


Objectif : Charger une librairie uniquement sur les pages produit.

  1. Créez une librairie pour les produits :

    # tailstore.libraries.yml
    product-zoom:
    version: 1.0.0
    js:
    js/product-zoom.js: {}
    dependencies:
    - tailstore/global
  2. Attachez conditionnellement dans tailstore.theme :

    function tailstore_preprocess_node(&$variables) {
    $node = $variables['node'];
    if ($node->bundle() === 'product') {
    $variables['#attached']['library'][] = 'tailstore/product-zoom';
    }
    }
  3. Videz le cache et testez :

    Fenêtre de terminal
    drush cr
  4. Vérifiez (F12 → Network) :

    • Sur une page produit : product-zoom.js est chargé
    • Sur une autre page : product-zoom.js n’est PAS chargé

Validation : ✅ La librairie ne se charge que sur les pages produit


Objectif : Passer des données PHP vers JavaScript.

  1. Dans tailstore.theme, ajoutez des settings :

    function tailstore_preprocess_page(&$variables) {
    $variables['#attached']['drupalSettings']['tailstore'] = [
    'siteUrl' => \Drupal::request()->getSchemeAndHttpHost(),
    'currency' => '€',
    'locale' => 'fr-FR',
    ];
    }
  2. Créez js/settings-test.js :

    (function (Drupal, drupalSettings) {
    'use strict';
    Drupal.behaviors.settingsTest = {
    attach: function (context, settings) {
    console.log('Site URL:', drupalSettings.tailstore.siteUrl);
    console.log('Currency:', drupalSettings.tailstore.currency);
    console.log('Locale:', drupalSettings.tailstore.locale);
    }
    };
    })(Drupal, drupalSettings);
  3. Déclarez la librairie et attachez-la

  4. Ouvrez la console (F12) et vérifiez les logs

Validation : ✅ Les valeurs PHP apparaissent dans la console JavaScript

1. Charger uniquement ce qui est nécessaire

// ❌ Mauvais - Charge partout
// Dans tailstore.info.yml
libraries:
- tailstore/slider
- tailstore/cart
- tailstore/filters
// ✅ Bon - Charge conditionnellement
function tailstore_preprocess_page(&$variables) {
// Slider uniquement sur homepage
if (\Drupal::service('path.matcher')->isFrontPage()) {
$variables['#attached']['library'][] = 'tailstore/slider';
}
// Panier uniquement si utilisateur connecté
if (\Drupal::currentUser()->isAuthenticated()) {
$variables['#attached']['library'][] = 'tailstore/cart';
}
}

2. Utiliser defer pour les scripts non critiques

analytics:
version: 1.0.0
js:
js/analytics.js:
attributes:
defer: true # Charge après le DOM

3. Précharger les ressources critiques

function tailstore_preprocess_html(&$variables) {
$variables['#attached']['html_head_link'][][] = [
'rel' => 'preload',
'href' => '/themes/custom/tailstore/fonts/inter.woff2',
'as' => 'font',
'type' => 'font/woff2',
'crossorigin' => 'anonymous',
];
}

4. Activer l’agrégation en production

Fenêtre de terminal
# Mode production
drush config:set system.performance css.preprocess true -y
drush config:set system.performance js.preprocess true -y
drush cr

1. Toujours déclarer les dépendances

# ❌ Dangereux - Suppose que Drupal est disponible
cart:
js:
js/cart.js: {}
# ✅ Sécurisé - Déclare explicitement
cart:
js:
js/cart.js: {}
dependencies:
- core/drupal
- core/once

2. Valider les données drupalSettings

// ❌ Dangereux - Peut crasher si non défini
const apiUrl = drupalSettings.tailstore.apiUrl;
// ✅ Sécurisé - Vérifie l'existence
const apiUrl = drupalSettings.tailstore?.apiUrl || '/api/default';

3. Éviter eval() et innerHTML

// ❌ Dangereux - Faille XSS
element.innerHTML = userInput;
// ✅ Sécurisé
element.textContent = userInput;
// Ou
element.insertAdjacentText('beforeend', userInput);

1. Nommage des librairies

# Format : theme-nom/fonctionnalite
tailstore/global # Librairie de base
tailstore/product-zoom # Fonctionnalité spécifique
tailstore/cart # Composant

2. Structure JavaScript

(function (Drupal, once) {
'use strict';
// 1. Namespace pour éviter les conflits
Drupal.tailstore = Drupal.tailstore || {};
// 2. Fonctions privées
function privateHelper() {
// ...
}
// 3. Behavior Drupal
Drupal.behaviors.myFeature = {
attach: function (context, settings) {
once('my-feature', '.selector', context).forEach(function (element) {
// Code d'initialisation
});
}
};
// 4. Fonctions publiques
Drupal.tailstore.publicMethod = function () {
return privateHelper();
};
})(Drupal, once);

3. Commentaires obligatoires

/**
* @file
* Product gallery functionality.
*/
/**
* Gallery behavior.
*
* Handles thumbnail clicks and updates the main image.
*/
Drupal.behaviors.productGallery = {
// ...
};

1. Annoncer les changements dynamiques

// Utiliser Drupal.announce() pour les lecteurs d'écran
button.addEventListener('click', function () {
addToCart(productId);
Drupal.announce(Drupal.t('Product added to cart'));
});

2. Gérer le focus

// Après ouverture d'une modale
modal.querySelector('.modal__close').focus();
// Piège du focus dans la modale
modal.addEventListener('keydown', function (e) {
if (e.key === 'Tab') {
// Logique de trap focus
}
});

3. Support clavier

element.addEventListener('keydown', function (e) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
this.click();
}
});
Fenêtre de terminal
# Mode développement
drush state:set system.performance css_preprocess 0
drush state:set system.performance js_preprocess 0
drush cr
# Mode production
drush state:set system.performance css_preprocess 1
drush state:set system.performance js_preprocess 1
drush cr

Inspectez le code source HTML pour voir les fichiers chargés.

LibrairieContenuAttachementStatut
globalCSS/JS de baseGlobal (info.yml)
buttonsStyles boutonsGlobal
product-zoomJS zoom imagesConditionnel (produits)
cartJS panierConditionnel
  1. Fichiers déclarés dans .libraries.yml

    Fenêtre de terminal
    # Vérifier la syntaxe YAML
    cat themes/custom/tailstore/tailstore.libraries.yml | grep -A 5 "global:"

    Attendu : Syntaxe YAML valide, version: obligatoire

  2. Fichiers existent sur le disque

    Fenêtre de terminal
    # Vérifier que les fichiers CSS/JS existent
    find themes/custom/tailstore -name "*.css" -o -name "*.js"

    Attendu : Tous les fichiers déclarés sont présents

  3. Librairies chargées dans le HTML

    Fenêtre de terminal
    # Inspecter le code source (Ctrl+U)
    # Chercher <link> et <script>
    curl -s http://localhost/ | grep -E "<link|<script" | head -10

    Attendu : Fichiers CSS dans <head>, JS avant </body>

  4. Pas d’erreurs 404

    Fenêtre de terminal
    # Ouvrir F12 → Network → Filtrer par "CSS" et "JS"
    # Vérifier que tous les fichiers sont en statut 200
  5. JavaScript fonctionnel

    Fenêtre de terminal
    # Ouvrir F12 → Console
    # Vérifier qu'il n'y a pas d'erreurs
  6. Agrégation (production uniquement)

    Fenêtre de terminal
    # Vérifier la config
    drush config:get system.performance css.preprocess
    drush config:get system.performance js.preprocess
    # En production, les deux doivent être à true
  • Les styles CSS sont appliqués (boutons, cartes, etc.)
  • Les Drupal.behaviors fonctionnent (interactions)
  • Les librairies conditionnelles se chargent au bon moment
  • drupalSettings est accessible en JavaScript
  • Pas d’erreurs dans la console (F12)
  • Performance acceptable (< 3s chargement)
  • Responsive fonctionne (mobile, tablette, desktop)
#!/bin/bash
echo "=== Vérification Assets TailStore ==="
# 1. Vérifier fichiers CSS
echo "\n🎨 Fichiers CSS :"
find themes/custom/tailstore -name "*.css" -type f | wc -l
echo "fichiers CSS trouvés"
# 2. Vérifier fichiers JS
echo "\n📜 Fichiers JavaScript :"
find themes/custom/tailstore -name "*.js" -type f | wc -l
echo "fichiers JS trouvés"
# 3. Vérifier syntaxe YAML
echo "\n⚙️ Validation YAML :"
if command -v yamllint &> /dev/null; then
yamllint themes/custom/tailstore/tailstore.libraries.yml
else
echo "yamllint non installé, validation manuelle nécessaire"
fi
# 4. Vérifier agrégation
echo "\n📦 Agrégation CSS/JS :"
drush config:get system.performance css.preprocess
drush config:get system.performance js.preprocess
# 5. Vérifier erreurs logs
echo "\n⚠️ Erreurs récentes :"
drush watchdog:show --severity=Error --count=5 | grep -i "javascript\|css" || echo "Aucune erreur asset"
echo "\n✅ Vérification terminée"

Temps estimé total : 2-3 heures pour créer et tester tous les assets

Symptôme : Erreur 404 dans Network (F12)

Causes possibles :

  • Chemin incorrect dans .libraries.yml
  • Fichier n’existe pas sur le disque
  • Permissions incorrectes

Solution :

Fenêtre de terminal
# 1. Vérifier le chemin exact
ls -la themes/custom/tailstore/css/style.css
# 2. Vérifier la déclaration dans .libraries.yml
cat themes/custom/tailstore/tailstore.libraries.yml | grep "css/style.css"
# 3. Vérifier les permissions
chmod 644 themes/custom/tailstore/css/*.css
chmod 644 themes/custom/tailstore/js/*.js
# 4. Vider le cache
drush cr

Symptôme : Pas d’erreur mais le code ne s’exécute pas

Causes :

  • Dépendances manquantes (core/drupal, core/once)
  • Syntaxe JavaScript incorrecte
  • once() mal utilisé

Solution :

// ❌ Mauvais - Dépendances manquantes
(function () {
document.querySelectorAll('.btn').forEach(function (btn) {
btn.addEventListener('click', function () {
// ...
});
});
})();
// ✅ Bon - Drupal.behaviors + once
(function (Drupal, once) {
'use strict';
Drupal.behaviors.myFeature = {
attach: function (context, settings) {
once('my-feature', '.btn', context).forEach(function (btn) {
btn.addEventListener('click', function () {
// ...
});
});
}
};
})(Drupal, once);
# Déclarer les dépendances
my-library:
js:
js/script.js: {}
dependencies:
- core/drupal
- core/once

Symptôme : Erreur console JavaScript

Cause : Librairie chargée avant core/drupal

Solution :

# Toujours déclarer core/drupal en dépendance
my-library:
js:
js/script.js: {}
dependencies:
- core/drupal # 👈 Obligatoire !

Symptôme : Fichier chargé (200) mais styles invisibles

Causes :

  • Spécificité CSS insuffisante
  • Cache navigateur
  • Ordre de chargement

Solution :

Fenêtre de terminal
# 1. Vider le cache Drupal
drush cr
# 2. Vider le cache navigateur
# Ctrl+Shift+R (Chrome/Firefox)
# 3. Vérifier l'ordre de chargement
# Utiliser weight dans .libraries.yml
global:
css:
theme:
css/base.css:
weight: -10 # Charge en premier
css/custom.css:
weight: 10 # Charge après

Symptôme : Erreur JavaScript en production

Cause : Dépendance core/once non déclarée

Solution :

my-library:
js:
js/script.js: {}
dependencies:
- core/drupal
- core/once # 👈 Nécessaire pour once()

Symptôme : Site fonctionne en dev mais casse en production

Cause : Ordre de chargement changé avec l’agrégation

Solution :

# Utiliser weight pour contrôler l'ordre
my-library:
js:
js/init.js:
weight: -10 # Charge en premier
js/main.js:
weight: 0 # Charge après
Fenêtre de terminal
# Tester avec agrégation activée localement
drush config:set system.performance js.preprocess true -y
drush cr
  • Vite : Bundler moderne ultra-rapide (utilisé dans ce projet)
  • PostCSS : Transformations CSS (autoprefixer, etc.)
  • ESLint : Linter JavaScript
  • Stylelint : Linter CSS
  • Prettier : Formateur de code automatique
  • Librairie global déclarée et attachée
  • CSS organisés par catégories
  • JavaScript avec Drupal.behaviors
  • Dépendances correctement déclarées
  • Librairies externes (CDN) configurées
  • drupalSettings utilisé si nécessaire
  • Librairies testées en dev et prod

Les assets sont configurés ! Passons à l’intégration de Tailwind CSS.