๋ฐ˜์‘ํ˜•

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
  },
];

 


๊ธ€ ์ˆ˜์ •์‚ฌํ•ญ์€ ๋…ธ์…˜ ํŽ˜์ด์ง€์— ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”
๋ฐ˜์‘ํ˜•