Incremental stylesheet linting with Stylelint and the Nx toolchain
Tags: nx, stylelint, angular, react
Published: 2020-12-17 22:22:38

---
title: Incremental stylesheet linting with Stylelint and the Nx toolchain
published: true
description: Add and configure Stylelint. Then automate Stylelint workflows in your Nx workspace.
tags: nx, stylelint, angular, react
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/2k8v3n5p41u0x7ppl4fv.jpg
---

*Original cover photo by [Carl Raw](https://unsplash.com/photos/qvY5qqOOFH4) on Unsplash.*

[Nx](https://nx.dev) integrates ESLint to our web projects out-of-the-box, but how about linting stylesheets? There are a few stylesheet linters out there, the major ones being [scss-lint](https://github.com/sds/scss-lint) and [Stylelint](https://stylelint.io/).

In this guide, we set `lint-styles` targets using Stylelint for a few projects in an Nx workspace. This way we can lint the styles of one, multiple, or all projects in an automated way and take advantage of Nx computation caching in Nx Cloud and/or locally,

The example workspace can be generated by following the instructions for setting up a new Nx 11 workspace using the empty workspace preset in "[The ultimate migration guide to angular-eslint, ESLint and Nx 11](https://dev.to/this-is-angular/the-ultimate-migration-guide-to-angular-eslint-eslint-and-nx-11-1eh2#option-1-use-the-empty-workspace-preset)".

# Adding Stylelint
In this first part, we add Stylelint by following the steps from their [Getting started guide](https://stylelint.io/user-guide/get-started).

1. Install Stylelint and the standard configuration.
    First, install Stylelint and the Stylelint standard configuration.

    Using NPM CLI:
    ```
    npm install --save-dev stylelint stylelint-config-standard
    ```

    Using PNPM CLI:
    ```
    pnpm add --save-dev stylelint stylelint-config-standard
    ```

    Using Yarn CLI:
    ```
    yarn add --dev stylelint stylelint-config-standard
    ```

1. Create Stylelint configuration.
    Next, we create a Stylelint configuration at the root of our workspace.

    Create the file `<nx-workspace-root>/.stylelintrc` with the following content:
    ```json
    {
      "extends": ["stylelint-config-standard"],
      "rules": {}
    }
    ```

1. Try Stylelint.
    To verify that our Stylelint configuration works, we run a Stylelint CLI command from our workspace root:
    
    Using NPM CLI:
    ```
    npx stylelint "{apps,libs}/**/*.{css,less,sass,scss,sss}"
    ```

    Using PNPM CLI:
    ```
    pnpx stylelint "{apps,libs}/**/*.{css,less,sass,scss,sss}"
    ```

    Using Yarn CLI:
    ```
    npx stylelint "{apps,libs}/**/*.{css,less,sass,scss,sss}"
    ```

1. Add Stylelint editor extension.
   Stylelint extensions exist for many code editors. See the full list in [Editor integrations](https://stylelint.io/user-guide/integrations/editor). For example, the official Stylelint extension for Visual Studio Code is [stylelint.vscode-stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint).

# Configuring Stylelint rules
1. Add Sass Guidelines configuration.
    The Stylelint standard configuration is a good, general purpose lint rule configuration, but in this guide we'll use SCSS.

    Install the Sass Guidelines configuration for Stylelint.

    Using NPM CLI:
    ```
    npm install --save-dev stylelint-config-sass-guidelines
    ```

    Using PNPM CLI:
    ```
    pnpm add --save-dev stylelint-config-sass-guidelines
    ```

    Using Yarn CLI:
    ```
    yarn add --dev stylelint-config-sass-guidelines
    ```

    Now, we add the Sass Guidelines rule configuration to our configuration in `.stylelintrc`:
    ```json
    {
      "extends": [
        "stylelint-config-standard",
        "stylelint-config-sass-guidelines"
      ],
      "rules": {}
    }
    ```

1. Use Idiomatic CSS ordering.
    If you're an experiened visual frontend developer, you might agree that ordering of CSS properties matter. In this step, we configure Stylelint to follow the [Idiomatic CSS](https://github.com/necolas/idiomatic-css#declaration-order) conventions.

    First, we install the `stylelint-config-idiomatic-order` configuration.

    Using NPM CLI:
    ```
    npm install --save-dev stylelint-config-idiomatic-order
    ```

    Using PNPM CLI:
    ```
    pnpm add --save-dev stylelint-config-idiomatic-order
    ```

    Using Yarn CLI:
    ```
    yarn add --dev stylelint-config-idiomatic-order
    ```

    Next, we add it to our Stylelint configuration in `.stylelintrc`:
    ```json
    {
      "extends": [
        "stylelint-config-standard",
        "stylelint-config-sass-guidelines",
        "stylelint-config-idiomatic-order"
      ],
      "rules": {
        "order/properties-alphabetical-order": null
      }
    }
    ```

    >Note that we have to disabled the rule `order/properties-alphabetical-order` as the Sass guidelines configuration enables it, but it conflicts with the order specified by Idiomatic CSS and enabled by the Idiomatic order configuration.

1. Customize Stylelint configuration.
    We should customize lint rules to our liking. For example, let's adjust our configuration to these preferences in `.stylelintrc`:
      1. Set the max line length to 80 characters.
      1. Limit allowed selector characters to lower case letters, digits in addition to the hyphen (`-`) and underscore (`_`) characters.
      1. Use single quotes (`'`).

    ```json
    {
      "extends": [
        "stylelint-config-standard",
        "stylelint-config-sass-guidelines",
        "stylelint-config-idiomatic-order"
      ],
      "rules": {
        "max-line-length": 80,
        "order/properties-alphabetical-order": null,
        "selector-class-pattern": "^([a-z][a-z0-9]*)(-_[a-z0-9]+)*$",
        "string-quotes": "single"
      }
    }
    ```

    Make sure that our configuration still works by running The Stylelint CLI locally.

    Using NPM CLI:
    ```
    npx stylelint "{apps,libs}/**/*.{css,less,sass,scss,sss}"
    ```

    Using PNPM:
    ```
    pnpx stylelint "{apps,libs}/**/*.{css,less,sass,scss,sss}"
    ```

    Using Yarn CLI:
    ```
    npx stylelint "{apps,libs}/**/*.{css,less,sass,scss,sss}"
    ```

# Automating our Stylelint workflow using Nx
1. Add `lint-styles` targets to projects.
    Now it's time to automate linting of our styles. Let's say that we have an application project called `booking-app` which has a feature library with the project name `booking-feature-flight-search`. Our global styles are in a project called `booking-common-styles`.

    Let's first create an execution target for the global styles.

    Using NPM CLI:
    ```
    npx json -I -f workspace.json -e "this.projects['booking-common-styles'].targets['lint-styles'] = { executor: '@nrwl/workspace:run-commands', options: { command: 'npx stylelint libs/booking/common/styles/src/**/*.scss' } };"
    ```

    Using PNPM CLI:
    ```
    npx json -I -f workspace.json -e "this.projects['booking-common-styles'].targets['lint-styles'] = { executor: '@nrwl/workspace:run-commands', options: { command: 'pnpx stylelint libs/booking/common/styles/src/**/*.scss' } };"
    ```

    Using Yarn CLI:
    ```
    npx json -I -f workspace.json -e "this.projects['booking-common-styles'].targets['lint-styles'] = { executor: '@nrwl/workspace:run-commands', options: { command: 'npx stylelint libs/booking/common/styles/src/**/*.scss' } };"
    ```

    Our workspace configuration (`workspace.json`) now has this project configuration:
    ```json
    {
      "version": 2,
      "projects": {
        "booking-common-styles": {
          "projectType": "library",
          "root": "libs/booking/common/styles",
          "sourceRoot": "libs/booking/common/styles/src",
          "targets": {
            "lint-styles": {
              "executor": "@nrwl/workspace:run-commands",
              "options": {
                "command": "npx stylelint libs/booking/common/styles/src/**/*.scss"
              }
            }
          }
        }
      }
    }
    ```
    Note: `npx` should be `pnpx` in the `command` option if we're using PNPM CLI.

    We use the `run-commands` executor from the `@nrwl/workspace` package to run Stylelint CLI commands.

    Using NPM CLI:
    ```
    npx stylelint libs/booking/common/styles/src/**/*.scss
    ```

    Using NPM CLI:
    ```
    pnpx stylelint libs/booking/common/styles/src/**/*.scss
    ```

    Using Yarn CLI:
    ```
    npx stylelint libs/booking/common/styles/src/**/*.scss
    ```

    This command runs Stylelint for all `scss` files in our common booking styles workspace library.

    We can use the same script to add a `lint-styles` execution target to our feature library project.

    Using NPM CLI:
    ```
    npx json -I -f workspace.json -e "this.projects['booking-feature-flight-search'].targets['lint-styles'] = { executor: '@nrwl/workspace:run-commands', options: { command: 'npx stylelint libs/booking/feature-flight-search/src/**/*.scss' } };"
    ```

    Using PNPM CLI:
    ```
    npx json -I -f workspace.json -e "this.projects['booking-feature-flight-search'].targets['lint-styles'] = { executor: '@nrwl/workspace:run-commands', options: { command: 'pnpx stylelint libs/booking/feature-flight-search/src/**/*.scss' } };"
    ```

    Using Yarn CLI:
    ```
    npx json -I -f workspace.json -e "this.projects['booking-feature-flight-search'].targets['lint-styles'] = { executor: '@nrwl/workspace:run-commands', options: { command: 'npx stylelint libs/booking/feature-flight-search/src/**/*.scss' } };"
    ```

1. Add computation caching.
    A great feature of the Nx toolchain is that computation caching can speed up our development workflow by hours and hours saved per month.

    Let's instruct Nx to cache computation results of `lint-styles` targets using this command:
    ```
    npx json -I -f nx.json -e "this.tasksRunnerOptions.default.options.cacheableOperations = [...this.tasksRunnerOptions.default.options.cacheableOperations, 'lint-styles'];"
    ```

    Our Nx configuration (`nx.json`) should now have something like these settings:
    ```json
    {
      "tasksRunnerOptions": {
        "default": {
          "runner": "@nrwl/workspace/tasks-runners/default",
          "options": {
            "cacheableOperations": [
              "build",
              "lint",
              "test",
              "e2e",
              "lint-styles" // 👈
            ]
          }
        }
      }
    }
    ```

1. Execute `lint-styles` targets.
    Now we can lint styles for one project:
    ```
    nx run booking-common-styles:lint-styles
    ```

    we can lint styles for multiple projects:
    ```
    nx run-many --target=lint-styles --projects=booking-common-styles,booking-feature-flight-search
    ```

    we can lint all styles:
    ```
    nx run-many --target=lint-styles --all
    ```

    after adjusting our styles we can rerun projects that failed style linting:
    ```
    nx run-many --target=lint-styles --all --only-failed
    ```

    or we can even lint project styles in parallel:
    ```
    nx run-many --target=lint-styles --all --parallel
    ```
    The output sometimes gets scrambled, so this should be followed by the `--only-failed` command from above.

    A failed run looks something like this:
    ```
    nx run-many --target=lint-styles --all
    
    >  NX  Running target lint-styles for projects:
    
      - booking-common-styles
      - booking-feature-flight-search
    
    ———————————————————————————————————————————————
    
    > nx run booking-common-styles:lint-styles 
    
    libs/booking/common/styles/src/lib/generic/_document.scss
     8:3  ×  Unexpected empty line before declaration   declaration-empty-line-before

    ERROR: Something went wrong in @nrwl/run-commands - Command failed: npx stylelint libs/booking/common/styles/src/**/*.scss

    ———————————————————————————————————————————————

    >  NX   ERROR  Running target "lint-styles" failed

      Failed projects:

      - booking-common-styles
    ```

1. Add lint styles scripts.
    To support our workflow, we add `lint-styles` and `affected:lint-styles` scripts to `package.json`:
    ```json
    {
      "scripts": {
        "affected:lint-styles": "nx affected --target=lint-styles",
        "lint-styles": "nx run-many --target=lint-styles --all"
      }
    }
    ```

    >Note that the affected command is based on implicit dependencies configured in `nx.json` as well as dependencies detected because of EcmaScript imports.

# Conclusion
This guide demonstrates how easy it is to integrate and automate tools with the Nx toolchain.

First, we added Stylelint and a few style lint configurations for demonstration purposes. We also customized the style lint rules to our liking.

Next, we added `lint-styles` targets to our frontend projects by using the `@nrwl/workspace:run-commands` executor.

Finally, we enabled computation caching and added scripts for linting all styles and affected styles.

This is all we need for both local development and for a deployment pipeline. In addition, everything in this guide applies to all frontend projects, whether they use Angular, React, Svelte, Vue, other frameworks, or vanilla.

## Caveats
Keep in mind that we need styles in separate files for Stylelint to work. This means that we can't use inline styles in for example Angular components.

The `affected` command is based on implicit dependencies configured in `nx.json` as well as dependencies detected because of EcmaScript imports. This means that `affected:lint-styles` might not always be accurate.

## Next steps
The next natural steps enabled by the Nx toolchain are to:
- Create a Stylelint executor instead of using the `run-commands` executor.
- Create a Stylelint `init` generator enabling something like `nx add <package-name>`.
- Extending a project generator with an option to add a `lint-styles` target.
- A generator to add a `lint-styles` target to a specific project.
- Create a generator which adds `lint-styles` and `affected:lint-styles` scripts.

 These are all known as Nx plugins.

Let me know if you accept this challenge.