Nuxt에서 코딩 컨벤션을 위해 ESLint 와 Prettier를 설정하며 겪었던 과정입니다.
처음에는 Nuxt에서 제공하는 ESLint 모듈이 있는지 몰라 일반적인 방법으로 ESLint를 설치했습니다.
pnpm add -D prettier eslint eslint-plugin-nuxt eslint-config-prettier
또한 ESLint가 v9로 업데이트되면서 설정파일이 기존에 .eslintrc 에서 eslint.config.js로 변경되었으며 Flat Config 시스템으로 변경되었습니다.
기존에 사용하던 설정 파일을 사용하니 사용되지 않는 options라며 아래와 같은 오류가 많이 발생했습니다.
~~% npx eslint .
Oops! Something went wrong! :(
ESLint: 9.19.0
A config object is using the "root" key, which is not supported in flat config system.
Flat configs always act as if they are the root config file, so this key can be safely removed.
==========================================
A config object is using the "env" key, which is not supported in flat config system.
Flat config uses "languageOptions.globals" to define global variables for your files.
ESLint 9부터 env, parser, parserOptions가 Flat Config 시스템에서 지원되지 않으며,
대신 languageOptions.globals와 languageOptions.parser를 사용해야 합니다.
설정한 내용은 아래와 같습니다.
import globals from 'globals';
import tseslint from 'typescript-eslint';
import tsParser from '@typescript-eslint/parser';
import vueParser from 'vue-eslint-parser';
import vuePlugin from 'eslint-plugin-vue';
import tsEslintPlugin from '@typescript-eslint/eslint-plugin';
import prettierConfig from 'eslint-config-prettier';
export default {
files: ['**/*.{vue,ts,js}'],
languageOptions: {
parser: vueParser,
parserOptions: {
parser: tsParser,
sourceType: 'module',
},
globals: {
...globals.browser,
...globals.node,
definePageMeta: 'readonly',
defineNuxtConfig: 'readonly',
defineNuxtPlugin: 'readonly',
useRuntimeConfig: 'readonly',
defineNuxtRouteMiddleware: 'readonly',
navigateTo: 'readonly',
},
},
plugins: {
vue: vuePlugin,
'@typescript-eslint': tsEslintPlugin,
},
rules: {
...tsEslintPlugin.configs.recommended.rules,
...tseslint.configs.recommended.rules,
...vuePlugin.configs['vue3-recommended'].rules,
...prettierConfig.rules,
'vue/multi-word-component-names': 'off',
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-undef': 'warn',
'no-unused-vars': 'warn',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-unused-expressions': 'warn',
// '@typescript-eslint/ban-types': 'warn',
'no-empty': 'warn',
'@typescript-eslint/no-empty-function': 'warn',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/naming-convention': [
'warn',
{
selector: 'enum',
format: ['PascalCase'],
},
{
selector: 'function',
format: ['camelCase'],
},
{
selector: 'interface',
format: ['PascalCase'],
custom: {
regex: '^I[A-Z]',
match: true,
},
},
{
selector: 'typeLike',
format: ['PascalCase'],
custom: {
regex: '^T[A-Z]',
match: true,
},
},
{
selector: 'typeParameter',
format: ['PascalCase'],
},
{
selector: 'class',
format: ['PascalCase'],
},
],
},
ignores: [
'**/node_modules/**',
'**/.nuxt/**',
'**/.output/**',
'**/dist/**',
'**/static/**',
'**/public/**',
'**/pnpm-lock.yaml',
'**/*.log',
'**/*.tmp',
'**/.env',
'**/.env.*',
],
};
나중에 Nuxt에서 공식적으로 제공하는 @nuxt/eslint 모듈이 있다는 걸 알게 됐습니다.
이 모듈은 Nuxt에 특화된 설정을 내장하고 있어, 따로 globals를 설정하지 않아도 됩니다.
https://eslint.nuxt.com/packages/module
pnpm add -D @nuxt/eslint eslint typescript
nuxt.config.ts에서 모듈 추가:
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@nuxt/eslint'
],
eslint: {
// options here
}
})
withNuxt를 이용해 커스텀 세팅을 할 수 있습니다.
기존에 nuxt에서 eslint를 사용하기 위해 readonly로 세팅했던 옵션들을 정리할 수 있었습니다.
import withNuxt from './.nuxt/eslint.config.mjs';
export default withNuxt()
.append({
rules: {
'no-undef': 'warn',
'no-unused-vars': 'warn',
'no-empty': 'warn',
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'vue/multi-word-component-names': 'off',
'vue/attribute-hyphenation': 'off',
'vue/html-self-closing': [
'warn',
{
html: {
void: 'always', // void elements (br, hr, img 등)는 항상 self-closing
normal: 'never', // 일반 HTML elements는 self-closing 사용하지 않음
component: 'always', // Vue components는 항상 self-closing
},
svg: 'always',
math: 'always',
},
],
},
})
.override('nuxt/typescript/rules', {
rules: {
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-unused-expressions': 'warn',
'@typescript-eslint/no-empty-function': 'warn',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/naming-convention': [
'warn',
{
selector: 'enum',
format: ['PascalCase'],
},
{
selector: 'function',
format: ['camelCase'],
},
{
selector: 'interface',
format: ['PascalCase'],
custom: {
regex: '^I[A-Z]',
match: true,
},
},
{
selector: 'typeLike',
format: ['PascalCase'],
custom: {
regex: '^T[A-Z]',
match: true,
},
},
{
selector: 'typeParameter',
format: ['PascalCase'],
},
{
selector: 'class',
format: ['PascalCase'],
},
],
},
});
{
"printWidth": 100, // 한줄 최대 길이
"tabWidth": 2, // 기본 공백 크기
"useTabs": false, // 탭 대신 공백을 사용할지 여부
"semi": true, // 코드 끝 세미콜론
"singleQuote": true, // 싱글 쿼트(') 사용
"quoteProps": "as-needed", // 객체의 속성 이름에 쿼테이션("")을 사용할지
"trailingComma": "es5", // 객체나 배열 등에서 마지막 요소 뒤에 쉼표를 추가할지
"bracketSpacing": true, // 중괄호와 내용 사이의 공백
"jsxBracketSameLine": false, //
"arrowParens": "always", // 화살표 함수의 매개변수에 괄호를 추가할지
"proseWrap": "preserve", // 마크다운 파일에서 텍스트를 자동 줄바꿈할지
"htmlWhitespaceSensitivity": "css", // HTML 파일에서 공백 처리를 설정
"vueIndentScriptAndStyle": false, // Vue 파일의 <script> 및 <style> 태그를 들여쓰기
"endOfLine": "auto" // 줄바꿈 방식 설정
}
| 옵션 | 설명 | 예시/기본값 |
|---|---|---|
| printWidth | 한 줄의 최대 길이 | 80 |
| tabWidth | 들여쓰기 공백 수 | 2 |
| semi | 세미콜론 사용 여부 | true (사용) |
| singleQuote | 싱글 쿼트(') 사용 여부 | false |
| quoteProps | 객체 속성 따옴표 처리 | as-needed |
| trailingComma | 마지막 쉼표 추가 여부 | es5 |
| bracketSpacing | 중괄호와 내용 사이의 공백 | true |
| arrowParens | 화살표 함수 괄호 처리 | always |
| vueIndentScriptAndStyle | Vue 파일 script/style 들여쓰기 | false |
| endOfLine | 줄바꿈 방식 | auto (OS에 따라) |
Prettier 적용:
prettier --ignore-path .eslintignore --write .