@moso/eslint-config
v3.0.3
Published
@moso's sharable ESLint config
Maintainers
Readme
@moso/eslint-config
Flat ESLint config for JavaScript, TypeScript, Vue, React, and more.
Features
- ESLint Flat config with reasonable but opinionated defaults
- Stylistic with single quotes, semi enabled, sorted imports, and dangling commas
- Aimed to be used without Prettier
- Designed to work with JSX, TypeScript, Vue, React, Astro, Nuxt, and Next.js out of the box
- Applies Functional, Perfectionist, and e18e by default
- Lints for JSDoc, JSON, RegEx, TOML, and YAML
- Ships its own small
@moso-ruleset with security and hygiene rules - Every config can be enabled/disabled
- Respects
.gitignoreby default - Requires Node.js >= v22.23.x/v24.18.x, ESLint v10+
[!NOTE] Note regarding TypeScript v7 See the FAQ
Configs
This section contains a list of the plugins used in the named configs that ships with this shareable config.
- ✅ Enabled by default
- ☑️ Enabled by auto-detect
- 🌟 Enabled unless lessOpinionated: true
- 🟨 Can be enabled manually
- 🎨 Stylistic rules enabled
- 💭 Type-aware rules available by setting the path to your tsconfig.json
- ℹ️ Accessibility rules available with a11y: true
eslint-plugin-astro, astro-eslint-parser, eslint-plugin-jsx-a11y
@eslint/js, eslint-plugin-de-morgan, eslint-plugin-unused-imports
@stylistic/eslint-plugin, eslint-plugin-jsx-a11y
@eslint-react, @eslint-react/dom, @eslint-react/hooks-extra, @eslint-react/naming-convention, @eslint-react/web-api, eslint-plugin-react-hooks, eslint-plugin-react-you-might-not-need-an-effect, eslint-plugin-jsx-a11y
@vitest/eslint-plugin, eslint-plugin-no-only-tests
@typescript-eslint, eslint-plugin-erasable-syntax-only
eslint-plugin-vue, eslint-plugin-vuejs-accessibility
Custom rules
This config ships its own small plugin, registered as @moso. Each rule has full documentation next to its implementation.
| Rule | Description | Default | Fixable |
| --- | --- | --- | --- |
| avoid-barrel-files | Disallow authoring barrel files | ✅ | |
| no-bidi | Detect and stop trojan source attacks | ✅ | 🔧 |
| no-force-cast-via-top-type | Disallow T as any as Q casts | ✅ | |
| no-import-duplicates | Fix duplicate specifiers within an import | ✅ | 🔧 |
| no-import-from-dist | Prevent importing from dist folders | ✅ | |
| no-import-node-modules-by-path | Prevent importing node_modules by path | ✅ | |
| no-invisible-characters | Disallow invisible characters | ✅ | 🔧 |
| no-redundant-variable | Disallow variables that are immediately returned | | 🔧 |
| no-string-interpolation | Disallow multiline expressions in template literals | | |
| no-top-level-await | Prevent top-level await | ✅ | |
| no-unneeded-array-flat-map | Disallow flatMap with identity callbacks | | 🔧 |
| prefer-early-return | Prefer guard clauses over wrapped function bodies | | 🔧 |
| prefer-fetch | Enforce fetch over legacy HTTP clients | | |
| prefer-reduce-over-chaining | Prefer one .reduce() pass over .map().filter() chains | ✅ | |
Usage
Install
[!NOTE] I like to use Bun because it's hella fast. Thus all the install instructions are with Bun. If you use something else, check the syntax with your favorite package manager.
bun add -d eslint @moso/eslint-configCreate eslint.config.js in the root of your project:
// eslint.config.js
import moso from '@moso/eslint-config';
export default moso();If you still use some configs from the legacy eslintrc format, you can use the @eslint/eslintrc package to convert them to the flat config.
// eslint.config.js
import moso from '@moso/eslint-config';
import { FlatCompat } from '@eslint/eslintrc';
const compat = new FlatCompat()
export default moso(
{
ignores: [],
},
// Legacy config
...compat.config({
extends: [
'eslint:recommended',
// Other extends...
],
})
// Other flat configs...
);[!NOTE]
.eslintignoreno longer works in Flat config, see customization for more details.
Add script for package.json
For example:
{
"scripts": {
"lint": "eslint",
"lint:fix": "eslint --fix"
}
}VS Code support (auto fix)
Install VS Code ESLint extension
Add the following settings to your .vscode/settings.json:
{
// Disable the default formatter, use eslint instead
"prettier.enable": false,
"editor.formatOnSave": false,
// Auto fix
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},
// You can silent specific rules in you IDE, but still auto fix them
"eslint.rules.customizations": [
{ "rule": "@stylistic/*", "severity": "off", "fixable": true },
{ "rule": "*-indent", "severity": "off", "fixable": true },
{ "rule": "*-spacing", "severity": "off", "fixable": true },
{ "rule": "*-spaces", "severity": "off", "fixable": true },
{ "rule": "*-order", "severity": "off", "fixable": true },
{ "rule": "*-dangle", "severity": "off", "fixable": true },
{ "rule": "*-newline", "severity": "off", "fixable": true },
{ "rule": "*quotes", "severity": "off", "fixable": true },
{ "rule": "*semi", "severity": "off", "fixable": true }
],
// Enable eslint for all supported languages
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"json",
"jsonc",
"yaml"
]
}Customization
Since v1.0.0, this config has been migrated to ESLint Flat config. It provides much better organization and composition. And speed!
Normally you only need to import the moso preset:
// eslint.config.js
import moso from '@moso/eslint-config';
export default moso();Configuration Options
Configure integrations by passing options to the main function:
// eslint.config.js
import moso from '@moso/eslint-config';
export default moso({
// Stylistic rules (enabled by default)
stylistic: false, // disable entirely
stylistic: {
indent: 4, // 2, or 'tab'
jsx: true, // or false to disable JSX support
quotes: 'single', // or 'double'
semi: true, // or false
},
// Auto-detected, or enable explicitly
astro: true,
react: true,
typescript: true,
vue: true,
// JSDoc support
jsdoc: true, // enable explicitly with reasonable defaults
// File format support
jsonc: false,
yaml: false,
// Replaces .eslintignore (no longer supported)
ignores: [
'**/dist',
'**/node_modules',
],
});Overriding Rules
Pass additional flat config objects as arguments. Rules are scoped to specific file types:
// eslint.config.js
import moso from '@moso/eslint-config';
export default moso(
{
// First argument: moso config options
vue: true,
typescript: true,
},
{
// Additional arguments: standard ESLint flat configs
files: ['**/*.vue'],
rules: {
'vue/multi-word-component-names': ['error', { ignores: [] }],
},
},
{
// You can pass multiple config objects
files: ['**/*.ts'],
rules: {
'@typescript-eslint/no-explicit-any': 'warn',
},
},
);Advanced Composition
The config returns a FlatConfigComposer object from eslint-flat-config-utils, enabling chainable methods:
// eslint.config.js
import moso from '@moso/eslint-config';
export default moso()
.prepend({
// Configs before the main config
})
.override('moso/javascript/rules', {
rules: {
'no-var': 'off',
},
})
.removeRules(
'no-console',
'no-debugger',
);You can import and compose individual configs directly. Only use this if you need granular control:
import {
combine,
comments,
ignores,
imports,
javascript,
jsdoc,
jsonc,
node,
sortPackageJson,
sortTsconfig,
stylistic,
typescript,
unicorn,
vue,
yaml,
} from '@moso/eslint-config';
export default combine(
comments(),
ignores(),
imports(),
javascript(/* options */),
jsdoc(),
jsonc(),
node(),
stylistic(/* options */),
typescript(/* options */),
unicorn(),
vue(/* options */),
yaml(),
);Optional configs
JSDoc
Disabled by default, will have to be enabled manually.
There are many ways JSDoc support can be configured. However, the most simple way is just enabling it which will use very reasonable defaults:
// eslint.config.js
import moso from '@moso/eslint-config';
export default moso({
jsdoc: true,
});This will enable the preset recommended, which is recommended starting rules for enforcing proper tag values, common tags exists, and tags are formatted and styled consistently.
Individual rules can be tweaked with the overrides property:
{
jsdoc: {
overrides: {
'jsdoc/require-description': 'off',
},
},
}Frameworks
Framework support is auto-detected based on installed packages, but can be enabled explicitly. Dependencies are not bundled and will be prompted for installation.
[!NOTE] Note: Framework support is just as opinionated as the rest of the config. If your favorite framework is missing, then you can always extend the config yourself.
Astro
Auto-detected if you have Astro installed. Enable explicitly:
// eslint.config.js
import moso from '@moso/eslint-config';
export default moso({
astro: true,
});Install dependencies when prompted, or manually:
bun add --dev eslint-plugin-astro astro-eslint-parser[!NOTE] Since Astro can be plugged with other frameworks as well, you'll be prompted to install the required packages for linting those as well. Example: If you have installed
@astrojs/react, you'll be prompted to install the required packages to lint React.
React
Auto-detected if you have React, Next.js, Nextra, Remix, Gatsby, or the @astrojs/react Astro framework integration installed. Enable explicitly:
// eslint.config.js
import moso from '@moso/eslint-config';
export default moso({
react: true,
});Install dependencies when prompted, or manually:
bun add --dev @eslint-react/eslint-plugin eslint-plugin-react-hooks eslint-plugin-react-refresh eslint-plugin-react-you-might-not-need-an-effectVue
Auto-detected if you have Vue, Nuxt, VitePress, or the @astrojs/vue Astro framework intefration installed. Enable explicitly:
// eslint.config.js
import moso from '@moso/eslint-config';
export default moso({
vue: true,
});Install dependencies when prompted, or manually (the Vue parser and SFC processors are already bundled as dependencies):
bun add --dev eslint-plugin-vue[!CAUTION] Since Vue 2 has reached EOL, this config does not support Vue 2. If you need to support for Vue 2, you'll need to disable the imported configs from Vue 3, and replace them with the Vue 2 ones. You can see an inspiring example on
eslint-plugin-vue. I recommend upgrading to Vue 3 if possible.
Typed Linting
You can optionally enable typed linting. These are also known as "rules that require types", or simply "type-aware rules". This enables for much deeper insight into your code.
You enable them by passing the path of your tsconfig.json to the projectRoot option.
[!WARNING] Enabling these rules will come with a slight performance cost, explained here.
To make things even more complicated, if you have enabled typed linting but disabled
@stylistic, it will also disable the type-aware rules considered stylistic.
// eslint.config.js
import moso from '@moso/eslint-config';
export default moso(
{
projectRoot: import.meta.dirname,
// If you wish to override any of these rules, use `overridesTypeAware`:
typescript: {
overridesTypeAware: {
'@typescript-eslint/no-deprecated': 'off',
},
},
},
);[!NOTE] TypeScript will still be auto-detected if you have the
typescript-package installed, and any non-type-aware@typescript-eslint-rules will still be applied. Typed Linting is considered as an extra option.
Disable type-aware
You can disable the type-aware layer entirely, or exclude certain globs or specific files from it:
// eslint.config.js
import moso from '@moso/eslint-config';
export default moso({
projectRoot: import.meta.dirname,
typescript: {
// Kill switch: keep regular TypeScript rules, drop the type-aware layer
disableTypeAwareRules: true,
// ...or keep it, but exclude globs or specific files from it
ignoresTypeAware: [
'**/*.{js,jsx}',
'**/components/User.tsx',
],
},
});Editor specific non-fixes
Some rules are deemed as 'non-fixable' when inside your editor with ESLint integration:
Before v1.0.0, they used to be hard disabled. But with a helper, they are now just marked as 'non-fixable'. They are re-applied when you're linting through a terminal, or by using Lint Staged.
// eslint.config.js
import moso from '@moso/eslint-config';
export default moso({
isInEditor: false,
});Lint Staged
Linting and auto-fixing before every commit is easy, you just add the following to your package.json:
{
"simple-git-hooks": {
"pre-commit": "bunx lint-staged"
},
"nano-staged": {
"*": "eslint --fix"
}
}and then
bun add --dev nano-staged simple-git-hooks
# to activate the hooks
bunx simple-git-hooksFAQ
ESLint v9, older Node.js versions?
ESLint v9 will reach EOL on August 6th, 2026. The world of development moves fast, and as with Vue v2.x, I don't intend to support versions that has reached EOL unless absolutely necessary. The same goes with older Node.js versions.
Even though ESLint v10 supports Node.js v20.19.0 (as of current status), this version has also reached EOL. The rest of their requirements mention v22.13.0 and >= 24. However, the minimum requirements of some of the configs that this sharable config apply rules from have a minimum requirement of v22.22.3, this project will stay within this range.
TypeScript 7?
TypeScript 7.0 does not ship a compiler API (a new one supposedly arrives in 7.1), so typescript-eslint - and therefore all TypeScript linting in this config - cannot run against it. TypeScript's supported setup is running TypeScript 6 side-by-side through the @typescript/typescript6 compatibility package, aliased so tooling that imports typescript keeps working:
bun add --dev typescript@npm:@typescript/typescript6Optionally keep TypeScript 7's own tsc around as well:
{
"devDependencies": {
"@typescript/native": "npm:typescript@^7.0.2",
"typescript": "npm:@typescript/typescript6@^6.0.2"
}
}With the alias in place, this config works unchanged. Without it, the config detects TypeScript >7 and fails fast: an explicit typescript: true throws with these instructions, while auto-detection prints a warning and degrades to JavaScript-only linting instead of crashing inside the parser.
Keep in mind, this is temporary until an API is provided by TypeScript for v7.
I want it less opinionated
No problem. I've extracted the things that I've deemed very opinionated in each integration, and made a setting that helps you disable all of it in one go.
// eslint.config.js
import moso from '@moso/eslint-config';
export default moso({
lessOpinionated: true,
});[!NOTE] The above will also disable
functionalandperfectionistcompletely. If you want to keep these enabled, you'll have to re-enable them explicitly, as demonstrated below
{
// Disable opinionated rules
lessOpinionated: true,
// Re-enable functional - a string sets the enforcement level directly
functional: 'lite', // 'lite' | 'recommended' | 'strict'
// ...or as an object:
functional: {
functionalEnforcement: 'recommended',
},
// Re-enable `perfectionist`
perfectionist: true,
}Prettier? dprint?
You can still use these to format files that aren't linted with this config, however, I strongly recommend you only format your code with ESLint, as Prettier and other AST-reading-then-reprint projects tend to ignore stuff like the original line breaks and might also cause inconsistent diffs when committing code.
How to format CSS?
You will need to install and configure stylelint yourself, unfortunately.
I am actively considering adding linting support for TailwindCSS, however.
I prefer this or that rule
No worries, you can always override the rules locally in your project to fit your needs. If that doesn't cut it, you're welcome to fork this project and maintain your own config.
Inspiration
This ESLint config is heavily inspired by (and uses some of the same logic of):
- @antfu/eslint-config - @rebeccastevens/eslint-config - @eslint-sukka/eslint-config
Anthony Fu's config inspired me to take the journey, but this project has since evolved into a personal project. Especially Rebecca's config and strict(er) approach to coding has pushed me towards a stricter coding environment, with Functional and Azat's Perfectionist and De Morgan enabled.
A personal thank-you to Anthony, Rebecca, Sukka, and Azat, and everyone else who contributed to their projects.
Some rules are the same, however, there are some differences:
- My own opinionated rules added
- Stylistic, Perfectionist, and Functional enabled per default
- More freedom to disable every individual config, or only disable the opinionated parts
- Deprecated Vue 2 support
- Simplification in some areas
- Better type-aware checks
- Plugin memoization
- Optional a11y support for JSX, React and Vue
- No dangerous plugin renaming (except for
node)
License
BSD 3-Clause License
