Refreshed Nuxt ESLint Integrations
TL;DR
We revamped our ESLint integrations to support ESLint v9 with the new flat config. Along the way, we have explored many new possibilities to make it more personalized, powerful, and with better developer experience.
You can run the following command to install the new @nuxt/eslint
module:
npx nuxi module add eslint
Continue reading the story or learn more with the documentation.
Background
ESLint has become an essential tool for today's web development. It helps you to catch errors and enforce a consistent coding style in your project. At Nuxt, we do our best to provide an out-of-the-box experience for ESLint, making it easy to use, configure and follow the best practices we recommend.
Since, both Nuxt and ESLint have evolved a lot. Historically, we ended up with a few different packages and integrations for ESLint in Nuxt, and it wasn't always obvious which one to use for what purpose. We have received a lot of feedback from our community.
To improve the situation and also make it future-proof, we recently refreshed our ESLint integrations to support ESLint v9 with the flat config. It opens up many more capabilities for customizing your ESLint setup, providing a more straightforward and unified experience.
Nuxt ESLint Monorepo
We moved ESLint-related packages scattered across different repositories into a single monorepo: nuxt/eslint
, with a dedicated new documentation site: eslint.nuxt.com.
To help understand the differences between each package and what to use, we also have a FAQ page comparing them and explaining their scopes.
This monorepo now includes:
@nuxt/eslint
- The new, all-in-one ESLint module for Nuxt 3, supporting project-aware ESLint flat config and more.@nuxt/eslint-config
- The unopinionated but customizable shared ESLint config for Nuxt 3. Supports both the flat config format and the legacy format.@nuxt/eslint-plugin
- The ESLint plugin for Nuxt 3 provides Nuxt-specific rules and configurations.- Two packages for Nuxt 2 in maintenance mode.
ESLint Flat Config
Before diving into new Nuxt integrations, let me introduce you to the concept of ESLint flat config.
Flat config is a configuration format introduced in ESLint v8.21.0
as experimental, and became the default format in ESLint v9.
A quick reference to differentiate:
- Flat config:
eslint.config.js
eslint.config.mjs
etc. - Legacy config:
.eslintrc
.eslintrc.json
.eslintrc.js
etc.
Why Flat Config?
This blog post from ESLint explains the motivation behind the flat config system in detail. In short, the legacy eslintrc
format was designed in the early time of JavaScript, when ES modules and modern JavaScript features were not yet standardized. Many implicit conventions involved, and the extends
feature makes the final config result hard to understand and predict. Which also makes shared configs hard to maintain and debug.
{
"extends": [
// Solve from `import("@nuxtjs/eslint-config").then(mod => mod.default)`
"@nuxtjs",
// Solve from `import("eslint-config-vue").then(mod => mod.default.configs["vue3-recommended"])`
"plugin:vue/vue3-recommended",
],
"rules": {
// ...
}
}
The new flat config moves the plugins and configs resolution from ESLint's internal convention to the native ES module resolution. This in turn makes it more explicit and transparent, allowing you to even import it from other modules. Since the flat config is just a JavaScript module, it also opens the door for much more customization.
Nuxt Presets for Flat Config
In the latest @nuxt/eslint-config
package, we leverage the flexibility we have to provide a factory function that allows you to customize the config presets easily in a more high-level way. Here is an example of how you can use it:
import { createConfigForNuxt } from '@nuxt/eslint-config/flat'
export default createConfigForNuxt()
@nuxt/eslint-config
starts with an unopinionated base config, which means we only include rules for best practices of TypeScript, Vue and Nuxt, and leave the rest like code style, formatting, etc for you to decide. You can also run Prettier alongside for formatting with the defaults.
The config also allows you to opt-in to more opinionated features as needed. For example, if you want ESLint to take care of the formatting as well, you can turn it on by passing features.stylistic
to the factory function (powered by ESLint Stylistic):
import { createConfigForNuxt } from '@nuxt/eslint-config/flat'
export default createConfigForNuxt({
features: {
stylistic: true
}
})
Or tweak your preferences with an options object (learn more with the options here):
import { createConfigForNuxt } from '@nuxt/eslint-config/flat'
export default createConfigForNuxt({
features: {
stylistic: {
semi: false,
indent: 2, // 4 or 'tab'
quotes: 'single',
// ... and more
}
}
})
And if you are authoring a Nuxt Module, you can turn on features.tooling
to enable the rules for the Nuxt module development:
import { createConfigForNuxt } from '@nuxt/eslint-config/flat'
export default createConfigForNuxt({
features: {
tooling: true
}
})
...and so on. The factory function in flat config allows the presets to cover the complexity of the underlying ESLint configuration, and provide high-level and user-friendly abstractions for end users to customize. All this without requiring users to worry about the internal details.
While this approach offers you a Prettier-like experience with minimal configurations (because it's powered by ESLint), you still get the full flexibility to customize and override fine-grained rules and plugins as needed.
We also made a FlatConfigComposer
utility from eslint-flat-config-utils
to make it even easier to override and extend the flat config. The factory function in @nuxt/eslint-config/flat
returns a FlatConfigComposer
instance:
import { createConfigForNuxt } from '@nuxt/eslint-config/flat'
export default createConfigForNuxt({
// ...options for Nuxt integration
})
.append(
// ...append other flat config items
)
.prepend(
// ...prepend other flat config items before the base config
)
// override a specific config item based on their name
.override(
'nuxt/typescript', // specify the name of the target config, or index
{
rules: {
// ...override the rules
'@typescript-eslint/no-unsafe-assignment': 'off'
}
}
)
// an so on, operations are chainable
With this approach, we get the best of both worlds: the simplicity and high-level abstraction for easy to use, and the power for customization and fine-tuning.
Nuxt ESLint Module
Taking this even further, we made the new, all-in-one @nuxt/eslint
module for Nuxt 3. It leverages Nuxt's context to generate project-aware and type-safe ESLint configurations for your project.
Project-aware Rules
We know that Vue's Style Guide suggests the use of multi-word names for components to avoid conflicts with existing and future HTML elements. Thus, in eslint-plugin-vue
, we have the rule vue/multi-word-component-names
enabled by default. It's a good practice to follow, but we know that in a Nuxt project, not all .vue
files are registered as components. Files like app.vue
, pages/index.vue
, layouts/default.vue
, etc. are not available as components in other Vue files, and the rule will be irrelevant to them.
Usually, we could turn off the rule for those files like:
export default [
{
files: ['*.vue'],
rules: {
'vue/multi-word-component-names': 'error'
}
},
{
files: ['app.vue', 'error.vue', 'pages/**/*.vue', 'layouts/**/*.vue'],
rules: {
// disable the rule for these files
'vue/multi-word-component-names': 'off'
}
}
]
It should work for the majority of the cases. However, we know that in Nuxt you can customize the path for each directory, and layers allow you to have multiple sources for each directory. This means the linter rules will be less accurate and potentially cause users extra effort to keep them aligned manually.
Similarly, we want to have vue/no-multiple-template-root
enabled only for pages
and layouts
, etc. As the cases grow, it becomes unrealistic to ask users to maintain the rules manually.
That's where the magic of @nuxt/eslint
comes in! It leverages Nuxt's context to generate the configurations and rules specific to your project structure. Very similar to the .nuxt/tsconfig.json
Nuxt provides, you now also have the project-aware .nuxt/eslint.config.mjs
to extend from.
To use it, you can add the module to your Nuxt project:
npx nuxi module add eslint
Config Inspector DevTools Integrations
During the migrations and research for the new flat config, I came up with the idea to make an interactive UI inspector for the flat config and make the configurations more transparent and easier to understand. We have integrated it into Nuxt DevTools when you have the @nuxt/eslint
module installed so you easily access it whenever you need it.
The inspector allows you to see the final resolved configurations, rules and plugins that have been enabled, and do quick matches to see how rules and configurations have applied to specific files. It's great for debugging and learning how ESLint works in your project.
We are delighted that the ESLint team also finds it useful and is interested in having it for the broader ESLint community. We later joined the effort and made it the official ESLint Config Inspector (it's built with Nuxt, by the way). You can read this announcement post for more details.
Type Generation for Rules
One of the main pain points of configuring ESLint was the leak of type information for the rules and configurations. It's hard to know what options are available for a specific rule, and it would require you to jump around the documentation for every rule to figure that out.
Thanks again for the new flat config being dynamic with so many possibilities. We figured out a new tool, eslint-typegen
, that we could generate the corresponding types from rules configuration schema for each rule based on the actual plugins you are using. This means it's a universal solution that works for any ESLint plugins, and the types are always accurate and up-to-date.
In the @nuxt/eslint
module, this feature is integrated out-of-box, so that you will get this awesome experience right away:
Dev Server Checker
With the new module, we took the chance to merge the @nuxtjs/eslint-module
and the dev server checker for ESLint into the new @nuxt/eslint
module as an opt-in feature.
To enable it, you can set the checker
option to true
in the module options:
export default defineNuxtConfig({
modules: [
'@nuxt/eslint'
],
eslint: {
checker: true // <---
}
})
Whenever you get some ESLint errors, you will see a warning in the console and the browser. To learn more about this feature, you can check the documentation.
Module Hooks
Since we are now in the Nuxt module with the codegen capabilities and the project-aware configurations, we can actually do a lot more interesting things with this. One is that we can allow modules to contribute to the ESLint configurations as well. Imagine that in the future, when you install a Nuxt module like @nuxtjs/i18n
it can automatically enable specific ESLint rules for i18n-related files, or when you install something like @pinia/nuxt
it can install the Pinia ESLint plugin to enforce the best practices for Pinia, etc.
As an experiment, we made a module nuxt-eslint-auto-explicit-import
that can do auto inserts for the auto-imports registered in your Nuxt project with a preconfigured ESLint preset. So that you can get the same nice developer experience with auto-imports on using APIs, but still have the auto-inserted explicit imports in your codebase.
This is still in the early stages, and we are still exploring the possibilities and best practices. But we are very excited about the potential and the opportunities it opens up. We will collaborate with the community to see how we can make the most out of it. If you have any ideas or feedback, please do not hesitate to share them with us!
Ecosystem
At Nuxt, we care a lot about the ecosystem and the community as always. During our explorations to adopt the new flat config and improve the developer experience, we made quite a few tools to reach that goal. All of them are general-purposed and can be used outside of Nuxt:
@eslint/config-inspector
- The official ESLint Config Inspector, provides interactive UI for your configs.eslint-typegen
- Generate TypeScript types for ESLint rules based on the actual plugins you are using.eslint-flat-config-utils
- Utilities for managing and composing ESLint flat configs.
We are committed to supporting the broader community and collaborating with developers to improve these tools and expand their possibilities. We are excited to see how these tools can benefit the ESLint ecosystem and contribute to the overall developer experience.
The Future
Looking ahead, we are eager to see how the ESLint ecosystem will continue to evolve and how we can leverage new capabilities and possibilities to further enhance Nuxt's developer experience. We are dedicated to providing a seamless and powerful development environment for Nuxt users, and we will continue to explore new ideas and collaborate with the community to achieve this goal.