[DevTools] ESLint 9 Flat Config + Prettier ์ค์ (TypeScript, React)
Flat Config
ESLint 8.21.0 ๋ฒ์ ๋ถํฐ ๊ตฌ์ฑ ํ์ผ์ ํฐ ๋ณํ๊ฐ ์๊ฒผ๋ค. ๊ธฐ์กด .eslintrc ํ์ผ ๋์ ํ๋ซ ๊ตฌ์ฑ(Flat Config)์ ์ฌ์ฉํ๋ eslint.config.js ํ์์ด ์๋ก ๋์ ๋ ๊ฒ. ํ๋ซ ๊ตฌ์ฑ์ extends๋ overrides ๊ฐ์ ๊ณ์ธต ๊ตฌ์กฐ์์ด ๊ฐ ๊ตฌ์ฑ์ ์ด๋ฃจ๋ ๊ฐ์ฒด๋ค์ ํฌํจํ 1์ฐจ์ ๋ฐฐ์ด๋ก ํํํ๋ค. ์ด๋ฅผ ํตํด ๊ท์น์ ์ธ๋ถํํด์ผ ํ๋ ์ํฉ์์ ๋ ์ ์ฐํ๊ฒ ๋์ํ ์ ์๊ฒ ๋๋ค. e.g., ๊ตฌ์ฑ ๊ฐ์ฒด 1-์๋ฐ์คํฌ๋ฆฝํธ ๊ท์น, ๊ตฌ์ฑ ๊ฐ์ฒด 2-ํ์ ์คํฌ๋ฆฝํธ ๊ท์น
ํ์ํ ํ๋ฌ๊ทธ์ธ์ ์ง์ import ํ ํ ์ฌ์ฉํ๋ ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝ๋ผ์ ์ข ์์ฑ์ ๋ ๋ช ํํ๊ฒ ๊ด๋ฆฌํ ์ ์๋ค.
// eslint.config.js ํ์ผ ์์
import eslint from '@eslint/js';
import reactPlugin from 'eslint-plugin-react';
export default [
// ๋ถ๋ฌ์จ ๊ตฌ์ฑ ๊ฐ์ฒด 1 { rules: { ... } }
eslint.configs.recommended,
// jsx, tsx ํ์ผ์๋ง ์ ์ฉํ ๊ตฌ์ฑ ๊ฐ์ฒด 2
{
files: ['**/*.{jsx,tsx}'],
plugins: { react: reactPlugin },
// ...
},
];
๋ฐฐ์ด์ ๋ค์ด๊ฐ๋ ๊ฐ ๊ตฌ์ฑ ๊ฐ์ฒด๋ ์๋ ์์ฑ์ ํฌํจํ๋ฉฐ, 1๊ฐ ์ด์์ ๊ตฌ์ฑ ๊ฐ์ฒด๋ก ์กฐํฉํด์ ์ฌ์ฉํ ์ ์๋ค.
- files : ๊ตฌ์ฑ์ด ์ ์ฉ๋ ํ์ผ ํจํด
- languageOptions : ํ์(Parser), ์ ์ญ ๋ณ์ ๋ฑ ์ธ์ด ๊ด๋ จ ์ต์
- plugins : ํ๋ฌ๊ทธ์ธ
- rules : ๊ท์น
- settings : ํ๋ฌ๊ทธ์ธ์ด๋ ๊ท์น์์ ์ฌ์ฉํ๋ ์ถ๊ฐ ์ค์
ํ์ง๋ง ์์ง๊น์ง ํ๋ซ ๊ตฌ์ฑ์ ์ง์ํ์ง ์๋ ํ๋ฌ๊ทธ์ธ์ด ๊ฝค ์๊ณ , ESLint 9 ๋ฒ์ ์์ ์ฌ๋ฌ API๊ฐ ๋ณ๊ฒฝ๋๊ธฐ ๋๋ฌธ์ ๊ธฐ์กด plugin(๊ท์น ์ ์ ๋ชจ์)/config(๊ท์น ์ ์ + ํจํค์ง ๋ชจ์)๊ฐ ์ง์ํ ๋๊น์ง 8.x ๋ฒ์ ์ ์ฌ์ฉํ๋๊ฒ ์ข๋ค.
์๋๋ 2024.06.28 ๊ธฐ์ค ์ฃผ์ ํ๋ฌ๊ทธ์ธ ์ง์ ํํฉ. ESLint์์ ์ ๊ณตํ๋ Compatibility Utilities๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ๋ณ๋์ Workaround๊ฐ ํ์ํ ํ๋ฌ๊ทธ์ธ์ ๐๋ก ํ์ํ๋ค. ๋ ์์ธํ ์ง์ ํํฉ์ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ์.
| ํ๋ฌ๊ทธ์ธ ์ด๋ฆ | Flat Config ์ง์ | ESLint v9 ์ง์ | ๊ด๋ จ Issue/PR |
| typescript-eslint | โ | โ | |
| eslint-config-airbnb | โ | โ | Issue |
| eslint-config-airbnb-typescript | โ | โ | Issue |
| eslint-config-standard | โ | ๐ | Issue |
| eslint-plugin-react | โ | ๐ | Issue |
| eslint-plugin-react-hooks | โ | ๐ | Issue |
| eslint-plugin-jsx-a11y | โ | ๐ | Issue |
| eslint-plugin-import | โ | โ | PR |
| eslint-plugin-prettier | โ | โ | |
| eslint-plugin-react-refresh | โ | โ | |
| eslint-plugin-tailwindcss | โ | ๐ | Issue |
| @tanstack/eslint-plugin-query | ๐ | ๐ | Issue |
ESLint ๋ฐ ํ๋ฌ๊ทธ์ธ ์ ์
๐ ์๋ ์ธํ ์ create vite ๊ธฐ์ค์ผ๋ก ์์ฑํ๋ค. ํจํค์ง ๋งค๋์ ๋ pnpm์ ์ฌ์ฉํ๋ค.
ํ๋ก์ ํธ๋ฅผ ์ฒ์ ์์ํ๋ค๋ฉด ESLint v9 Flat Config ๋ก ์ค์ ํด๋๊ณ , ํฅํ ํ์ํ ํ๋ฌ๊ทธ์ธ์ด Flat Config๋ฅผ ์ง์ํ์ ๋ ๊ตฌ์ฑ ํ์ผ์ ์ ๋ฐ์ดํธํ๋๊ฒ ๋ ํธ๋ฆฌํ ์ ์๋ค.
eslint-config-airbnb๋ ์์ง Flat Config๋ฅผ ์ง์ํ์ง ์๊ธฐ ๋๋ฌธ์ ๋์์ผ๋ก eslint-config-standard๋ฅผ ์ฌ์ฉํ ์ ์๋ค. Airbnb๋ ๋๋ฌด ์๊ฒฉํ ๊ท์น ๋๋ฌธ์ ์ผ์ผ์ด ํ๋ก์ ํธ์ ๋ง์ถฐ ์์ ํ๋ ๊ณผ์ ์ด ๋ฒ๊ฑฐ๋ก์ธ ๋๊ฐ ์ข ์ข ์๋๋ฐ, Standard JS๋ ๊ท์น์ด ์ข ๋ ์ ์ฐํ ์ฅ์ ์ด ์๋ค.
ESLint ์ค์น
์๋ ESLint ์ด๊ธฐํ ๋ช ๋ น์ด๋ฅผ ์ ๋ ฅํ๋ฉด ์ด๋ค ๋ชจ๋, ํ๋ ์์ํฌ ๋ฑ์ ์ฌ์ฉํ๋์ง ๋ฌผ์ด๋ณด๋ ๋ํํ ์ฐฝ์ด ๋์จ๋ค. ๋ชจ๋ ์ ํ์ ์๋ฃํ๋ฉด ๊ด๋ จ๋ ํจํค์ง๋ฅผ ์ค์นํ๊ณ , ๊ธฐ๋ณธ ์ค์ ์ด ํฌํจ๋ ESLint ๊ตฌ์ฑ ํ์ผ์ ์์ฑํ๋ค.
npm init @eslint/config@latest
# How would you like to use ESLint? problems
# What type of modules does your project use? esm
# Which framework does your project use? react
# The React plugin doesn't officially support ESLint v9 yet. What would you like to do? 9.x
# Does your project use TypeScript? typescript
# Where does your code run? browser
# Would you like to install them now? Yes
# Which package manager do you want to use? pnpm
์์ฒ๋ผ ์ ํํ์ ๋ ์ค์น๋๋ ํจํค์ง๋ ์๋์ ๊ฐ๋ค
eslint@9.x, @eslint/js, @eslint/compat, globals, typescript-eslint, eslint-plugin-react
์๋๋ ์๋์ผ๋ก ์์ฑ๋๋ eslint.config.js ํ์ผ์ ์ด๊ธฐ ์ํ. parseOptions ์ค์ ์ languageOptions ์์ฑ์ ํตํฉ๋๊ณ , ํ๊ฒฝ๋ณ์ ์ค์ ์ env ๋์ languageOptions.globals ์์ฑ์ ์ค์ ํ๋ ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝ๋๋ค. globals ๊ฐ์ฒด์ node, jest ๋ฑ์ ์์ฑ์ด ํฌํจ๋์ด ์์ด์ ํ์ํ ๋ณ์๋ง ์ ๊ฐ ์ฐ์ฐ์๋ฅผ ์ด์ฉํด์ ์ถ๊ฐํ ์ ์๋ค.
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import pluginReactConfig from "eslint-plugin-react/configs/recommended.js";
import { fixupConfigRules } from "@eslint/compat";
export default [
{ files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"] },
{ languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } } },
{ languageOptions: { globals: globals.browser } },
pluginJs.configs.recommended,
...tseslint.configs.recommended,
...fixupConfigRules(pluginReactConfig),
];
์๋์ฒ๋ผ ๋ณ์๋ช , ์ฝ๋๋ฅผ ์ด์ง ์ ๋ฆฌํด๋๋ฉด ๋ ๋ณด๊ธฐ ๊น๋ํด์ง๋ค.
import globals from "globals";
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
import reactPlugin from "eslint-plugin-react/configs/recommended.js";
import { fixupConfigRules } from "@eslint/compat";
export default [
eslint.configs.recommended,
...tseslint.configs.recommended,
...fixupConfigRules(reactPlugin),
{ files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"] },
{
languageOptions: {
parserOptions: { ecmaFeatures: { jsx: true } },
globals: { ...globals.browser },
},
},
];
typescript-eslint
๊ตฌ์ฑ ๋ํ
typescript-eslint๋ config() ํจ์๋ฅผ ๋ณ๋๋ก ์ ๊ณตํ๋ค. ์ด ํจ์๋ฅผ ์ด์ฉํด ๊ธฐ์กด ๊ตฌ์ฑ์ ๋ํํ๊ณ // @ts-check ์ฃผ์์ ์ถ๊ฐํด๋๋ฉด ์์ฑ์ ๋ํ ์๋ ์์ฑ๊ณผ ์๋ชป๋ ๊ฐ์ ์ฌ์ฉํ์ ๋ ํ์
์ค๋ฅ๋ฅผ ํ์ธํ ์ ์๋ค.
// @ts-check
import globals from "globals";
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
import reactPlugin from "eslint-plugin-react/configs/recommended.js";
import { fixupConfigRules } from "@eslint/compat";
// ๋ณ๊ฒฝ ์ : export default [...]
// ๋ณ๊ฒฝ ํ: export default tseslint.config(...)
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
...fixupConfigRules(reactPlugin),
{ files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"] },
{
languageOptions: {
parserOptions: { ecmaFeatures: { jsx: true } },
globals: { ...globals.browser },
},
},
);
// @ts-check ์ฃผ์์ ์๋ฐ์คํฌ๋ฆฝํธ ํ์ผ์ ํ์
๊ฒ์ฌ๋ฅผ ์ ์ฉํ๊ธฐ ์ํ ์ง์์ด๋ค. ์ด ์ฃผ์์ ์ถ๊ฐํ๋ฉด ํ์
์คํฌ๋ฆฝํธ ์ปดํ์ผ๋ฌ๊ฐ ํด๋น ํ์ผ์ ์์ฑํ JSDoc ์ฃผ์์ ๊ธฐ์ค์ผ๋ก ํ์
๊ฒ์ฌ๋ฅผ ์ํํ๋ค. ์ ๊ตฌ์ฑ ํ์ผ์์ tseslint.config๊ฐ ํ์
์ ๋ณด๋ฅผ ์ง์ ํ์ธํ๊ธฐ ๋๋ฌธ์ ํ์
์ฒดํฌ๋ฅผ ์ํ JSDoc์ ์์ฑํ์ง ์์๋ ๋๋ค.
ํ์ ์ ๋ณด
๐ ํ์ ์คํฌ๋ฆฝํธ์ ๋ฅ์ํ๋ค๋ฉด ๋ ์๊ฒฉํ ๊ท์น์ธ strict-type-checked์ ์ ์ฉํ ์๋ ์๋ค.
recommended-type-checked ๊ณต์ ๊ตฌ์ฑ์ ์ฌ์ฉํ๋ฉด TS์์ ์ ๊ณตํ๋ ํ์ ์ฒดํฌ API๋ฅผ ์ด์ฉํด ์ฝ๋ ์ ํ์ฑ์ ๋ ๊น์ด์๊ฒ ๋ถ์ํ ์ ์๋ ์ถ๊ฐ ๊ท์น๋ค์ด ํฌํจ๋๋ค. ์ด๋ฅผ ํตํด ์ ์ฌ์ ์ธ ๋ฒ๊ทธ๋ฅผ ๋ ํจ๊ณผ์ ์ผ๋ก ์ก์๋ผ ์ ์๋ค.
project ์์ฑ์ TSConfig(ํ์
์คํฌ๋ฆฝํธ ํ๋ก์ ํธ ์ค์ ) ๊ฒฝ๋ก๋ฅผ ์ง์ ํ ๋ ์ฌ์ฉํ๋ฉฐ null, ๋ถ๋ฆฌ์ธ, ๋ฌธ์์ด, ๋ฐฐ์ด ํ์์ผ๋ก ์ง์ ํ ์ ์๋ค. true๋ก ์ค์ ํ๋ฉด ์์ค ํ์ผ์์ ๊ฐ์ฅ ๊ฐ๊น์ด tsconfig.json ํ์ผ์ ์ฌ์ฉํ๋ค.
export default tseslint.config(
eslint.configs.recommended,
// ...tseslint.configs.recommended, (์ ๊ฑฐ)
...tseslint.configs.recommendedTypeChecked, // (์ถ๊ฐ)
...fixupConfigRules(reactPlugin),
{ files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"] },
{
languageOptions: {
parserOptions: {
ecmaFeatures: { jsx: true },
tsconfigRootDir: import.meta.dirname, // (์ถ๊ฐ - ํ์ฌ ๋ชจ๋์ ๋๋ ํ ๋ฆฌ ๊ฒฝ๋ก)
project: ["tsconfig.node.json", "tsconfig.app.json"], // (์ถ๊ฐ)
},
globals: { ...globals.browser },
},
},
);
์์ฒ๋ผ ์์ ํ๋ฉด Parsing error: ESLint was configured to run on … ํ์ฑ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค. ESLint๋ eslint.config.js ํ์ผ์ ํ์ฑํ๋ ค๊ณ ํ์ง๋ง ์ปดํ์ผ ์ต์ ์ ์ด ํ์ผ์ด ํฌํจ๋์ด ์์ง ์์์ ๋ฐ์ํ๋ ๋ฌธ์ ๋ค.
tsconfig.node.json ํ์ผ include ๋ฐฐ์ด์ "eslint.config.js" ๋ฅผ ์ถ๊ฐํ ํ IDE๋ฅผ ์ข
๋ฃํ๋ค๊ฐ ๋ค์ ์์ํ๋ฉด ์๋ฌ๊ฐ ์ฌ๋ผ์ง๋ค. — ์ฐธ๊ณ ์ด์
{
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true,
"noEmit": true
},
// include ๋ฐฐ์ด์ "eslint.config.js" ์ถ๊ฐ
"include": ["vite.config.ts", "eslint.config.js"]
}
ํ์ดํ ์คํ์ผ
stylistic-type-checked ๊ณต์ ๊ตฌ์ฑ์ ํ๋ก๊ทธ๋จ ๋ก์ง์ ์ํฅ์ ์ฃผ์ง ์๋ ํ์
์คํฌ๋ฆฝํธ Best Practice ๊ท์น ๋ชจ์์ด๋ค. ์๋ฅผ๋ค์ด type, interface ํค์๋๋ฅผ ํผ์ฉํ๋๋์ ์ผ๊ด์ ์ธ interface ์ฌ์ฉ ๊ท์น ๋ฑ์ด ํฌํจ๋๋ค.
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
...tseslint.configs.stylisticTypeChecked, // (์ถ๊ฐ)
...fixupConfigRules(reactPlugin),
// ...
);
JS ํ์ ์ฒดํฌ ๋นํ์ฑ
์๋ฐ์คํฌ๋ฆฝํธ ํ์ผ์ ํ์
์ฒดํฌ๊ฐ ํ์ ์์ผ๋ฏ๋ก disable-type-checked ๊ณต์ ๊ตฌ์ฑ์ผ๋ก ํ์
์ฒดํฌ๋ฅผ ๋นํ์ฑํํ ์ ์๋ค. ์๋ ๊ตฌ์ฑ ๊ฐ์ฒด๋ฅผ tseslint.config(...) ์ธ์์ ์ถ๊ฐํ๋ค.
export default tseslint.config(
// ... ์๋ต
{
files: ["**/*.{js,mjs,cjs,jsx}"],
extends: [tseslint.configs.disableTypeChecked],
}, // ๊ฐ์ฒด ์ถ๊ฐ
);
์ฐธ๊ณ ๋ก extends ์์ฑ์ typescript-eslint์์ ์ ๊ณตํ๋ ์ ํธ๋ฆฌํฐ ํจ์๋ค. ๊ตฌ์ฑ ๊ฐ์ฒด ์์์ { ...config } ์ ๊ฐ์ฐ์ฐ์๋ฅผ ์ฌ์ฉํ ๊ฒ๊ณผ ๋์ผํ๋ค๊ณ ๋ณด๋ฉด ๋๋ค. ๊ท์น์ด๋ ์ต์
์ ์ฌ์ ์ํ ๋ ์ ์ฉํ ํจ์.
tsconfig.json ๋ฆฐํธ ๋นํ์ฑ
create vite๋ก ๋ฆฌ์กํธ ํ๋ก์ ํธ๋ฅผ ์์ฑํ๋ฉด tsconfig.json(ํน์ tsconfig.app.json) ํ์ผ์ ํ์ ์คํฌ๋ฆฝํธ์ ๊ด๋ จ๋ noUnusedLocals, noUnusedParameters, noFallthroughCasesInSwitch ๋ฆฐํ ํญ๋ชฉ์ด ์๋์ผ๋ก ์ถ๊ฐ๋๋ค. ํ์ง๋ง ์ด ํญ๋ชฉ๋ค์ ESLint์์ ์ ๊ณตํ๋ ๊ท์น๊ณผ ๊ฒน์น๋ฏ๋ก ๋นํ์ฑํ ํ๋ค.
{
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
// "noUnusedLocals": true, (๋นํ์ฑ)
// "noUnusedParameters": true, (๋นํ์ฑ)
// "noFallthroughCasesInSwitch": true (๋นํ์ฑ)
},
"include": ["src"]
}
eslint-plugin-prettier (eslint-config-prettier)
# ์ค์น ๋ช
๋ น์ด
pnpm add -D prettier eslint-plugin-prettier eslint-config-prettier
ESLint์ Prettier๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ฉด ํฌ๋งคํ ๊ท์น ์ถฉ๋์ด ๋ฐ์ํ๋ค. Prettier ๊ท์น์ ESLint ๊ท์น์ผ๋ก ํตํฉํด์ฃผ๋ eslint-plugin-prettier์, Prettier์ ์ถฉ๋ํ ์ ์๋ ESLint ๊ท์น์ ๋นํ์ฑํํ๋ eslint-config-prettier๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ค.
Pretter ํ๋ฌ๊ทธ์ธ์ ๋ค๋ฅธ ๊ณต์ ๊ตฌ์ฑ๋ณด๋ค ๋ค์ ์์น์์ผ์ผ ์ ๋๋ก ์๋ํ๋ค. — ์ฐธ๊ณ ๋งํฌ
// ...
import prettierPluginRecommended from "eslint-plugin-prettier/recommended"; // (์ถ๊ฐ)
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
...tseslint.configs.stylisticTypeChecked,
...fixupConfigRules(reactPlugin),
prettierPluginRecommended, // (์ถ๊ฐ)
// ...
{ rules: { "prettier/prettier": "warn" } } // (์ถ๊ฐ)
);
eslint-plugin-react ๐
# ์ค์น ๋ช
๋ น์ด
pnpm add -D eslint-plugin-react
React์์ ์ฝ๋ ํ์ง์ ์ ์งํ๊ณ ์ผ๊ด์ฑ์ ๋ณด์ฅํ๊ธฐ ์ํด eslint-plugin-react ํ๋ฌ๊ทธ์ธ ์ฌ์ฉํ๋ค. ๋ฆฌ์กํธ ์ ์ ๋ผ๋ฉด eslint-plugin-react-hooks์ ํจ๊ป ๊ฑฐ์ ํ์์ ์ผ๋ก ์ฌ์ฉํ๋ ํ๋ฌ๊ทธ์ธ์ด๋ค.
ESLint 9 ๋ฒ์ ๋ถํฐ Flat Config๋ฅผ ์ง์ํ์ง ์๋ ํ๋ฌ๊ทธ์ธ์ ํธํ์ ์ํด fixupConfigRules ๊ฐ์ ์ ํธ๋ฆฌํฐ ํจ์๋ฅผ ์ ๊ณตํ๋ค. ESLint๋ฅผ ์ด๊ธฐํํ๋ฉด ์๋์ฒ๋ผ ๋ฆฌ์กํธ ํ๋ฌ๊ทธ์ธ์ fixupConfigRules์ด ์ ์ฉ๋ผ ์๋ค.
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
...tseslint.configs.stylisticTypeChecked,
...fixupConfigRules(reactPlugin),
prettierPluginRecommended,
// ...
);
ํ์ง๋ง ์ด ์ํ์์ tsx ํ์ผ์ ์ด์ด๋ณด๋ฉด react/react-in-jsx-scope ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค. React๋ฅผ import ํ์ง ์์์ ๋ฐ์ํ๋ ๋ฌธ์ ์ธ๋ฐ, React 17 ๋ฒ์ ๋ถํฐ JSX ๋ณํ์ ์๋์ผ๋ก ์ฒ๋ฆฌํ๋ฏ๋ก ๋ณ๋์ import ๊ตฌ๋ฌธ์ ํ์์๋ค.

๊ตฌ์ฑ ํ์ผ์ ์๋์ฒ๋ผ ์์ ํ๋ฉด ์ค๋ฅ๋ ์ฌ๋ผ์ง๊ณ ๋ฆฐํธ๋ ์ ์๋ํ๋ค. — ์ฐธ๊ณ ์ด์
// ...
// import reactPlugin from "eslint-plugin-react/configs/recommended.js"; (์ ๊ฑฐ)
import reactPlugin from "eslint-plugin-react"; // (์ถ๊ฐ)
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
...tseslint.configs.stylisticTypeChecked,
// ...fixupConfigRules(reactPlugin), (์ ๊ฑฐ)
prettierPluginRecommended,
// ...
{
plugins: { react: reactPlugin }, // (์ถ๊ฐ)
settings: { react: { version: "detect" } }, // (์ถ๊ฐ)
rules: {
...reactPlugin.configs["recommended"].rules, // (์ถ๊ฐ)
...reactPlugin.configs["jsx-runtime"].rules, // (์ถ๊ฐ)
"prettier/prettier": "warn",
},
},
);
eslint-plugin-react-hooks ๐
# ์ค์น ๋ช
๋ น์ด
pnpm add -D eslint-plugin-react-hooks
eslint-plugin-react-hooks๋ ๋ฆฌ์กํธ ํ (Hooks)์ ๊ท์น์ ๊ฐ์ ํ๊ธฐ ์ํ ํ๋ฌ๊ทธ์ธ์ด๋ค. ํจ์ ์ปดํฌ๋ํธ/์ปค์คํ ํ ์์๋ง ํธ์ถ, ๋ฐ๋ณต๋ฌธ/์กฐ๊ฑด๋ฌธ/์ค์ฒฉ ํจ์ ๋ด์์ ํธ์ถ ๋ถ๊ฐ ๋ฑ์ ๊ท์น์ด ํฌํจ๋์ด ์๋ค.
๊ตฌ์ฑ ๊ฐ์ฒด์ plugins, rules ์์ฑ์ ์๋์ฒ๋ผ ์์ ํ๋ค. eslint-plugin-react-hooks ํ๋ฌ๊ทธ์ธ์ ESLint v9์ ๋ณ๊ฒฝ๋ API๋ฅผ ์์ง ์ง์ํ์ง ์๊ธฐ ๋๋ฌธ์ ๊ทธ๋๋ก ์ฌ์ฉํ๋ฉด context.getSource is not a function ์๋ฌ๊ฐ ๋ฐ์ํ๋ค. fixupPluginRules ์ ํธ๋ฆฌํฐ ํจ์๋ฅผ ์ฌ์ฉํ๋ฉด ์ค๋ฅ๋ฅผ ํด๊ฒฐํ ์ ์๋ค.
// ...
import { fixupPluginRules } from "@eslint/compat";
import reactHooksPlugin from "eslint-plugin-react-hooks"; // (์ถ๊ฐ)
export default tseslint.config(
// ...
{
plugins: {
react: reactPlugin,
"react-hooks": fixupPluginRules(reactHooksPlugin), // (์ถ๊ฐ)
},
settings: { react: { version: "detect" } },
rules: {
...reactPlugin.configs["recommended"].rules,
...reactPlugin.configs["jsx-runtime"].rules,
...reactHooksPlugin.configs.recommended.rules, // (์ถ๊ฐ)
"prettier/prettier": "warn",
},
},
);
eslint-plugin-react-refresh
# ์ค์น ๋ช
๋ น์ด
pnpm add -D eslint-plugin-react-refresh
React Fast Refresh๋ ๊ฐ๋ฐ ํ๊ฒฝ์์ ์ปดํฌ๋ํธ ์์ ์ ์ ์ฒด ์ฝ๋๋ฅผ ์๋ก๊ณ ์นจ ํ์ง ์๊ณ ๋ ์ฆ๊ฐ์ ์ธ ํผ๋๋ฐฑ์ ์ ๊ณตํ๊ธฐ ์ํ ๊ธฐ๋ฅ์ผ๋ก Webpack, Vite ๋ฑ ์ฌ๋ฌ ๋น๋ ๋๊ตฌ์์ ์ ๊ณตํ๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก ์ปดํฌ๋ํธ๋ง ํฌํจ๋ ํ์ผ์ ์์ ํ๋ฉด ํด๋น ํ์ผ์ ์ฝ๋๋ง ์ ๋ฐ์ดํธํ ํ ์ปดํฌ๋ํธ๋ฅผ ๋ค์ ๋ ๋๋งํ๊ณ , ์ปดํฌ๋ํธ ์ธ์ ๋ค๋ฅธ ๋ด๋ณด๋ด๊ธฐ๊ฐ ์๋ ํ์ผ์ ์์ ํ๋ฉด ํด๋น ํ์ผ๊ณผ ์ด๋ฅผ ๊ฐ์ ธ์ค๋ ๋ค๋ฅธ ๋ชจ๋ ํ์ผ์ ๋ค์ ์คํํ๋ค.
eslint-plugin-react-refresh ํ๋ฌ๊ทธ์ธ์ Fast Refresh๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๋์ํ๋๋ก ๋ณด์ฅํ๊ธฐ ์ํด React ์ฝ๋์ ํ์ํ ๊ท์น๋ค์ ์ ์ฉํ๋ค. ์๋ฅผ ๋ค์ด ์๋ ์ฝ๋๋ react-refresh/only-export-components ๊ท์น ์๋ฌ๊ฐ ๋ฐ์ํ๋๋ฐ, Fast Refresh๋ ์ปดํฌ๋ํธ๋ง์ ๋ด๋ณด๋์ ๋ ์๋ํ๊ธฐ ๋๋ฌธ์ ์ฌ๋ฌ ๊ณณ์์ ์ฌ์ฉํ๋ ๋ณ์๋ ์์๋ ์๋ก์ด ํ์ผ์ ์์ฑํ๋ ๊ฒ์ ์ ๋ํ๋ค.
// Error! react-refresh/only-export-components
export const myVariable = 88;
export const MyComponent = () => <div>Hello World</div>;
eslint-plugin-react-refresh ํ๋ฌ๊ทธ์ธ์ ๊ธฐ๋ณธ์ ์ผ๋ก tsx, jsx ํ์ผ์์๋ง ์ฌ์ฉํ ์ ์๋ค.
// ...
import reactRefresh from "eslint-plugin-react-refresh"; // (์ถ๊ฐ)
export default tseslint.config(
// ...
{
plugins: {
react: reactPlugin,
"react-hooks": fixupPluginRules(reactHooksPlugin),
"react-refresh": reactRefresh, // (์ถ๊ฐ)
},
settings: { react: { version: "detect" } },
rules: {
...reactPlugin.configs["recommended"].rules,
...reactPlugin.configs["jsx-runtime"].rules,
...reactHooksPlugin.configs.recommended.rules,
"prettier/prettier": "warn",
"react-refresh/only-export-components": "warn", // (์ถ๊ฐ)
},
},
);
eslint-plugin-jsx-a11y
# ์ค์น ๋ช
๋ น์ด
pnpm add -D eslint-plugin-jsx-a11y
eslint-plugin-jsx-a11y์ React ํ๋ก์ ํธ์์ ์ ๊ทผ์ฑ์ ๊ฐ์ ํ๊ธฐ ์ํ ํ๋ฌ๊ทธ์ธ์ผ๋ก, JSX ์ฝ๋์์ ๋ฐ์ํ๋ ์ ๊ทผ์ฑ ๊ด๋ จ ๋ฌธ์ ๋ฅผ ๊ฐ์งํ๊ณ ๊ฒฝ๊ณ ๋ฅผ ํ์ํ๋ค.
// Error! jsx-a11y/anchor-is-valid
<a href="#">Click here</a>
// Error! jsx-a11y/alt-text
<img src="image.jpg" />
eslint-plugin-jsx-a11y ํ๋ฌ๊ทธ์ธ์ ์์ง ESLint v9 ํ์ ์ ์๋ฅผ ์ง์ํ์ง ์์ง๋ง ์ฌ์ฉํ๋๋ฐ ํฐ ๋ฌธ์ ๋ ์๋ค. ๊ณต์ ๊ตฌ์ฑ(Shareable Configs)์ recommended, strict๋ฅผ ์ ๊ณตํ๋ค. ์ผ๋ฐ์ ์ธ ์ํฉ์ด๋ผ๋ฉด recommended ์ฌ์ฉ์ ์ถ์ฒํ๋ค.
// ...
import jsxA11y from "eslint-plugin-jsx-a11y"; // (์ถ๊ฐ)
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
...tseslint.configs.stylisticTypeChecked,
jsxA11y.flatConfigs.recommended, // (์ถ๊ฐ)
prettierPluginRecommended,
// ...
);
eslint-config-standard ๐
# ์ค์น ๋ช
๋ น์ด
pnpm add -D eslint-config-standard
eslint-config-standard๋ JavaScript Standard Style์ ๋ฐ๋ฅด๋ ESLint ๊ณต์ ์ค์ ์ด๋ค. ๋ค์ฌ์ฐ๊ธฐ 2์นธ, ๋ฌธ์์ด์ ์์ ๋ฐ์ดํ ์ฌ์ฉ, ๋ณ์/ํจ์ ์ด๋ฆ์ ์นด๋ฉ์ผ์ด์ค ์ฌ์ฉ ๋ฑ ์๋ฐ์คํฌ๋ฆฝํธ ์ง์์์ ๋๋ฆฌ ์ฐ์ด๋ ์ฝ๋ ์คํ์ผ ๊ท์น์ ํฌํจํ๋ค.
eslint-config-standard ํ๋ฌ๊ทธ์ธ์ ์์ง ESLint 9๋ฅผ ์ง์ํ์ง ์๊ธฐ ๋๋ฌธ์ FlatCompat ํจ์๋ฅผ ์ด์ฉํด์ ์ ์ฉํด์ผ ํ๋ค. FlatCompat ํจ์๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด @eslint/eslintrc ํจํค์ง๋ฅผ ์ค์นํด์ผ ํ๋ค.
// ...
import { FlatCompat } from "@eslint/eslintrc"; // (์ถ๊ฐ)
const compat = new FlatCompat(); // (์ถ๊ฐ)
export default tseslint.config(
eslint.configs.recommended,
...compat.extends("eslint-config-standard"), // (์ถ๊ฐ)
...tseslint.configs.recommendedTypeChecked,
...tseslint.configs.stylisticTypeChecked,
jsxA11y.flatConfigs.recommended,
prettierPluginRecommended,
// ...
);
๊ธฐํ ํ๋ฌ๊ทธ์ธ
@tanstack/eslint-plugin-query ๐
# ์ค์น ๋ช
๋ น์ด
pnpm add -D @tanstack/eslint-plugin-query
@tanstack/eslint-plugin-query๋ TanStack Query(React Query)์ ๊ด๋ จ๋ ๊ท์น์ ์ ๊ณตํ๋ ํ๋ฌ๊ทธ์ธ์ด๋ค. ์์ง ESLint v9๋ฅผ ์ง์ํ์ง ์๊ธฐ ๋๋ฌธ์ ์๋์ ๊ฐ์ Workaround๋ก ํ์ฑํ์ํฌ ์ ์๋ค.
// ...
import * as tanstackQueryPlugin from "@tanstack/eslint-plugin-query"; // (์ถ๊ฐ)
export default tseslint.config(
// ...
{
plugins: {
// ...
"@tanstack/query": tanstackQueryPlugin, // (์ถ๊ฐ)
},
rules: {
// ...
...tanstackQueryPlugin.configs.recommended.rules, // (์ถ๊ฐ)
},
},
);
eslint-plugin-tailwindcss
# ์ค์น ๋ช
๋ น์ด
pnpm add -D eslint-plugin-tailwindcss
eslint-plugin-tailwindcss๋ Tailwind CSS ํด๋์ค ์ด๋ฆ ๊ฒ์ฆ, ์ค๋ณต ํด๋์ค ๋ฐฉ์ง, ํด๋์ค ์๋ ์ ๋ ฌ ๋ฑ์ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ํ๋ฌ๊ทธ์ธ์ด๋ค. ํนํ ํด๋์ค ์๋ ์ ๋ ฌ ๊ธฐ๋ฅ์ด ์ ์ฉํ๋ค. (๋ ์์ธํ ๋ด์ฉ์ ํฌ์คํ ๋งํฌ ์ฐธ๊ณ )
// ...
import tailwindPlugin from "eslint-plugin-tailwindcss"; // (์ถ๊ฐ)
export default tseslint.config(
...tailwindPlugin.configs["flat/recommended"], // (์ถ๊ฐ)
// ...
);
์ต์ข ๊ตฌ์ฑ
TypeScript, React, Tailwind CSS, TanStack Query, Prettier๋ฅผ ์ฌ์ฉํ์ ๋ ์ต์ข ๊ตฌ์ฑ์ ์๋์ ๊ฐ๋ค.
// @ts-check
import globals from "globals";
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
import prettierPluginRecommended from "eslint-plugin-prettier/recommended";
import reactPlugin from "eslint-plugin-react";
import reactHooksPlugin from "eslint-plugin-react-hooks";
import tailwindPlugin from "eslint-plugin-tailwindcss";
import * as tanstackQueryPlugin from "@tanstack/eslint-plugin-query";
import { FlatCompat } from "@eslint/eslintrc";
import jsxA11y from "eslint-plugin-jsx-a11y";
import reactRefresh from "eslint-plugin-react-refresh";
import { fixupPluginRules } from "@eslint/compat";
const compat = new FlatCompat();
export default tseslint.config(
eslint.configs.recommended,
/** @see https://github.com/standard/eslint-config-standard/issues/411 */
...compat.extends("eslint-config-standard"),
...tseslint.configs.recommendedTypeChecked,
...tseslint.configs.stylisticTypeChecked,
...tailwindPlugin.configs["flat/recommended"],
jsxA11y.flatConfigs.recommended,
prettierPluginRecommended,
{
languageOptions: {
parserOptions: {
ecmaFeatures: { jsx: true },
tsconfigRootDir: import.meta.dirname,
/** @see https://github.com/vitejs/vite/issues/13747#issuecomment-1636870022 */
project: ["tsconfig.node.json", "tsconfig.app.json"],
},
globals: { ...globals.browser },
},
},
{
/** @see https://typescript-eslint.io/packages/typescript-eslint/#flat-config-extends */
files: ["**/*.{js,mjs,cjs,jsx}"],
extends: [tseslint.configs.disableTypeChecked],
},
{
plugins: {
react: reactPlugin,
/** @see https://github.com/facebook/react/issues/28313 */
"react-hooks": fixupPluginRules(reactHooksPlugin),
/** @see https://github.com/TanStack/query/issues/7544 */
"@tanstack/query": tanstackQueryPlugin,
"react-refresh": reactRefresh,
},
settings: { react: { version: "detect" } },
rules: {
...reactPlugin.configs["recommended"].rules,
...reactPlugin.configs["jsx-runtime"].rules,
...reactHooksPlugin.configs.recommended.rules,
...tanstackQueryPlugin.configs.recommended.rules,
"prettier/prettier": "warn",
"react-refresh/only-export-components": "warn",
"@typescript-eslint/no-unused-vars": "warn",
camelcase: "off",
},
},
);
์ค์นํ ํจํค์ง ๋ชฉ๋ก. ์๋ ์ฝ๋๋ฅผ ๋ณต์ฌ ๋ถ์ฌ๋ฃ๊ธฐ ํ๋ฉด ๋ชจ๋ ์ค์น๋๋ค.
pnpm add -D \
eslint \
@eslint/compat \
@eslint/eslintrc \
@eslint/js \
globals \
prettier \
typescript-eslint \
eslint-config-prettier \
eslint-config-standard \
eslint-plugin-jsx-a11y \
eslint-plugin-prettier \
eslint-plugin-react \
eslint-plugin-react-hooks \
eslint-plugin-react-refresh \
eslint-plugin-tailwindcss \
@tanstack/eslint-plugin-query
CLI ๋ณ๊ฒฝ ์ฌํญ
create vite๋ก ํ๋ก์ ํธ๋ฅผ ์ธํ ํ๋ฉด package.json ํ์ผ scripts ์์ฑ์ ์๋ ๋ช ๋ น์ด๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ์ถ๊ฐ๋๋ค. ํ์ฌ ๋๋ ํ ๋ฆฌ์ ๋ชจ๋ ํ์ ์คํฌ๋ฆฝํธ ํ์ผ์ ๊ฒ์ฌํ๊ณ , ๋ถํ์ํ eslint-disable ์ง์๋ฌธ์ ๋ณด๊ณ ํ๋ฉฐ, ๊ฒฝ๊ณ ๊ฐ ํ๋๋ผ๋ ์์ผ๋ฉด ์ค์งํ๋๋ก ์ค์ ๋์ด ์๋ค.
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
์ ๋ช
๋ น์ด์ --ext ํ๋๊ทธ๋ ์ถ๊ฐ ํ์ฅ์๋ฅผ ์ง์ ํ ๋ ์ฌ์ฉํ๋๋ฐ, Flat Config์์ ๋ ์ด์ ์ง์ํ์ง ์๊ธฐ ๋๋ฌธ์ ์ญ์ ํด์ผ ํ๋ค(์ฐธ๊ณ ๋งํฌ). ๋์ ์๋์ฒ๋ผ ESLint ๊ตฌ์ฑ ํ์ผ์ files ์์ฑ์ ๋ฆฐํธ ๋์ ํ์ผ์ ์ง์ ํ๋ ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝํด์ผ ํ๋ค.
export default [
{
files: ["**/*.ts", "**/*.tsx"],
// any additional configuration for these file types here
},
];
๊ธ ์์ ์ฌํญ์ ๋ ธ์ ํ์ด์ง์ ๊ฐ์ฅ ๋น ๋ฅด๊ฒ ๋ฐ์๋ฉ๋๋ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํด ์ฃผ์ธ์
'๐ช Programming' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๋๊ธ
์ด ๊ธ ๊ณต์ ํ๊ธฐ
-
๊ตฌ๋
ํ๊ธฐ
๊ตฌ๋ ํ๊ธฐ
-
์นด์นด์คํก
์นด์นด์คํก
-
๋ผ์ธ
๋ผ์ธ
-
ํธ์ํฐ
ํธ์ํฐ
-
Facebook
Facebook
-
์นด์นด์ค์คํ ๋ฆฌ
์นด์นด์ค์คํ ๋ฆฌ
-
๋ฐด๋
๋ฐด๋
-
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
-
Pocket
Pocket
-
Evernote
Evernote
๋ค๋ฅธ ๊ธ
-
[JS] ์๋ฐ์คํฌ๋ฆฝํธ Set ๊ฐ์ฒด์ ์งํฉ ์ฐ์ฐ ๋ฉ์๋ (๊ต์งํฉ, ํฉ์งํฉ ๋ฑ)
[JS] ์๋ฐ์คํฌ๋ฆฝํธ Set ๊ฐ์ฒด์ ์งํฉ ์ฐ์ฐ ๋ฉ์๋ (๊ต์งํฉ, ํฉ์งํฉ ๋ฑ)
2024.07.04 -
[Git] RSS ๊ธ ๋ฐํ ์(ํฐ์คํ ๋ฆฌ ๋ฑ) GitHub ์๋ ์ปค๋ฐ ๋ฐฉ๋ฒ
[Git] RSS ๊ธ ๋ฐํ ์(ํฐ์คํ ๋ฆฌ ๋ฑ) GitHub ์๋ ์ปค๋ฐ ๋ฐฉ๋ฒ
2024.07.03 -
[DevTools] nvm๋ณด๋ค 40๋ฐฐ ๋น ๋ฅธ ๋ ธ๋ ๋ฒ์ ๊ด๋ฆฌ ๋๊ตฌ — fnm
[DevTools] nvm๋ณด๋ค 40๋ฐฐ ๋น ๋ฅธ ๋ ธ๋ ๋ฒ์ ๊ด๋ฆฌ ๋๊ตฌ — fnm
2024.06.18 -
[DevTools] Prettier ์ฃผ์ ํฌ๋งทํ ์ต์ ๊ณผ ์ถ์ฒ ์ค์
[DevTools] Prettier ์ฃผ์ ํฌ๋งทํ ์ต์ ๊ณผ ์ถ์ฒ ์ค์
2024.06.15