Original cover photo by Carl Raw on Unsplash.
Nx 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 and Stylelint.
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".
Adding Stylelint
In this first part, we add Stylelint by following the steps from their Getting started guide.
-
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
-
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:
{ "extends": ["stylelint-config-standard"], "rules": {} }
-
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}"
Add Stylelint editor extension.
Stylelint extensions exist for many code editors. See the full list in Editor integrations. For example, the official Stylelint extension for Visual Studio Code is stylelint.vscode-stylelint.
Configuring Stylelint rules
-
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
:
{ "extends": [ "stylelint-config-standard", "stylelint-config-sass-guidelines" ], "rules": {} }
-
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 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
:
{ "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. -
Customize Stylelint configuration.
We should customize lint rules to our liking. For example, let's adjust our configuration to these preferences in.stylelintrc
:- Set the max line length to 80 characters.
- Limit allowed selector characters to lower case letters, digits in addition to the hyphen (
-
) and underscore (_
) characters. - Use single quotes (
'
).
{ "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
-
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 calledbooking-app
which has a feature library with the project namebooking-feature-flight-search
. Our global styles are in a project calledbooking-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:
{ "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 bepnpx
in thecommand
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' } };"
-
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:
{ "tasksRunnerOptions": { "default": { "runner": "@nrwl/workspace/tasks-runners/default", "options": { "cacheableOperations": [ "build", "lint", "test", "e2e", "lint-styles" // 👈 ] } } } }
-
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
-
Add lint styles scripts.
To support our workflow, we addlint-styles
andaffected:lint-styles
scripts topackage.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 likenx 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
andaffected:lint-styles
scripts.
These are all known as Nx plugins.
Let me know if you accept this challenge.
Top comments (9)
Thanks for this article! It helped me out a lot, but I did want to point out your regex expecting your selectors to be "some-thing" rather than expecting a dash OR an underscore. I ended up doing ^([a-z][a-z0-9]*)((-|)[a-z0-9]+)*$.
Thanks, CJ 👍
I wrote a nx plugin which provides this features.
Please check it out: github.com/Phillip9587/nx-stylelint
Nx Stylelint provides a set of power ups on Nx to lint your projects with Stylelint.
Very nice, Phillip! 👏
nx run-many --target=lint-styles --all runs with no errors. But, if I run npx lint-staged as part of husky pre-commit hook, it gives lint style errors. Have you made list-staged work with the same rules and config as stylelint?
Thank you for your article! It saved me a lot of time!
You can use stylelint cache,
to improve stylelint's speed. 😄
Thanks for your feedback and for the caching tip
I took your challenge and wrote a simple Stylelint executor. I’ll try to add an init generator when I find some time.
Thanks for another great write-up.
Thanks for letting me know, Frederik. You're very welcome to submit a PR to nxworker for these features.