🛠️

npm scriptsでSassをコンパイルする(Dart Sassと共通管理、StyleLint、PostCSS)

2023/10/02に公開

要件

  • srcディレクトリにあるSassファイルを対象にコンパイルしてhtdocsディレクトリに出力する
  • PostCSSをhtdocsディレクトリ内のCSSファイルに適用し、cssnanoによる最適化はproductionビルドのみで実行する
  • Dart Sassを使用し、変数や関数を共通化して@useで使えるようにする
  • Sassファイルを保存するたびにStylelint(自動修正付き)を実行する

パッケージをインストールする

CSS関連の必要なパッケージをインストールします。

npm i -D autoprefixer cssnano postcss postcss-cli prettier sass stylelint stylelint-config-standard-scss stylelint-prettier stylelint-scss

リセットCSSとメディアクエリを管理するライブラリをインストールします(任意)。

npm i normalize.css sass-mq

クロスプラットフォームに対応して簡潔に記述できるnpm-run-allとファイルの監視機能があるonchangeもインストールします。

npm i -D npm-run-all onchange

ディレクトリ構造

以下のディレクトリ構造で構成しています。

.
├── postcss.config.js
└── src
    └── assets
        └── css
            ├── components
            │   ├── _Button.scss
            │   ├── _LinkText.scss
            │   └── _index.scss
            ├── global
            │   ├── _base.scss
            │   ├── _index.scss
            │   ├── function
            │   │   ├── _index.scss
            │   │   ├── _rem.scss
            │   │   └── _strip-unit.scss
            │   ├── mixin
            │   │   ├── _hack.scss
            │   │   ├── _index.scss
            │   │   └── _sr-only.scss
            │   └── variable
            │       ├── _easing.scss
            │       ├── _global.scss
            │       ├── _index.scss
            │       └── _mq.scss
            ├── namespace
            │   └── common
            │       ├── _Br.scss
            │       ├── _Button.scss
            │       ├── _LinkText.scss
            │       ├── _SrOnly.scss
            │       └── _index.scss
            └── site.scss
  • global
    • function:サイト共通のfunction
    • variable:サイト共通の変数
    • mixin:サイト共通のmixin
    • _base.scss:サイト共通のデフォルトスタイル
  • components:mixin化したコンポーネントのスタイル
  • namespace:サイト共通やカテゴリごとのスタイル

Dart Sassを設定する

site.scss

site.scssには変数や関数を除いた、実際に出力する記述を@useで読み込みます。

site.scss
@charset "UTF-8";
@use "../../../node_modules/normalize.css/normalize";
@use "global/_base";
@use "namespace/common";

namespace/common

namespace/common/_index.scssは以下のようにサイト共通のスタイルを@forwardします。

namespace/common/_index.scss
@forward "_Br";
@forward "_Button";
@forward "_Delimiter";
@forward "_LinkText";
@forward "_SrOnly";

たとえば_Delimiter.scssは以下のように.common-からクラス名を付けます。

namespace/common/_Delimiter.scss
.common-Delimiter {
  display: inline-block;
}

プロダクトページであればnamespace/product/_List.scssなどを作り、.product-Listのようなクラス名を付けていきます。

global

global/_index.scssは以下のように変数と関数、ライブラリを読み込んでいます。

global/_index.scss
@forward "function";
@forward "variable";
@forward "mixin";
@import "../../../../node_modules/sass-mq/mq";

functionvariablemixinともに_index.scss@forwardで各ファイルをインポートしています。

global/function/_index.scss
@forward "_rem";
@forward "_strip-unit";
global/variable/_index.scss
@forward "_global";
@forward "_easing";
@forward "_mq";
global/mixin/_index.scss
@forward "_hack";
@forward "_sr-only";

../../../../node_modules/sass-mq/mqは「sass-mq」をnode_modulesから直接インポートしています。

_mq.scssは以下のようにしています。

_mq.scss
$mq-breakpoints: (
  sm: 375px,
  md: 768px,
  lg: 1024px,
  xl: 1440px,
);

@import "../../../../../node_modules/sass-mq/_mq";

components

components/_index.scssは以下のようにコンポーネント用のスタイルを読み込んでいます。

components/_index.scss
@forward "_Button";
@forward "_LinkText";

たとえばcomponents/_Button.scssで以下のようにデフォルトスタイルを作成しておきます。

components/_Button.scss
@use "../global" as g;

@mixin Button() {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  max-width: 100%;
  margin: 0;
  padding: g.rem(15) g.rem(44) g.rem(14);
  text-align: center;
  text-decoration: none;
  font-family: inherit;
  font-size: g.rem(15);
  line-height: g.div(21, 15);
  border: 1px solid transparent;
  border-radius: g.rem(999);
  background: transparent;
  color: inherit;
  cursor: pointer;
  transition-timing-function: g.$ease;
  transition-duration: g.$transition-duration;
  appearance: none;

  &[type="button"],
  &[type="reset"],
  &[type="submit"] {
    appearance: none;
  }
}

globalとcomponentsを@useで使用する

namespace/common/_Button.scssで使用する例です。

  • @useglobalgとして、componentscとして参照する
  • @include c.Button;としてボタンコンポーネントをインクルードする
  • padding-top: g.rem(14);のようにサイト共通のfunctionでpxからremに変換する
namespace/common/_Button.scss
@use "../../global" as g;
@use "../../components" as c;

.common-Button {
  @include c.Button;
}

.common-Button.-full {
  width: 100%;
  max-width: none;
}

.common-Button.-auto {
  width: auto;
  min-width: auto;
}

.common-Button.-large {
  min-height: g.rem(70);
  padding-top: g.rem(14);
  padding-bottom: g.rem(14);
  font-size: g.rem(17);
  border-width: 2px;
}

.common-Button.-small {
  padding: g.rem(12) g.rem(28) g.rem(11);
  font-size: g.rem(13);
}

Dart Sassの設定は以上です。

Stylelintを設定する

.stylelintrcを作成します。

touch .stylelintrc

以下のように最小構成を設定しています。

.stylelintrc
{
  "extends": [
    "stylelint-config-standard-scss",
    "stylelint-prettier/recommended"
  ],
  "syntax": "scss",
  "plugins": [
    "stylelint-scss",
    "stylelint-prettier"
  ],
  "rules": {
    "prettier/prettier": [
      true,
      {
        "printWidth": 100,
        "tabWidth": 2,
        "useTabs": false,
        "singleQuote": false,
        "trailingComma": "all",
        "bracketSpacing": false
      }
    ],
    "selector-class-pattern": null,
    "scss/selector-no-union-class-name": true,
    "scss/at-mixin-pattern": null
  }
}
  • Stylelintのv14からscss用の拡張を加えたコンフィグファイルが必要になっているので、「stylelint-config-standard-scss」を使用しています(.stylelintrcに設定を追加するだけでもSassが使えるようになりますが、コンフィグファイルのほうがわかりやすいので採用しました)
  • Stylelintのv15からフォーマットに関するルールが非推奨になっているので、Deprecated一覧に記載されている設定を外しています
  • ルールは次の通りです
    • selector-class-pattern:ケバブケース以外のクラス名がエラーになってしまうので、nullで制限をかけないようにしています(正規表現が使えるのでプロジェクトによっては詳細に設定してもいいかもしれません)
    • scss/selector-no-union-class-name&_Elementのように新しいクラス名を生成できてしまうのをtrueで禁止しています
    • scss/at-mixin-pattern:ケバブケース以外のmixin名がエラーになってしまうので、nullで制限をかけないようにしています(正規表現が使えるのでプロジェクトによっては詳細に設定してもいいかもしれません)

PostCSSを設定する

postcss.config.jsを作成します。

touch postcss.config.js

以下のように最小構成を設定しています。

postcss.config.js
module.exports = (ctx) => {
  return {
    plugins: {
      autoprefixer: {},
      cssnano: ctx.env === "production" ? {} : false,
    },
  };
};
  • autoprefixer:初期値で実行します
  • cssnano:npm scriptsでNODE_ENV=productionを渡した場合は実行、developmentなどのそれ以外を渡した場合は実行しないようにします

npm scriptsを設定する

CSSに関連する処理だけを抜き出しています。

package.json
    "start": "run-s -c dev watch",
    "build": "npm-run-all -s clean -p build:*",
    "watch": "run-p watch:*",
    "watch:css": "onchange \"src/**/*.scss\" -- npm run dev:css",
    "dev": "run-p dev:*",
    "dev:css": "NODE_ENV=development run-s -c css:*",
    "build:css": "NODE_ENV=production run-s -c css:*",
    "css:stylelint": "stylelint \"src/**/*.scss\" --fix",
    "css:sass": "sass src/assets/css/site.scss htdocs/assets/css/site.css",
    "css:postcss": "postcss htdocs/assets/css/site.css -o htdocs/assets/css/site.css",
  • devは開発用のビルド、buildは本番用のビルドとしています
  • run-sは順番に実行、-cはエラーが起きても最後の処理まで止まらずに実行するオプションです
  • run-s -c css:*のようにすると、scriptsにあるcss:から始まるスクリプトを上から順番に実行します(今回はstylelint→sass→postcssの順)
  • stylelint \"src/**/*.scss\" --fixのようにすべてのSassファイルを対象に、強制的に自動修正します
  • sass src/assets/css/site.scss htdocs/assets/css/site.cssはコンパイル元ファイルと出力先を指定しています
  • postcss htdocs/assets/css/site.css -o htdocs/assets/css/site.cssはコンパイル元ファイルと出力先(-o)を指定しています
  • onchange \"src/**/*.scss\" -- npm run dev:cssの部分でSassファイルの保存を監視、ファイルが上書きされたら一連の処理を開始します

Discussion