+ 250416 ์์ ๋ด์ฉโผ
- eslint-plugin-import, eslint-plugin-jsx-a11y ๋ถ๋ถ import, plugin, rules์ ๋นผ๋จน์๊ฑฐ ์์
- ๊ทธ ์ธ ์์ด๋น์๋น ๊ท์น ์ผ๋ถ ์ถ๊ฐ
์๋ ์ ๋ชฉ์๋ airbnb ํ์์ด๋ผ๊ณ ์ผ์๋๋ฐ
์ฌ์ค airbnb ํจํค์ง๋ ๋ด๊ฐ ์ด ํฌ์คํธ์์ ์์ฑํ Flat Config ํ์์์๋ ์์ง ์ง์ํ์ง ์๊ณ ์๋ค...
์์ ์ ์๋ eslint-config-airbnb๋ flat config์์ ์์ง์ ์ฌ์ฉํ ์ ์๊ธฐ ๋๋ฌธ์ rules์์ ์๋์ผ๋ก ๋ถ๋ฌ์์ค์ผ ํ๋ค๊ณ ์ค๊ฐ์ ์์ฑํด๋๊ธด ํ์ผ๋ ์์ ๋์ฌํด๋๋ค...
์๋ํ๋ฉด ๊ธฐ์กด .eslintrc ํ์ผ ํ์์์๋ "extends":["airbnb"] ๋ฅผ ์ถ๊ฐํด์ฃผ๋ ์์ผ๋ก ์ฌ์ฉ์ด ๊ฐ๋ฅํ์ง๋ง
Flat Config ํ์์์๋ ๋ฃฐ์ ์ง์ ํ์ด์ ์จ์ผํ๊ธฐ ๋๋ฌธ์ ์ง์ ๋ค ๊ฐ์ ธ์ค๋๊ฒ ์๋๊ณ ์์ผ ์ด์ ์ฒ๋ผ ํจํค์ง ์ค์น, ํ์ค์ถ๊ฐ๋ก ์ฌ์ฉํ ์๊ฐ ์๋ค๊ณ ํ๋ค.
* react + javascript ํ๋ก์ ํธ ๊ธฐ์ค์ ๋๋ค.
1. ํ๋ก์ ํธ ์์ฑ
npm create vite@latest my-app -- --template react
// ์๋ฃ๋๋ฉด ํ๋ก์ ํธ ํด๋๋ก ์ด๋
cd my-app
// ์ค์น
npm install
- my-app : ์์ฑํ ํ๋ก์ ํธ ์ด๋ฆ
- -- --template react : ์๋ฐ์คํฌ๋ฆฝํธ ๊ธฐ๋ฐ React ํ
ํ๋ฆฟ์ผ๋ก ์ง์
- --template react : React + JavaScript
- --template react-ts : React + TypeScript
- --template vanilla : ์ผ๋ฐ JS
- --template vanilla-ts : ์ผ๋ฐ TS
* -- --๋ก ์จ์ผํ๋ ์ด์
- ์ฒซ๋ฒ์งธ -- : ๊ตฌ๋ถ ์ญํ (my-app๊น์ง๋ง create vite๋ก ๋๊ธฐ๊ณ ๊ทธ ๋ค ๋ด์ฉ์ ์ค์ Vite CLI์ ์ ๋ฌํ๋๋ก ๊ตฌ๋ถ, ๊ฒฝ๊ณ ํ์์
- ๋๋ฒ์งธ -- : ์ค์ Vite CLI์ ์ ๋ฌ๋ ์ต์
2. ํ์ ํจํค์ง ์ค์น
npm install -D eslint@8.57.1 prettier @eslint/js eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks@4.6.0 eslint-config-prettier eslint-plugin-prettier
์ด๋ npm install -D์์ -D๋ --save-dev์ ์ค์๋ง๋ก, ๊ฐ๋ฐ ์์กด์ฑ์ผ๋ก ํจํค์ง๋ฅผ ์ค์นํ๊ฒ ๋ค๋ ๋ป์ด๋ค. (ํ๋ก์ ํธ๋ฅผ ์คํํ ๋๋ ํ์ ์๊ธฐ ๋๋ฌธ)
- eslint-plugin-react-hooks : use๋ก ์์ํ๋ ํจ์๋ฅผ ํ ์ผ๋ก ๊ฐ์ฃผํ๊ณ (์ปค์คํ ํ ๋ ํฌํจ) ๊ฒ์ฌ
- eslint-plugin-jsx-a11y : JSX ๋ด์ ์ ๊ทผ์ฑ ๋ฌธ์ ์ ๋ํด ์ฆ๊ฐ์ ์ธ AST ๋ฆฐํ ํผ๋๋ฐฑ์ ์ ๊ณต
- eslint-plugin-import :์ฃผ๋ก ๋ชจ๋์ด๋ import๋ฌธ ๊ด๋ จ ๊ท์น์ ๊ฒ์ฌํ๋ eslint ํ๋ฌ๊ทธ์ธ
3. ESLint ์ค์ ํ์ผ ์์
eslint v9๋ถํฐ ๊ธฐ์กด์ ์ฌ์ฉํ๋ .eslintrc.json|js|yaml ์ ์ค์ ํ์ผ์ ์ด์ ๋์ด์ ์ฌ์ฉ๋์ง ์์ผ๋ฉฐ, Flat config๋ฅผ ์ด๋ค๊ณ ํ๋ค.
๋ฌผ๋ก ๋ด ํ๋ก์ ํธ๋ v8.57.1์ ์ฌ์ฉํด์ .eslintrc.json ๋ฑ์ ์จ๋ ๋๊ธดํ์ง๋ง ์ด์ฐจํผ ์ด์ ์์ฌ์ฉ๋๋ค๋๋ฐ ๊ตณ์ด...?
์๋ฌดํผ 2๋ฒ๊น์ง ์งํ๋๋ค๋ฉด eslint.config.js๊ฐ ์๋ ์์ฑ๋ผ์์ ๊ฒ์ด๋ค.
eslint.config.js
import js from '@eslint/js';
import importPlugin from 'eslint-plugin-import';
import jsxA11y from 'eslint-plugin-jsx-a11y';
import prettier from 'eslint-plugin-prettier';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import globals from 'globals';
export default [
{
ignores: ['dist', 'node_modules'],
},
{
files: ['**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
globals: globals.browser,
},
plugins: {
react,
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
prettier,
'jsx-a11y': jsxA11y,
import: importPlugin,
},
rules: {
...js.configs.recommended.rules,
...react.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
...jsxA11y.configs.recommended.rules,
...importPlugin.configs.recommended.rules,
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
singleQuote: true,
trailingComma: 'all',
tabWidth: 2,
useTabs: false,
},
],
'react/jsx-filename-extension': ['warn', { extensions: ['.js', '.jsx'] }],
'react/react-in-jsx-scope': 'off',
'no-unused-vars': ['warn', { varsIgnorePattern: '^[A-Z_]' }],
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'no-var': 'warn', // ← var ์ฌ์ฉํ๋ฉด ๊ฒฝ๊ณ
'prefer-const': 'warn', // ← const ์ธ ์ ์์ ๋ let ์ฐ๋ฉด ๊ฒฝ๊ณ
'import/order': [
'warn',
{
// ← import ์์ ๊ด๋ฆฌ
groups: ['builtin', 'external', 'internal'],
pathGroups: [
{
pattern: 'react',
group: 'external',
position: 'before',
},
],
pathGroupsExcludedImportTypes: ['react'],
'newlines-between': 'always',
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
},
],
},
settings: {
react: {
version: 'detect',
},
},
},
];
eslint.config.js ์์ธ ์ค๋ช
์ฑ์งํผํฐ์ ํ์ ๋น๋ ธ์ต๋๋ค. ๋ ๊ฐ์ฌํฉ๋๋ค...
import ๋ถ๋ถ
import js from '@eslint/js';
import globals from 'globals';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import prettier from 'eslint-plugin-prettier';
- @eslint/js: ESLint ๊ณต์ ๊ธฐ๋ณธ ๊ท์น ๋ชจ์. (ex. no-unused-vars, no-console ๋ฑ)
- globals: ๋ธ๋ผ์ฐ์ ํ๊ฒฝ์์์ ์ ์ญ ๋ณ์๋ค์ ์ ์ (window, document, console, ๋ฑ).
- eslint-plugin-react: ๋ฆฌ์กํธ ๊ด๋ จ ๊ท์น์ ์ ๊ณตํ๋ ํ๋ฌ๊ทธ์ธ.
- eslint-plugin-react-hooks: useEffect, useState ๋ฑ ๋ฆฌ์กํธ ํ ๊ท์น์ ๊ฒ์ฌ.
- eslint-plugin-react-refresh: Vite์ Fast Refresh ๊ด๋ จ ๊ท์น.
- eslint-plugin-prettier: Prettier์ ESLint ํตํฉ์ฉ. Prettier ํฌ๋งทํ ์ค๋ฅ๋ฅผ ESLint ์๋ฌ๋ก ๋ณด์ฌ์ค
ํ์ผ ๋ฌด์ ์ค์
{ // ESLint๊ฐ ๋ถ์ํ์ง ์์ ๋๋ ํ ๋ฆฌ ๋ชฉ๋ก
ignores: ['dist', 'node_modules'],
},
์ค์ Lint ์ค์
{ // .js, .jsx ํ์ฅ์์ ๋ชจ๋ ํ์ผ์ ์๋ ์ค์ ์ ์ฉ
files: ['**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 'latest', // ๊ฐ์ฅ ์ต์ ECMAScript ๋ฌธ๋ฒ ์ฌ์ฉ
sourceType: 'module', // ES ๋ชจ๋ ์ฌ์ฉ ๊ฐ๋ฅ (import, export)
parserOptions: {
ecmaFeatures: {
jsx: true, // jsx ๋ฌธ๋ฒ ์ฌ์ฉ ๊ฐ๋ฅ
},
},
globals: globals.browser,// window, document ๋ฑ ๋ธ๋ผ์ฐ์ ์ ์ญ๊ฐ์ฒด ํ์ฉ
},
plugins: {
// ๊ฐ ํ๋ฌ๊ทธ์ธ์ ์ค์ ์์ ์ฌ์ฉํ ์ ์๋๋ก ๋ถ๋ฌ์ด
// ์ด ์ด๋ฆ๋ค์ ์๋ rules์์ ํค๋ก ์ฌ์ฉ(reactHooks, reactRefresh...)
react,
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
prettier,
'jsx-a11y': jsxA11y,
import: importPlugin,
},
rules: {
// ๊ธฐ๋ณธ ESLint + React + React Hooks ์ถ์ฒ ๊ท์น
...js.configs.recommended.rules,
...react.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
...jsxA11y.configs.recommended.rules,
...importPlugin.configs.recommended.rules,
// .jsx ํ์ฅ์๊ฐ ์๋ ํ์ผ์์ JSX ์ฌ์ฉํ๋ ๊ฑธ ๊ฒฝ๊ณ ๋ง ํ๋๋ก ํจ
'react/jsx-filename-extension': ['warn', { extensions: ['.js', '.jsx'] }],
// React 17+์์๋ JSX ์ธ ๋ import React from 'react' ์ ํด๋ ๋๋ฏ๋ก ๋
'react/react-in-jsx-scope': 'off',
// ์ ์ฐ๋ ๋ณ์๋ฅผ ๊ฒฝ๊ณ ๋ก ํ์ํ๋, ๋๋ฌธ์๋ _๋ก ์์ํ๋ ๋ณ์๋ ๋ฌด์ (๋ณดํต ์์๋ค)
'no-unused-vars': ['warn', { varsIgnorePattern: '^[A-Z_]' }],
// Vite์์ React Fast Refresh๊ฐ ์ ๋๋ก ์๋ํ๊ฒ ํ๊ธฐ ์ํ ๊ท์น
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'no-var': 'warn', // ← var ์ฌ์ฉํ๋ฉด ๊ฒฝ๊ณ
'prefer-const': 'warn', // ← const ์ธ ์ ์์ ๋ let ์ฐ๋ฉด ๊ฒฝ๊ณ
'import/order': [
'warn',
{
// ← import ์์ ๊ด๋ฆฌ
groups: ['builtin', 'external', 'internal'],
pathGroups: [
{
pattern: 'react',
group: 'external',
position: 'before',
},
],
pathGroupsExcludedImportTypes: ['react'],
'newlines-between': 'always',
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
},
],
},
settings: {
react: {
// ์ฌ์ฉ ์ค์ธ React ๋ฒ์ ์ ์๋์ผ๋ก ๊ฐ์งํด์ ๋ง์ถค ๊ท์น์ ์ ์ฉ
version: 'detect',
},
},
4. prettier ์ค์ ํ์ผ
.prettierrc
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 80,
"tabWidth": 2,
"semi": true,
"endOfLine": "auto"
}
5. vscode์ ์ ์ฉ
./vscode/settings.json ํ์ผ์ ๋ค์ ๋ด์ฉ์ ์ถ๊ฐํ๋ค.
ํนํ ๋ง์ง๋ง์์ ๋๋ฒ์งธ ํญ๋ชฉ์ธ "eslint.experimental.useFlatConfig": true์ ์ถ๊ฐํด์ผ flat config๊ฐ ์ ์ฉ์ด ๋๋ค. ...
{
"editor.formatOnSave": true,
"editor.tabSize": 2, // ํญ ํฌ๊ธฐ๋ฅผ 2๋ก ์ค์
"editor.insertSpaces": true, // ํญ ๋์ ๊ณต๋ฐฑ ์ฌ์ฉ
"editor.detectIndentation": false, // ํ์ผ ๋ด์ฉ์ ๋ฐ๋ผ ์๋์ผ๋ก ๋ค์ฌ์ฐ๊ธฐ ๊ฐ์ง ๋นํ์ฑํ
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"eslint.experimental.useFlatConfig": true,
"prettier.requireConfig": true
}
์ฐธ๊ณ
https://medium.com/@Dodo3/eslint-airbnb-%EC%84%A4%EC%A0%95-84b22d0cd31a
https://yangtinomad.com/entry/ReactTS-airbnb-eslint-prettier%EC%A0%81%EC%9A%A9