An Overview of the ESLint Configuration Options

ESLint

Over time it has become increasingly more common for developers to create their applications using some kind of bootstrapping tool, such as Create React App or Vue CLI. This tooling often provides out-of-the-box support for things like Typescript, Babel or PWA integration. Whilst there’s nothing necessarily wrong with this, the pursuit of convenience does have a tendency to obscure the inner-workings of some of these technologies. Then, when the necessity to modify or extend the default configuration arises, the best approach is sometimes less obvious due to a lack of fundamental understanding. What usually then ensues is indiscriminately copy/pasting code snippets from Stackoverflow, without much understanding of why the solution may or may not work.

In my experience, one such example of this is ESLint. Specifically, how to use the ESLint configuration object to use existing (typically third-party) rulesets, override certain rules or add new rules altogether. While this post doesn’t aim to be a general introduction to ESLint, it does intend to cover some of the key aspects of the ESLint configuration. The main sections will cover what I consider to be the more important options, being ‘rules’, ‘extends’ and ‘plugins’. I will then provide an addendum of sorts containing information about some less important options, that may not be necessary to know in detail, but might be helpful to be aware of.

Note that the vast majority of the following information is derived from the official ESLint documentation, with the hope of simply presenting it in a more digestible form. This article will not explain the specifics of how to install or run ESLint, start here if you are looking for that kind of information.

Rules

A rule in ESlint can simply be thought of as a pattern that can identify syntax errors, common bad practices or inconsistent coding styles within your Javascript code. ESLint immediately comes with a large list of rules included as part of the core package. You can find the full list of included rules here. However, it’s important to realise that unless you specify otherwise, no rules will be enabled by default, meaning ESLint will effectively do nothing. To start enabling rules, you can use the ‘rules’ property of the ESLint configuration object. Here’s a very minimal ESLint configuration with the ‘rules’ property specified.

module.exports = {
  rules: {
    eqeqeq: ['error', 'always'],
    quotes: ['warn', 'double'],
  },
};

Note that throughout this post I will be using CommonJS syntax for the configuration object as I am assuming the configuration object will be stored in an .eslintrc.js file. Your configuration file may be represented as JSON or YAML - it may even be in your package.json. In these cases the syntax will differ slightly but the core concepts remain the same.

The above example enables two rules from the included ruleset. Each key in the ‘rules’ object is the name of the rule, if you open the list of included rules you should be able to find two rules named specifically ‘eqeqeq’ and ‘quotes’. The value of each rule, often represented as an array, dictates how the rule should be applied. The first entry in the array is the error level: off, warn or error. error will cause ESLint to abort with exit code 1 on failure, which is useful when running ESLint as part of a Continuous Integration pipeline or pre-commit/push hook. The next entry is usually a rule-specific option, for example, the code above states that type-safe equality operators should ‘always’ be required (rather than ‘never’) and that ‘double’ quotes should be enforced (rather than ‘single’ quotes).

As far as rules go, that’s about it. As long as you have access to a certain rule (which initially will just be the ones ESLint provides, but we will see later how to get access to new rules), you can specify it by name and configure it how you see fit. It’s also worth remembering that some rules, though not all, can be fixed automatically by providing the --fix flag when running ESLint.

Extends

In reality, it’s rare to manually specify all the rules we want to use via the ‘rules’ option. Instead, we commonly use the ‘extends’ option to enable a subset of recommended rules via an existing ESLint configuration. Then, we would typically only use ‘rules’ to apply specific overrides, like turning off a single rule or changing its error level. These ‘existing’ configurations are usually referred to as ‘shareable configs’ - and that’s all they are - normal ESLint configuration objects that enable/disable certain rules (possibly amongst other things). In fact, these shareable configs can themselves extend other shareable configs.

Conveniently, the core ESLint package includes such a shareable config, enabling a set of the included ESLint rules that the ESLint team have determined important enough to be commonly turned on (though, you are of course free to disagree, either by not using this config at all or overriding certain rules). We can use it as follows.

module.exports = {
  extends: 'eslint:recommended',
};

That’s it. If you go back to the list of included rules you can see which ones would now be enabled with this configuration - they are the ones marked with a checkmark. To solidify the understanding that this is simply another configuration file under the hood, we can check out the source code for this shareable config here, you will see it just contains a configuration object with several rules enabled via the ‘rules’ property.

You may however want to use a shareable config from a third-party outside of ESLint. For example, try searching eslint-config on npmjs.org and you will find several shareable configs available to use. For example, if you wanted to use a config called eslint-config-myfakeconfig, you would start by installing it as a development dependency (for example, npm install --save-dev eslint-config-myfakeconfig) to ensure it can be resolved by ESLint (i.e., it exists within node_modules) and then simply do the following.

module.exports = {
  extends: 'myfakeconfig',
};

Notice how you can omit the eslint-config- part of the package name. Also, note that you can use an array if you wish to extend multiple configs.

module.exports = {
  extends: ['eslint:recommended', 'myfakeconfig'],
};

However, you should note that most shareable configs you find on npm won’t immediately work on their own as they attempt to enable rules that aren’t included in the ESLint core ruleset. This is where we introduce the final main concept, plugins.

Plugins

Sometimes the included ESLint ruleset isn’t enough. Sometimes you might want to use rules specific to a certain framework you’re using, such as React or Vue, to enforce respective best practices. We can add these new rules through the ‘plugins’ option. It now becomes important to understand the distinction between adding a rule and enabling a rule. Adding a plugin (and therefore one or more rules) on its own will have no effect until you actually enable any of the new rules (either through the ‘rules’ or ‘extends’ options). Helpfully, most plugins also come with one or more shareable configs (similar to ‘eslint:recommended’) so we don’t need to manually decide which rules to use. Let’s see how we might use a plugin for a React app.

Start by installing (as usual, as a development dependency) a very popular React ESLint plugin.

npm install --save-dev eslint-plugin-react

We can then modify the ESLint configuration file as follows.

module.exports = {
  plugins: ['react'],
  extends: ['eslint:recommended', 'plugin:react/recommended'],
};

First we added the plugin (via the ‘plugins’ property) to make a new React-specific ruleset available, then used a shareable config from the same package to actually enable some of those rules. Notice how similarly to sharable configs, you can omit the eslint-plugin- part of the package name when adding to the ‘plugins’ array. That’s it, now when we run ESLint any React-specific linting errors will be reported (for example, not adding prop validation).

Conclusion

These three options: ‘rules’, ‘extends’ and ‘plugins’ are likely the only concepts you will need to understand in any great detail as they provide the majority of ESLint’s functionality. In summary, you can use the ‘plugins’ options to add rules outside of the included ESLint core ruleset. You can use the ‘extends’ option to use shareable configs that will enable some subset of the rules you have access to. Finally, you can use the ‘rules’ option to specifically turn a rule on/off, pass different options or change its error level.

Addendum

Below are a couple of options that you might not come across very often, but are still somewhat useful to know about.

Globals

One common rule to use is no-undef which means ESLint will report an error for any undefined variables. However, particularly in client-side Javascript code, it can be fairly common to need to access at least some global variables, such as window. In these cases, we don’t want ESLint to consider those variables as being undefined, which would result in errors. The ‘globals’ option allows us to specify the names of certain global variables so that ESLint won’t report them as errors. This example below informs ESLint that window is a global variable, meaning it will no longer be considered undefined when accessed.

module.exports = {
  globals: ['window'],
};

Environments

Another - perhaps better - way of solving the issue above is to use the ‘env’ option. Rather than you trying to manually figure out all the commonly used global variables, you can use a pre-defined environment which define most of these global variables for you. The example below informs ESLint that our code is running in a browser, meaning all the typical global variables (such as window) will be globally defined according to ESLint. See the list of available environments here.

module.exports = {
  env: {
    browser: true,
  },
};