2回目の投稿です。優しくしてください。
前回の投稿ではwebpack3について書きましたが、今回はその中で使用していた stylelint について掘り下げていきます。
最近リリースされたばかりのwebpack4ではなく、あえてstylelint。
なぜstylelintなのか。
前回の投稿ではstylelintをあまり理解せず良さを出しきれていなかったこと、
webpack?PostCSS?という人でも簡単に導入できるので、積極的に取り入れてほしいという思いから。
そもそもstylelintってなに?
CSSがコーディング規約に準拠しているかどうか機械的に検知するためのnpm製ツール。
設定ルールを元にリンティングしてくれます。
定義されているルールはおよそ160以上。
パーサーとしてPostCSSが使えるため、もともとPostCSSを取り入れている場合は速やかに導入が可能。
今回はPostCSSを使わずstylelintのみで構築します。
導入するメリット
-
コードスタイルの一貫性を保つことができる
誰でも嫌でもコーディング規約に準拠したコードを書くことができます。
可読性・品質が向上することにより、保守性がぐんと増します。 -
潜在的エラーや非推奨パターンを見分けられる
無効な構文、非推奨プロパティ、宣言の重複等、エラーを検知することが可能です。 -
手間を省くことができる
設定ファイルを元に違反しているスタイルを自動修正してくれます。
※ 現在では一部ルールにのみ適用。 -
レビュー負担軽減
コーディング規約に準拠しているか血眼になってチェックする時間と手間が省けます。
やりたいこと
- CSSやSass独自の構文、スタイルをチェック
- ブロック内コンテンツやプロパティの順序をチェック
- スタイル違反を自動修正
必要なプラグインをインストール
yarnの場合は npm i
を yarn add
に読み替えてください。
$ npm i stylelint stylelint-scss stylelint-order
ディレクトリ階層
┃
┣━ sass
┃ ┣━ common
┃ ┃ ┣━ common.scss
┃ ┃ ┣━ _parts.scss
┃ ┃ ...
┃ ┃
┃ ┣━ main
┃ ┃ ┣━ main.scss
┃ ┃ ┣━ _parts.scss
┃ ┃ ...
┃ ┃
┃ ┣━ sub
┃ ┃ ┣━ sub.scss
┃ ┃ ┣━ _parts.scss
┃ ┃ ...
┃ ┃
┃ ...
┃
┣━ package.json
┗━ .stylelintrc.json
設定ファイルについて
package.json
npmプラグインを管理するための設定ファイルです。
stylelintを実行するためのエイリアスコマンドを "scripts"
に設定します。
オプションに --fix
を付与することで自動修正をしてくれます。
{
"name": "hello-webpack",
"version": "1.0.0",
"description": "",
"scripts": {
"stylelint": "stylelint \"sass/**/*.scss\" --fix",
},
"author": "",
"license": "ISC",
"dependencies": {
"stylelint": "^9.1.1",
"stylelint-order": "^0.8.1",
"stylelint-scss": "^2.4.0",
}
}
.stylelintrc.json
リンターの設定ファイルです。
- ignoreFiles
無視したいファイルを設定できます。 - syntax
今回はSCSSファイルをチェックするのでscss
を設定します。
PostCSSを使っている場合は、syntaxを設定しないと// comment
が/*comment*/
にされてしまうので注意。 - plugins
- stylelint-scss (
scss/Rule...
)
SCSS独自の構文ルールを追加。 - stylelint-order (
order/Options...
)
スタイル順序を設定。
- stylelint-scss (
- rules
設定したいルールを指定します。
ルールの詳細については公式サイトをご参照ください。
{
"ignoreFiles": [
"sass/common/*.scss"
],
"syntax": "scss",
"plugins": [
"stylelint-scss",
"stylelint-order"
],
"rules": {
"color-no-invalid-hex": true,
"color-hex-case": "lower",
"color-hex-length": "short",
"font-family-name-quotes": "always-where-recommended",
"font-family-no-duplicate-names": true,
"function-calc-no-unspaced-operator": true,
"function-comma-space-after": "always-single-line",
"function-comma-space-before": "never",
"function-max-empty-lines": 0,
"function-name-case": [
"lower",
{
"ignoreFunctions": [
"DXImageTransform.Microsoft.gradient",
"Alpha"
]
}
],
"function-parentheses-space-inside": "always",
"function-url-quotes": "always",
"function-whitespace-after": "always",
"number-leading-zero": "never",
"number-no-trailing-zeros": true,
"string-no-newline": true,
"string-quotes": "double",
"length-zero-no-unit": true,
"unit-case": "lower",
"unit-no-unknown": true,
"shorthand-property-no-redundant-values": true,
"value-keyword-case": "lower",
"value-list-comma-newline-after": "always-multi-line",
"value-list-comma-space-after": "always-single-line",
"value-list-comma-space-before": "never",
"value-list-max-empty-lines": 0,
"property-case": "lower",
"property-no-unknown": true,
"declaration-bang-space-after": "never",
"declaration-bang-space-before": "always",
"declaration-block-no-duplicate-properties": [
true,
{
"ignore": [
"consecutive-duplicates"
]
}
],
"declaration-block-no-redundant-longhand-properties": true,
"declaration-block-no-shorthand-property-overrides": true,
"declaration-block-semicolon-newline-after": "always",
"declaration-block-semicolon-space-after": "always-single-line",
"declaration-block-semicolon-space-before": "never",
"declaration-block-single-line-max-declarations": 1,
"declaration-block-trailing-semicolon": "always",
"declaration-colon-space-after": "always",
"declaration-colon-space-before": "never",
"block-closing-brace-empty-line-before": "never",
"block-closing-brace-newline-before": "always-multi-line",
"block-no-empty": true,
"block-opening-brace-space-before": "always",
"selector-attribute-brackets-space-inside": "never",
"selector-attribute-operator-space-after": "never",
"selector-attribute-operator-space-before": "never",
"selector-attribute-quotes": "always",
"selector-combinator-space-after": "always",
"selector-combinator-space-before": "always",
"selector-descendant-combinator-no-non-space": true,
"selector-list-comma-newline-after": "always",
"selector-list-comma-newline-before": "never-multi-line",
"selector-list-comma-space-before": "never",
"selector-max-empty-lines": 1,
"selector-pseudo-class-case": "lower",
"selector-pseudo-class-parentheses-space-inside": "never",
"selector-pseudo-element-case": "lower",
"selector-pseudo-element-colon-notation": "single",
"selector-type-case": "lower",
"media-feature-colon-space-after": "always",
"media-feature-colon-space-before": "never",
"media-feature-name-case": "lower",
"media-feature-parentheses-space-inside": "always",
"media-feature-range-operator-space-after": "always",
"media-feature-range-operator-space-before": "always",
"media-query-list-comma-newline-after": "always-multi-line",
"media-query-list-comma-newline-before": "never-multi-line",
"media-query-list-comma-space-after": "always-single-line",
"media-query-list-comma-space-before": "never",
"at-rule-name-case": "lower",
"at-rule-name-newline-after": "always-multi-line",
"at-rule-name-space-after": "always-single-line",
"at-rule-semicolon-newline-after": "always",
"indentation": 2,
"max-empty-lines": 2,
"no-eol-whitespace": true,
"no-extra-semicolons": true,
"no-missing-end-of-source-newline": true,
"scss/at-if-closing-brace-newline-after": "always-last-in-chain",
"scss/at-mixin-parentheses-space-before": "never",
"scss/dollar-variable-colon-space-after": "always",
"scss/dollar-variable-colon-space-before": "never",
"order/order": [
"dollar-variables",
"custom-properties",
{
type: 'at-rule',
name: ' extend'
},
{
type: 'at-rule',
name: 'include'
},
"declarations",
"at-variables",
{
type: 'at-rule',
name: 'media',
hasBlock: true
},
"rules"
],
"order/properties-order": [
"position",
"width",
"margin",
"font-size",
"border",
"content",
"animation"
... omit ...
]
}
}
stylelint実行
$ npm run stylelint
$ yarn stylelint
.sample {
@media #{$sp} {
display: none;
}
span {
font-size: 1.0rem;
}
@include box-shadow;
width: 30.0rem;
font-size: 0.8rem;
$color: #fff;
background: $color;
position: relative;
@extend %reset-box;
}
.sample {
$color: #fff;
@extend %reset-box;
@include box-shadow;
position: relative;
width: 30rem;
font-size: .8rem;
background: $color;
@media #{$sp} {
display: none;
}
span {
font-size: 1rem;
}
}