⚠️ 주의사항 ⚠️
- .eslintrc 기준으로 작성되었습니다.(현재 deprecated).
- CJS 기준으로 작성되어 있습니다.
최신 문법(eslint9 flat config)은 다음 글에서 다룰 예정입니다.
eslint 를 세팅하다보면 여러가지 config 와 plugin 을 마주치게 됩니다. 각 라이브러리마다 설정 방법을 알려주고, 다양한 블로그에 세팅 예제가 존재하지만 다 조금씩 다릅니다. 많은 블로그에서 제공하는 세팅 예제들을 복사/붙여넣기 형식으로 사용했던 경험이 있지만, 조금 더 정확한 세팅 방법을 알기 위해서 조사하고 정리했습니다.
eslint-config 와 eslint-plugin 의 개념을 상세하게 서술했고 실제 라이브러리에 적용된 예제를 통해 한 번 더 이해할 수 있도록 구성했습니다.
여러 프로젝트에서 공통으로 사용할 ESLint 설정(규칙 세트, 파서 옵션, 플러그인 사용법 등)을 묶어서 배포하는 패키지입니다. 또한 다른 플러그인을 의존성으로 가져와서 함께 설정할 수 있습니다. (자체적으로 새로운 규칙을 정의하지는 않음) 어떤 규칙들을 어떻게 적용할 지 정리한 설정 세트에 가깝습니다.
ex) eslint-config-prettier
, eslint-config-next
간단히 말해, .eslintrc 파일에 포함된 규칙과 설정을 여러 프로젝트에서 공통으로 사용할 수 있도록 모아 놓은 것입니다. 공식 문서에서도 ‘shareable config’ 즉, 공유가 가능한 configuration 이라고 명명하고 있습니다. 따라서 .eslintrc 에서 설정하는 필드와 동일한 설정 필드를 사용할 수 있습니다.
.eslintrc 와 다른 점은 eslint-config-xxx 의 경우 범용적으로 사용하는 것을 목적으로 하기 때문에 특정 프로젝트 경로에 의존적인 설정보다는 일반적인 코드 스타일/규칙 위주의 설정이 들어가는 편이며, root 옵션을 사용해도 효과가 없다는 점입니다. (root: true 옵션을 사용하면 상위 디렉토리까지 설정 탐색을 하지 않으나 .eslintrc 에만 사용)
eslint config 는 prefix 로 eslint-config
를 붙여서 패키지명을 정합니다. (공식 문서)
// eslint-config-configName/package.json
{
"name": "eslint-config-configName", 또는 "@scopeName/eslint-config-configName"
...
}
eslint-config 에서 정의할 수 있는 필드 중 일부를 소개합니다. (전체는 문서 참고)
env
: 미리 정의된 전역 변수를 제공할 지 여부 (공식 문서)plugins
: 플러그인 모음 객체 (이름 - 플러그인 객체 로 매핑된 객체) (공식 문서)rules
: 룰 모음 객체 (이름 - 룰 옵션 으로 매핑된 객체) (공식 문서)parserOptions
: js 를 어떤 버전, 옵션을 기준으로 파싱하고 lint 할 지의 규칙 (공식 문서)parser
: custom parser 옵션module.exports = {
// 다른 ESLint 설정을 확장할 수 있음
extends: [
"eslint:recommended", // eslint 에서 제공하는 config
"plugin:react/recommended", // eslint-plungin-react 의 configs 에서 제공하는 recommended config
"prettier" // eslint-config-prettier: Prettier와 충돌할 수 있는 규칙 비활성화하는 config
],
parserOptions: {
ecmaVersion: 2021,
sourceType: "module",
ecmaFeatures: {
jsx: true
}
},
env: {
browser: true,
node: true,
es2021: true
},
plugins: [
"react"
],
rules: {
// custom 규칙 예시
"no-console": "warn",
"semi": ["error", "always"],
"quotes": ["error", "double"],
},
settings: {
react: {
version: "detect"
}
}
};
.eslintrc 의 extends
에 패키지명을 적어주면 되고(공식 문서) prefix(eslint-plugin) 은 생략할 수 있습니다. (공식 문서)
작성하면 config 에서 정의한 룰이 곧바로 eslint 에 확장/적용됩니다. 가장 마지막에 작성한 config 의 우선순위가 가장 높으며 이보다 eslintrc 에 직접 정의한 설정이 우선순위가 높습니다.
우선순위: 프로젝트의 .eslintrc
> configNameB
> configNameA
{
"extends": [..., "configNameA", "configNameB"] or
[..., "eslint-config-configNameA", "eslint-config-configNameB"]
}
eslint-plugin-xxx 에서는 새로운 규칙(rule)을 정의할 수 있습니다. 각 라이브러리나 환경에 필요한 룰을 정의하는 것이 일반적입니다.
ex) eslint-plugin-import, eslint-plugin-next
플러그인 안에서 정의한 규칙을 활용하여 config(설정 세트) 를 제공하기도 합니다. 규칙을 정의할 뿐 아니라 추천하는 설정 세트를 함께 제공하고 싶을 때 주로 사용하는 방식입니다.
이 때 config 는 eslint-config-xxx 가 제공하는 파일의 형식과 동일하지만 차이점은 eslint-config-xxx 는 단순히 다른 외부의 plugin 이나 config 를 불러와서 규칙 세트를 정의한다면, eslint-plugin-xxx 안에서 제공하는 config는 동일 패키지에 있는 플러그인을 불러와서 추천 설정을 정의하는 데에 목적이 있다는 것입니다.
eslint plugin 은 prefix 로 eslint-plugin
을 붙여서 패키지명을 정합니다.
// eslint-plugin-pluginName/package.json
{
"name": "eslint-plugin-pluginName", 또는 "@scopeName/eslint-plugin-pluginName"
...
}
eslint-plugin 에서 정의할 수 있는 필드는 다음과 같습니다. (문서)
meta
: 플러그인 정보configs
: eslint config 객체 (문서)recommended
라고 명명한 경우, extends: ["plugin:플러그인명/recommended"] 의 형태로 사용할 수 있음. (문서)rules
- 커스텀 lint 룰을 정의한 객체processors
- 커스텀 프로세서 (공식 문서) (안 써봐서 잘 모르겠음)// 예시: eslint-plugin-custom/index.cjs
const enforeFooBarRule = require('./rules/enforce-foo-bar.cjs');
const vueTestRule = require('./rules/vue-test.cjs');
const recommendedConfig = require('./recommended.cjs');
/** @type {import('eslint').Linter.Config} */
module.exports = {
meta: {
name: 'eslint-plugin-custom',
version: '1.0.0',
},
configs: {
recommended: recommendedConfig,
},
rules: {
'enforce-foo-bar': enforeFooBarRule,
'vue-test': vueTestRule,
},
};
// 예시: eslint-plugin-custom/recommended.cjs
module.exports = {
// eslint-plugin 을 생략하고 작성 가능
// 현재 자기 자신이 소속되어 있는 패키지를 불러올 수 있음. (물론 다른 외부 패키지도 가능)
plugins: ['custom'],
rules: {
'custom/enforce-foo-bar': 'error',
'custom/vue-test': 'error',
},
};
eslint config 와 유사하게, .eslintrc 의 plugins
에 패키지명을 적어주면 되고 (공식 문서) prefix(eslint-plugin) 은 생략할 수 있습니다. (공식 문서). plugins 에 작성하면 플러그인에서 정의한 rules 들을 불러오기만 할 뿐, 실제로 eslint 에 규칙을 켜고 싶으면 rules 에서 정의해주어야 하며 rules 에 정의되지 않은 규칙은 적용되지 않습니다.
// ex) eslint-plugin-custom 을 사용하는 패키지/.eslintrc.js
{
// prefix 생략 가능
"plugins": [..., "custom"] 또는 [..., "eslint-plugin-custom"],
"rules": {
"custom/some-rule": "error"
"custom/some-rule2": "warning"
}
}
eslint-plugins 에서 configs 를 정의한 경우(위의 예시 참고)에는 eslint-config-xxx 와 유사한 방법으로 extends 에 작성해서 바로 rule을 적용할 수 있습니다. 주로 eslint plugin 에서 정의한 custom rule 을 포함한 config 을 제공하고 싶을 때 라이브러리에서 차용하는 방식입니다.
플러그인에서 불러오기 위해서는 앞에 prefix 로 plugins:
를 붙이며, 뒤에 [pluginName/configName] 을 작성합니다.
{
"extends": [..., "plugin:[custom/recommended]"]
}
구분 | ESLint Config | ESLint Plugin |
---|---|---|
설명 | 설정(규칙/옵션) 모음 패키지 | 새로운 규칙(룰) 구현 로직 제공 |
npm 이름 | eslint-config-xxx | eslint-plugin-xxx |
.eslintrc 적용 | "extends": ["xxx"] | "plugins": ["xxx"] 또는 "extends": ["plugin:xxx/recommended"] |
예시 | eslint-config-prettier , eslint-config-next | eslint-plugin-import , eslint-plugin-prettier |
eslint-config 에서 eslint-plugin 을 불러오기도 하고, 그 반대로 plugin 에서 config 를 불러오기도 합니다. 이때 양뱡향으로 다 import 하면 순환참조가 일어나므로 필요 시에 한 곳에서만 참조해야 합니다.
eslint-config-prettier 에는 eslint 기본 룰과 다른 플러그인에서 제공하는 룰 중 prettier 와 포맷팅 규칙이 다른 경우에 eslint 의 룰을 끄는 설정이 정의되어 있습니다.
module.exports = {
rules: {
// The following rules can be used in some cases. See the README for more
// information. These are marked with `0` instead of `"off"` so that a
// script can distinguish them. Note that there are a few more of these
// in the deprecated section below.
"curly": specialRule,
"no-unexpected-multiline": specialRule,
"@stylistic/lines-around-comment": specialRule,
"@stylistic/max-len": specialRule,
"@typescript-eslint/block-spacing": "off",
"@typescript-eslint/brace-style": "off",
"babel/object-curly-spacing": "off",
"flowtype/boolean-style": "off",
"react/jsx-closing-bracket-location": "off",
"standard/array-bracket-even-spacing": "off",
"unicorn/empty-brace-spaces": "off",
"vue/array-bracket-newline": "off",
"vue/array-bracket-spacing": "off",
...
}
외부 plugin/config 에 정의되어 있는 룰을 불러와서 설정값을 끄는 역할만 수행합니다. stylistic, typescript-eslint, babel, react 등 외부 plugin rule 의 세팅값을 정해놓은 것을 확인할 수 있습니다. (설정값만 정의해놓은 것이며 해당하는 plugin 이나 config 는 불러오지는 않습니다.)
제대로 적용되기 위해서는 외부 plugin, config 가 먼저 적용되어야 하므로 eslint-config-prettier 는 extends 필드의 가장 마지막에 작성해야 합니다. 이를 공식 문서에서도 안내하고 있습니다.
eslintrc: Add
"prettier"
to the "extends" array in your.eslintrc.*
file. Make sure to put it last, so it gets the chance to override other configs.
{
"extends": [
...,
"some-other-config-you-use",
"prettier"
]
}
eslint-plugin-prettier 에서는 eslint-config-prettier 를 의존성으로 가지고 있습니다. (package.json 참고) extends 에 작성하여 eslint-config-prettier 에서 제공하는 config 를 확장하고 있으며, eslint-plugin-prettier 에서 정의한 rules 를 사용하기 위해서 plugins 에도 ‘prettier’
를 작성해주었습니다.
const eslintPluginPrettier = {
meta: { name, version },
configs: {
recommended: {
// eslint-config-prettier 를 의미
// eslint-config-prettier 에서 정의한 rules 를 자동 확장
extends: ['prettier'],
// eslint-plugin-prettier 를 의미
// custom rule 을 불러와서 rules 에서 세팅함
plugins: ['prettier'],
rules: {
'prettier/prettier': 'error',
...
},
},
},
rules: {
prettier: {
meta: {
...
},
},
}
따라서 아래와 같이 쓰면 eslint-config-prettier 에서 정의한 룰과 eslint-plugin-prettier 에서 정의한 커스텀 룰을 (eslint-config-prettier 를 세팅하지 않고도!) 모두 사용할 수 있게 됩니다. (공식 문서)
{
"extends": ["plugin:prettier/recommended"]
}
eslint-plugin-next 의 index.js 를 살펴보면 next.js 를 위한 rule 을 정의하고 있습니다. 또한 custom rule 을 사용한 config 를 제공하고 있습니다. custom rule 을 사용하기 위해 plugins 에 ’@next/next’
를 작성하고 rules 에서 필요한 설정값을 정의하고 있습니다. (package.json 에 name: @next/eslint-plugin-next 으로 정의했기 때문에 prefix 를 생략하고 나머지 scopeName 과 packageName 을 작성한 것)
// index.js
module.exports = {
// next.js 용 custom rule
rules: {
'google-font-display': require('./rules/google-font-display'),
'google-font-preconnect': require('./rules/google-font-preconnect'),
...
},
// recommended, core-web-vitals config 를 제공
configs: {
recommended: {
plugins: ['@next/next'],
rules: {
// warnings
'@next/next/google-font-display': 'warn',
'@next/next/google-font-preconnect': 'warn',
...
},
},
'core-web-vitals': {
plugins: ['@next/next'],
extends: ['plugin:@next/next/recommended'],
rules: {
'@next/next/no-html-link-for-pages': 'error',
'@next/next/no-sync-scripts': 'error',
},
},
},
}
eslint-plugin-next 를 사용할 때는 @next/eslint-plugin-next
설치 후 아래와 같이 plugins 에 “@next/next”
를 작성하고 rules 에서 원하는 rule 의 옵션을 정의할 수 있습니다.
{
"plugins": [
"@next/next",
],
"rules": {
"@next/next/no-page-custom-font": "off",
}
}
또는 plugin 에서 제공하는 recommended 나 core-web-vitals config preset 을 사용할 수 있습니다.
{
"extends": [
"plugin:next/recommended",
"plugin:next/core-web-vitals",
],
}
eslint-config-next 의 index.js 는 살펴보면 eslint-plugin-next 에서 제공하는 recommended 를 불러오고 있으며 다른 플러그인에서 제공하는 rule 도 추가로 커스텀해서 제공합니다. (공식 문서) 즉, eslint-config-next 가 eslint-plugin-next 에 의존성을 가지고 있음을 알 수 있습니다.
module.exports = {
extends: [
'plugin:react/recommended',
'plugin:react-hooks/recommended',
// @next/eslint-plugin-next 의 recommended config 불러오기
'plugin:@next/next/recommended',
],
plugins: ['import', 'react', 'jsx-a11y'],
rules: {
'import/no-anonymous-default-export': 'warn',
'react/no-unknown-property': 'off',
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
'jsx-a11y/alt-text': [
'warn',
{
elements: ['img'],
img: ['Image'],
},
],
'jsx-a11y/aria-props': 'warn',
'jsx-a11y/aria-proptypes': 'warn',
'jsx-a11y/aria-unsupported-elements': 'warn',
'jsx-a11y/role-has-required-aria-props': 'warn',
'jsx-a11y/role-supports-aria-props': 'warn',
'react/jsx-no-target-blank': 'off',
},
}
eslint-plugin-import 의 recommended.js 를 살펴보면, src/index.js 에서 정의한 플러그인을 사용하기 위해 plugins 에 ‘import’
를 기입하여 패키지를 로드하고 있습니다. (‘import’ 는 eslint-config-import 에서 eslint-config 를 생략한 것)
그리고 rules 에 eslint-plugin-import 에서 정의되었던 custom rule 의 세팅값을 정의함으로 추천 세팅값 세트를 제공하고 있습니다.
// recommended.js
module.exports = {
// eslint-plugin-import 의 rule 을 사용한다고 명시
plugins: ['import'],
rules: {
// analysis/correctness
'import/no-unresolved': 'error',
'import/named': 'error',
'import/namespace': 'error',
'import/default': 'error',
'import/export': 'error',
// red flags (thus, warnings)
'import/no-named-as-default': 'warn',
'import/no-named-as-default-member': 'warn',
'import/no-duplicates': 'warn',
},
// need all these for parsing dependencies (even if _your_ code doesn't need
// all of them)
parserOptions: {
sourceType: 'module',
ecmaVersion: 2018,
},
};
eslint-plugin-import 를 사용할 때는 아래와 같이 extends 에 작성하여 recommended preset 을 사용하거나 직접 rule 을 세팅할 수 있습니다. (문서)
// import 에서 제공하는 추천 세팅값 사용
{
"extends": [
"eslint:recommended",
"plugin:import/recommended",
],
}
// 또는 rules 에 직접 작성
{
"rules": {
"import/no-unresolved": ["error", { "commonjs": true, "amd": true }],
"import/named": "error",
"import/namespace": "error",
"import/default": "error",
"import/export": "error",
// etc...
},
},
eslint-plugin 에서 config 를 제공하고 있기 때문에 따로 eslint-config-import 를 제공하고 있지는 않습니다. 주로 라이브러리들이 이처럼 plugin 내부에서 recommended 와 같은 설정을 제공하기 때문에 eslint-config 가 따로 없는 경우가 많습니다. (ex. @tanstack/eslint-plugin-query)
다른 라이브러리들이 eslint config, eslint plugin 을 어떻게 제공하는지를 살펴보며 eslint 를 조금 더 깊게 이해하게 되었습니다. config 에서 plugin 을 불러오는 경우도 있고 그 반대의 경우도 있다보니 헷갈릴 수 있는 개념이라 자세하게 정리해놓았습니다.
custom eslint plugin 을 만들기 위해서 시작한 글이었는데 쓰다보니 내용이 너무 길어져서 eslint config, plugin 의 옵션 설명과 동작 과정으로 시리즈의 첫 글을 마무리합니다. 다음에는 eslint custom plugin 만드는 방법을 다룰 예정입니다.