A comprehensive ESLint plugin providing shareable configurations in ESLint flat config format (ESLint 9+) with support for Vue, Node.js, and TypeScript projects. Includes custom rules for Vue/Pinia development patterns.
- @claylevering/claxxon-lint
- ESLint 9+ Flat Config Format - Modern array-based configuration
- Multiple Presets - Choose configurations for your stack:
- Node.js/JavaScript
- TypeScript
- Vue 3
- Vue 3 + TypeScript
- Nuxt 4
- Nuxt 4 + TypeScript
- Custom Rules - Specialized rules for Vue and Pinia development
- Opinionated Defaults - Battle-tested rules for production applications
All projects require the base package and ESLint:
pnpm add -D @claylevering/eslint-config eslintDepending on which configuration you're using, install the appropriate peer dependencies:
No additional dependencies needed! The base installation is sufficient.
pnpm add -D typescript-eslintpnpm add -D eslint-plugin-vue vue-eslint-parser typescript-eslintNote: typescript-eslint is required even for pure JavaScript Vue projects to support the hybrid parser that allows mixing JS and TS components.
pnpm add -D @nuxt/eslint eslint-plugin-vue vue-eslint-parser typescript-eslintAll examples use the defineConfig() helper from the ESLint package to create flat config arrays.
Create eslint.config.js in your project root:
import { defineConfig } from 'eslint/config';
import claxxonLint from '@claylevering/eslint-config';
export default defineConfig([
...claxxonLint.configs['node'],
{
// Your custom configurations here
}
]);import { defineConfig } from 'eslint/config';
import claxxonLint from '@claylevering/eslint-config';
export default defineConfig([
...claxxonLint.configs['typescript'], // Includes Node config + TypeScript rules
{
// Your custom configurations here
}
]);The vue config supports hybrid JavaScript and TypeScript Vue components in the same project:
import { defineConfig } from 'eslint/config';
import claxxonLint from '@claylevering/eslint-config';
export default defineConfig([
...claxxonLint.configs['vue'], // Supports both JS and TS Vue components + standalone .ts files
{
// Your custom configurations here
}
]);When you're dealing with specific frameworks (e.g. Nuxt) simply spreading the configurations may not always work.
The eslint Nuxt plugin loads the eslint-plugin-vue
plugin during their withNuxt() method and eslint likes to barf if differing / conflicting plugins exist from
within the same configuration. I suppose you could leverage the FlatConfigComposer / etc. from the
Nuxt plugin itself but the whole intention of
this particular library is to make this as dead-ass simple as possible for config. So here you go:
import claxxonLint from '@claylevering/eslint-config';
import withNuxt from './.nuxt/eslint.config.mjs';
const claxxonNuxtConfig = claxxonLint.configs['nuxt'];
export default withNuxt({
// Your custom config options / objects
}).prepend([
/**
* Prepend the custom Claxxon configurations. This includes Node and Vue
* configurations but WITHOUT the Vue plugin itself so Nuxt/ESLint don't conflict.
* Supports both JavaScript and TypeScript Vue components.
*/
...claxxonNuxtConfig
]);All configurations include custom rules automatically. To use individual custom rules:
import claxxonLint from '@claylevering/eslint-config';
export default [
{
plugins: {
claxxon: claxxonLint
},
rules: {
'claxxon/pinia-store-top-level': 'error',
'claxxon/no-switch-statements': 'warn',
'claxxon/no-vue-global-imports': 'error',
'claxxon/pinia-store-pattern': 'error'
}
}
];While these rules are already opinionated-AF, I recognize that some folks don't love Prettier and just want some established rules for safety (which is totally fine). That being said - if you wanted to add the eslint-config-prettier to your config so that those two place nicely with each other, just have it be the last thing in your config. Here's how:
import claxxonLint from '@claylevering/eslint-config';
import eslintConfigPrettier from 'eslint-config-prettier/flat';
export default defineConfig([
...claxxonLint.configs['vue'], // For Vue SFC files
{
// Your custom configurations here
},
eslintConfigPrettier // Prettier goes last to disable conflicting rules
]);import claxxonLint from '@claylevering/eslint-config';
import withNuxt from './.nuxt/eslint.config.mjs';
import eslintConfigPrettier from 'eslint-config-prettier/flat';
const claxxonNuxtConfig = claxxonLint.configs['nuxt'];
export default withNuxt({
// Your custom config options / objects
})
.prepend([
/**
* Prepend the custom Claxxon configurations. This includes Node and Vue
* configurations but WITHOUT the Vue plugin itself so Nuxt/ESLint don't conflict.
* Supports both JavaScript and TypeScript Vue components.
*/
...claxxonNuxtConfig
])
.append([
// Prettier goes last to disable conflicting rules
eslintConfigPrettier
]);Enforces Pinia store definitions only at top-level scope to prevent stores being created in loops or conditionals.
Valid:
// Top-level in script setup
const myPiniaStore = useMyPiniaStore();
// In setup function
export default {
setup() {
const myPiniaStore = useMyPiniaStore();
}
};Invalid:
// Inside conditional
if (condition) {
const myPiniaStore = useMyPiniaStore(); // ❌ Error
}
// Inside loop
for (let i = 0; i < 10; i += 1) {
const store = useMyStore(); // ❌ Error
}Disallows switch statements to prevent fallthrough bugs and improve readability.
Invalid:
switch (
value // ❌ Error
) {
case 1:
return 'one';
default:
return 'other';
}Valid:
if (value === 1) {
return 'one';
} else {
return 'other';
}Prevents importing Vue 3 compiler macros (defineProps, defineEmits, defineExpose, defineOptions, defineSlots, defineModel, withDefaults) that are automatically available in <script setup>. This is a complementary rule to vue/no-import-compiler-macros.
Note: defineComponent and defineAsyncComponent are not flagged as they are runtime functions that must be imported.
Invalid:
import { defineProps, defineEmits } from 'vue'; // ❌ ErrorValid:
// Compiler macros: just use them directly in <script setup>
const props = defineProps({...});
const emit = defineEmits(['update']);
// Runtime functions: these must be imported
import { defineComponent, defineAsyncComponent } from 'vue'; // ✅ OKEnforces defining stores as variables before accessing properties.
Invalid:
const userId = useMyPiniaStore().id; // ❌ ErrorValid:
const myPiniaStore = useMyPiniaStore();
const userId = userStore.id;Requires blank lines around block statements (if, for, while, switch, try) for improved readability. This prevents code from being too compressed and makes control flow easier to scan.
Before (triggers warning):
const onClickEvent = () => {
if (!myMovieRef.value) {
return;
}
if (myMovieRef.value?.title_id) {
const title = myMovieRef.value?.title;
if (title) {
modalStore.openModal({ ... });
}
}
};After (auto-fixed):
const onClickEvent = () => {
if (!myMovieRef.value) {
return;
}
if (myMovieRef.value?.title_id) {
const title = myMovieRef.value?.title;
if (title) {
modalStore.openModal({ ... });
}
}
};- ESLint 9.0.0 or higher (peer dependency, always required)
- Node.js 18 or higher (recommended)
- Framework-specific packages (optional peer dependencies):
typescript-eslint>= 8.0.0 for TypeScript supporteslint-plugin-vue>= 10.0.0 for Vue supportvue-eslint-parser>= 10.0.0 for Vue support@nuxt/eslint>= 1.0.0 for Nuxt support
Install only the packages you need for your project type (see Installation section above).
MIT © Clay Levering
https://github.com/claylevering/claxxon-lint