create-react-appでsass導入 + stylelint

[create-react-appをベースにeslint(airbnb)とprettierを導入](https://qiita.com/junkor-1011/items/782f1f242096cbaaabc2) の続編。css周りも改良したくなり、[sass](https://sass-lang.com/)(scss)を使えるようにした。（ついでにcss-modulesも試した）
更にstylelintを利用して`*.css`, `*.scss`ファイルのフォーマットチェックを行い、prettierと連携させて自動整形出来るようにした。

## 0. 前提条件など

検証環境:

- Linux(Ubuntu20.04LTS)
- node 14.16.0
    * npm6.14.11

prettierが予め入っている状態にしたいので、[前回](https://qiita.com/junkor-1011/items/782f1f242096cbaaabc2)の最終形からスタートする。

## 1. css周り改善

- sass: https://sass-lang.com/
    * cssの完全上位互換なので、既存の`*.css`ファイルを`*.scss`にそのまま置き換えるだけで移行出来る
- css-modules: https://github.com/css-modules/css-modules
    * あくまで擬似的にだがcssをモジュール化することができ、ファイル分割の際にファイル間でのidやクラス名のバッティングによる意図しない挙動を減らすことが出来そう
    * [create-react-appでは標準でサポート](https://create-react-app.dev/docs/adding-a-css-modules-stylesheet/)されており、別に設定作業は必要無いがついでで試してみる


### 1-1. sass導入

[node-sass](https://github.com/sass/node-sass)をインストールすればOK

```sh
npm i node-sass
```

なお、やや古くから使っている環境で↑をやろうとする（最新版のnode-sassを入れようとする）と、バージョン不整合でエラーが起こることがある。そうした場合、
https://stackoverflow.com/questions/64625050/error-node-sass-version-5-0-0-is-incompatible-with-4-0-0 などを参考に少し古いバージョンのnode-sassを入れることで対処出来る。

create-react-appの場合、node-sassをインストールしておけばそのまますぐにsassを使い始めることができ、例えば`src/`以下にある
`index.css`および`App.css`をそれぞれ`index.scss`, `App.scss`にリネームし、
`index.js`および`App.js`におけるimport文を修正（`import 'index.css'` → `import 'index.scss'`といった感じ）で移行出来る。
（勿論cssも以前通りに使い続けることが出来る）

### 1-2. css-modules

sassと併用できる。
試しに簡単なファイルを追加して試してみる。

```sh
# before
$ tree src
src
├── App.js
├── App.scss
├── App.test.js
├── index.js
├── index.scss
├── logo.svg
├── reportWebVitals.js
└── setupTests.js

# after
$ tree src
src
├── App.js # 編集
├── App.scss
├── App.test.js
├── dummy.module.scss # 追加
├── index.js
├── index.scss
├── logo.svg
├── reportWebVitals.js
└── setupTests.js
```

`App.js`で`dummy.module.scss`をimportしてみる。

```scss:dummy.module.scss
$color: #e30;
$dummy-test-color: #d0d;

.test {
  color: $color;
}

.dummy-text {
  color: $dummy-test-color;
}
```
ごくシンプルな例だが、sassの機能を使うために一応変数を使ってみている。

```javascript:App.js
import logo from './logo.svg';
import './App.scss';
import styles from './dummy.module.scss'; // 追加

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
        {/* 追加始まり */}
        <div className={`test-class ${styles.test}`}>
          hello css-modules with sass
        </div>
        <div className={styles['dummy-text']}>
          dummy-text
        </div>
        {/* 追加終わり */}
      </header>
    </div>
  );
}

export default App;
```
create-react-appで自動生成されるお馴染みのコードを少し編集して、`dummy.module.scss`からインポートしたスタイルをあてがっている。
css-modulesを使う場合の注意点（面倒な点）として、クラス名がハイフン`-`を含んでいたりすると単純なドット`.`でクラスを呼び出せないので、上記の例では`styles['dummy-text']`のようにしている。また、一つの`className`内に複数のクラスを割り当てることもあるが、そのような場合は`{}`とバッククウォートで囲ってあげるなどの必要がある。（上の例で`test-class`は別に何の意味もないが、複数クラスを書く場合の例として書いている。）

上記のようにして`npm run start`などとすると、↓のように`dummy.module.scss`から読み込んだスタイルをあてがったテキストを表示出来ていることが分かる:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680173/62e78501-d39f-9f3e-7cce-975a1760d936.png)


## 2. prettier, stylelintの設定

prettierはcssやscssにも使えるようなので設定してみる。
https://wemo.tech/3307 の後半部分を大幅に参考にさせて頂いているが、自分用の整理として一応書いておく。

npmで必要なパッケージをインストール

```sh
npm i -D stylelint-prettier stylelint-config-prettier stylelint-scss 

# 必要に応じて設定をインストール(今回はstandard)
npm i -D stylelint-config-standard
```
なお[`stylelint-config-standard`](https://github.com/stylelint/stylelint-config-standard)に関して、本当は[`stylelint-config-airbnb`](https://www.npmjs.com/package/stylelint-config-airbnb)を入れようとしていた（eslint設定がairbnbだったため）が、バージョンが`0.0.0`だったり、最後のアップデートが3年前だったりしたので今回は見送った。

次に、 https://stylelint.io/user-guide/configure を参考にstylelintの設定ファイルを作成する必要がある。
yaml, jsonなどの選択肢もあるが、今回は`.stylelintrc.js`を作成する。

```javascript:.stylelintrc.js
module.exports = {
  plugins: ['stylelint-scss'],
  extends: [
    'stylelint-config-standard',
    'stylelint-prettier/recommended',
  ],
  rules: {
    'at-rule-no-unknown': null,
    'scss/at-rule-no-unknown': true,
  }
};
```

- sassを使うので、`plugins`に`stylelint-scss`を入れている
- stylelintとprettierの連携については、 https://github.com/prettier/stylelint-prettier の通りに行う
- sassを使う際に不要なエラーが出ないように`rules`以下を設定している
    * ([参考](https://github.com/kristerkari/stylelint-scss/blob/master/src/rules/at-rule-no-unknown/README.md))



これでstylelintをlinterとして使えるようになっている。
（他にも必要であれば[stylelint-order](https://github.com/hudochenkov/stylelint-order)など、適宜追加していったりする）


また、前回は`npm run format`で`*.js`, `*.jsx`がprettierで整形されるようにしていた。
`package.json`の`script`部分を編集して、

```:package.json(抜粋)
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "format": "prettier --write ./src/**/*.{js,jsx,css,scss}"
  },
```
のようにすることで、`*.css`, `*.scss`ファイルも一緒にprettierでフォーマット出来るようになる。

## 3. まとめ

前回記事と合わせると、プロジェクトのルートディレクトリ直下のファイルなどは

```sh
$ tree -a -L 1
.
├── .env.development
├── .env.production
├── .eslintrc.js
├── .git
├── .gitignore
├── .prettierrc.js
├── .stylelintrc.js # 追加
├── README.md
├── node_modules
├── package-lock.json # 変更(但し自動で変更されるので意識しない)
├── package.json # 変更
├── public
└── src # 必要に応じて変更(cssをscssに置き換える、css-modulesに則ったファイルを追加するなど)
```
といった感じ。

ここで、最終的な`package.json`があると`npm i`などで一気に必要なライブラリをインストール出来るなど後で便利なので、自分用に置いておく:

```json:package.json
{
  "name": "test-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.11.9",
    "@testing-library/react": "^11.2.5",
    "@testing-library/user-event": "^12.8.3",
    "node-sass": "^5.0.0",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-scripts": "4.0.3",
    "web-vitals": "^1.1.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "format": "prettier --write ./src/**/*.{js,jsx,css,scss}"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "eslint": "^7.22.0",
    "eslint-config-airbnb": "^18.2.1",
    "eslint-config-prettier": "^8.1.0",
    "eslint-plugin-import": "^2.22.1",
    "eslint-plugin-jsx-a11y": "^6.4.1",
    "eslint-plugin-react": "^7.22.0",
    "eslint-plugin-react-hooks": "^4.2.0",
    "prettier": "^2.2.1",
    "stylelint": "^13.12.0",
    "stylelint-config-prettier": "^8.0.2",
    "stylelint-config-standard": "^21.0.0",
    "stylelint-prettier": "^1.2.0",
    "stylelint-scss": "^3.19.0"
  }
}
```

----------

## 参考

- https://www.i-ryo.com/entry/2020/12/11/065700
- https://dev.classmethod.jp/slide/scss-tutorial/
- https://kenjimorita.jp/react-css-modules-multiple/
- https://qiita.com/Ryo__M/items/01a1621768bc3882d5dd
- https://wemo.tech/3307
- http://cluex-developers.hateblo.jp/entry/2019/01/10/171237
- https://qiita.com/bijutubu_no_e-su/items/b1a105ea7c3defa880a8
- https://qiita.com/miwashutaro0611/items/437db965a819018f86d5
- https://github.com/kristerkari/stylelint-scss/issues/196