Flat config: Setting up Eslint and Prettier with eslint-prettier-plugin
Eslint flat config setup with eslint-prettier-plugin, a great practical example
Overview
- we are choosing to implement
linting
usingeslint
withprettier
◌ will show what’s not right followingprettier
- And to use
eslint
as aformatter
◌ through theplugin
, it will useprettier
- Setting up the
new flat config
🔥 - eslint-plugin-prettier vs eslint-config-prettier 🔥
Integration with linters
possible ways
Besides eslint
and or others
There are two ways
▪️ Disable linter conflicting rules with prettier formatting & run prettier separately
- So
prettier
formatting doesn’t affect linter rules - Advantage
◌ formatting performance generally doesn’t get affected by the linter
▪️ Use the linter to do all through a prettier plugin
- Make the linter show prettier formatting issues
- Format through
fix this issues
- or use the
linter
asformatter
(eslint
supportformatting
) - Advantages
◌ the above
- one solution
- seeing formatting mistakes through the linter (red lines) - disadvantage
◌ at least for eslint
- people saying less performance
- personal experience: it’s perfect and it’s my go-to solution
✨ eslint
▪️ Way 1:
eslint-config-prettier
to disable conflicting rules of eslint- use prettier separately
- use
prettier
directly, no problems with eslint
◌ all work well - can be faster or not really against choice 2
▪️ Way 2: ✅ my choice
eslint-plugin-prettier
◌ useeslint
to do all, through theprettier
plugin
◌ Which does useeslint-config-prettier
to disable conflicting rules
◌ and does addlinting
ability
◌ and does runprettier
for us to fix things
▪️ Way 3:
prettier-eslint
project- While
prettier-eslint
useseslint --fix
to change the output ofprettier
,eslint-plugin-prettier
keeps theprettier
output as-is and integrates it with the regularESLint
workflow. - running
prettier
alone is faster
How to pick
It’s the recommended practice to let Prettier
handle formatting
and ESLint
for non-formatting
issues
▪️ prettier-eslint
❌ is not in the same direction as that practice
- hence
prettier-eslint
is not recommended anymore.
▪️ You can use eslint-plugin-prettier
or eslint-config-prettier ✅
eslint-plugin-prettier
◌ does useeslint-config-prettier
by default in it'srecommended
configuration
Choose my choice
eslint-plugin-prettier
. Why ? => u seelinting
withred
when usingvscode extension
. it's immediate feedback. 🔥🔥
▪️ https://prettier.io/docs/en/integrating-with-linters.html
- official doc
◌ suggesting using prettier alone
- I don’t agree - Some of the reasoning. Which can hold.
The downsides of those plugins are:
- You end up with a lot of
red squiggly lines
in your editor, which gets annoying. Prettier is supposed to make you forget about formatting — and not be in your face about it!
- [❗❗❗ I absolutely disagree — see for yourself — I see the
red
great. Like to see how things got formatted, and immediate feedback …]
- They are slower than running Prettier directly.
- [❗❗ my experience say! u can’t notice any difference ]
- And I don’t think it would affect a big project if you set vscode to show problems only per active file.
- In the worst case, if somehow in a big project, you notice performance issues (I never had any ever). By then, I would use prettier directly.
- They’re yet one layer of indirection where things may break.
- [🔥 agree but I never had an issue. You configure it once. And all great. Also! if an issue. you can still run prettier directly. So you know which layer it is on. So not really a point.]
Table comparing the 3
✨ eslint-plugin-prettier
in flat config
🔥
(eslint-plugin-prettier
, flat config
)
▪️ How to set extends
doc:
▪️ At the moment of writing [2023–09–04 and then 2023–11–11 (publishing)]
- There is no
flat config
support
◌ you can check the doc. And you’ll find the oldeslintrc
config
{
"extends": ["prettier"],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error",
"arrow-body-style": "off",
"prefer-arrow-callback": "off"
}
}
- by going to
node_modules/eslint-plugin-prettier/eslint-plugin-prettier.js
we get the recommended
config
const eslintPluginPrettier = {
configs: {
recommended: {
extends: ['prettier'],
plugins: ['prettier'],
rules: {
'prettier/prettier': 'error',
'arrow-body-style': 'off',
'prefer-arrow-callback': 'off',
},
},
},
- it does hold
extends
and plugins in the old array format
◌ notrules
only
◌ so it’s not compatible withflat config
Two ways to set the equivalent
▪️ Using @eslint/eslintrc
package and FlatCompat
class
FlatCompat
utility will help with the translation
◌ Doesn’t get affected by any updates
- will work always
▪️ Understand the recommended
config and apply directly [ my choice ✅ ] 🔥
- In case of an update, we may need to recheck and update as well
◌ low probability that would happen
◌ choosing to go best withflat config
style
◌ It’s granular. You add exactly what you need and at exactly the right part of yourFlat config
✨ Using @eslint/eslintrc
package and FlatCompact
class
(@eslint/eslintrc
, FlatCompact
)
because the config is
{
"extends": ["plugin:prettier/recommended"]
}
adding on the bottom of the array will do
// prettier config all above this one are supposed to use prettier
...compat.extends("plugin:prettier/recommended"),
];
▪️ at the end
or after the configs
and matched files
that do use prettier
- because
eslint-config-prettier
is used inextends
part of"plugin:prettier/recommended"
◌ Which does disable rules ofeslint
that conflicts withprettier
formatting
- because of disabling 🔥
- it needs to be last so it wouldoverride
=>disable
It’s nice after u setup
to use eslint
output console, for any possible errors
- let’s disable that one rule
- and reload the window
And la perfecta
- it’s working
Total config testing and focusing on js files
import { FlatCompat } from '@eslint/eslintrc';
import globals from 'globals';
import prettierPlugin from 'eslint-plugin-prettier';
import eslintConfigPrettier from 'eslint-config-prettier';
import { jsonFiles } from './.eslintrc.conf.js';
import * as url from 'url';
const __filename = url.fileURLToPath(import.meta.url);
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
const compat = new FlatCompat({
baseDirectory: __dirname, // optional; default: process.cwd()
resolvePluginsRelativeTo: __dirname, // optional
});
const jsRules = {
quotes: [
'error',
'single',
{ avoidEscape: true, allowTemplateLiterals: false },
],
// 'import/no-cycle': 'warn',
'class-methods-use-this': 'off',
// 'import/prefer-default-export': 'off',
'comma-dangle': 'off',
'object-curly-newline': 'off',
'operator-linebreak': 'off',
'implicit-arrow-linebreak': 'off',
'function-paren-newline': 'off',
'import/no-extraneous-dependencies': 'off',
'import/extensions': 'off',
'import/no-absolute-path': 'off',
'generator-star-spacing': 'off',
'no-prototype-builtins': 'off',
'no-underscore-dangle': 'off',
'no-plusplus': 'off',
'no-undef': 'warn',
'no-case-declarations': 'warn',
};
/** @type {import('eslint').Linter.FlatConfig[]} */
export default [
// js files
{
files: ['**/*.{mjs,cjs,js}'],
languageOptions: {
globals: {
...globals.es2021,
},
// ecmascriptVersion, and sourceType, default is right
},
// extends: ['plugin:prettier/recommended', 'airbnb-base'],
// plugins: {
// prettier: prettierPlugin
// },
rules: {
// ...eslintPluginPrettier.rules,
...jsRules,
},
},
// json files
// {
// files: jsonFiles,
// parser: 'jsonc-eslint-parser',
// extends: [
// 'plugin:jsonc/recommended-with-jsonc',
// 'plugin:prettier/recommended',
// ],
// plugins: ['prettier'],
// },
// prettier config all above this one are supposed to use prettier
...compat.extends('plugin:prettier/recommended'),
];
✨ Using flat config
style, with no FlatCompat
- We will use objects directly
- and inject things more adequately (Granular)
- give us better control
◌ Setting configs for the right block, right matching files
◌ and no magic likeFlatCompat
— —-
✨ The config
— — -
{
"extends": ["plugin:prettier/recommended"]
}
- So let’s check it
node_modules/eslint-plugin-prettier/eslint-plugin-prettier.js
const eslintPluginPrettier = {
configs: {
recommended: {
extends: ['prettier'],
plugins: ['prettier'],
rules: {
'prettier/prettier': 'error',
'arrow-body-style': 'off',
'prefer-arrow-callback': 'off',
},
},
},
— — -
✨ Let’s put it in Flat config
— — -
✨First the rules
rules
are allowed inflat config
◌ remaining the same
plugin.configs.recommended.rules
In our Eslint
config rules
prop of js files matching
block
rules: {
// ...eslintPluginPrettier.rules,
...jsRules,
...prettierPlugin.configs.recommended.rules
}
- we add it at the end because the rules are a disabling one
- And prettier rule is prettier one
◌ no overriding issue
Next the extends
- no
extends
is allowed inflat config
extends: ['prettier'],
- that translates to use what there is in
eslint-config-prettier
- so we check what
eslint-config-prettier
have to offer node_modules/eslint-config-prettier/index.js
rules
only- Checking the actual code source is better than just typing
Anywho. We can see. it just provides rules
- If it was providing more.
- We would follow in the same way
- and add from each
extends
module what they offer
Let’s add the rule then,
rules: {
...jsRules,
...prettierPlugin.configs.recommended.rules,
...eslintConfigPrettier.rules
},
▪️ We add it at the end again,
- because it’s a
disabling rules set
🔥
◌ meant to removeeslint
rules thatconflict
withprettier formatting
◌ last =>override
=>disable
👉 Quote of the day
When you understand things => it’s easy to reason
— — -
✨ Next the plugins
— — -
plugins: ['prettier'],
plugins
=> prefix witheslint-plugin-
=>eslint-plugin-prettier
- our module
- So now it comes to how the
plugins
are configured inFlat config
plugins: ["jsdoc"],
=>
plugins: {
jsdoc: jsdoc
},
▪️ usage as above is straightforward
- and suggesting no difference in
plugins
implementation - old plugins would work directly 🔥
◌ except for the config part that we already handled 🔥 - resources
◌ https://eslint.org/blog/2022/08/new-config-system-part-2/#more-powerful-and-configurable-plugins
◌ https://eslint.org/docs/latest/use/configure/migration-guide#importing-plugins-and-custom-parsers
◌ https://eslint.org/docs/latest/use/configure/configuration-files-new#using-plugins-in-your-configuration
▪️ What about creating a plugin
- https://eslint.org/docs/latest/extend/plugins
- Seems to remain the same 🔥
Now we can load our plugin
and check
▪️ if you are fully new
- you would expect it to work or not
- in case it doesn’t
◌ you know what to check
▶︎ in this case https://eslint.org/docs/latest/extend/plugins
- processor part
- speaking surely, if theplugin
in question doesn’t mention flat config in the doc yet
Let’s do it
adding
import prettierPlugin from 'eslint-plugin-prettier';
// ...
plugins: {
prettier: prettierPlugin
},
- That should do it. The whole config is
/** @type {import('eslint').Linter.FlatConfig[]} */
export default [
// js files
{
files: ['**/*.{mjs,cjs,js}'],
languageOptions: {
globals: {
...globals.es2021,
},
// ecmascriptVersion, and sourceType, default is right
},
// extends: ['plugin:prettier/recommended', 'airbnb-base'],
plugins: {
prettier: prettierPlugin
},
rules: {
...jsRules,
...prettierPlugin.configs.recommended.rules,
...eslintConfigPrettier.rules
},
},
// json files
// {
// files: jsonFiles,
// parser: 'jsonc-eslint-parser',
// extends: [
// 'plugin:jsonc/recommended-with-jsonc',
// 'plugin:prettier/recommended',
// ],
// plugins: ['prettier'],
// },
// prettier config all above this one are supposed to use prettier
// ...compat.extends('plugin:prettier/recommended'),
];
- cleaner
/** @type {import('eslint').Linter.FlatConfig[]} */
export default [
// js files
{
files: ['**/*.{mjs,cjs,js}'],
languageOptions: {
globals: {
...globals.es2021,
},
// ecmascriptVersion, and sourceType, default is right
},
plugins: {
prettier: prettierPlugin
},
rules: {
...jsRules,
...prettierPlugin.configs.recommended.rules,
...eslintConfigPrettier.rules
},
}
];
jsRules
◌ my rules for js, you can have yours
const jsRules = {
quotes: [
'error',
'single',
{ avoidEscape: true, allowTemplateLiterals: false },
],
// 'import/no-cycle': 'warn',
'class-methods-use-this': 'off',
// 'import/prefer-default-export': 'off',
'comma-dangle': 'off',
'object-curly-newline': 'off',
'operator-linebreak': 'off',
'implicit-arrow-linebreak': 'off',
'function-paren-newline': 'off',
'import/no-extraneous-dependencies': 'off',
'import/extensions': 'off',
'import/no-absolute-path': 'off',
'generator-star-spacing': 'off',
'no-prototype-builtins': 'off',
'no-underscore-dangle': 'off',
'no-plusplus': 'off',
'no-undef': 'warn',
'no-case-declarations': 'warn',
};
Let’s test
- reload window
eslint
server starting with no issue
◌ config OK ✅- let’s check on a file
- And bingo, working, perfect ✅
How do you name the plugins
- Check the resume (lessons) part
▪️ eslint-plugin-{name}
- name
{name}
▪️ @{scope}/eslint-plugin
- name
@{scope}
▪️ Otherwise if @{scope}/eslint-plugin-{some_name}
- name
@{scope}/{some_name}
Examples:
eslint-plugin-prettier
◌ nameprettier
@typescript-eslint/eslint-plugin
◌ name@typescript-eslint
- For the details check my write-up on the exploration of
FlatCompat
eslintrc
translation tool
◌ Deep dive Article
Which way should you pick up flat config
or FlatCompat
helpers
▪️ Your choice
- whatever works
▪️ But for me
- I wanted to understand well how things work
◌ we resumed this here in this article - And also, I want granular control, with no magic behind the scene
- I want to insert things in a specific block or blocks
- Without needing to have
eslint
do the merges - Still, either way, we can manage well
FlatCompat
▪️ You have to use eslint
merging
▪️ Set your plugins either after or above the one, depending on the plugins settings and what they do
disabling
propertiesplugins
◌ generally comes after (after to override and disable the one before)- activating and adding
rules
◌ they come before
◌ to make them a base - So either
◌ you make aconfig
a base
◌ Or as an overriding one
◌ decide depending on theplugin
and the logic is simple
- does it need to override
- or act as base
◌ order your configs in the right order
What about if you want to do some things for just some files 🔥
✨ What is the magic or the output of FlatCompact
- The output is basically multiple flat config objects that get appended to the object. You need to be careful and to understand relatively the overriding.
✨ So, how do we do it?
- Mainly if you need to apply a config only to some files using
FlatCompat
◌ The solution is to useoverrides
in theeslintrc config
[
// ...,
...flatCompat.config({
"overrides": [
{
"files": [
"src/**/*.{ts,mts,cts,tsx}"
],
"extends": [
"plugin:@typescript-eslint/recommended",
"airbnb-base",
"airbnb-typescript/base"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": ["./tsconfig.json"],
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"rules": {
"quotes": [
"error",
"single",
{ "avoidEscape": true, "allowTemplateLiterals": false }
],
"import/no-cycle": "warn",
"class-methods-use-this": "off",
"import/prefer-default-export": "off",
"comma-dangle": "off",
"no-underscore-dangle": "off",
"no-prototype-builtins": "off",
"object-curly-newline": "off",
"operator-linebreak": "off",
"implicit-arrow-linebreak": "off",
"function-paren-newline": "off",
"import/no-extraneous-dependencies": "off",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/comma-dangle": "off",
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/indent": "off",
"@typescript-eslint/no-loop-func": "off"
}
},
// JAVASCRIPT files configuration
{
"files": [
"./jest.config.ts"
],
"parserOptions": {
"project": ["./jest.tsconfig.json"],
"ecmaVersion": "latest",
"sourceType": "module"
}
},
{
"files": [
"e2e-tests/**/*.ts"
],
"parserOptions": {
"project": ["./e2e-tests.tsconfig.json"],
"ecmaVersion": "latest",
"sourceType": "module"
}
},
{
"files": [
"e2e-tests/**/*.test.js"
],
"env": {
"jest": true,
"jasmine": true
}
}
]
})
]
- You got the idea
FlatCompat
does manage theoverrides
- Personally, I would always prefer the
granular 100% FlatConfig
way ✅ 🔥
My other Eslint Prettier articles
Helpful