PostCSSでCSSをSassに近づける

## この記事の概要

## 再現する対象

以下の7つの特徴を再現できるように挑みます。

- Variables
- Nesting
- Partials
- Modules
- Mixins
- Extend/Inheritance
- Operators

こちらの特徴は、公式ドキュメントのガイドページを参照しています。

https://sass-lang.com/guide

## 準備

まずはPostCSSのインストールからです。

```shell
npm i -D postcss postcss-cli
```

ひとまず空の`postcss.config.js`を作成します。

```javascript:postcss.config.js
module.exports = {}
```

`package.json`に変換用のコマンドを追加します（コマンド名や書き出したCSSのファイル名は何でもOKです）。

```json-doc:package.json
{
//省略
  "scripts": {
    "css:watch": "postcss 変換前.css -o dist.css -w"
  },
}
```

あとはCSSを監視します。

```shell
npm run css:watch
```

## Variables

Sassの変数とは違いますが、今やCSS純正のCustom Propertiesが使えるのでそちらを使います。
そのため、特別な設定は不要です。

https://caniuse.com/css-variables

## Nesting

[postcss-nested](https://github.com/postcss/postcss-nested)を使います。

```shell
npm i -D postcss-nested
```

`postcss.config.js`に設定を追加します。

```diff_javascript:postcss.config.js
  module.exports = {
+   plugins: [
+     require('postcss-nested')
+   ],
  }
```

あとは通常のSassと同じくネストして書けばOKです。
以下のコードもそうですが、例として示すコードはリポジトリに載っているコード（またはそれを多少いじったもの）です。

```css:変換前
.phone {
  &_title {
    width: 500px;
    @media (max-width: 500px) {
      width: auto;
    }
    body.is_dark & {
      color: white;
    }
  }
  img {
    display: block;
  }
}

.title {
  font-size: var(--font);

  @at-root html {
    --font: 16px
  }
}
```

```css:変換後
.phone_title {
  width: 500px;
}

@media (max-width: 500px) {
  .phone_title {
    width: auto;
  }
}

body.is_dark .phone_title {
  color: white;
}

.phone img {
  display: block;
}

.title {
  font-size: var(--font);
}

html {
  --font: 16px
}
```

## Partials, Modules

分割するのとそれを読み込むのはセットなので、まとめて紹介します。
[postcss-import](https://github.com/postcss/postcss-import)を使います。

```shell
npm i -D postcss-import
```

`postcss.config.js`に設定を追加します。

```diff_javascript:postcss.config.js
  module.exports = {
    plugins: [
      require('postcss-nested'),
+     require('postcss-import')
    ],
  }
```

あとはimportするだけです。
Sassだとimportが廃止になってuseに変わりましたが、こちらではimportのままです。

```css:変換前1
/* base.cssというファイル名 */

body {
  font: 100% Helvetica, sans-serif;
  color: #333;
}
```

```css:変換前2
@import "base.css";

.inverse {
  background-color: #333;
  color: white;
}
```

```css:変換後
body {
  font: 100% Helvetica, sans-serif;
  color: #333;
}

.inverse {
  background-color: #333;
  color: white;
}
```

## Mixins

[postcss-mixins](https://github.com/postcss/postcss-mixins)を使います。

```shell
npm i -D postcss-mixins
```

`postcss.config.js`に設定を追加します。

```diff_javascript:postcss.config.js
  module.exports = {
    plugins: [
      require('postcss-nested'),
      require('postcss-import'),
+     require('postcss-mixins')
    ],
  }
```

あとはmixinを定義して使うのですが、Sassとは微妙に書き方が違います。
mixinの定義時は`@extend`ではなく`@define-mixin`で、使うときは`@include`ではなく`@mixin`です。
入れ替わっている感じが若干ややこしいですが、致し方ありません。

```css:変換前
@define-mixin icon $network, $color: blue {
  .icon.is-$(network) {
    color: $color;
    @mixin-content;
  }
  .icon.is-$(network):hover {
    color: white;
    background: $color;
  }
}

@mixin icon twitter {
  background: url(twt.png);
}

@mixin icon youtube, red {
  background: url(youtube.png);
}
```

```css:変換後
.icon.is-twitter {
  color: blue;
  background: url(twt.png);
}

.icon.is-twitter:hover {
  color: white;
  background: blue;
}

.icon.is-youtube {
  color: red;
  background: url(youtube.png);
}

.icon.is-youtube:hover {
  color: white;
  background: red;
}
```

## Extend/Inheritance

[postcss-extend-rule](https://github.com/csstools/postcss-extend-rule)を使います。

```shell
npm i -D postcss-extend-rule
```

`postcss.config.js`に設定を追加します。

```diff_javascript:postcss.config.js
  module.exports = {
    plugins: [
      require('postcss-nested'),
      require('postcss-import'),
      require('postcss-mixins'),
+     require('postcss-extend-rule'),
    ],
  }
```

あとは通常のSassと同じように書けばOKです。

```css:変換前
%thick-border {
  border: thick dotted red;
}

.serious-modal {
  font-style: normal;
  font-weight: bold;

  @media (max-width: 240px) {
    @extend .modal:hover;
  }
}

.modal {
  @extend %thick-border;
  color: red;
}

.modal:hover:not(:focus) {
  outline: none;
}
```

```css:変換後
.serious-modal {
  font-style: normal;
  font-weight: bold;
}

@media (max-width: 240px) {
  .serious-modal:not(:focus) {
    outline: none;
  }
}

.modal {
  border: thick dotted red;
  color: red;
}

.modal:hover:not(:focus) {
  outline: none;
}
```

## Operators

純正の`calc()`が使えるので、特別な設定は不要です。
なんならSassの除算は`math.div(600px, 960px)`といった書式のため、書きづらくなるまであります。

https://caniuse.com/calc

## 最後に

「最近のCSSは進化してるよなあ」とボンヤリと思っていたのですが、こうして調べてみるとより明確に感じました。
あえてSassを使わずともできることが増えているので、無理に導入する必要もないかもしれません。

今回の記事ではNestingにもPostCSSを使いましたが[CSS Nesting Moduleの策定も進んでいる](https://www.w3.org/TR/css-nesting-1/)ようですし、この記事がいつまで役に立つやら……という気持ちもあります。

しばらくの間、ちょっとした資料としてでも役立てば幸いです。

---

最後まで読んでくださってありがとうございます！
Twitterでも情報を発信しているので、良かったらフォローお願いします！

https://twitter.com/xrxoxcxox

Devトークでのお話してくださる方も募集中です！

https://jobs.qiita.com/employers/qiita-inc/dev_talks/489