Kurs HTML i CSS · Moduł 9: Nowoczesny CSS
Stylelint - linter dla CSS
W tej lekcji6
W zespole pięciu budowniczych każdy pisze CSS po swojemu: jeden wkleja ten sam selektor dwa razy, drugi zagnieżdża style na siedem poziomów. Przegląd kodu zamienia się w spór o drobiazgi, a prawdziwe błędy przechodzą niezauważone. Stylelint to potężne narzędzie do analizy statycznej kodu CSS, czyli sprawdzania go bez uruchamiania strony. Pomaga w utrzymaniu spójności, wykrywaniu błędów i egzekwowaniu dobrych praktyk w arkuszach stylów.
Podstawy Stylelint
Instalacja i konfiguracja
Stylelint instalujesz jako zależność deweloperską projektu, razem z gotowym zestawem reguł i opcjonalnymi wtyczkami:
# Instalacja podstawowa
npm install --save-dev stylelint stylelint-config-standard
# Z dodatkowymi pluginami
npm install --save-dev stylelint-scss stylelint-order stylelint-config-prettierPakiet stylelint-config-standard to rozsądny zestaw reguł na start, stylelint-scss uczy Stylelinta składni SCSS, a stylelint-order pilnuje kolejności właściwości. Pakiet stylelint-config-prettier jest od Stylelinta 15 zbędny.
Podstawowy plik konfiguracyjny .stylelintrc.json
Konfigurację zapisujesz w pliku .stylelintrc.json. Pole extends wczytuje gotowy zestaw, plugins dodaje wtyczki, a rules włącza, wyłącza lub dostraja pojedyncze reguły:
{
"extends": [
"stylelint-config-standard"
],
"plugins": [
"stylelint-order",
"stylelint-scss"
],
"rules": {
"indentation": 2,
"string-quotes": "single",
"no-duplicate-selectors": true,
"color-hex-case": "lower",
"color-hex-length": "short",
"color-named": "never",
"selector-combinator-space-after": "always",
"selector-combinator-space-before": "always",
"selector-pseudo-element-colon-notation": "double",
"selector-pseudo-class-parentheses-space-inside": "never",
"selector-no-qualifying-type": true,
"property-no-unknown": true,
"keyframe-declaration-no-important": true,
"declaration-block-trailing-semicolon": "always",
"declaration-no-important": true,
"declaration-block-single-line-max-declarations": 1,
"selector-class-pattern": "^[a-z0-9\\-]+$",
"selector-id-pattern": "^[a-z0-9\\-]+$",
"custom-property-pattern": "^[a-z0-9\\-]+$",
"max-nesting-depth": 3,
"order/order": [
"custom-properties",
"declarations"
],
"order/properties-order": [
"position",
"top",
"right",
"bottom",
"left",
"z-index",
"display",
"flex",
"flex-direction",
"flex-wrap",
"justify-content",
"align-items",
"width",
"height",
"margin",
"padding",
"background",
"color",
"font-family",
"font-size",
"line-height",
"border",
"border-radius",
"transition",
"transform"
]
}
}Ważna uwaga: reguły czysto stylistyczne, takie jak indentation, string-quotes, color-hex-case czy declaration-block-trailing-semicolon, zostały oznaczone jako przestarzałe w Stylelincie 15 i usunięte w wersji 16, która zgłosi je jako nieznane. Usuń je albo przenieś do wtyczki @stylistic/stylelint-plugin, a formatowanie zostaw Prettierowi. Reguły wykrywające błędy, jak no-duplicate-selectors, działają bez zmian.
Rozszerzona konfiguracja dla większych projektów
W większym projekcie dochodzą wtyczki do konwencji nazw, wymuszanie zmiennych i osobne reguły dla różnych typów plików:
{
"extends": [
"stylelint-config-standard",
"stylelint-config-prettier"
],
"plugins": [
"stylelint-order",
"stylelint-scss",
"stylelint-selector-bem-pattern",
"stylelint-declaration-use-variable"
],
"rules": {
"plugin/selector-bem-pattern": {
"preset": "bem",
"presetOptions": {
"namespace": "c-"
}
},
"sh-waqar/declaration-use-variable": [
[
"color",
"background-color",
"border-color",
"font-family",
"font-size",
"z-index"
]
],
"color-function-notation": "modern",
"alpha-value-notation": "percentage",
"color-hex-alpha": "never",
"font-family-name-quotes": "always-where-recommended",
"function-url-quotes": "always",
"import-notation": "string",
"keyframe-selector-notation": "percentage-unless-within-keyword-only-block",
"media-feature-range-notation": "context",
"selector-not-notation": "complex",
"at-rule-no-vendor-prefix": true,
"media-feature-name-no-vendor-prefix": true,
"property-no-vendor-prefix": true,
"selector-no-vendor-prefix": true,
"value-no-vendor-prefix": true,
"max-line-length": 120,
"no-empty-source": null,
"declaration-block-no-redundant-longhand-properties": true,
"shorthand-property-no-redundant-values": true,
"comment-empty-line-before": [
"always",
{
"except": ["first-nested"],
"ignore": ["stylelint-commands"]
}
],
"custom-property-empty-line-before": [
"always",
{
"except": ["after-custom-property", "first-nested"],
"ignore": ["after-comment", "inside-single-line-block"]
}
],
"declaration-empty-line-before": [
"always",
{
"except": ["after-declaration", "first-nested"],
"ignore": ["after-comment", "inside-single-line-block"]
}
]
},
"overrides": [
{
"files": ["*.scss", "**/*.scss"],
"customSyntax": "postcss-scss",
"rules": {
"scss/at-rule-no-unknown": true,
"scss/selector-no-redundant-nesting-selector": true,
"scss/no-duplicate-dollar-variables": true,
"scss/dollar-variable-pattern": "^[a-z0-9\\-]+$",
"scss/percent-placeholder-pattern": "^[a-z0-9\\-]+$"
}
},
{
"files": ["*.vue", "**/*.vue"],
"customSyntax": "postcss-html"
}
]
}Sekcja overrides stosuje dodatkowe reguły do wskazanych plików, a customSyntax pozwala czytać SCSS czy pliki Vue. Tu też usuń przestarzałą regułę max-line-length.
Integracja z narzędziami deweloperskimi
Package.json scripts
Stylelinta uruchamiasz skryptami w package.json, dzięki czemu cały zespół używa tych samych poleceń:
{
"scripts": {
"lint:css": "stylelint \"src/**/*.{css,scss}\"",
"lint:css:fix": "stylelint \"src/**/*.{css,scss}\" --fix",
"lint:css:report": "stylelint \"src/**/*.{css,scss}\" --formatter json > stylelint-report.json",
"prebuild": "npm run lint:css",
"test:css": "stylelint \"src/**/*.{css,scss}\" --formatter tap"
}
}Polecenie npm run lint:css tylko sprawdza pliki, a lint:css:fix z flagą --fix automatycznie poprawia te błędy, które narzędzie potrafi naprawić samo. Skrypt prebuild npm uruchomi przed każdym buildem.
Integracja z VS Code (.vscode/settings.json)
Oficjalne rozszerzenie Stylelint do VS Code podkreśla problemy od razu podczas pisania, a te ustawienia je konfigurują:
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.stylelint": true
},
"css.validate": false,
"less.validate": false,
"scss.validate": false,
"stylelint.validate": ["css", "postcss", "scss", "sass"],
"stylelint.snippet": ["css", "postcss", "scss", "sass"],
"stylelint.enable": true
}Ustawienie source.fixAll.stylelint poprawia plik przy każdym zapisie, a wyłączenie wbudowanej walidacji CSS zapobiega podwójnym komunikatom.
Pre-commit hook z Husky
Ostatnia linia obrony to sprawdzenie plików tuż przed commitem, żeby błędny kod nie trafił do repozytorium:
# Instalacja husky i lint-staged
npm install --save-dev husky lint-staged
# package.json
{
"lint-staged": {
"*.{css,scss}": [
"stylelint --fix",
"git add"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}
}To zapis dla starszych wersji narzędzi. W aktualnym Husky hook tworzysz poleceniem npx husky init, które zakłada plik .husky/pre-commit, a lint-staged od wersji 10 sam dodaje poprawione pliki, więc git add jest zbędne.
Przykłady reguł i ich zastosowania
1. Formatowanie i spójność
Zobaczmy, jakie problemy wyłapuje linter. Najpierw kod z błędami, a potem jego poprawiona wersja:
/* Błędy wykrywane przez Stylelint */
/* Nieprawidłowe wcięcia */
.component {
margin: 0;
padding: 1rem;
}
/* Brak spacji */
.component{margin:0;padding:1rem;}
/* Nieprawidłowe quotes */
.component {
font-family: "Arial";
background: url(image.jpg);
}
/* Nieprawidłowe kolory */
.component {
color: RED;
background: #ffffff;
border: 1px solid #fff;
}
/* Poprawiony kod zgodny z regułami */
/* Poprawne wcięcia */
.component {
margin: 0;
padding: 1rem;
}
/* Poprawne spacje */
.component {
margin: 0;
padding: 1rem;
}
/* Poprawne quotes */
.component {
font-family: 'Arial', sans-serif;
background: url('image.jpg');
}
/* Poprawne kolory */
.component {
color: #f00;
background: #fff;
border: 1px solid #fff;
}Wcięcia poprawi dziś Prettier, ale Stylelint nadal wyłapie na przykład nazwane kolory czy zapis #ffffff zamiast #fff.
2. Kolejność właściwości
Wtyczka stylelint-order pilnuje, by właściwości stały zawsze w kolejności ustalonej w konfiguracji:
/* Nieprawidłowa kolejność */
.component {
color: #333;
position: absolute;
background: #fff;
top: 0;
margin: 1rem;
display: flex;
left: 0;
padding: 1rem;
}
/* Poprawiona kolejność według konfiguracji */
.component {
position: absolute;
top: 0;
left: 0;
display: flex;
margin: 1rem;
padding: 1rem;
background: #fff;
color: #333;
}
/* Jeszcze lepsza organizacja z grupowaniem */
.component {
/* Positioning */
position: absolute;
top: 0;
left: 0;
z-index: 1;
/* Layout */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
/* Box model */
width: 100%;
height: 200px;
margin: 1rem;
padding: 1rem;
/* Visual */
background: #fff;
color: #333;
border: 1px solid #ddd;
border-radius: 4px;
/* Typography */
font-family: 'Inter', sans-serif;
font-size: 1rem;
line-height: 1.5;
/* Animation */
transition: all 0.3s ease;
transform: translateY(0);
}Z flagą --fix Stylelint sam przestawi deklaracje. Wygląd strony się nie zmienia, rośnie tylko czytelność.
3. BEM pattern validation
BEM to konwencja nazw: blok (.card), element po dwóch podkreślnikach (.card__title) i modyfikator po dwóch myślnikach (.card--featured). Wtyczka BEM sprawdza, czy klasy trzymają się tego wzoru:
/* Nieprawidłowe nazwy klas BEM */
.Component { } /* PascalCase */
.component_element { } /* single underscore */
.component-element-modifier { } /* dash instead of -- */
.componentElement { } /* camelCase */
/* Poprawne nazwy klas BEM */
.component { }
.component__element { }
.component__element--modifier { }
.component--variant { }
.multi-word-component { }
.multi-word-component__element-name { }
.multi-word-component__element-name--modifier-name { }Poprawna jest więc .component__element--modifier, a błędne są pojedynczy podkreślnik, PascalCase i camelCase.
4. SCSS specific rules
Dla SCSS wtyczka stylelint-scss sprawdza nazwy zmiennych, placeholdery i mixiny, a reguła max-nesting-depth zbyt głębokie zagnieżdżenia:
/* Nieprawidłowy SCSS */
$primaryColor: #007bff; /* camelCase variable */
$primary_color: #007bff; /* underscore variable */
%button-style { /* nieużywany placeholder */
padding: 1rem;
}
.component {
.nested {
.deep {
.too {
.deep { /* zbyt głębokie zagnieżdżenie */
color: red;
}
}
}
}
}
@mixin buttonMixin() { /* camelCase mixin */
padding: 1rem;
}
/* Poprawny SCSS */
$primary-color: #007bff;
$secondary-color: #6c757d;
%button-base {
padding: 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.component {
@include button-mixin;
&__element {
color: $primary-color;
&--modifier {
background: $secondary-color;
}
}
}
@mixin button-mixin($size: medium) {
@extend %button-base;
@if $size == small {
padding: 0.5rem;
} @else if $size == large {
padding: 1.5rem;
}
}Zmienne i mixiny zapisane w kebab-case, czyli $primary-color i button-mixin, pasują do nazw właściwości CSS.
Zaawansowana konfiguracja
Custom rules dla specific projektów
Reguły z końcówką -pattern przyjmują wyrażenia regularne, a listy dozwolonych wartości pozwalają zawęzić CSS do potrzeb projektu:
{
"rules": {
"comment-pattern": "^[A-Z][\\s\\S]*\\.$",
"custom-property-pattern": "^([a-z][a-z0-9]*)(-[a-z0-9]+)*$",
"keyframes-name-pattern": "^([a-z][a-z0-9]*)(-[a-z0-9]+)*$",
"selector-class-pattern": [
"^[a-z]([a-z0-9-]+)?(__([a-z0-9]+-?)+)?(--([a-z0-9]+-?)+){0,2}$",
{
"message": "Expected class selector to be kebab-case BEM format"
}
],
"declaration-property-value-allowed-list": {
"border": ["/^0$/"],
"border-top": ["/^0$/"],
"border-right": ["/^0$/"],
"border-bottom": ["/^0$/"],
"border-left": ["/^0$/"]
},
"property-allowed-list": [
"position",
"top",
"right",
"bottom",
"left",
"z-index",
"display",
"flex",
"flex-direction",
"flex-wrap",
"justify-content",
"align-items",
"align-content",
"gap",
"width",
"height",
"min-width",
"min-height",
"max-width",
"max-height",
"margin",
"margin-top",
"margin-right",
"margin-bottom",
"margin-left",
"padding",
"padding-top",
"padding-right",
"padding-bottom",
"padding-left",
"background",
"background-color",
"background-image",
"background-size",
"background-position",
"background-repeat",
"color",
"font-family",
"font-size",
"font-weight",
"line-height",
"text-align",
"text-decoration",
"text-transform",
"border",
"border-top",
"border-right",
"border-bottom",
"border-left",
"border-radius",
"box-shadow",
"opacity",
"visibility",
"overflow",
"overflow-x",
"overflow-y",
"cursor",
"transition",
"transform",
"animation"
]
}
}property-allowed-list dopuszcza tylko wymienione właściwości, więc to reguła bardzo surowa, dobra dla systemów projektowych.
Performance-focused rules
Te reguły ograniczają złożoność selektorów: liczbę identyfikatorów, klas, zagnieżdżeń oraz specyficzność:
{
"rules": {
"selector-max-id": 0,
"selector-max-universal": 1,
"selector-max-type": 2,
"selector-max-class": 4,
"selector-max-attribute": 2,
"selector-max-pseudo-class": 3,
"selector-max-compound-selectors": 4,
"selector-max-specificity": "0,4,0",
"max-nesting-depth": 3,
"declaration-block-no-redundant-longhand-properties": true,
"shorthand-property-no-redundant-values": true,
"no-unknown-animations": true
}
}selector-max-id: 0 zakazuje identyfikatorów w selektorach, a selector-max-specificity ustawia górną granicę.
Integracja z build tools
Webpack integration
Stylelinta podepniesz też do narzędzia budującego, a błędy zobaczysz w konsoli podczas budowania:
// webpack.config.js
const StylelintPlugin = require('stylelint-webpack-plugin');
module.exports = {
plugins: [
new StylelintPlugin({
configFile: '.stylelintrc.json',
context: 'src',
files: '**/*.{css,scss}',
failOnError: false,
quiet: false,
emitErrors: true
})
]
};Przy failOnError: false błędy są raportowane, ale nie przerywają budowania.
Gulp integration
W projektach opartych na Gulpie linter to osobne zadanie, obok zadania naprawiającego pliki:
// gulpfile.js
const gulp = require('gulp');
const stylelint = require('gulp-stylelint');
gulp.task('lint-css', function() {
return gulp
.src('src/**/*.{css,scss}')
.pipe(stylelint({
reporters: [
{ formatter: 'string', console: true }
]
}));
});
gulp.task('fix-css', function() {
return gulp
.src('src/**/*.{css,scss}')
.pipe(stylelint({
fix: true
}))
.pipe(gulp.dest('src'));
});Zadanie fix-css zapisuje poprawione pliki z powrotem do katalogu src.
PostCSS integration
Stylelint działa również jako wtyczka PostCSS. Kolejność wtyczek ma znaczenie, bo linter musi zobaczyć kod przed przekształceniem:
// postcss.config.js
module.exports = {
plugins: [
require('stylelint')({
config: require('./.stylelintrc.json')
}),
require('autoprefixer'),
require('cssnano')({
preset: 'default'
})
]
};Stylelint sprawdza źródło, Autoprefixer dodaje prefiksy, a cssnano na końcu minifikuje wynik.
CI/CD Integration
GitHub Actions
Na serwerze ciągłej integracji lint uruchamia się przy każdym pushu i pull requeście:
# .github/workflows/css-lint.yml
name: CSS Lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install dependencies
run: npm ci
- name: Run Stylelint
run: npm run lint:css
- name: Upload lint results
uses: actions/upload-artifact@v4
if: failure()
with:
name: stylelint-results
path: stylelint-report.jsonWersje akcji co jakiś czas się zmieniają, więc sprawdzaj je w dokumentacji GitHub Actions.
Custom reporting
Przez Node.js API przygotujesz własne podsumowanie, na przykład liczbę błędów według reguł:
// scripts/css-report.js
const stylelint = require('stylelint');
const fs = require('fs');
stylelint.lint({
files: 'src/**/*.{css,scss}',
formatter: 'json'
}).then(function(resultObject) {
const results = JSON.parse(resultObject.output);
const summary = {
totalFiles: results.length,
totalErrors: results.reduce((sum, file) => sum + file.warnings.filter((w) => w.severity === 'error').length, 0),
errorsByRule: {}
};
results.forEach(file => {
file.warnings.forEach(warning => {
const rule = warning.rule;
summary.errorsByRule[rule] = (summary.errorsByRule[rule] || 0) + 1;
});
});
console.log('CSS Lint Summary:');
console.log(`Total files analyzed: ${summary.totalFiles}`);
console.log(`Total errors found: ${summary.totalErrors}`);
console.log('Errors by rule:');
Object.entries(summary.errorsByRule)
.sort(([,a], [,b]) => b - a)
.forEach(([rule, count]) => {
console.log(` ${rule}: ${count}`);
});
fs.writeFileSync('css-lint-summary.json', JSON.stringify(summary, null, 2));
});Skrypt liczy ostrzeżenia o wadze error, grupuje je według nazwy reguły i zapisuje raport do pliku JSON.
Stylelint jest niezbędnym narzędziem w profesjonalnym developmencie CSS, a właściwa konfiguracja znacząco poprawia jakość i utrzymywalność stylów. Moja rada: wdrażaj go w czterech krokach, czyli instalacja pakietów, plik .stylelintrc.json, skrypt lint:css w package.json i pierwsze uruchomienie. W następnej lekcji sprawdzimy, jak Twoje style wyglądają w różnych przeglądarkach.
Pamiętaj: Stylelint to nadzorca budowy, który ogląda każdy blok kamienia, zanim trafi on na piramidę.
Kod do tej lekcji: index.html
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<title>:is() i :where()</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<article>
<h2>Artykul o piramidach</h2>
<p>Tekst z <a href="#">linkiem</a>.</p>
</article>
<section>
<h2>Sekcja o faraonach</h2>
<p>Tekst z <a href="#">linkiem</a>.</p>
</section>
<nav>
<h2>Nawigacja</h2>
<ul>
<li><a href="#">Link 1</a></li>
<li><a href="#">Link 2</a></li>
</ul>
</nav>
</main>
</body>
</html>Hasła z dokumentacji w tej lekcji
Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Czym jest Stylelint?
2. Co robi flaga --fix uruchomiona ze Stylelint?
To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Układanie w pionie
Ułóż kroki wdrażania Stylelint w projekcie we właściwej kolejności: