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