stylelint
stylelint copied to clipboard
Fix config resolution performance
What steps are needed to reproduce the bug?
Run stylelint via the CLI on any medium to large-sized project.
After profiling different operations (did this both by logging, as well as a more scientific profiler like https://clinicjs.org), notice the inordinate amount stylelint spends in my project for just figuring out what configuration to use to lint my files and which files to ignore. Top operations by time —
| Operation | File | % Time taken | What it does |
|---|---|---|---|
| extendConfig | lib/augmentConfig.js | 11.9% | Updates config |
| getPostCssResult | lib/getPostcssResult.js | 10.9% | Parses code to lint |
| lintPostCssResult | lib/lintPostCssResult.js | 8.5% | Runs lint rules |
| augmentConfigBasic | lib/ augmentConfig.js | 3% | Updates config |
| augmentConfigFull | lib/ augmentConfig.js | 4.1% | Updates config |
| runLoad | cosmiconfig/dist/ Explorer.js | 35.9% | Updates config |
| filterFilePaths | lib/utils/ filterFilePaths.js | 18.3% | Updates config |
| Others | - | 8.4% | - |
In total, stylelint spends a whopping 73.2% of its time in just resolving configuration and ignore files in a project where there is only one config file at root and a stylelintignore. It only spends about ~20% of its time in reading source files, parsing them and running rules for them.
To confirm this, I replaced the CLI version of the run with the programmatic use of stylelint through its node API — supplying the config through the config object instead of using configFile, and it cut down runtime by at-least 2.5x.
This seems like a rather wasteful spend of a stylint run, and there seem to be some juicy opportunities to cut down this time, or at-least warn users that using the CLI on medium / large projects can slow down execution by almost 2-2.5x!
What Stylelint configuration is needed to reproduce the bug?
Doesn't really matter what configuration is used, as the actual time spent in linting is minimal.
{
"processors": ["stylelint-processor-styled-components"],
"extends": [
"stylelint-config-standard-scss",
"stylelint-config-styled-components",
"stylelint-config-prettier"
],
"customSyntax": "postcss-scss"
}
How did you run Stylelint?
stylelint '**/*.{js,ts,tsx}' -i .stylelintignore --config stylelint/.stylelintrc.json
Which version of Stylelint are you using?
14.5.3
What did you expect to happen?
I expect styelint to be spending majority of its time doing linting, rather than resolving configuration. Especially when my configuration is static and lies at the root level, and I have no folder-specific configuration / ignores.
What actually happened?
Stylelint is slow because of large deoptimizations.
Proposal to fix the bug
Rethink how configuration is loaded / cached.
@pastelsky Thanks for the clearly written report, especially the analysis, and for using the template.
We're a small team of volunteers maintaining Stylelint so we'd appreciate any help in identifying the code change opportunities to cut down config configuration time, and we'd gratefully accept any pull requests that improve the performance.
Please consider contributing if you have time.
Thanks, @jeddy3. Really appreciate all of the work that goes into maintaining stylelint.
I guess a part of the problem is that configuration management for JS tools is very complex at the moment and each tool (Stylelint / ESLint) etc has to do so much repetitive work to resolve rool level, but also folder level configuration.
The quickest win here could be if developers can indicate that they use only one centralized config, which should make it a lot easier for stylelint to use the same code path as supplying a direct config object to the Node JS API.
There are certainly opportunities to introduce caching. I did try glancing over how stylelint does configuration management — but it seems to be quite involved with cosmic config and many additional layers on top. But in-case I give this another look, will try to contribute.
The required change is simple, yet hard to implement as it has to follow an idea directly opposite to cosmic config.
What
cosmic configdoes?
It scans directories up to find configuration files to use
What is a problem with this approach
You need to run a search for every directory, as config files can appear in any place.
- Technically, this makes the lookup O(n^2)
- however, there is a built-in cache lowering complexity to O(nlogn)
- however, as cosmic-config is created on every
getConfigForFilecall this cache is unused
Why cosmic-config is created every time?
As per my understanding - due to the unique transformer. Once it's moved to the post process - one can utilize cosmic-config caches and why not improve caching for the stuff inside transformer as well? One can use nearest cosmic config file as a cachekey.
This can be a small change with a big impact, but still not very efficient. It's important to separate two use cases:
- when stylelint is executed for one or a few files
- when many files are processed
The solution above would work perfectly for a "simple" case, but if one need to handle more files a impactful solution can be in pre-resolving cosmic config files, and then wiring them directly to options.config, which is already supported by getConfigForFile, including caching.
@theKashey Thank you so much for digging into the issue more and for your two proposals!
If anyone has time, feel free to implement.
This issue is older than one month. Please ask before opening a pull request, as it may no longer be relevant.