PostCSS est un outil de transformation CSS qui analyse le code source pour générer un arbre de syntaxe abstraite (AST). Cet arbre se compose de plusieurs types de nœuds :
- Root : nœud racine représentant le fichier CSS complet.
- AtRule : instructoins commençant par @, comme @charset ou @media.
- Rule : sélecteur contenant des déclarations, par exemple
input, button {}. - Declaration : paire clé-valeur telle que
color: black. - Comment : commentaire autonome, les commentaires internes étant stockés dans la propriété raws du nœud.
Structures fondamentales
Tokeniseur (lib/tokenize.js)
Le tokeniseur effectue l'analyse lexicale en transformant une chaîne CSS en une liste de jetons. Chaque jeton décrit un élément syntaxique comme un sélecteur, une valeur ou un commentaire, et peut inclure des informations de poistion pour le débogage.
Considérons cet exemple CSS :
.bouton { background: #ABC; }
Les jetons générés pourraient être représentés ainsi :
[
["selecteur", ".bouton", 1, 1, 1, 7],
["espace", " "],
["accolade", "{", 1, 9],
["espace", " "],
["propriete", "background", 1, 11, 1, 20],
["deux-points", ":", 1, 21],
["espace", " "],
["valeur", "#ABC", 1, 23, 1, 26],
["point-virgule", ";", 1, 27],
["espace", " "],
["accolade", "}", 1, 29]
]
Chaque jeton est un tableau où le premier élément indique le type, suivi de la valeur correspondante. Les positions sont optionnelles et suivent le format ligne, colonne. Le tokeniseur privilégie la performance en évitant les structures complexes, exposant une méthode nextToken() pour une consommation efficace de la mémoire.
Analyseur (lib/parse.js, lib/parser.js)
L'analyseur utilise les jetons pour construire l'AST. Il interagit avec le tokeniseur via des méthodes comme nextToken et back pour créer des nœuds. Tous les nœuds héritent d'une classe de base Node.
Processeru (lib/processor.js)
Cette structure gère l'initialisation des plugins et l'exécution des transformations sur l'AST. Son API publique permet de traiter le CSS avec une série de plugins.
Stringifieur (lib/stringify.js, lib/stringifier.js)
Le stringifieur convertit l'AST modifié en une chaîne CSS pure. Il parcourt l'arbre à partir d'un nœud donné et génère la représentation textuelle en appelant des méthodes appropriées, avec une méthode principale stringify.
Utilisation des plugins
Pour créer un plugin, exportez une fonction qui retourne un objet avec le nom du plugin et des méthodes de visite :
module.exports = (options = {}) => {
return {
postcssPlugin: 'mon-plugin',
Declaration: {
color: (decl, { result }) => {
// Logique pour transformer les déclarations de couleur
}
}
}
}
module.exports.postcss = true
Pour appliquer le plugin, utilisez :
await postcss([monPlugin]).process('a { color: blue }', { from: 'input.css' })
Extrait du code source
La fonction principale initialise le processeur :
function creerPostCSS (...extensions) {
if (extensions.length === 1 && Array.isArray(extensions[0])) {
extensions = extensions[0]
}
return new Processeur(extensions, creerPostCSS)
}
class Processeur {
constructor (extensions = []) {
this.version = '8.1.10'
this.extensions = this.normaliser(extensions)
}
normaliser (extensions) {
let normalises = []
// Traitement pour ajouter les extensions
normalises.push(ext)
return normalises
}
traiter (css, opts = {}) {
return new ResultatDiffere(this, css, opts)
}
}
La classe ResultatDiffere gère l'exécution asynchrone :
class ResultatDiffere {
constructor (processeur, css, opts) {
this.traitementTermine = false
let racine = analyseur(css, opts)
this.resultat = new Resultat(processeur, racine, opts)
// Préparation des plugins
this.extensions = this.processeur.extensions.map(ext => {
if (typeof ext === 'object' && ext.preparer) {
return { ...ext, ...ext.preparer(this.resultat) }
}
return ext
})
}
async executerAsync () {
this.pluginIndex = 0
for (let i = 0; i < this.extensions.length; i++) {
let plugin = this.extensions[i]
let promesse = this.appliquerSurRacine(plugin)
if (estPromesse(promesse)) await promesse
}
return this.stringifier()
}
stringifier () {
// Conversion de l'AST en CSS
let mapGenerateur = new GenerateurCarte(str, this.resultat.racine, this.resultat.opts)
let donnees = mapGenerateur.generer()
this.resultat.css = donnees[0]
return this.resultat
}
}
L'analyseur CSS fonctionne en deux étapes :
function analyser (css, opts) {
let entree = new Entree(css, opts)
let analyseurSyntaxique = new AnalyseurSyntaxique(entree)
analyseurSyntaxique.analyser()
return analyseurSyntaxique.racine
}
Exemple : Autoprefixer
Autoprefixer est un plugin PostCSS qui ajoute des préfixes vendeur aux règles CSS en se basant sur les données de Can I Use. Sa structure typique :
module.exports = (...navigateurs) => {
return {
postcssPlugin: 'autoprefixer',
preparer (resultat) {
let prefixes = chargerPrefixes({
depuis: resultat.opts.depuis,
environnement: options.environnement
})
return {
UneFois (racine) {
// Application des préfixes
if (options.ajouter !== false) {
prefixes.processeur.ajouter(racine, resultat)
}
}
}
},
options,
navigateurs
}
}