Migrate ESLint To The New Flat Configuration

Robert Isaac
5 min readSep 4, 2023

--

Migrate ESLint To The New Flat Configuration

have you heard about the new ESLint flat configuration? do you know that it’s much faster than the old, also it’s more predictable and consistent for applying the lint rules, that’s specially true when you have a Monorepo.

And while it seems just changing the structure of the configuration, it’s actually a bit complicated and not well documented (at least not yet), and the goal of this guide is to make it simpler for everyone.

I will use in this Article an Angular repo with one application and library in it, but this applies to any project currently using ESlint, you can skip the TypeScript parser part if your project is only JavaScript, so let’s get started.

The example

this is a new Angular project with one library called ui using the old eslint configuration

First Step In Migration: Creating the new eslint.config.js file

the new flat configuration use only one format, js format and its name should be eslint.config.js

here is basic one for Angular

import tsParser from '@typescript-eslint/parser';
import ngParser from '@angular-eslint/template-parser';

export default [
{
files: ["**/*.ts"],
languageOptions: {
parser: tsParser,
}
},
{
files: ["**/*.html"],
languageOptions: {
parser: ngParser,
}
},
];

notice that files changed from "*.ts" to "**/*.ts" , and now we need to add the parser ourselves since trying to load plugin:@typescript-eslint/recommended-type-checked which should be now ts.configs[‘recommended-requiring-type-checking’] like we did before will fail because it has extends

running ng lint can fail now, to fix it add "type": "module to your package.json file, otherwise you need to change the format to commonjs one which would look like

const tsParser = require('@typescript-eslint/parser');
const ngParser = require('@angular-eslint/template-parser');

module.exports = [
{
files: ["**/*.ts"],
languageOptions: {
parser: tsParser,
}
},
{
files: ["**/*.html"],
languageOptions: {
parser: ngParser,
}
},
];

for the rest of the article to make it simpler I will only show the module format, but everything apply otherwise the same, you only need to change how the import work

Starting adding basic rules

now let’s add the default eslint rules which was loaded before using eslint:recommended now it will be

import js from '@eslint/js';

...

rules: {
...js.configs.recommended.rules,
}

one more thing, there is a change in the globals in ESLint new flat, we need to specify them explicitly now for example for Angular application it needs browser ones, and for testing we need jasmine to add them we will add

globals: {
...globals.browser
}

to the main one, and add a new rule for the spec files

{
files: ["**/*.spec.ts"],
languageOptions: {
globals: {
...globals.jasmine,
}
}
},

you may need to install globals as a dev dependency for it to work

the file till now should be

import tsParser from '@typescript-eslint/parser';
import ngParser from '@angular-eslint/template-parser';
import js from '@eslint/js';
import globals from 'globals';

export default [
{
files: ["**/*.ts"],
languageOptions: {
parser: tsParser,
globals: {
...globals.browser
}
},
rules: {
...js.configs.recommended.rules,
}
},
{
files: ["**/*.html"],
languageOptions: {
parser: ngParser,
}
},
{
files: ["**/*.spec.ts"],
languageOptions: {
globals: {
...globals.jasmine,
}
}
},
];

now running ng lint should work okay for you

Migrating TypeScript Rules

to do this we need to import the ts plugin, and extend its rules

here is how to do it I added comment <<<<< for the new lines

import tsParser from '@typescript-eslint/parser';
import ngParser from '@angular-eslint/template-parser';
import js from '@eslint/js';
import globals from 'globals';
import ts from '@typescript-eslint/eslint-plugin'; // <<<<<

export default [
{
files: ["**/*.ts"],
plugins: {
'@typescript-eslint': ts, // <<<<<
},
languageOptions: {
parser: tsParser,
globals: {
...globals.browser
},
parserOptions: {
project: [
"tsconfig.app.json", // <<<<<
"tsconfig.spec.json" // <<<<<
]
},
},
rules: {
...js.configs.recommended.rules,
...ts.configs['recommended-requiring-type-checking'].rules, // <<<<<
...ts.configs['stylistic-type-checked'].rules, // <<<<<
}
},
{
files: ["**/*.html"],
languageOptions: {
parser: ngParser,
}
},
{
files: ["**/*.spec.ts"],
languageOptions: {
globals: {
...globals.jasmine,
}
}
},
];

now you need to add new file projects/ui/eslint.config.js with this content

import rootConfig from '../../eslint.config.js'

export default [
...rootConfig,
{
"files": ["**/*.ts"],
languageOptions: {
"parserOptions": {
tsconfigRootDir: "projects/ui",
project: [
"tsconfig.lib.json",
"tsconfig.spec.json"
]
},
},
}
];

and inside angular.json file you need to add "eslintConfig": "projects/ui/eslint.config.js" inside projects > PROJECT_NAME > architect > lint > options > eslintConfig otherwise it will use the root ESLint file and will cause problems since its tsconfig doesn’t include the library files

Migrating everything else

everything else is more straightforward, you just import it in the begging e.g. import ng from ‘@angular-eslint/eslint-plugin’;

then define it inside plugins e.g. @angular-eslint’: ng

then if you want to extend its rules just do ...ng.configs.recommended.rules

here is the final version of the eslint files

import tsParser from '@typescript-eslint/parser';
import ngParser from '@angular-eslint/template-parser';
import js from '@eslint/js';
import globals from 'globals';
import ts from '@typescript-eslint/eslint-plugin';
import ng from '@angular-eslint/eslint-plugin';
import ngTeplate from '@angular-eslint/eslint-plugin-template';
import esImport from 'eslint-plugin-import';

export default [
{
files: ["**/*.ts"],
plugins: {
'@typescript-eslint': ts,
'@angular-eslint': ng,
'import': esImport,
},
languageOptions: {
parser: tsParser,
globals: {
...globals.browser
},
parserOptions: {
project: [
"tsconfig.app.json",
"tsconfig.spec.json"
]
},
},
rules: {
...js.configs.recommended.rules,
...ts.configs['recommended-requiring-type-checking'].rules,
...ts.configs['stylistic-type-checked'].rules,
...ng.configs.recommended.rules,
...esImport.configs.errors.rules,
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "app",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "app",
"style": "kebab-case"
}
],
"sort-imports": [
"error",
{
"ignoreDeclarationSort": true
}
],
"import/no-unresolved": "off",
"import/newline-after-import": "error",
"import/order": [
"error",
{
"alphabetize": {"order": "asc"}
}
]
}
},
{
files: ["**/*.html"],
plugins: {
'@angular-eslint/template': ngTeplate,
},
languageOptions: {
parser: ngParser,
},
rules: {
...ngTeplate.configs.recommended.rules,
...ngTeplate.configs.accessibility.rules,
}
},
{
files: ["**/*.spec.ts"],
languageOptions: {
globals: {
...globals.jasmine,
}
}
},
];

here is the repo if you want to see the whole process

https://github.com/robertIsaac/ng_eslint_flat

I hope I make your migration process simple, if you have any questions please add your comment

--

--

Robert Isaac
Robert Isaac

Written by Robert Isaac

I am a Senior Front-End Developer who fall in love with Angular and TypeScript.

No responses yet