iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🛠️

Compiling Sass with npm scripts (Dart Sass Shared Management, Stylelint, and PostCSS)

に公開

Requirements

  • Target Sass files in the src directory for compilation and output to the htdocs directory.
  • Apply PostCSS to the CSS files in the htdocs directory, and execute optimization with cssnano only for production builds.
  • Use Dart Sass and share variables and functions so they can be used with @use.
  • Run Stylelint (with auto-fix) every time a Sass file is saved.

Installing Packages

Install the necessary packages related to CSS.

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

Install libraries to manage reset CSS and media queries (optional).

npm i normalize.css sass-mq

Also install npm-run-all, which allows for concise cross-platform descriptions, and onchange, which provides file watching functionality.

npm i -D npm-run-all onchange

Directory Structure

The project is configured with the following directory structure.

.
├── 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: Site-wide functions
    • variable: Site-wide variables
    • mixin: Site-wide mixins
    • _base.scss: Site-wide default styles
  • components: Styles for components converted into mixins
  • namespace: Site-wide styles or styles per category

Setting up Dart Sass

site.scss

In site.scss, we use @use to load the actual declarations to be output, excluding variables and functions.

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

namespace/common

namespace/common/_index.scss forwards site-wide styles as follows:

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

For example, _Delimiter.scss assigns class names starting with .common- as follows:

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

For a product page, you would create something like _List.scss in namespace/product/ and assign class names like .product-List.

global

global/_index.scss loads variables, functions, and libraries as follows:

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

For function, variable, and mixin, each file is imported via @forward in their respective _index.scss files.

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";

The path ../../../../node_modules/sass-mq/mq imports "sass-mq" directly from node_modules.

_mq.scss is configured as follows:

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

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

components

components/_index.scss loads styles for components as follows:

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

For example, you can define default styles in components/_Button.scss like this:

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;
  }
}

Using global and components with @use

Here is an example of using them in namespace/common/_Button.scss.

  • Refer to global as g and components as c using @use.
  • Include the button component with @include c.Button;.
  • Convert px to rem using site-wide functions, such as padding-top: g.rem(14);.
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);
}

This concludes the Dart Sass configuration.

Setting up Stylelint

Create .stylelintrc.

touch .stylelintrc

I have configured a minimal setup as follows:

.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
  }
}
  • Since Stylelint v14, a configuration file with SCSS extensions is required, so I'm using "stylelint-config-standard-scss" (you can use Sass just by adding settings to .stylelintrc, but I chose this for better clarity).
  • Since Stylelint v15, formatting-related rules have been deprecated, so I've removed the settings listed in the Deprecated list.
  • The rules are as follows:
    • selector-class-pattern: Since class names other than kebab-case cause errors, I've set it to null to remove restrictions (you could configure this in detail using regular expressions depending on the project).
    • scss/selector-no-union-class-name: Setting this to true prohibits generating new class names like &_Element.
    • scss/at-mixin-pattern: Since mixin names other than kebab-case cause errors, I've set it to null to remove restrictions (regular expressions are also available for project-specific configurations).

Setting up PostCSS

Create postcss.config.js.

touch postcss.config.js

I have configured a minimal setup as follows:

postcss.config.js
module.exports = (ctx) => {
  return {
    plugins: {
      autoprefixer: {},
      cssnano: ctx.env === "production" ? {} : false,
    },
  };
};
  • autoprefixer: Executes with default values.
  • cssnano: Executes if NODE_ENV=production is passed via npm scripts; otherwise (e.g., development), it does not execute.

Setting up npm scripts

Here are the parts specifically related to 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 is for development builds, and build is for production builds.
  • run-s runs commands sequentially, and the -c option ensures that processing continues to the end even if an error occurs.
  • Using run-s -c css:* runs scripts in scripts starting with css: in order (in this case: stylelint → sass → postcss).
  • stylelint \"src/**/*.scss\" --fix targets all Sass files and forces automatic fixes.
  • sass src/assets/css/site.scss htdocs/assets/css/site.css specifies the source file for compilation and the output destination.
  • postcss htdocs/assets/css/site.css -o htdocs/assets/css/site.css specifies the source file and the output destination (-o).
  • The onchange \"src/**/*.scss\" -- npm run dev:css part monitors Sass file saves and starts the sequence of processes whenever a file is overwritten.

Discussion