Astro 5.1 + SCSSプロジェクトにStylelint導入する
いま『Webアプリケーションアクセシビリティ』を読んでいて、あんま関係ないけどStylelintの存在を初めて知ったので、サイト・ブログ用のAstroプロジェクトに導入してみることにした。
前提
今回Stylelintを導入するプロジェクトは、Astroを使ったもの。
SCSSを使用してて、だいたい半分くらいのスタイルは *.scss
ファイルに書いてimport、もう半分くらいのスタイルは *.astro
ファイル内の <style lang="scss">
タグ内に記述してる。また、部分的にはSCSSを使わず *.css
ファイルをimportしている箇所もある。
MDXは使用していないことを留意のうえ、上記全部をカバーしていきたい。
Stylelint導入
だいたいStylelintのGetting Startedと、AstroのエディタのセットアップのStylelint項を見る。
まず、各種パッケージをインストール。
pnpm install --save-dev stylelint stylelint-config-standard-scss postcss-html stylelint-config-html
stylelint
… Stylelint本体stylelint-config-standard-scss
… SCSS用のStylelint設定。stylelint-config-standardとstylelint-config-recommended-scssの内容を含むらしい。あと、stylelint-scssもこれに含まれてるから、別途インストールは不要らしい。postcss-html
,stylelint-config-html
… HTMLファイルやAstroファイルの<style>
タグ内のスタイルもチェックできるようにする。
次に、プロジェクトルートに以下の内容で .stylelintrc.mjs
を作成。これがstylelintの設定ファイルとなる。
/** @type {import('stylelint').Config} */
export default {
extends: ['stylelint-config-standard-scss', 'stylelint-config-html'],
rules: {
// Astroの疑似クラス :global は指摘させない (後述)
'selector-pseudo-class-no-unknown': [true, { ignorePseudoClasses: ['global'] }],
},
overrides: [
// *.astroファイルのカスタムプロパティは指摘させない (後述)
{
files: ['*.astro'],
rules: {
'custom-property-pattern': null,
},
},
],
};
あと、自分はthe-new-css-resetをCSSファイルにコピペしてimportしているので、stylelintの対象から除外するために .stylelintignore
も作成。
the-new-css-reset.css
最後に、自分はVSCodeを使っているので、VSCode用のstylelint拡張機能をインストール。
code --install-extension stylelint.vscode-stylelint
この拡張機能用に、VSCodeの settings.json
に以下を追加。
{
// stylelintの拡張機能の対象にするファイルの拡張子
"stylelint.validate": ["css", "scss", "html", "astro"],
// 保存時に自動で --fix するように
"editor.codeActionsOnSave": {
"source.fixAll.stylelint": "explicit"
},
}
実行してみる
どんなもんか見てみましょう
あばばばばwwww そんなにだめ!?
以下の通りにコマンドラインから実行して、総エラー件数を確認してみる。
npx stylelint "**/*.{css,scss,html,astro}"
✖ 449 problems (449 errors, 0 warnings)
412 errors potentially fixable with the "--fix" option.
思ったより……か?
次項に、ここまでの設定を使って自分のプロジェクトで出たものをいくつか抜粋して掲載しておく。
Note
pnpmを使ってるから実行コマンドは pnpm dlx
でもいいはずだと思うんだけど、Error: Cannot resolve custom syntax module "postcss-html". Check that module "postcss-html" is available and spelled correctly.
エラーが出るので諦めて npx
使った。一応、GitHubのIssueにそれっぽいのがあったので↓に貼っておく。
出てきたもの実例
Tip
だいたいのルールは、オプションでどの書き方に統一するかを指定できる。
selector-pseudo-class-no-unknown
Unexpected unknown pseudo-class selector ":global" (selector-pseudo-class-no-unknown)
存在しない疑似クラスが使われている。
ul {
margin: 1.5rem 0 1.5rem 0.5rem;
&:global(> li) {
margin: 0.2rem 0;
padding-left: 1.4rem;
@include list-mark-light;
}
}
……が、:global
はAstroで使える疑似クラスなので、指摘してほしくない。
.stylelintrc.mjs
に以下を追加して無視してもらう。
export default {
rules: {
'selector-pseudo-class-no-unknown': [true, { ignorePseudoClasses: ['global'] }],
},
};
custom-property-pattern
Expected custom property name "--maxWidth" to be kebab-case (custom-property-pattern)
カスタムプロパティがケバブケースでない。
<style lang="scss" define:vars={{ maxWidth: `${maxWidth}px` }}>
.container {
margin: 0 auto;
max-width: var(--maxWidth);
}
main {
padding: 0 0.5rem;
}
</style>
……が、Astroの define:vars
で渡したカスタムプロパティは公式サンプルでもキャメルケースなので、指摘してほしくない。
.stylelintrc.mjs
に以下を追加して、*.astro
ファイル内のカスタムプロパティは無視してもらう。
export default {
overrides: [
{
files: ['*.astro'],
rules: {
'custom-property-pattern': null,
},
},
],
};
function-url-quotes
Expected quotes around "url" function argument (function-url-quotes)
url()
関数の引数にクォートが無い。
:root {
--article-section-header-img: url(/assets/common/header-kiriukun-colored.svg);
}
:root {
--article-section-header-img: url('/assets/common/header-kiriukun-colored.svg');
}
クォートつけられるの知らなかった。
selector-pseudo-element-colon-notation
Expected double colon pseudo-element notation (selector-pseudo-element-colon-notation)
疑似要素の ::
が :
になっている。
blockquote:before,
blockquote:after,
q:before,
q:after {
content: '';
}
blockquote::before,
blockquote::after,
q::before,
q::after {
content: '';
}
シンプルに書き間違いで恥ずかしい。感謝。
rule-empty-line-before
Expected empty line before rule (rule-empty-line-before)
ルールのブロックの前に空行が無い。
a {
text-decoration: underline;
transition: color 0.08s linear;
&:link {
color: var(--link-color);
}
&:visited {
color: var(--link-visited-color);
}
}
a {
text-decoration: underline;
transition: color 0.08s linear;
&:link {
color: var(--link-color);
}
&:visited {
color: var(--link-visited-color);
}
}
これは見逃してほしい。OFFにする。
property-no-vendor-prefix
Unexpected vendor-prefix "-webkit-text-size-adjust" (property-no-vendor-prefix)
Autoprefixerがなんとかしてくれるプロパティにベンダープレフィックスが書かれている。
html {
-webkit-text-size-adjust: 100%;
}
html {
text-size-adjust: 100%;
}
-webkit-text-size-adjust
に関しては思考停止で書いてた……。
selector-class-pattern
Expected class selector ".c_red" to be kebab-case (selector-class-pattern)
クラスセレクタがケバブケースでない。
.c_red {
color: #f33 !important;
}
.c-red {
color: #f33 !important;
}
昔から使ってる汎用クラスだから移行が面倒で……。
color-function-notation
Expected modern color-function notation (color-function-notation)
色の指定方法が古い。
code {
margin: 0 1px;
padding: 0.2rem;
border-radius: 0.2rem;
background-color: var(--code-bg-color);
text-shadow: 0 1px 1px hsla(0, 0%, 0%, 0.3);
}
code {
margin: 0 1px;
padding: 0.2rem;
border-radius: 0.2rem;
background-color: var(--code-bg-color);
text-shadow: 0 1px 1px hsl(0deg 0% 0% / 30%);
}
知らなかった。そんな違ったのか。
media-feature-range-notation
Expected "context" media feature range notation (media-feature-range-notation)
範囲指定するメディアクエリは >=
とか <=
とかで書くべき (Media Queries Level 4)。
@mixin header-barisan {
@media (min-resolution: 2dppx) {
border-image-source: url('/assets/common/border-barisan_base@2x.gif');
border-image-slice: 0 20 4 36;
background-image: url('/assets/common/border-barisan_top@2x.gif');
}
}
@mixin header-barisan {
@media (min-resolution >= 2dppx) {
border-image-source: url('/assets/common/border-barisan_base@2x.gif');
border-image-slice: 0 20 4 36;
background-image: url('/assets/common/border-barisan_top@2x.gif');
}
}
Retinaディスプレイ用に高解像度の素材を用意してたやつ。主要ブラウザは軒並み対応したらしいけど、他ならぬ自分がiOSのアップデートをサボってる人種だからなんとも……。今回はオプション prefix
で従来の表記にしておく。
no-descending-specificity
Expected selector "img" to come before selector ".markdown-content figure.img img" (no-descending-specificity)
より具体的なセレクタ=優先度が高いセレクタが、それよりも低い優先度のセレクタの手前に書かれている。
セレクタの優先度とソース上の順序を合わせるべきということです。
.tl-ev-label {
display: flex;
column-gap: 0.35rem;
margin-left: 0.4rem;
line-height: 1;
font-size: 0.85rem;
}
.tl-ev-label + .tl-ev-content {
margin-top: -0.6rem;
padding-top: 1rem;
}
.tl-ev-content {
border: 1px solid hsla(var(--text-color-hsl), 0.4);
}
.tl-ev-label {
display: flex;
column-gap: 0.35rem;
margin-left: 0.4rem;
line-height: 1;
font-size: 0.85rem;
}
.tl-ev-content {
border: 1px solid hsla(var(--text-color-hsl), 0.4);
}
.tl-ev-label + .tl-ev-content {
margin-top: -0.6rem;
padding-top: 1rem;
}
言われてみると「なるほど」と思うが、絶対に併用しないことがわかってるところまで言われると厳しく感じるかも。行単位で無効化するのがよさそう。
font-family-name-quotes
Unexpected quotes around "Meiryo" (font-family-name-quotes)
フォント名にクォートがついている。
html {
font-size: 14px;
font-family: '游ゴシック体', 'Yu Gothic', 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', 'メイリオ', 'Meiryo', 'MS Pゴシック',
'MS PGothic', sans-serif;
text-size-adjust: 100%;
}
html {
font-size: 14px;
font-family: '游ゴシック体', 'Yu Gothic', 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', 'メイリオ', Meiryo, 'MS Pゴシック',
'MS PGothic', sans-serif;
text-size-adjust: 100%;
}
なぜ Meiryo
だけエラー? と思ったけど、空白・数字・ハイフン以外の句読点が入ってないからつけなくていいよ、ということらしい。個人的にはそういうの面倒だから、オプション always-unless-keyword
でキーワード以外全部クォート必須に変更する。
scss/double-slash-comment-empty-line-before
Expected empty line before comment (scss/double-slash-comment-empty-line-before)
//
コメントの前に空行が無い。
a,
button {
&:focus {
outline: auto;
outline-offset: 3px;
// outline: none;
// box-shadow:
// 0 0 0 3px var(--accent-color),
// 0 0 0 5px var(--inverted-gb-color);
}
}
a,
button {
&:focus {
outline: auto;
outline-offset: 3px;
// outline: none;
// box-shadow:
// 0 0 0 3px var(--accent-color),
// 0 0 0 5px var(--inverted-gb-color);
}
}
お試しで書いたものをコメントアウトしてそのまま残したりするので、これはほっといてほしい。OFFにする。
最終的な設定ファイル
今回はこんな感じになった。
/** @type {import('stylelint').Config} */
export default {
extends: ['stylelint-config-standard-scss', 'stylelint-config-html'],
rules: {
'declaration-empty-line-before': null,
'media-feature-range-notation': 'prefix',
'font-family-name-quotes': 'always-unless-keyword',
'comment-empty-line-before': null,
'rule-empty-line-before': null,
'at-rule-empty-line-before': null,
'scss/double-slash-comment-empty-line-before': null,
'selector-class-pattern': null,
'selector-id-pattern': null,
'selector-pseudo-class-no-unknown': [true, { ignorePseudoClasses: ['global'] }],
},
overrides: [
{
files: ['*.astro'],
rules: {
'custom-property-pattern': null,
},
},
],
};
これで --fix
かけたら軒並み消えたので、終わり。