Hello Kitty Eyes Shut
๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๐Ÿ’ป๊ณต๋ถ€ ๊ธฐ๋ก/๐Ÿ“Œ Frontend

[Frontend] React Native + ESLINT v9 Flat Config

๋ฐ˜์‘ํ˜•

 

 

 

 

 

๐Ÿ“‘ ๋“ค์–ด๊ฐ€๋ฉฐ

์•ž์„  ํฌ์ŠคํŒ…์—์„œ ์•Œ์•„๋ดค๋“ฏ์ด

ESLint v9๋ถ€ํ„ฐ ๊ธฐ์กด์˜ .eslintrc๋Š” ์ •์‹ ์ง€์›์ด ์ข…๋ฃŒ๋˜๊ณ ,
์ƒˆ๋กœ์šด Flat Config ๋ฐฉ์‹ (eslint.config.js)์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

 

์ด๋ฒˆ์— ๋™์•„๋ฆฌ์—์„œ ์ƒˆ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•˜๋ฉด์„œ
Expo + React Native ํ™˜๊ฒฝ์— ๋งž์ถฐ ESLint๋ฅผ ๋ฏธ๋ฆฌ ์„ธํŒ…ํ•ด๋‘๋ ค๊ณ  ํ–ˆ๋Š”๋ฐ  ,,

Expo + React Native ํ™˜๊ฒฝ์—์„œ๋Š” ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ•˜๋Š”์ง€ ์ •๋ฆฌํ•ด ๋‘” ๋ธ”๋กœ๊ทธ๊ฐ€ ๋”ฑํžˆ ์—†์–ด์„œ

์„ค์ •ํ•˜๋Š” ๋ฐ ์—ฌ๋Ÿฌ ์—๋Ÿฌ๋“ค์ด ์ž๊พธ ๋‚˜์„œ ์ƒ๋‹นํžˆ ํ™”๊ฐ€ ๋‚ฌ์—ˆ๋‹ค ,, ^^ ,,๐Ÿคฏ

 

๊ทธ๋ž˜์„œ ์ดํ›„์— ํ˜น์‹œ ์„ค์ •ํ•˜์‹ค ๋ถ„๋“ค์ด ์ฐธ๊ณ ํ•  ์ˆ˜ ์žˆ๋„๋ก

์งง๊ฒŒ๋‚˜๋งˆ ๋ฐœ์ƒํ–ˆ๋˜ ์—๋Ÿฌ๋“ค์„ ์–ด๋–ป๊ฒŒ ์ˆ˜์ •ํ–ˆ๋Š”์ง€ ์ •๋ฆฌํ•ด๋‘๊ณ ์ž ํ•œ๋‹ค.

 

 


๐ŸŸฅ MODULE_TYPELESS_PACKAGE_JSON ๊ฒฝ๊ณ 

Warning: Module type of file:///.../eslint.config.js is not specified

 

๐Ÿ’ก ์›์ธ

eslint.config.js ๋‚ด๋ถ€์— ES Module ๋ฌธ๋ฒ•์„ ์“ฐ๋ฉด
Node๊ฐ€ “์ด ํŒŒ์ผ ESM์ธ์ง€ CJS์ธ์ง€ ๋ชจ๋ฅด๊ฒ ์Œ” ํ•˜๊ณ  ๊ฒฝ๊ณ ๋ฅผ ๋„์šฐ๋Š” ๊ฒƒ์ด๋‹ค.

(์ฆ‰, ํŒŒ์ผ์˜ ๋ชจ๋“ˆ ํƒ€์ž…์ด ๋ช…ํ™•ํ•˜์ง€ ์•Š์•„์„œ ์ƒ๊ธฐ๋Š” ๋ฌธ์ œ์ด๋‹ค.)

 

โœ… ํ•ด๊ฒฐ

Flat Config๋Š” CommonJS๋กœ ์ž‘์„ฑํ•˜๋Š” ๊ฒŒ ๊ฐ€์žฅ ์•ˆ์ •์ ์ด๋ผ๊ณ  ํ•œ๋‹ค.

๊ทธ๋ž˜์„œ ํŒŒ์ผ๋ช…์„ .cjs๋กœ ๋ฐ”๊พธ๊ณ  require ๋ฐฉ์‹์œผ๋กœ ์ž‘์„ฑํ•ด์ฃผ๋‹ˆ ๊ฒฝ๊ณ ๊ฐ€ ์‚ฌ๋ผ๋‹ค.

// eslint.config.cjs ๋กœ ์ž‘์„ฑ
const js = require("@eslint/js");

 

๐Ÿ“Œ ์ฐธ๊ณ 

์‚ฌ์‹ค ์ฒ˜์Œ์—๋Š” package.json์— ์•„๋ž˜์ฒ˜๋Ÿผ "type": "module"์„ ์ถ”๊ฐ€ํ•ด์„œ
ESM์œผ๋กœ ๊ฐ•์ œ ์ธ์‹์‹œํ‚ค๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์ ‘๊ทผํ–ˆ์—ˆ๋‹ค.

"type": "module"

 

ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ๋ณ€๊ฒฝํ•˜๋‹ˆ ESM ๊ด€๋ จ ์˜ค๋ฅ˜๋“ค์ด ์—ฐ์‡„์ ์œผ๋กœ ํ„ฐ์ง€๊ธฐ ์‹œ์ž‘ํ–ˆ๊ณ ,
Expo + RN ํ™˜๊ฒฝ์—์„œ๋Š” ๊ทธ ์—๋Ÿฌ๋“ค์„ ํ•˜๋‚˜์”ฉ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒƒ์ด ํ›จ์”ฌ ๋” ๊นŒ๋‹ค๋กœ์› ๋‹ค.

 

๊ทธ๋ž˜์„œ ์ข€ ๋” ๊ฒ€์ƒ‰ํ•ด๋ณด๋‹ˆ, React Native ๊ธฐ๋ฐ˜ ํ”„๋กœ์ ํŠธ์—์„œ๋Š”

CJS ๋ฐฉ์‹์œผ๋กœ Flat Config๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ์‚ฌ๋ก€๊ฐ€ ํ›จ์”ฌ ๋งŽ๊ณ , ๋” ์•ˆ์ •์ ์ด๋ผ๋Š” ์ ์„ ํ™•์ธํ–ˆ๋‹ค.
๊ทธ๋ž˜์„œ ๊ตณ์ด ESM์„ ๊ณ ์ง‘ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๋Š” ๊ฒฐ๋ก ์„ ๋‚ด๋ฆฌ๊ณ , ๊น”๋”ํ•˜๊ฒŒ CJS๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค ๐Ÿค—

 

 


๐ŸŸง A config object is using the "extends" key ์—๋Ÿฌ

A config object is using the "extends" key, which is not supported in flat config system.

 

๐Ÿ’ก ์›์ธ

Flat Config์—์„œ๋Š” ๋” ์ด์ƒ "extends"๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์–ด์„œ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ์ด๋‹ค.

(์ฆ‰, ๊ธฐ์กด .eslintrc ๋ฐฉ์‹์˜ ํ™•์žฅ ๊ตฌ์กฐ๊ฐ€ Flat Config์—์„œ๋Š” ์™„์ „ํžˆ ์ œ๊ฑฐ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•˜๋Š” ์˜ค๋ฅ˜๋‹ค.)

 

โœ… ํ•ด๊ฒฐ

๊ธฐ์กด์— ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ–ˆ์—ˆ๋˜ ์ฝ”๋“œ๋ฅผ

extends: ["eslint:recommended", "plugin:react/recommended"];

 

์ด๋ ‡๊ฒŒ ์•„๋ž˜์ฒ˜๋Ÿผ ์•„๋ž˜์ฒ˜๋Ÿผ ๊ฐ ํ”Œ๋Ÿฌ๊ทธ์ธ์˜ preset์„ ๋ฐฐ์—ด๋กœ ์ง์ ‘ ๋‚˜์—ดํ•ด์ฃผ๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ฐ”๊พธ์–ด์ฃผ์—ˆ๋‹ค.

module.exports = [
  js.configs.recommended,
  reactPlugin.configs.flat.recommended,
  ...
];

 

 


๐ŸŸจ React version not specified ๊ฒฝ๊ณ 

Warning: React version not specified in eslint-plugin-react settings.

 

๐Ÿ’ก ์›์ธ

Flat Config ๊ตฌ์กฐ์—์„œ๋Š” settings๊ฐ€ ์ตœ์ƒ๋‹จ์œผ๋กœ ๋ถ„๋ฆฌ๋ผ ์žˆ์–ด์„œ
eslint-plugin-react์˜ react.version ์ž๋™ ๊ฐ์ง€๊ฐ€ ์ œ๋Œ€๋กœ ์•ˆ ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค๊ณ  ํ•œ๋‹ค.

๊ทธ๋ž˜์„œ React ๋ฒ„์ „์„ ๋ช…์‹œํ•˜์ง€ ์•Š์œผ๋ฉด ์œ„์™€ ๊ฐ™์€ ๊ฒฝ๊ณ ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

 

โœ… ํ•ด๊ฒฐ

์–œ ๊ฐ„๋‹จํžˆ ๊ทธ๋ƒฅ ๋ช…์‹œ์ ์œผ๋กœ ์ ์–ด์ฃผ๊ธฐ๋งŒ ํ•˜๋ฉด ๋์ด๋‹ค.

settings: {
  react: {
    version: "19.1.0", // ๊ฐ์ž ๋ฒ„์ „์— ๋งž๊ฒŒ ์•Œ์•„์„œ ,,
  },
}

 

 


๐ŸŸฉ babel.config.js: 'module' is not defined

'module' is not defined no-undef

 

๐Ÿ’ก ์›์ธ

babel.config.js๋Š” ๋ฌด์กฐ๊ฑด CJS ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š”๋ฐ,
ํ•˜์ง€๋งŒ ESLint๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ CJS ์ „์—ญ(module, require)์„ ๋ชจ๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ์ด๋‹ค.

(์ฆ‰, Babel์€ CJS ํŒŒ์ผ์ธ๋ฐ ESLint๋Š” ESM ๊ธฐ์ค€์œผ๋กœ ๊ฒ€์‚ฌํ•ด์„œ ์ถฉ๋Œํ•˜๋Š” ์ƒํ™ฉ์ด๋‹ค.)

 

โœ… ํ•ด๊ฒฐ

๊ทธ๋ƒฅ ESLint ์„ค์ • ํ•˜๋‹จ์—๋‹ค๊ฐ€ babel.config.js์„ ๋”ฐ๋กœ ๋นผ์„œ ignore ์ฒ˜๋ฆฌ ํ•ด์ฃผ์—ˆ๋‹ค.

{
    files: ["babel.config.js"],
    languageOptions: {
      globals: {
        module: "readonly",
        require: "readonly",
      },
    },
    rules: {},
  }

 

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด Babel ์„ค์ • ํŒŒ์ผ๋งŒ ์˜ˆ์™ธ์ ์œผ๋กœ ์ •์ƒ ๊ฒ€์‚ฌ๋˜๋ฉฐ,
Flat Config์˜ ๋‚˜๋จธ์ง€ ๊ทœ์น™๊ณผ๋„ ์ถฉ๋Œํ•˜์ง€ ์•Š๋Š”๋‹ค.

 


๐ŸŸฆ React Hooks / Refresh๊ฐ€ Flat Config์—์„œ ๋™์ž‘ ์•ˆ ํ•จ

๐Ÿ’ก ์›์ธ

Flat Config์—์„œ๋Š” ๊ธฐ์กด .eslintrc์ฒ˜๋Ÿผ
plugins: ["react", "react-hooks", ...] ๊ฐ™์€ ๋ฌธ์ž์—ด ๊ธฐ๋ฐ˜ ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ ์–ธ์ด ์ง€์›๋˜์ง€ ์•Š๋Š”๋‹ค.

๋Œ€์‹ , ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๋ฐ˜๋“œ์‹œ ๊ฐ์ฒด ํ˜•ํƒœ๋กœ ์ง์ ‘ ๋“ฑ๋กํ•ด์•ผ๋งŒ ์ •์ƒ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์—
๋ฌธ์ž์—ด๋กœ ๋„ฃ์œผ๋ฉด ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ฝ์ง€ ๋ชปํ•ด Hooks ๊ทœ์น™๊ณผ Refresh ๊ทœ์น™์ด ์ „ํ˜€ ์ ์šฉ๋˜์ง€ ์•Š๋Š” ๊ฒƒ์ด๋‹ค.

 

โœ… ํ•ด๊ฒฐ

ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์•„๋ž˜์ฒ˜๋Ÿผ ๊ฐ์ฒด ํ˜•ํƒœ๋กœ ๋ช…์‹œ์ ์œผ๋กœ ์„ ์–ธํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

plugins: {
  react: reactPlugin,
  "react-hooks": reactHooks,
  "react-refresh": reactRefresh,
},

 

 


๐ŸŸช lint ์‹คํ–‰ํ–ˆ๋Š”๋ฐ ์•„๋ฌด ๊ทœ์น™์ด ์ ์šฉ๋˜์ง€ ์•Š์Œ

๐Ÿ’ก ์›์ธ

์ผ๋ฐ˜ ๊ทœ์น™๋ณด๋‹ค ํ™•์žฅ ๊ทœ์น™(js.configs.recommended ๋“ฑ)์„ ๋จผ์ € ๋„ฃ์–ด์•ผ ํ•ด์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋ผ๊ณ  ํ•œ๋‹ค.

์ˆœ์„œ๊ฐ€ ๋’ค๋ฐ”๋€Œ๋ฉด ํ™•์žฅ preset์ด ๋ฎ์–ด์“ฐ์ด์ง€ ๋ชปํ•˜๊ณ ,
๊ฒฐ๊ณผ์ ์œผ๋กœ ESLint๊ฐ€ ๊ทœ์น™์„ ์ œ๋Œ€๋กœ ๋กœ๋”ฉํ•˜์ง€ ๋ชปํ•ด ์•„๋ฌด ๊ทœ์น™๋„ ์ ์šฉ๋˜์ง€ ์•Š๋Š” ์ƒํƒœ๊ฐ€ ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

โœ… ํ•ด๊ฒฐ

๋”ฐ๋ผ์„œ ๊ทธ๋ƒฅ ์•„๋ž˜์™€ ๊ฐ™์€ ์ˆœ์„œ๋กœ ๋ฐ”๊ฟ”์ฃผ๊ธฐ๋งŒ ํ•˜ ๋ฐ”๋กœ ํ•ด๊ฒฐ๋œ๋‹ค.

module.exports = [
  { ignores: [...] },

  // ํ™•์žฅ config ๋จผ์ €
  js.configs.recommended,
  reactPlugin.configs.flat.recommended,
  eslintConfigPrettier,

  // ๊ทธ๋‹ค์Œ์— ๋‚ด ๊ทœ์น™
  {
    files: ["**/*.{js,jsx}"],
    rules: { ... }
  }
];

 

์ฐธ๊ณ ๋กœ, Flat Config๋Š” ๋ฐฐ์—ด์„ ์œ„์—์„œ ์•„๋ž˜๋กœ ์ˆœ์ฐจ์ ์œผ๋กœ ๋ณ‘ํ•ฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—
์ด ์ˆœ์„œ๋ฅผ ์ง€์ผœ์•ผ ๋ชจ๋“  ๊ทœ์น™์ด ์ •์ƒ์ ์œผ๋กœ ์ ์šฉ๋œ๋‹ค๊ณ  ํ•œ๋‹ค.

 


โฌ› ์ตœ์ข… ์„ค์ • ํŒŒ์ผ

๋‚ด๊ฐ€ ์‚ฌ์šฉ ์ค‘์ธ ์„ค์ •์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

(์‚ฌ์šฉํ•˜๋‹ค๊ฐ€ ๋ณ€๊ฒฝ์ด ์ƒ๊ธธ ์ˆ˜๋„ ์žˆ๋Š”๋ฐ, ๊ทธ๋Ÿผ ์ถ”ํ›„ ์ˆ˜์ •ํ•˜๋Ÿฌ ์˜ค๊ฒ ์Šต๋‹ˆ๋‹น ,,๐Ÿคง)

const js = require("@eslint/js");
const eslintConfigPrettier = require("eslint-config-prettier");
const reactPlugin = require("eslint-plugin-react");
const reactHooks = require("eslint-plugin-react-hooks");
const reactRefresh = require("eslint-plugin-react-refresh");
const globals = require("globals");

module.exports = [
  {
    settings: {
      react: {
        version: "19.1.0",
      },
    },

    ignores: [
      "node_modules",
      "dist",
      "build",
      "coverage",
      "android",
      "ios",
      ".expo",
      ".expo-shared",
    ],
  },

  js.configs.recommended,
  reactPlugin.configs.flat.recommended,
  eslintConfigPrettier,

  {
    files: ["**/*.{js,jsx}"],

    languageOptions: {
      ecmaVersion: 2020,
      sourceType: "module",
      globals: {
        ...globals.browser,
        ...globals.es2021,
        __DEV__: "readonly",
      },
    },

    plugins: {
      react: reactPlugin,
      "react-hooks": reactHooks,
      "react-refresh": reactRefresh,
    },

    rules: {
      // ์ฝ˜์†”์€ ๊ฒฝ๊ณ ๋งŒ
      "no-console": ["warn", { allow: ["warn", "error"] }],
      "no-debugger": "error",

      // ์ตœ์‹  React + RN์—์„œ ํ•„์š” ์—†๋Š” ๊ทœ์น™ ๋„๊ธฐ
      "react/react-in-jsx-scope": "off",
      "react/prop-types": "off",

      // react-hooks ์ถ”์ฒœ ๊ทœ์น™ ์ง์ ‘ ์ถ”๊ฐ€
      "react-hooks/rules-of-hooks": "error",
      "react-hooks/exhaustive-deps": "warn",

      // react-refresh ๊ธฐ๋ณธ ๊ถŒ์žฅ ๊ทœ์น™ ์ง์ ‘ ์ถ”๊ฐ€
      "react-refresh/only-export-components": "warn",

      // ์•ˆ ์“ฐ๋Š” ๋ณ€์ˆ˜ ๊ฒฝ๊ณ  ์ž„์‹œ๋กœ ๊บผ๋‘ 
      // (๊ฐœ๋ฐœ ๋๋‚˜๋ฉด ๋‹ค์‹œ ํ‚ฌ๊ฑฐ๋‹ˆ๊นŒ ์ตœ๋Œ€ํ•œ ์•ˆ ์“ฐ๊ฒŒ ํ•˜์„ธ์š” !!)
      "no-unused-vars": "off",
    },
  },

  {
    files: ["babel.config.js"],
    languageOptions: {
      globals: {
        module: "readonly",
        require: "readonly",
      },
    },
    rules: {},
  },
];

 

 

๋ฐ˜์‘ํ˜•