Setting up efficient workflows with ESLint, Prettier and TypeScript

Post Editor

From this the article you will learn how to handle ESLint and Prettier in a good way.

17 min read
Original cover photo by Nathan Dumlao on Unsplash.

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.

Original cover photo by Nathan Dumlao on Unsplash.
Original cover photo by Nathan Dumlao on Unsplash.
17 min read
17 min read

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 beganLink to this section

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?Link to this section

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?Link to this section

I assume that you have node and npm installed on your operating system and are familiar with it.

Create a playground directoryLink to this section

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 ESLintLink to this section

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 scriptsLink to this section

{ "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.jsLink to this section

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-lineLink to this section

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!Link to this section

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 .eslintrcLink to this section

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 configure env 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 as let and const.
See ESLint specifying-parser-options.

Now we should add some rules to our .eclintrcLink to this section

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?Link to this section

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?Link to this section

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?Link to this section

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?Link to this section

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?Link to this section

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!Link to this section

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.


{ "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 because eslint-plulgin-prettier and eslint-config-prettier will do this job for us.

What do these settings and options mean?Link to this section

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?Link to this section

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 conventionLink to this section

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 conventionLink to this section

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 installed eslint-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?Link to this section

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?Link to this section

There are different ways to find them!

Firstly, you should look at the 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 is which I personally find much more effective. It is useful if the 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-baseLink to this section

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 or directly in the corresponding config rule set. Pretty easy once you figure out how to activate them, right?

Usage:Link to this section

"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-prettierLink to this section

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.jsLink to this section

module.exports = { configs: { recommended: { extends: ['prettier'], plugins: ['prettier'], rules: { 'prettier/prettier': 'error' } } } ... ... ...

Usage:Link to this section

"extends": [ "plugin:prettier/recommended" ]

Tip: Plugins have to be registered in plugins and activated in extends using the :plugin prefix.

eslint-config-prettierLink to this section

eslint-config-prettier (repository) | -- index.js | -- @typescript-eslint.js | -- babel.js | -- flowtype.js | -- react.js | -- standard.js | -- unicorn.js | -- vue.js

Usage:Link to this section

"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 in unicorn 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.

.eslintrcLink to this section

"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 in error. 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?Link to this section

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?Link to this section

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?Link to this section

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?Link to this section

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?Link to this section

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?Link to this section

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 wayLink to this section

My personal open-source project also motivated me to use ESLint and Prettier.

ConclusionLink to this section

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

Discuss with community


About the author


I write compiler plugins for Typescript / Javascript (mostly in Babel) and for CSS (PostCSS) to hold my code clean, beautiful, small and fast, aka AOT. I also love Typescript, RxJS, Angular and NgRx.


About the author

Serkan Sipahi

I write compiler plugins for Typescript / Javascript (mostly in Babel) and for CSS (PostCSS) to hold my code clean, beautiful, small and fast, aka AOT. I also love Typescript, RxJS, Angular and NgRx.

About the author


I write compiler plugins for Typescript / Javascript (mostly in Babel) and for CSS (PostCSS) to hold my code clean, beautiful, small and fast, aka AOT. I also love Typescript, RxJS, Angular and NgRx.


Featured articles

10 May 20219 min read
Angular Forms: reactive design patterns catalog

In this post, you'll find a set of design patterns for building Angular forms based on two pillars: separation of responsibilities and reactive programming to tackle the complexity of rich and complex Angular forms.

6 May 20216 min read
A journey into NgRx Selectors

This article dives deep into NgRx selectors and will help you understand what role that play in NgRx architecture and how they help decrease the complexity of a codebase