Upgrade to Pro — share decks privately, control downloads, hide ads and more …

CSS Linter の現在地 2025年のベストプラクティスを探る

Avatar for mattsuu mattsuu
September 20, 2025

CSS Linter の現在地 2025年のベストプラクティスを探る

フロントエンドカンファレンス東京 2025 (2025/09/21) での発表資料
https://fec-tokyo.connpass.com/event/352581/

Avatar for mattsuu

mattsuu

September 20, 2025
Tweet

More Decks by mattsuu

Other Decks in Programming

Transcript

  1. Quiz: この CSS どう思いますか? .test-1 { colro: red; padding: 16pp;

    } .test-2 { background-color: red; background: url("images/bg.gif") no-repeat left top; } .test-3 { field-sizing: content; } 3
  2. This talk 2025 年時点の Stylelint / Biome / ESLint を横断比較し、

    最適な CSS Linter の“ 選定と運用” の勘所を解説します。 9
  3. 目次 ツールの現在地と推奨構成(Stylelint / Biome / ESLint ) カスタマイズのしやすさ Tailwind ・CSS

    Modules ・CSS-in-JS ・SCSS 互換性の基準(Baseline / browserslist ) MCP との組み合わせ 段階的な導入 10
  4. Stylelint: 特徴 100 を超える builtin ルール + 豊富なエコシステム 誤検知を極力避ける設計 →

    警告が出たら迷わずに直せる Plugin に委ねる領域 文脈依存/ 助言的(a11y 、互換性、フォーマット) 12
  5. Stylelint: 推奨構成 recommended = 壊さない最小セット CSS 仕様的な不正(無効値・未知な構文 など)を検出 standard =recommended

    + モダンな表記の統一 例:ベンダープレフィックス禁止 / kebab-case 命名 / range の context 記法 ( @media (width >= 768px) ) // 壊さない土台 { "extends": ["stylelint-config-recommended"] } // 規約まで含める { "extends": ["stylelint-config-standard"] } 13
  6. ESLint: 特徴 2025/02 に CSS を公式サポート(@eslint/css ) 10 を超える built

    in ルール プロパティ/ At-rule の妥当性 ( no-invalid-* 系) Baseline 準拠 ( use-baseline ) Cascade Layers など最新CSS 潮流にも対応 14
  7. ESLint: 推奨構成 css/recommended で 正当性+互換性の土台を一括で有効化 import { defineConfig } from

    "eslint/config"; import css from "@eslint/css"; export default defineConfig([ { files: ["**/*.css"], language: "css/css", plugins: { css }, extends: ["css/recommended"], }, ]); 15
  8. Biome: 特徴 Lint / Format を同梱(1 ツールで整形+検証) まずは 壊さない土台優先 Stylelint

    の recommended のルールを実装 自前パーサ+データを一元管理 tokenizer / parser / プロパティ・At-rule 定義を内包 → 依存が少 ない 16
  9. 3 つのツール比較 Stylelint Biome ESLint リリース 2015 年 2023 年

    2025 年2 月 言語 JavaScript Rust JavaScript ルール数 100+ 20+ 10+ 特徴 CSS 専門 豊富なエコシステム 高速 Formatter 同梱 一元管理 シンプルな設定 一元管理 18
  10. At-rule の validation 、これだけで大丈夫? @charset "UTF-8"; /* OK */ @foo;

    /* NG ? */ 未知の At-rule を弾くだけで十分に見えるが、それだけでは不十分 22
  11. 実際に必要な「4 つの正当性」 at-rule 名 → @foo のような未知 at-rule を禁止 prelude

    → @property --x {} の --x が妥当か descriptor → @counter-style foo { bar: red; } の bar が妥当か descriptor value → @counter-style foo { system: baz; } の baz が妥当か At-rule の妥当性チェックは この4 軸 が必要。 23
  12. At-rule 検証の設計差 Stylelint: 4 つの個別ルールに分割して検出 ルールごとに secondary options (例: ignoreAtRules

    )でピン ポイント除外 ESLint: 1 つのルールでまとめて検出 言語定義( languageOptions.customSyntax )拡張で誤検知を減ら す Biome: at-rule 名の validation のみ オプションなし 24
  13. Stylelint の「細粒度」なルール設計 { "rules": { // 1) 未知の at-rule を禁止(Tailwind

    等は個別に逃がす) "at-rule-no-unknown": [true, { "ignoreAtRules": ["tailwind", "apply", "screen", "theme", "layer"] }], // 2) at-rule の不正な prelude を禁止(必要なら特定 at-rule を除外) "at-rule-prelude-no-invalid": [true, { "ignoreAtRules": ["property"] }], // 3) at-rule 内の未知 descriptor を禁止 "at-rule-descriptor-no-unknown": true, // 4) at-rule 内の未知の descriptor の値を禁止 "at-rule-descriptor-value-no-unknown": true } } 25
  14. ESLint: 言語定義で“ 許容範囲” を広げる export default defineConfig([ { //... languageOptions:

    { customSyntax: { // @my-at-rule "hello world!"; が正しい at-rule として認識される atrules: { "my-at-rule": { prelude: "<string>", }, }, }, }, }, ]); 26
  15. ルール設計のまとめ ツール 基本方針 オプション 誤検知への対処 Stylelint 検出軸を細分化 (ルール分割) あり (

    ignoreAtRules 等) オプション /* stylelint-disable */ ESLint/css 1 ルールで包括 少なめ 言語定義拡張で対応 customSyntax で拡張 /* eslint-disable */ Biome Stylelint の recommended 相当を 実装中 未実装 /* biome-ignore */ 27
  16. Stylelint :プラグインで実装 特徴:PostCSS AST を直接扱える export default stylelint.createPlugin("plugin/no-foo", () =>

    { return (root, result) => { root.walkRules((rule) => { if (rule.selector.includes("foo")) { stylelint.utils.report({ result, ruleName: "plugin/no-foo", message: ' セレクタに "foo" は使用禁止', node: rule, }); } }); }; }); 30
  17. ESLint :カスタムルールで実装 特徴:ESLint の作法で書ける create(context) { return { Rule(node) {

    const selector = context.sourceCode.getText(node.prelude); if (selector.includes("foo")) { context.report({ node: node.prelude, message: ' セレクタに "foo" は使用禁止', }); } }, }; } 31
  18. Biome :GritQL でルール記述 language css; `$selector { $props }` where

    { $selector <: r".*foo.*", register_diagnostic( span = $selector, message = " セレクタに 'foo' は使用禁止" ) } 注意: GritQL はまだ alpha 版:v0.1.0-alpha.1743007075 最終リリース:2025/03/27 33
  19. カスタムルール開発の比較 項目 Stylelint ESLint Biome 学習コ スト 中(PostCSS AST )

    低〜中(ESLint 流儀) 高(DSL 学習が必要) 開発体 験 CSS 専門API が 充実 JS 開発者に馴染 む パターンマッチ / 直 感的な記述 34
  20. State of CSS 2025 から見える“ 互換性” の課題 Browser support が多くのカテゴリでペインポイントの上位に

    Interactions: 1 位(16%) Other: 1 位(19%) Typography: 2 位(13%) Layout: 3 位(9%) Shapes & Graphics: 3 位(9%) Colors: 3 位(7%) ref: https://2025.stateofcss.com/en-US/features 36
  21. Baseline というもう一つの基準 Web 標準の成熟度を 3 段階で表す Limited / Newly available

    / Widely available (30 ヶ月以上) 対応ブラウザ一覧ではなく、広く安全に使えるしきい値で判断 ツール 実装状況 ESLint Built-in Stylelint Plugin Biome 未実装 39
  22. Baseline の設定例 builtin ルールで提供されている { rules: { // widely /

    newly / 年指定(例: 2024 ) "css/use-baseline": ["warn", { available: "widely" }] } } 40
  23. Stylelint: Baseline の設定例 (plugin) ESLint と同じ I/F で設定可能 { plugins:

    ["stylelint-plugin-use-baseline"], rules: { "plugin/use-baseline": [true, { available: "widely" }] }, } 41
  24. Tailwind CSS おさらい <!-- 従来のCSS: クラスに意味を持たせる --> <button class="btn btn-primary">

    送信</button> <!-- Tailwind CSS: ユーティリティクラスを組み合わせる --> <button class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"> 送信 </button> 小さな単機能のユーティリティクラスを組み合わせる p-4 = padding 、 bg-red-500 = 背景色、 text-xl = 文字サイズ 44
  25. クラス文字列は CSS Linter の対象外 // JSX の中のクラス文字列 <div className="p-2 p-3

    bg-red-500" /> 通常の CSS Linter は CSS の構文(宣言, at-rule など) を解析。 ただの文字列であるクラス( p-2 p-3 )は、そのままでは対象外。 /* CSS Linter が期待する形式 */ .button { padding: 0.5rem; /* ← property: value の宣言 */ background-color: red; } 45
  26. eslint-plugin-tailwindcss の登場 発想の転換:CSS 宣言ではなく「クラス名の語彙」を検査 // 検出できる問題 <div className="p-2 p-3" />

    // padding の競合 <div className="text-foo my-custom" /> // 存在しないクラス名 // 自動修正も可能 <div className="mx-5 my-5" /> // → m-5 ( 短縮形) <div className="pt-2 pb-4" /> // → pb-4 pt-2 ( 推奨順序にソート) JSX, Vue, HTML... 幅広くサポート Tailwind CSS v4 対応中 (beta で公開) 46
  27. Stylelint × Tailwind CSS stylelint-config-tailwindcss で誤検知を回避 @tailwind , @apply ,

    theme() など Tailwind 構文でエラーを出さない 通常の CSS を書く領域 (Custom CSS, Global CSS) に適用し品質担保 export default { extends: ["stylelint-config-tailwindcss"] }; 48
  28. ESLint + tailwind-csstree languageOptions.customSyntax で Tailwind 構文を追加し、 @eslint/css が 未知扱いせず構文検証が可能に

    import { tailwind4 } from "tailwind-csstree"; export default defineConfig([ { language: "css/css", plugins: { css }, languageOptions: { customSyntax: tailwind4 } }, ]); 49
  29. Tailwind 構文を適切に読み込む @tailwind base; /* 正しい値 */ @tailwind foo; /*

    不正な値を検出 */ @apply text-white bg-blue-500; /* 正しい構文 */ @apply { color: red; } /* 無効な @apply 構文 */ a { background: theme(colors.blue.500); /* 正しい関数呼び出し */ color: theme(fake.value); /* 不正なキー */ } 50
  30. Tailwind CSS サポートまとめ ツール クラス文字列 Tailwind 構文 eslint-plugin-tailwindcss ◎ 専門

    × ESLint + tailwind-csstree × ◎ 構文解釈 Stylelint × △ 誤検知の抑制 Biome △ ソート × クラス文字列の検査 と CSS 構文の検査 を分けて設計するのが ◎ 52
  31. CSS Modules: サポート比較 機能 / 構文 Stylelint (+stylelint-config- css-modules )

    Biome @value 対応 対応 composes: 対応 対応 compose-with: 対応 未対応 :local() 対応 未対応 :global() 対応 対応 :export 対応 未対応 ※ ESLint は全て未対応。 @value を使うとパースエラーになる。 54
  32. CSS-in-JS の対応 Stylelint のみ可能 Biome, ESLint は未実装 import styled from

    "styled-components"; /* CSS エラーを含む例 */ const ErrorButton = styled.button` foo: bar; /* 未知プロパティ */ color: baz; /* 不正な値 */ width: 100zz; /* 未知の単位 */ `; 55
  33. Stylelint × CSS-in-JS customSyntax を指定して、JSX 内の CSS を lint 可能に

    { "customSyntax": "postcss-styled-syntax", } ❯ npx stylelint Button.jsx Button.jsx 5:3 Unexpected unknown property "foo" property-no-unknown 6:10 Unexpected unknown value "baz" for property "color" declaration-property-value-no-unknown 7:10 Unexpected unknown value "100zz" for property "width" declaration-property-value-no-unknown 7:13 Unexpected unknown unit "zz" unit-no-unknown 4 problems (4 errors, 0 warnings) 56
  34. Less / Sass / SCSS 対応 ツール SCSS Less Sass

    備考 Stylelint 共有設定 or customSyntax で対応 ESLint SCSS サポートの PR は出てるが止まっている Biome 57
  35. MCP サポート状況 ESLint :公式 MCP サーバーあり( eslint --mcp ) Stylelint

    :コミュニティ製 stylelint-mcp /公式移管は議論中 Biome :MCP サーバーは未提供(RFC あり) 。 JS API Bindings (alpha ) で自作は可能 59
  36. 実例: 初回実行 /* style.css */ a {} /* 空のブロック */

    a { foo: red; /* 未知のプロパティ */ } $ npx eslint style.css style.css 2:3 error Unexpected empty block found css/no-empty-blocks 5:3 error Unknown property 'foo' found css/no-invalid-properties 2 problems (2 errors, 0 warnings) 66
  37. 実例: Suppressions 生成 # 既存違反を一括抑制 $ npx eslint . --suppress-all

    // 生成される `eslint-suppressions.json` { "style.css": { "css/no-empty-blocks": { "count": 1 }, "css/no-invalid-properties": { "count": 1 } } } ファイル× ルール× 件数のカウントが json に記録される 67
  38. 実例: 新規違反は検出 a {} /* 既存: 抑制済み */ a {

    foo: red; /* 既存: 抑制済み */ foo: red; /* ⬅︎ 新規追加 */ } $ npx eslint style.css style.css 5:3 error Unknown property 'foo' found css/no-invalid-properties 6:3 error Unknown property 'foo' found css/no-invalid-properties 2 problems (2 errors, 0 warnings) 69
  39. Bulk suppressions を利用した導入 1. 現在のエラー → JSON に保存 2. 新規コードは厳格にチェック

    3. 既存エラーは自分のペースで修正 → 今すぐルールを有効化できる 70
  40. 各ツールの Bulk suppressions の対応状況 ツール 状況 備考 ESLint 利用可能 v9.24+

    / IDE 未対応 Stylelint 実装中 PR #8564 Biome 未対応 (JS 系のコメント挿入は別機能) 72
  41. Suppressions なしで段階導入 A. overrides で新規だけ厳しく // eslint.config.js export default [{

    files: ["src/new/**/*.css"], rules: { "css/no-invalid-properties": "error" } }]; B. コメントで局所的に回避 /* stylelint-disable no-empty-blocks */ a {} /* stylelint-enable */ 73
  42. まとめ Stylelint 規約系・細粒度な調整・SCSS/Less ・CSS-in-JS まで面倒見が良い ESLint (@eslint/css ) 妥当性+Baseline をまず担保。単一

    ESLint 運用にフィット。 Biome Lint/Format 一体・高速。まずは “ 壊さない土台” から始めたいとき に軽快(機能は拡充中) 。 75