[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