@claylevering/eslint-config
v2.0.22
Published
A set of ESLint configurations supporting flat config format with Vue (and with TypeScript), Node, and TypeScript rules
Maintainers
Readme
@claylevering/claxxon-lint
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.
TOC
- @claylevering/claxxon-lint
Features
- 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
Installation
Base Installation
All projects require the base package and ESLint:
pnpm add -D @claylevering/eslint-config eslintFramework-Specific Dependencies
Depending on which configuration you're using, install the appropriate peer dependencies:
For Node.js/JavaScript Only
No additional dependencies needed! The base installation is sufficient.
For TypeScript Projects
pnpm add -D typescript-eslintFor Vue 3 Projects
pnpm 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.
For Nuxt Projects
pnpm add -D @nuxt/eslint eslint-plugin-vue vue-eslint-parser typescript-eslintUsage
All examples use the defineConfig() helper from the ESLint package to create flat config arrays.
Node.js/JavaScript Projects
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
}
]);TypeScript Projects
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
}
]);Vue 3 Projects
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
}
]);Framework configurations
When you're dealing with specific frameworks (e.g. Nuxt) simply spreading the configurations may not always work.
Nuxt projects
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:
Nuxt Configuration
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
]);Using Custom Rules
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'
}
}
];Prettier note
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:
Vue w/Prettier
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
]);Nuxt + Prettier
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
]);Custom Rule Definitions
pinia-store-top-level
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
}no-switch-statements
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';
}no-vue-global-imports
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'; // ✅ OKpinia-store-pattern
Enforces defining stores as variables before accessing properties.
Invalid:
const userId = useMyPiniaStore().id; // ❌ ErrorValid:
const myPiniaStore = useMyPiniaStore();
const userId = userStore.id;Built-in Rule Configurations
padding-line-between-statements (warn, auto-fixable)
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({ ... });
}
}
};Requirements
- 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).
License
MIT © Clay Levering
