ちょっとがんばるWebサイト開発 on Webpack5 - Sass（SCSS）を導入してWebデザインの開発環境を強化する

# 前書き

## 本記事のゴール

この記事では、**Sass (SCSS)** を導入してWebデザインの開発環境を強化するための方法を理解することを目指します。

## この記事で解説する内容

- Sassの概要について説明します
- Sass (SCSS)をプロジェクトに導入する方法について説明します
- Sass (SCSS)を利用した簡単な例を紹介します

## この記事で解説しない内容

- Sassの詳細な文法については説明しません

> [参考] Sass関連記事
> - [Sass: Syntax](https://sass-lang.com/documentation/syntax)
> - [SCSS　まとめ１（基本文法） - Qiita](https://qiita.com/hiro-hiro/items/c981a6a11ee69fabcb74)
> - [SCSS　まとめ２（関数） - Qiita](https://qiita.com/hiro-hiro/items/b8bb3f3ee99cbac5c828)
> - [SCSS　まとめ３（コンパイル方法） - Qiita](https://qiita.com/hiro-hiro/items/eaaad48372d2dfb32066)

<!-- なお、「〜」の方法については []() において説明しているので、必要に応じてご覧ください。 -->

## シリーズ記事

この記事はシリーズ **「ちょっとがんばるWebサイト開発 on Webpack5」** の記事です。
シリーズ全体の概要は [以下のリンク先記事](https://qiita.com/Sharkkii/items/df707cb68f3729493800) にまとめられています。

https://qiita.com/Sharkkii/items/df707cb68f3729493800

:::note info
この記事では **Webpack5** (version5.X.X) を対象としています。
:::

# 背景知識

## Sassとは何か

**[Sass (Syntactically Awesome Style Sheets)](https://sass-lang.com/)** はスタイルシートを記述するための言語仕様です。

https://sass-lang.com/

Webサイト開発において最もよく用いられるスタイルシート言語は **CSS (Cascading Style Sheet)** だと思いますが、生のCSS言語による開発は困難が生じる場合が多くあります。そうした問題を解決し、より効率的なコーディングを可能にするという目的で作られたのがSassです。

Sassでは、CSS言語の機能を内包しつつ、CSS言語にはない便利な構文や機能を提供しています。例えば、Sassではネストを利用して階層構造を表現したり、関数・ミックスイン・継承の機能等を利用して再利用性の高いコードを記述したりすることができます。一般的なプログラミング言語の構文を利用することで、より効率的にスタイルシートの開発ができるようになることが期待されます。

Sassには2種類の記法 **SASS** と **SCSS** が存在します。このうち特に人気があるのがSCSS言語で、階層構造の表現方法がわかりやすくCSS言語との互換性もあるといった利点から多くの人に利用されています。そのため、この記事でもSCSS言語による実装に焦点を当てて説明していきます。

SCSS言語で実装されたファイルは、CSSファイルを生成するためのソースファイルとして利用します。SCSSで作成したスタイルシートを利用したい場合には、SCSSファイルをコンパイルした結果得られるCSSファイルを読み込むという形で利用します。

## Sassを使うと何が良いのか

CSS言語によるWebデザイン実装には2つの難しさがあると考えています。

1つ目は「再利用性の低さ」です。
CSS言語はWebサイトのデザインには欠かせない言語であり、かつ学習コストもかなり小さいため、非常に多くの人が触れたことのある言語だと思います。一方で、CSS言語は文法等が単純な割に最も保守管理が困難なプログラミング言語の1つであるように思います（個人の感想です）。

一般的なプログラミング言語では、変数・関数宣言の仕組みが標準で備わっています。同一の値や処理ブロックを複数箇所で利用したい場合には変数や関数を定義しておくというのはプログラミングの定石です。また、値の意味を明示するために変数を利用するということもあるでしょう[^1]。

しかし、CSS言語には限定的な機能しか実装されていません。CSS言語にも変数宣言の仕組みはありますが、格納できる値の種類はCSSプロパティ値のみとなっています。また、関数宣言のような複数の処理をブロックにまとめて再利用する仕組みはありません[^2]。ほとんど同一の記述を複数箇所に置くことになるため、可読性や保守性が低くなる傾向にあります。

> [参考] CSSカスタムプロパティ
> - [CSS カスタムプロパティ (変数) の使用 - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/Using_CSS_custom_properties#%E5%9F%BA%E6%9C%AC%E7%9A%84%E3%81%AA%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95)
> *カスタムプロパティの宣言は、ハイフン2つ (--) で始まるカスタムプロパティ名と、何らかの有効なCSS値になるプロパティ値を使用することで行われます。*

2つ目は「可読性の低さ」です。
Webサイトのデザインでは、CSSファイルはHTMLファイルにスタイル定義を提供する役割を担っています。スタイルの適用先であるHTMLファイルは階層的なドキュメントツリーを構成していることはご存知の通りですが、それにも関わらずCSSファイルは階層的なスタイル定義（入れ子構造を含むスタイル定義）を行うことはできません。もう少し詳細に言えば、CSS言語ではスタイル定義の際にセレクタを階層的に指定することは許されていません。

階層的なスタイル定義を行う場合には、階層を構成するすべてのセレクタをすべて並べて要素群を指定する必要があります。例えば `#header` ID要素配下の　`.logo` クラス要素に対するスタイル定義を行う場合には、以下のようにセレクタを記述します。

```css
/* `#header` ID要素配下の　`.logo` クラス要素 */
#header .logo {
  /* スタイル定義 */
}
```

この記法では、HTMLの階層構造が深くなるにつれてセレクタ指定が大変になることが予想されます。あえてHTMLの階層構造を崩してスタイル定義を行うことになるため、可読性も高くありません。

ちなみに、CSSのセレクタでは空白1つで意味が大きく変わります。例えば上記のセレクタ指定と下記のセレクタ指定はどちらも妥当なCSSの記法ですが、意味が全く異なります。空白1つで階層を意味するCSSのセレクタが直感的かどうかは意見が分かれるような気がします。

```css
/* `#header` ID要素かつ　`.logo` クラス要素 */
#header.logo {
  /* スタイル定義 */
}
```

こうした課題を解決するために、これまでに様々なCSS設計方法が提案されています。有名なものをいくつか挙げると、 **BEM (Block Element Modifier)** や **OOCSS (Object-Oriented CSS)** などがあります。こうしたCSS設計手法をプロジェクトに導入することで可読性や保守性の向上を期待することはできますが、それらを厳密に運用することは実践的に大変です。

> [参考] CSS設計手法
> - [CSS の整理 - ウェブ開発を学ぶ | MDN](https://developer.mozilla.org/ja/docs/Learn/CSS/Building_blocks/Organizing#%E3%81%9D%E3%81%AE%E4%BB%96%E3%81%AE%E5%BD%B9%E7%AB%8B%E3%81%A4%E3%83%84%E3%83%BC%E3%83%AB)

**Sass(SASS/SCSS)** はCSSの可読性や再利用性を向上するより良いアプローチの1つです。

Sassでは変数・関数宣言やミックスインを利用することができるため、スタイル定義ブロックの再利用性を高めることができます。ほとんど同一の記述を繰り返すことも少なくなるため、コード全体の記述量を減らすことができ、可読性を高めることができます。
また、Sassでは階層的なスタイル定義を行うことが可能なため、より直感的にコーディングすることができます。階層的なセレクタ指定を行う場合でも、CSS言語のような冗長な記述を避けることができるため、コーディングの効率向上も期待することができます。

# 方法

:::note info
サンプルコードをGitHubで公開しています。記事と併せてご覧ください。
**[/website/website-dev-on-webpack5/scss-on-webpack5](https://github.com/Sharkkii/sample-codes/tree/develop/website/website-dev-on-webpack5/scss-on-webpack5)**
:::

:::note info
実装例の説明では、ファイル/フォルダパスを *ルート相対パス* の形式で記述しています。ここでルートはプロジェクトルートまたはWebサイトのドキュメントルートに相当します。
:::

## 準備

WebpackおよびSCSS等のnpmパッケージの利用には [Node.js](https://nodejs.org/ja/) が必要です。
インストールが未実施の場合には、以下のリンクからインストールしておいてください。

https://nodejs.org/ja/

また、動作確認を行うためのNode.jsプロジェクトを作成しておきます。
適当な名前のディレクトリ（ここでは `my-scss-project` ）を用意した上で、そのディレクトリにおいて `npm init` コマンドを実行することでNode.jsプロジェクトを新規作成することができます。

```shell:ターミナル
# my-scss-projectディレクトリの作成
mkdir /path/to/my-scss-project
cd /path/to/my-scss-project

# Node.jsプロジェクトの作成
npm init
```

`npm init` コマンドを実行するとプロジェクトに関する情報を入力するよう促されますが、今回は適当な設定にしておいて問題ありません。今後必要に応じて適宜変更しましょう。
コマンド実行完了後、 `/package.json` という設定ファイルが新規作成されていることを確認したら事前準備完了です。

## 手順概要

1. [npmパッケージを導入する](#手順1-npmパッケージを導入する)
SCSSからCSSを生成するのに必要なnpmパッケージ　`webpack` `webpack-cli` `sass` `sass-loader` `css-loader` `mini-css-extract-plugin` をプロジェクトに追加します。

2. [Webpackの設定ファイルを記述する](#手順2-webpackの設定ファイルを記述する)
SCSSからCSSを生成する手順やルールを設定ファイル `/webpack.config.js` に記述します。

3. [SCSSファイルを作成する](#手順3-scssファイルを作成する)
CSSファイルを生成するSCSSファイル `/src/source.scss` を作成します。

4. [SCSSファイルをコンパイルしてCSSファイルを生成する](#手順4-scssファイルをコンパイルしてcssファイルを生成する)
Webpackを実行することでSCSSファイルのコンパイルを実行し、CSSファイル `/dist/target.css` を生成します。

<!--
4. [HTMLファイルを作成する](#手順4-htmlファイルを作成する)
動作確認用のHTMLファイル `/src/source.html` を作成します。
-->

## 手順詳細

### [手順1] npmパッケージを導入する

今回はWebpackを利用してSCSSファイルのコンパイルを行います。
まず初めに、Webpackを利用するのに必要な　`webpack` `webpack-cli` パッケージをインストールします。
Webpack5（version5.X.X）をインストールするため、バージョン指定を行っています。

```shell:ターミナル
npm install --save-dev webpack@5 webpack-cli@5
```

https://webpack.js.org/

次に、SCSSをコンパイルするのに必要な `sass` `sass-loader` `css-loader` `mini-css-extract-plugin`　パッケージをインストールします。

```shell:ターミナル
npm install --save-dev sass sass-loader css-loader mini-css-extract-plugin
```

https://www.npmjs.com/package/sass

https://www.npmjs.com/package/sass-loader

https://www.npmjs.com/package/css-loader

https://www.npmjs.com/package/mini-css-extract-plugin

### [手順2] Webpackの設定ファイルを記述する

Webpackは、適切に設定を記述することで様々な処理を実行することができます。
ここではSCSSファイル `/src/source.scss` をコンパイルして `/dist/target.css` を生成するための必要最小限のWebpack設定を以下に示します。

```js:/webpack.config.js
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = function() {
  return {

    mode: "development",

    entry: {
      /**
       * /src/source.scss -> OUTPUT_DIRECTORY/target.css
       */
      target: path.resolve(__dirname, "src", "source.scss"),
    },

    output: {
      /**
       * OUTPUT_DIRECTORY = /dist
       */
      path: path.resolve(__dirname, "dist"),
    },

    module: {
      rules: [
        {
          test: /\.scss$/,
          use: [
            /**
             * SassLoader -> CssLoader -> MiniCssExtractPluginLoader
             */
            MiniCssExtractPlugin.loader,
            "css-loader",
            "sass-loader",
          ],
        }
      ]
    },
    
    plugins: [
      new MiniCssExtractPlugin(),
    ],

  }
}
```

<!-- SCSS→CSS変換に関する設定で特に注目すべきポイントは以下の2点です。 -->

上記設定で特に注目すべきポイントは以下の2点です。

1) **Webpackのエントリ設定箇所に、入力SCSSファイル/出力CSSファイルのパスを指定する**
2) **Webpackのルール設定箇所に、拡張子 `*.scss` のファイルに対して3つのローダー `sass-loader` `css-loader` `MiniCssExtractPlugin.loader `を適用するというルールを記述する**

上記のWebpack設定には同シリーズ別記事 [「ちょっとがんばるWebサイト開発 on Webpack5 - CSSをコピー＆ペーストする」](https://qiita.com/Sharkkii/items/7903c117dfa99042239e) と共通する部分が多数あります。共通部分に関する説明として、同シリーズ別記事の説明を一部改変して引用したものを載せておきます。

> 1つ目のエントリ設定は、ソースCSSファイルをWebpackのビルド対象とするために必要なものです。
Webpackは、原則としてエントリ設定で指定されたファイル群とそれが依存するファイル群をビルド対象とし、そのファイル群に対して各種最適化/変換処理を行います。
`entry: { target: path.resolve(__dirname, "src", "source.scss") }` という設定を記述することで、ソースSCSSファイル `/src/source.scss` をWebpackのビルドプロセスに入力することができ、各種最適化/変換処理の結果を配信用ディレクトリに出力することができます。
>
> 2つ目のルール設定は、CSSファイルをコピー＆ペーストするために必要なものです。
Webpackは、ルール設定で指定された内容に従って最適化/変換処理等を行います。
`{ test: /\.scss$/ }` および　`{  use: [ MiniCssExtractPlugin.loader, "css-loader", "sass-loader" ] }` という設定を記述することで、拡張子 `*.scss` のファイルに対して `sass-loader` の変換処理、 `css-loader` の変換処理、 `MiniCssExtractPlugin.loader` の変換処理をこの順で適用することができます。

同シリーズ別記事 [「ちょっとがんばるWebサイト開発 on Webpack5 - CSSをコピー＆ペーストする」](https://qiita.com/Sharkkii/items/7903c117dfa99042239e) では、2つのプラグイン　`css-loader`　`MiniCssExtractPlugin.loader` を利用してCSSファイルを処理していました。この2つに加えて　`sass-loader` プラグインを利用することで、SCSSファイルを処理することができるようになります。
ここで `sass-loader` は、SCSSスタイルをCSSスタイルに変換し、出力結果を　`css-loader` に渡す役割を果たしています。

> [参考] Webpack設定
> - [https://webpack.js.org/concepts/#entry](https://webpack.js.org/concepts/#entry)
> - [https://webpack.js.org/configuration/module/#modulerules](https://webpack.js.org/configuration/module/#modulerules)
>
> [参考] sass-loader / css-loader / mini-css-webpack-plugin
> - [sass-loader - npm](https://www.npmjs.com/package/sass-loader)
> - [css-loader - npm](https://www.npmjs.com/package/css-loader)
> - [mini-css-extract-plugin - npm](https://www.npmjs.com/package/mini-css-extract-plugin)

<!--
:::note info
Webpackはプロジェクトに含まれる多数のアセットを1つ以上のJavaScriptモジュールにまとめるという仕事をしています。CSS等のスタイルファイルを利用する場合、Webpackが内部で `style-loader` ローダー等を用いてJavaScriptプログラムに変換した上で1つ以上のJavaScriptモジュールに統合するため、CSSファイルが出力されることはありません。単体のCSSファイルが必要な場合には、上記のように `mini-css-extract-plugin` 等のローダやプラグインを使います。
:::
-->

<!--
HTMLファイルのコピーに関する設定で注目すべきポイントは `plugin` 設定です。ここでは 　`html-webpack-plugin` プラグインを利用してHTMLファイルを `/src/source.html` から `/dist/target.html` にコピーするようにしています。 

:::note warn
`HtmlWebpackPlugin` の定義の際に `inject` というオプションを指定していますが、これは「Webpackによって生成されたモジュール群をHTMLファイルに自動的に追加するか」を制御するオプションになっています。ここではHTML/CSSの連携は手動で管理することにしているので、 `{ inject: false }` を指定しています。

> [参考] HtmlWebpackPluginのオプション
> [https://github.com/jantimon/html-webpack-plugin#options](https://github.com/jantimon/html-webpack-plugin#options)
:::

:::note info
[*執筆中の記事*](#) において　`html-webpack-plugin` のより高度な使い方（EJSファイルをHTMLテンプレートとして利用する方法）を紹介しているので、併せてご覧ください。
:::
-->

### [手順3] SCSSファイルを作成する

手順3では、コンパイル対象となるSCSSファイルを作成します。
上記設定ファイル `/webpack.config.js` では入力SCSSファイルパスを `/src/source.scss` に指定したので、その位置に `source.scss` ファイルを作成しましょう。
ファイル内容はどんなものでも構いませんが、ここでは例として以下のように記述することにします（折りたたみ要素の中にあります）。

<details>
<summary> /src/source.scss の入力内容例</summary>

```scss:/src/source.scss
$color-red: red;
$color-green: green;

#main {
  h1 {
    color: $color-red;
  }
  p {
    color: $color-green;
  }
}
```
</details>

<!--
### [手順4] HTMLファイルを作成する

手順4では、手順3で実装したSCSSの動作を確認するためのHTMLファイルを作成します。
上記設定ファイル `/webpack.config.js` では入力HTMLファイルパスを　`/src/source.html` に指定したので、その位置に `source.html` ファイルを作成しましょう。
ファイル内容はどんなものでも構いませんが、ここでは例として以下のように記述することにします（折りたたみ要素の中にあります）。

<details>
<summary> /src/source.html の入力内容例</summary>

```html:/src/source.html
<!doctype html>
<html>
  <head>
    <title>My SCSS Project</title>
    <link rel="stylesheet" href="./target.css">
  </head>
  <body>
    <h1>My SCSS Project</h1>
    <ul id="main">
      <li class="text-in-red"><p>RED</p></li>
      <li class="text-in-green"><p>GREEN</p></li>
      <li class="text-in-blue"><p>BLUE</p></li>
    </ul>
  </body>
</html>
```
</details>
-->

### [手順4] SCSSファイルをコンパイルしてCSSファイルを生成する

手順1〜3でSCSS→CSS変換のための準備が完了しました！
それではWebpackを用いて `/src/source.scss` から `/dist/target.css` を生成してみましょう。
ターミナルで `npx webpack` コマンドを実行することでビルド作業が開始します。

```shell:ターミナル
# ディレクトリ移動
cd /path/to/my-scss-project

# Webpackコマンド実行
npx webpack
```

ビルドが正常に完了すれば、CSSファイル `/dist/target.css` が生成されていることを確認できると思います。ファイルの内容を確認すると、SCSSで記述された部分が展開されてCSSになっているはずです（折りたたみ要素の中にあります）。 `sass-loader` `css-loader` の変換プロセスにおいてコメントが挿入されていることに注意してください。プロジェクトの開発段階ではこの情報をデバッグ等に利用することができます。

<details>
<summary>　/dist/target.css の出力内容例</summary>

```css:/dist/target.css
/*!**********************************************************************************************************!*\
  !*** css ./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/source.scss ***!
  \**********************************************************************************************************/
#main h1 {
  color: red;
}
#main p {
  color: green;
}
```
</details>

<!--
また、HTMLファイル `/dist/target.html` が生成されたCSSファイル `/dist/target.css` を正常に読み込むことができているかをWebブラウザ等で確認しておきましょう。上記例のコードを正常に読み込むことができていれば、 `h1` 要素のフォントサイズおよび `p` 要素のフォントサイズ・文字色が変化しているはずです。
-->

:::note info
上記のWebpack設定ではSCSS→CSS変換の際に不必要なJavaScriptファイル `/dist/target.js` が生成されるようになっています。不必要なJavaScriptモジュールを削除したい場合には `WebpackRemoveEmptyScripts` プラグイン等を利用すると良いでしょう。この記事では詳細な使用方法については省略します。

> [参考] WebpackRemoveEmptyScripts プラグイン
> [https://github.com/webdiscus/webpack-remove-empty-scripts](https://github.com/webdiscus/webpack-remove-empty-scripts)
:::

# 後書き

**サンプルコード**
**[/website/website-dev-on-webpack5/scss-on-webpack5](https://github.com/Sharkkii/tech-blog-code/tree/develop/website/website-dev-on-webpack5/scss-on-webpack5)**

[^1]: マジックナンバーをなくすことでプログラムの可読性や保守性が高まると言われています。特にCSS言語では属性と値の組を大量に取り扱うため、その値の意味を把握しやすくすることは非常に重要です。
[^2]: 一般的なプログラミング言語の「処理ブロック」は、CSSにおいてはスタイル定義の集合に相当すると考えることができます。CSS言語でも `@import` 文を利用することでファイル単位の処理ブロックを扱うことはできますが、一般的なプログラミング言語の「関数」と比較するとその再利用性はあまり高くありません。