I spent the last week working on ESLint configuration and ensuring that syntax checks were built into the developer workflow. In the process, I read a bunch of different docs, which is usually my signal that an "all in one" blog post needs to be written! So here we go.
What is Eslint?
For starters, ESLint is a tool that statically analyzes your code. Typically, it's used to ensure consistent syntax across a project with multiple collaborators. You've likely used ESLint without realizing it because it was already configured in your project. Ever seen those red squiggly lines in VS code? Those are often courtesy of Eslint.
One thing to keep in mind is that ESLint is incredibly powerful. It has the ability to not only analyze code, but transform it. We'll get to that later.
Configuration
ESLint allows you to set project-level rules using an .eslintrc
file. Since every team and project are slightly different, the control you have over your ruleset is quite extensive.
Rules
For every rule, let's say you're setting the no-console
rule, you can decide whether the rule should be off
, or set to warn
or error
. Like this:
module.exports = {
rules: {
'no-console': 'warn',
},
}
In the above example, the no-console
rule determines whether console log statements should exist in the codebase. If the rule is set to off
then console.log can be littered through your code and the linter won't care. If it's set to warn
, the linter will let you know the there are console.log statements in the code, but it won't be a showstopper. But if the rule is set to error
, linting will fail if a console.log statement shows up in the code.
While this is helpful, some rules need to get more specific. For example, ESLint has a rule called import/no-extraneous-dependencies
. The goal of this rule is to catch situations in which you've imported a dependency into your file that is not included in your project's package.json.
While you could use off
, warn
, or error
, it's not as helpful as it could be. That's because there are different types of dependencies, like devDependencies and peerDependencies. A more nuanced configuration of the rule would look like this:
module.exports = {
rules: {
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: false,
optionalDependencies: false,
peerDependencies: false,
},
],
},
}
The above code will only show a linting error when core dependencies are imported but not included. Any other dependency type can be safely ignored.
Extends
You may be thinking that this seems a bit tedious. Do you really want to go through and determine your preferences for all of these individual rules? You may, but probably not. In fact, in most cases, you'll only need to configure a handful of individual rules; the rules that differ from the ruleset you're extending.
Many projects use the core ESLint rules, as shown here.
module.exports = {
extends: 'eslint:recommended', // highlight-line
rules: {
'no-console': 'warn',
},
}
However, ESLint also allows you to extend rulesets that are exported by other projects. So you may opt to use the React recommendations, for example.
Root
Another interesting thing about ESLint is that it follows a cascade model. Suppose you're using a monorepo structure with multiple packages that each have their own .eslintrc
file. You can include a configuration file in the root of your repo. In that case, ESLint will check the configuration file closest to a given line of code first and move up the tree, merging as it goes.
Typically, the top-level directory will include root: true
so ESLint knows it can stop looking for additional config files.
module.exports = {
root: true, // highlight-line
extends: 'eslint:recommended',
rules: {
'no-console': 'warn',
},
}
However, this rule can exist in any .eslintrc
. So, if you wanted to include a standalone package in your monorepo that should not comply with the top-level .eslintrc
, you can do that. This is a great trick so that you don't need to supersede all of the rules at the top level.
Overrides
Alternatively, you may want to supersede individual files that wouldn't have their own .eslintrc
. In that case, you can use overrides
, like this:
module.exports = {
root: true,
rules: {
'no-console': 'warn',
},
// highlight-start
overrides: [
{
files: ['example/*.js'],
rules: {
'no-console': 'error',
},
},
], // highlight-end
}
CLI
Now that you have ESLint configured, what can it actually do?
If you run an ESLint command it will go through the files in your project and spit out all the warnings and errors to the command line.
eslint .
You may remember that I mentioned up top that ESLint can perform transforms. Running ESLint with the --fix
flag means it will attempt to change any syntax that errors out! It's worth noting that it can't fix every error it finds, but it can handle some of them.
You can also use the --debug
flag which will show you what rules ESLint is using. This is helpful if you're attempting to determine why something is failing/passing that shouldn't be.
Scripts
While running ESLint locally is helpful, the point of ESLint is repeatability and consistency in your project. To get that you likely want to add ESLint commands to your package.json scripts.
{
"scripts": {
"lint": "eslint 'packages/**/*.{js,jsx,ts,tsx}'"
}
}
When you do that you can make use of things like husky! We'll talk about that next time.
Wow
There is a lot in this post but there is, even more, I didn't cover. In the scripts example, I used a glob, there are flags like --quiet
, you can even ignore certain files throughout your project. But this is a good start towards helping you understand the setup of an existing project or how to start setting up your own.
And who knows, an ESLinterror may lead to finding and solving a bug! It did for me ๐.
Top comments (5)
Really like the use of
//highlight-line
,//highlight-start
and//highlight-end
to indicate where the changes are between code blocks!Might need to make some adjustments to some of my posts ๐
Lol, I forgot to take those out. On my site they actually highlight things :)
Great post, thank you!
Super informative and easy to follow! Thanks!
Thanks for clear explanation! I found it very useful