Best ESLint Configuration for TypeScript and ESM

ESLint is a handy open-source tool that finds and fixes problems in your JavaScript and TypeScript code. Who doesn't want a tiresome mentor suggesting best practices constantly? You can adjust its suggestion according to your preferences using the config file.

Best ESLint Configuration for TypeScript and ESM

In this guide, we will

  1. Decide between new and old configuration files,
  2. Decide configuration file type,
  3. Add TypeScript support,
  4. Decide and use a style guide,
  5. Use the TypeScript version of the style guide,
  6. Extend the style guide with our own rules.
  7. BONUS: My favorite config

ESLint Configuration

Although you can configure ESLint with comments, using a configuration file would be much better.

The Old vs. New Configuration File

ESLint version 8 introduced a new config file format (eslint.config.js), but it is not enabled by default and, therefore, not widely supported. The next major version (ver. 9) will make the new format default.
We will stick to the current version's old config format (.eslintrc) to avoid glitches.

Config File Extension: "js", "cjs", "json," or "yml"?

TLDR; Use .eslint.cjs and write a programmatic configuration:

module.exports = {
  // Your config here.
}

ESLint supports the following configuration files:

  • .eslintrc.js
  • .eslintrc.cjs
  • .eslintrc.yaml
  • .eslintrc.yml
  • .eslintrc.json
  • package.json

json and yaml configurations are more straightforward, but I suggest js or cjs because you can write JavaScript code and develop advanced structures using JavaScript comments, conditionals, imports, etc.
The old ESLint configuration file does not support ESM syntax inside the configuration file. If our TypeScript module is an ESM module, we must use the cjs extension to indicate that the config is a CommonJS module.

TypeScript Support

ESLint does not know how to parse TypeScript files out of the box. We will install and add the TypeScript parser to the config.

$ npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint typescript

.eslintrc.cjs

module.exports = {
  extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
  parser: '@typescript-eslint/parser',
  plugins: ['@typescript-eslint'],
  root: true,
};

Style Guides

The best configuration is the configuration that matches your needs. However, starting from scratch and getting a perfect setup is very hard. Thanks to the community, there are tested and proven configurations used by lots of developers. These are called style guides.

Some popular style guides are:

You better start with a style guide and modify the rules when needed according to your coding style.
I suggest Airbnb JavaScript Style Guide because there is a slightly modified TypeScript version is available: eslint-config-airbnb-typescript
Using eslint-config-airbnb-typescript, your final configuration should be like the below:

.eslintrc.cjs

module.exports = {
  extends: [
    'airbnb',
    'airbnb-typescript',
    'airbnb/hooks',
    'plugin:@typescript-eslint/recommended-type-checked', // @typescript-eslint @v6
    'plugin:@typescript-eslint/stylistic-type-checked', // @typescript-eslint @v6
    // 'plugin:@typescript-eslint/recommended',                          // @typescript-eslint @v5
    // 'plugin:@typescript-eslint/recommended-requiring-type-checking',  // @typescript-eslint @v5
  ],
};

Add Your Rules

What if you don't like something in the preset style guides? You can add your own rules with rules property. For example, ESLint reports console statements as errors, but I would like to allow console statements except console.log().

{
  rules: {
    "no-console": ["error", { allow: ["info", "warn", "error"] }],
  }
}

BONUS: What is Best for Me?

My favorite rules/configs are:

Final Configuration

Here is a sample configuration with my favorite configs:

.eslintrc.cjs

module.exports = {
  parser: "@typescript-eslint/parser",
  parserOptions: { project: true },
  extends: [
    "airbnb-base",
    "airbnb-typescript/base", // TURN ON airbnb-base rules.
    "eslint:recommended", // TURN ON ESLint recommended rules.
	"plugin:@typescript-eslint/strict-type-checked",
	"plugin:@typescript-eslint/stylistic-type-checked"
    "plugin:promise/recommended",
    "plugin:unicorn/recommended",
    "prettier", // TURNS OFF all ESLint rules conflicting with prettier.
  ],
  rules: {
    "no-prototype-builtins": "off", // Too restrictive, writing ugly code to defend against a very unlikely scenario: https://eslint.org/docs/rules/no-prototype-builtins
    "no-useless-constructor": "off",
    "no-use-before-define": ["error", { functions: false, classes: true, variables: true }],
    "@typescript-eslint/no-use-before-define": ["error", { functions: false, classes: true, variables: true, typedefs: true }],
    "@typescript-eslint/no-useless-constructor": "error",
    "@typescript-eslint/non-nullable-type-assertion-style": "off",
    "lines-between-class-members": ["error", "always", { exceptAfterSingleLine: true }],
    "import/prefer-default-export": "off",
    "unicorn/prevent-abbreviations": "off", // Common abbreviations are known and readable
    "unicorn/no-array-for-each": "off", // Airbnb prefers forEach
    "import/no-extraneous-dependencies": "off",
    "no-console": ["error", { allow: ["info", "warn", "error"] }],
    // TypeScript handles rules below as builtin.
    "import/named": "off",
    "import/namespace": "off",
    "import/default": "off",
    "import/no-named-as-default-member": "off",
  },
}

Conclusion

I published the config above to the npm. You can try the above configuration using eslint-config-ozum. You can find docs about how to use it on the linked page.

What is your favorite style guide and configs? Share your favorites in the comments to help other developers