Setting up efficient workflows with ESLint, Prettier and TypeScript
From this the article you will learn how to handle ESLint and Prettier in a good way.

In this article I would like to start very easily and go into more depth from topic to topic. In the first step we will use simple rules and options. Then we will learn the correct use of configs and plugins. During the whole learning process you will get useful tips and information so you will be able to build your own ESLint and Prettier environment.
How it all began
Actually I did not want to use ESLint and Prettier because I never felt the need for it because Angular, which i use in my daily life, brings a linting tool and a simple code formatter. But in the end several other things made my decision to use ESLint and Prettier in a more professional way.
First the endless discussions how code should be written or formatted. This is a really annoying subject, for me at least. Personal preferences should go away here. There are more important things to consider.
Secondly, if I want to introduce this to the team, team members will definitely ask me questions and I don't want to stand there helplessly. It can happen that colleagues lose interest if you do not answer questions or if you cannot promote it.
Thirdly, the power of these tools. It helps you to focus on the essentials in your daily life instead of consistently being forced out of your flow because of for example code has been formatted incorrectly which is mostly reported by a colleague in a code-review.
At the end of the day its all about business no matter how much fun we have in what we do. It is just waste of time. You can spend your time better.
As you can see, for a developer there are many disruptive factors in daily business. Let's eliminate these disturbances together based on established web tools.
Info: There will be maybe no TSLint with Angular in the near future because TypeScript decided to support ESLint instead of TSLint. The Angular Team is currently working on a migration from TSLint to ESLint. See here.
What is ESLint and how can it help us?
ESLint is a tool which can analyze written code. Basically it is a static code analyzer and it can find syntax errors, bugs or styling improvements. In other languages such as Go is the a core part of the programming language.
What do I need to get started with ESLint?
I assume that you have node and npm installed on your operating system and are familiar with it.
Create a playground directory
You should change into a directory where your JavaScript or TypeScript project is or like me create a test directory "lint-examples" where we can work on it according to this article. On a Linux-based operating system just type mkdir lint-examples
in the command-line and then change to this directory with cd lint-examples
.
Install ESLint
Now let's create a package.json so we can install ESLint. Execute the following command: npm init
create a package.json which is required for installing eslint
in your directory.
Add eslint
to your npm scripts
{
"name": "eslint-examples",
"version": "0.0.0",
"description": "",
"main": "index.js",
"scripts": {
"eslint": "eslint"
},
"devDependencies": {
"eslint": "7.1.0"
}
}
Tip:
"eslint": "eslint"
in scripts is a shortcut for node_modules/.bin/eslint
Create test.js
Now let's create a simple JavaScript file in lint-examples
directory where we can apply ESLint on. Don't worry about why the code sample is weirdly formatted. We need this as a starting point.
var foo = 1
console.log(foo)
var bar
bar = 1
function test(
) {
console.log(baz)
}
var baz = 123
First try on command-line
If you now run the test.js
file through ESLint, nothing will happen. By default, ESLint will only check for syntax errors. It will use ES5 as the default option. See ESLint specifying parser options.
If you had used const
or let
in the above code example, ESLint would have thrown an error because as already mentioned ES5 is the default setting.
Tip: with
--
you can pass arguments trough npm scripts to the eslint command line service.
npm run eslint -- ./test.js
Now it is getting Interesting!
Depending on how modern your project is, you should set the right options. In our examples we assume that you want to use the modern ES6 syntax.
Let's create our first .eslintrc
There are several ways to provide a configuration to ESLint. I prefer the .eslintrc
. Here you can find other formats: Configuration-File Formats
{
"env": {
"es6": true
}
}
Info:
env
is required for global variables. When we configureenv
with es6 set to true, ESLint will enable the globals for the new types such as Set. It will also enable the ES6 syntax such aslet
andconst
.
See ESLint specifying-parser-options.
Now we should add some rules to our .eclintrc
Can we just define rules? Yes we can because we have installed ESLint and it brings lot of rules out-of-the-box. For special rules like TypeScript or new features that are not supported by ESLint, we have to install either a eslint-config-xxx
or a eslint-plugin-xxx
module. But we will come to that later. The rules can be found here: ESLint-Rules.
{
"env": {
"es6": true
},
"rules": {
"no-var": "error",
"semi": "error",
"indent": "error",
"no-multi-spaces": "error",
"space-in-parens": "error",
"no-multiple-empty-lines": "error",
"prefer-const": "error",
"no-use-before-define": "error"
}
}
If you now run npm run eslint
you should get roughly the following output.
error 'foo' is never reassigned. Use 'const' instead prefer-const
error Missing semicolon semi
error Expected indentation of 0 spaces but found 4 indent
error 'bar' is never reassigned. Use 'const' instead prefer-const
error Multiple spaces found before ')' no-multi-spaces
error There should be no space after this paren space-in-parens
error There should be no space before this paren space-in-parens
error More than 2 blank lines not allowed no-multiple-empty-lines
error 'baz' was used before it was defined no-use-before-define
error 'baz' is never reassigned. Use 'const' instead prefer-const
26 problems (26 errors, 0 warnings)
20 errors and 0 warnings potentially fixable with the `--fix` option.
Now we are a big step further and know how our coding and styling guidelines should be, but in a real life there are of course more rules. I just wanted to show you how easy it is to configure your own rules.
Maybe you noticed in ESLint's output that 20 problems of 26 can be solved automatically. We will come to that in next section.
ESLint and code formatting?
Until a certain point, ESLint can format your code automatically. As you may have noticed in the above log output, an additional --fix
argument can be used to format written code based on eslint
rules. For example if a semicolon is missing it will be added automatically, if there are multiple empty lines it will be removed. The same applies to other fixable rules.
Let's fix the code by executing: npm run eslint -- ./ --fix
var foo = 1;
console.log(foo);
var bar;
var = 1;
function test(
) {
console.log(baz);
}
var baz = 123;
1:1 error Unexpected var, use let or const instead no-var
3:1 error Unexpected var, use let or const instead no-var
11:1 error Unexpected var, use let or const instead no-var
3 problems (3 errors, 0 warnings)
You have seen that not all rules can be fixed by ESLint. For the remaining 3 errors you have to do this manually but the other reported errors by ESLint such as "Missing semicolon", "Expected indentation", "Multiple spaces", and so on were fixed automatically!
Note: The reason why "var" cannot be fixed has something to do with the browser context. You can find more information here.
In the ESLint documentation you can find which rules can be activated with a "check mark" icon. Code that can be auto-formatted is highlighted with a wrench icon.
- The rules can be found here: ESLint-Rules.
- There are almost 300 rules and it is constantly growing
- About 100 of these are auto-formatting rules
The whole thing becomes even more powerful if the code is formatted by your IDE on file-change (save) or if any pipeline tool such as travis-ci can take over this task when something is pushed by Git.
Knowing that ESLint can format your code, what does Prettier even do?
It seems that ESLint does some code formatting as well. As seen in the code example above, it does not go far enough. The code does not look very nice, especially when you look at the function. This is where the wheat is separated from the chaff. ESLint is primarily intended for code quality. Prettier, as the name implies, makes your code Prettier. Let's see how far Prettier will take us.
What do I need to get started with Prettier?
Not much. You just need to add the following to your NPM scripts "prettier": "prettier"
and run npm install prettier
.
As we can remember, this code was formatted by ESLint and it is not well formed.
// test.js
const foo = 1;
console.log(foo);
let bar;
bar = 1;
function test(
) {
console.log(baz);
}
const baz = 123;
When we apply npm run prettier -- --write ./test.js
the code format becomes even better.
const foo = 1;
console.log(foo);
let bar;
bar = 1;
function test() {
console.log(baz);
}
const baz = 123;
That's much better. The larger the code base, the greater the benefit.
Can I also set options with Prettier?
Yes you can. The options for the Prettier parser are by far not as extensive as ESLint's. With Prettier, you are almost at the mercy of the Prettier parser. It decides based on a few options how your code should look like in the end.
This are my settings which is defined in .prettierrc
. The full list of code-style options can be found on prettier-options. Let's create a .prettierrc
file with these options.
{
"semi": true,
"trailingComma": "all",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2,
"arrowParens": "avoid"
}
Do we need to start ESLint and Prettier at the same time?
It is not desirable to start ESLint and Prettier separately to apply coding and format rules. Furthermore, ESLint and Prettier would get in each other's way because they have overlapping rules and this could lead to unexpected behavior. In the next section this problem is addressed and will be solved. In short you will just call eslint
in our command-line and prettier
will be included.
Back to how it all began!
As I described at the beginning of this article, I had never used ESLint and Prettier before. Therefore I did not know how to get the whole tooling working. Like every developer, I copied the best possible code snippet from the depths of the Internet into my .eslintrc
without knowing what it actually does. The main thing was to get it working.
Here is a little snippet from my .eslintrc
config which was originally copied from several sources and adapted by me over time as I understood what the config does.
In short, there are configs and plugins for ESLint provided by the open source community. We don't have to do it all ourselves. The main thing is to know what is happening under the hood.
.eslintrc
{
"plugins": [
"@typescript-eslint",
"prettier",
"unicorn" ,
"import"
],
"extends": [
"airbnb-typescript/base",
"plugin:@typescript-eslint/recommended",
"plugin:unicorn/recommended",
"plugin:prettier/recommended",
"prettier",
"prettier/@typescript-eslint"
],
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"env": {
"es6": true,
"browser": true,
"node": true
},
"rules": {
"no-debugger": "off",
"no-console": 0
}
}
Note: maybe you noticed
prettier
in the plugins section and you still remember when I mentioned above: "Do we have to execute ESLint and Prettier for code formatting at the same time?" The answer is no becauseeslint-plulgin-prettier
andeslint-config-prettier
will do this job for us.
What do these settings and options mean?
After I made it work, I wondered what it all meant. Literally, it knocked me out. If you would run ESLint on your command line now with these options, an error would be thrown that the configs (extends) and plugins are not installed. But how do we know what to install? Everybody knows this, you find a code snippet on Stackoverflow or in some repositories and then you don't know how to install them.
You can keep in mind that all the modules inside of extends
and plugins
can be installed. But first you have to know how to interpret the naming convention within the properties to be able to install them via npm
.
What are the "plugins" options?
Plugins contain rules that have been written by using a parser. These can be proposal rules from TC39 that are not yet supported by ESLint or special coding guidelines that are not provided by ESLint such as unicorn/better-regex, import/no-self-import.
Imagine that you want to introduce a rule which says that always at the beginning of a file, before any line of code is written, a comment should start with a emoji code. Sounds weird but you can do that with ESLint Plugin.
// :penguin: emoji
Let's find out how to interpret plugin naming convention
As long as the plugin name does not start with eslint-plugin-
or @
or ./
then you have to just prefix the plugin name with eslint-plugin-
.
plugins: [
"prettier", // npm module "eslint-plugin-prettier"
"unicorn" // npm module "eslint-plugin-unicorn"
]
This is the same as above example and works as well:
plugins: [
"eslint-plugin-prettier", // the same as "prettier"
"eslint-plugin-unicorn" // the same as "unicorn"
]
It gets a little bit more complicated when you come across plugin names that start with a @
(namespace). As you can see in the example below, the use of /
is limited to one level. You should consider that @mylint
and @mylint/foo
are under the same namespace but they are two different plugins (npm modules).
plugins: [
"@typescript-eslint", // npm module "@typescript-eslint/eslint-plugin"
"@mylint", // npm module "@mylint/eslint-plugin"
"@mylint/foo", // npm module "@mylint/eslint-plugin-foo"
"./path/to/plugin.js // Error: cannot includes file paths
]
The code example below it the same as above.
plugins: [
"@typescript-eslint/eslint-plugin", // the same as "@typescript-eslint"
"@mylint/eslint-plugin", // the same as "@mylint"
"@mylint/eslint-plugin-foo" // the same as "@mylint/foo"
]
Tip: Use the short form (first example) instead of the long form (second example). The important thing is that you know how ESLint converts it internally.
We now know how the naming convention works for plugins. Install the following ESLint plugins via NPM.
npm i eslint-plugin-prettier eslint-plugin-unicorn
In the ESLint documentation you can find further information about the naming convention, see plugin namingconvention.
For testing purposes your .eslintrc
should look like this.
{
"plugins": [
"prettier",
"unicorn"
],
"env": {
"es6": true
}
}
Prettier: ESLint plugin for formatting code. See here.
Unicorn: Additional rules which are not supported by ESLint. See here.
Now if you run npm run eslint
on your command-line, you will not get an error but also not a lint output. This is because we have to register the plugin module within the extends
property of our .eslintrc
or apply it by activating them in the rules
section.
Let's find out how to interpret the extends naming convention
First of all, if you think the naming convention of the extends section is the same as plugins, I have to disappoint you. There are differences. I must honestly admit that it took me a long time to notice the difference. This was partly because ESLint is a complex and extensive topic, at least for me.
As long as you only use the simple name (like foo
) without a prefixed namespace (@
) or with a path (./to/my/config.js
) the principle of the naming conventions in extends
is the same as with the plugins
option. So foo
becomes eslint-config-foo
extends: [
"airbnb-base", // npm module "eslint-config-airbnb-base"
"prettier" // npm module "eslint-config-prettier"
]
is equal to
extends: [
"eslint-config-airbnb-base", // shortform is "airbnb-base"
"eslint-config-prettier" // shortform is "prettier"
]
Now we come to the point where differences between the plugins
and extends
naming conventions exist. This is the case when you use namespaces (@) in extends
section. The following @mylint
ESLint config is still the same, it points to @mylint/eslint-config
NPM module but @mylint/foo
can lead to an error when it is used in extends
because omitting the prefix eslint-config-
in @mylint/eslint-congif-foo
can result in an error.
extends: [
"@bar", // npm module "@bar/eslint-config"
"@bar/eslint-config-foo", // npm module "@bar/eslint-config-foo"
"@bar/my-config" // npm module "@bar/eslint-config/my-config"
]
As I wrote in the introduction to the previous section, the following @mylint/my-config
is a bit special because it contains an NPM module but at the same time it points internally from ESLint perspective to a rule set (my-config). We will clear this up shortly. Here is the official documentation of the naming convention of extends
, see shareable-configs
Let's install the rest the NPM modules for our example app.
npm i eslint-config-airbnb-base eslint-config-prettier
Note: You may have noticed that we installed
eslint-plugin-prettier
earlier and now we have installedeslint-config-prettier
. These two modules are two different things but only work together. We will discuss this later.
What does extends
exactly do in .eslintrc
?
A config provides preconfigured rules. These rules can consist of ESLint rules, third party plugin rules or other configurations such as the parser (babel
, esprima
, ...), options (sourceType
, ...), env (ES6
, ...), and so on.
Sounds good? Yes, this is good for us because we don't have to do this by ourselves. Smart developers and teams have invested a lot of time to provide this to us. All we have to do is activate them by pointing to a config or to a plugin rule set.
Where can I find these rule sets?
There are different ways to find them!
Firstly, you should look at the README.md of the relevant repository and read exactly what is written. Usually these rule sets are called "recommended" and must be activated for plugins
. For the extends
, it is not always necessary.
Secondly, knowing which rule set to use without reading the README.md is which I personally find much more effective. It is useful if the README.md is incomplete or incorrect.
Basically you can say that "plugins" point to a single file where the configurations (rule sets) are contained in an object and "extends" points to rule sets which are in different files.
eslint-config-airbnb-base
eslint-config-airbnb-base (repository)
| -- index.js
| -- legacy.js
| -- whitespace.js
You can activate all configurations at once, but be careful with that. You should find out in beforehand what they do. I explained that before by looking into related README.md or directly in the corresponding config rule set. Pretty easy once you figure out how to activate them, right?
Usage:
"extends": [
"airbnb-base", // index.js
"airbnb-base/whitespace" // whitespace.js
]
Keep in mind: The order plays a role because a ruleset will extend or overwrite the previous ones. So do not overdo with configs and plugins. See good explanation on Stackoverflow.
eslint-plugin-prettier
Now we come to the exciting part of the article. How we can use Prettier directly in ESLint without running it as a separate service on our command line or IDE.
We start by activating the eslint-plugin-prettier
in the extends
section and then the related config eslint-config-prettier
which is responsible for deactivating some ESLint rule sets which can conflict with Prettier.
eslint-plugin-mylint (repository)
| -- eslint-plugin-prettier.js (because this is specified as entrypoint in package.json)
eslint-plugin-prettier.js
module.exports = {
configs: {
recommended: {
extends: ['prettier'],
plugins: ['prettier'],
rules: {
'prettier/prettier': 'error'
}
}
}
...
...
...
Usage:
"extends": [
"plugin:prettier/recommended"
]
Tip: Plugins have to be registered in
plugins
and activated inextends
using the:plugin
prefix.
eslint-config-prettier
eslint-config-prettier (repository)
| -- index.js
| -- @typescript-eslint.js
| -- babel.js
| -- flowtype.js
| -- react.js
| -- standard.js
| -- unicorn.js
| -- vue.js
Usage:
"extends": [
"prettier", // index.js
"prettier/unicorn", // unicorn.js
"prettier/@typescript-eslint" // @typescript-eslint.js
]
Note: The standalone "prettier" in
extends
is necessary here because it disables certain ESLint core rules. The others are necessary for disabling rules inunicorn
and@typescript-eslint
.
My personal ESLint config looks like the above usage example. I use TypeScript and the Unicorn plugin. I don't want them to conflict with ESLint. Therefore, certain rules of TypeScript and Unicorn are disabled via Prettier.
Previously we have activated rule sets which are internally nothing else than grouped rules, but you don't have to use a combined rule set. You can also change or disable individual rules.
Activating everything yourself instead of using a rule set makes no sense. But it often happens that you don't agree with one or the other rule or its setting. In this case you can deactivate a single rule. See example.
.eslintrc
"rules": {
"unicorn/prevent-abbreviations": "off"
}
So, we come back to our test example. Now our .eslintrc
should look like this.
{
"plugins": [
"prettier",
"unicorn"
],
"extends": [
"airbnb-base",
"plugin:unicorn/recommended",
"plugin:prettier/recommended",
"prettier",
"prettier/unicorn"
],
"env": {
"es6": true,
"browser": true
},
rules: {
"unicorn/prevent-abbreviations": "off"
}
}
Strategy: During a transition to ESLint it can happen that many errors are displayed in ESLint output. Adjusting them immediately can take a lot of time or can even lead to side effects. If you want to have a smooth periodic transition, it is recommended to leave the rules in the
warning
mode instead of inerror
. See configuring rules.
If you now run our example code through ESLint with npm run eslint -- --fix
, Prettier will be executed through ESLint so that you only need one command to run both.
How we can this be integrated into an IDE?
At this point I don't want to write a tutorial about how to activate ESLint in a IDE of your choice. In any case you can say that all modern IDEs (IntelliJ and VS Code) support ESLint. It is important to note that you have to pass in some cases the --fix
parameter as an argument in your IDE settings to make everything work automatically.
Why do different types of "ESLint" parsers exist?
ESLint only supports new JavaScript syntax that reached the final stage in TC39. Maybe not many people know this, but the Babel compiler supports features that are not in the final stage. A well-known feature is the decorator feature. The one Angular is based on was abandoned. The new one has different syntax and semantics. The old one reached stage 2 and the new one is in early state.
In this case ESLint will not help you. Either you have to find the right plugin for it or you write your own eslint plugin which uses for example the babel parser instead of the espree parser which is the default parser of ESLint.
See eslint-parser settings.
How about Angular and ESLint?
The Angular Team hold the opinion that we should wait with the rollout of ESLint. This is legitimate because they want to make it as smooth as possible but if you still want to try it, here are some suggestions. See Github.
Performance and ESLint?
It can happen that ESLint is not as performant as one would expect in some code parts, but that's normal and can happen also in TSLint. To solve this problem you can use the internal caching of ESLint or another ESLint deamon. Here you can find very useful tips, see Stackoverflow issue.
Does Prettier exist only for Javascript?
Prettier officially supports several other languages. These include PHP, Ruby, Swift, and so on. Furthermore there are community plugins for the following languages like Java, Kotlin, Svelte and many more.
What about ESLint v7?
All examples in our article were originally based on version ESLint v6 but recently ESLint v7 has been released. Don't worry, even in version 7 ESLint works without having to change anything. If you are interested in what has changed or been added you can check out release notes of ESLint v7.
Example repo in a comprehensive way
My personal open-source project https://github.com/pregular/core also motivated me to use ESLint and Prettier.
Conclusion
I think you don't need to know more about ESLint and Prettier. From now on you can do the whole thing by yourself. The important thing is that you practice it over and over again so you can solidify that knowledge.
I would like to thank the InDepth community for having published my article. A big thanks goes here to Lars Gyrup Brink Nielsen who supported me on my first article