์ฐ๋ฆฌ ํ์ ์ํ ESLint, Prettier ๊ณต์ ์ปจํผ๊ทธ ๋ง๋ค์ด๋ณด๊ธฐ
์ฐ๋ฆฌ ํ์ ์ํ ESLint, Prettier ๊ณต์ ์ปจํผ๊ทธ ๋ง๋ค์ด๋ณด๊ธฐ ๊ด๋ จ
์ต์ข
์ฝ๋ ์์ ๋ Github ์์ ํ๋ก์ ํธ (iicdii/shared-config-example
)์์ ํ์ธํ ์ ์์ต๋๋ค.
์๋ก
ESLint์ Prettier๋ JavaScript๋ TypeScript์ ์ฝ๋ ํ์ง์ ๋์ด๊ณ ์ผ๊ด๋ ํ์์ ์ ์งํ๋ ๋ฐ ์์ฃผ ์ฌ์ฉํ๋ ๋๊ตฌ์ ๋๋ค. ESLint๋ฅผ ์ฌ์ฉํ๋ฉด ์ ์ฌ์ ์ธ ๋ฌธ์ ๋ฅผ ๋น ๋ฅด๊ฒ ํ์ธํ ์ ์๊ณ , Prettier๋ฅผ ์ฌ์ฉํ๋ฉด ์ฝ๋ ์์์ ์ ๊ฒฝ์ฐ์ง ์๊ณ ์ฝ๋ ์์ฑ์๋ง ์ง์คํ ์ ์์ด ํธ๋ฆฌํฉ๋๋ค.
ํ์ง๋ง, ๋งค๋ฒ ํ๋ก์ ํธ๋ฅผ ์์ฑํ ๋๋ง๋ค ESLint/Prettier ๋ฑ์ ์ค์ ํ๋ ์์ ์ ๊ฝค ๋ฒ๊ฑฐ๋กญ์ต๋๋ค. ์ปจํผ๊ทธ ํ์ผ์ ๋ง๋ค๊ณ , ํ๋ฌ๊ทธ์ธ์ ์ค์นํ๊ณ , ์ถ์ฒ ๊ท์น์ ์ ์ฉํ๋ ์์ ์ด ๋ฐ๋ณต๋๋ฉฐ, ์์ ๋ค๋ฅธ ์ ์ฅ์ ์ค์ ์ ๊ทธ๋๋ก ๊ฐ์ ธ์์ ์ฐ๊ธฐ๋ ํฉ๋๋ค.
eslint-config-airbnb
๊ฐ์ด ์ค๋๋๊ณ ์ ๋ช
ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๊ท์น์ ํต์ผํ๋ ๋ฐฉ๋ฒ๋ ์์ง๋ง, ์ฌ์ฉ ํด๋ณด๋ ํ์ ์ด์์ผ๋ก ์๊ฒฉํ ๊ท์น์ด ์์
ํ๋ฆ์ ๋ฐฉํดํ๊ณ ์์ฐ์ฑ์ ์ ํ ์ํจ๋ค๊ณ ๋๊ผ์ต๋๋ค. ESLint๊ฐ ๊ณ ์น๋ผ๊ณ ํด์ ๊ณ ์น๊ธด ํ๋๋ฐ, ์ด๊ฑธ ์ ๊ณ ์ณ์ผ ํ์ง?๋ผ๋ ์๊ฐ๋ ๋ค์์ต๋๋ค. ๋ํ, ์ ์ฅ์๋ง๋ค ๋ค๋ฅธ ์ปจํผ๊ทธ ์ค์ ๋๋ฌธ์ ํผ๋์ ๊ฒช๊ธฐ๋ ํ์ต๋๋ค.
์ฝ์ด์นํ๋ก ํธ๊ฐ๋ฐํ์ ์ด๋ฐ ๋ฌธ์ ์ ์ ๊ทผ๋ณธ์ ์ผ๋ก ํด๊ฒฐํ๊ธฐ ์ํด, ์ฐ๋ฆฌ์๊ฒ ํ์ํ ๊ท์น๋ค๋ง ๋ชจ์๋์ ๊ณต์ ์ปจํผ๊ทธ ํจํค์ง๋ฅผ ๋์ ํ๊ฒ ๋์์ต๋๋ค. ๊ทธ ๊ฒฐ๊ณผ, ๊ฐ ์ ์ฅ์์์ ์ผ๊ด๋ ๊ฐ๋ฐ ๊ฒฝํ์ ์ ๊ณตํ๊ฒ ๋์๊ณ , ์ด๋ ๊ฐ๋ฐ์๋ค์ ์์ฐ์ฑ ํฅ์์ ํฐ ๋์์ ์ฃผ์์ต๋๋ค.
๊ณต์ ์ปจํผ๊ทธ๋ฅผ ๋ง๋ค๋ฉด์ ํ์๋ผ๋ฆฌ ์์ฐ์ค๋ฝ๊ฒ ๊ท์น์ ๋ ผ์ํ๊ธฐ๋ ํ์ต๋๋ค. ๋ ผ์ ๊ณผ์ ์์ ์๊ฒฌ์ด ๊ฐ๋ฆฌ๋ ๊ท์น๋ค์ ๋ํ ์กฐ์จ์ด ํ์ํ๋๋ฐ, ์ด๋ฅผ ํตํด ์ฝ๋ ๋ฆฌ๋ทฐ์์์ ๋ถํ์ํ ๋ ผ์์ ์ค์ด๋ ํจ๊ณผ๋ฅผ ์ป๊ธฐ๋ ํ์ต๋๋ค.
ํจํค์ง๋ฅผ ๊ตฌํํ๋ ๊ฒ์ ์๊ฐ๋ณด๋ค ์ด๋ ต์ง ์์์ง๋ง, ๋ ์ด๋ ค์ ๋ ๊ฑด ๋ฐ๋ก ํ์ ๊ฐ์ ํฉ์์์ต๋๋ค. ๊ฐ๋ฐ์๋ง๋ค ์ ํธํ๋ ๊ท์น๊ณผ ์๊ฐ์ด ๋ค๋ฅด๋ค ๋ณด๋ ์๊ฒฌ์ ์กฐ์จํ๋ ๊ณผ์ ์ ๊ฝค ์๊ฐ์ด ๊ฑธ๋ ธ์ต๋๋ค. ๊ธด ๋ ผ์๊ฐ ํ์ํ ๊ฒฝ์ฐ์๋ Slack ํฌํ๋, ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๋ค์ด ๋ชจ์ฌ์๋ ์ฑ๋์์ ์๊ฒฌ์ ๋ชจ์ ๊ฒฐ์ ํ๊ธฐ๋ ํ์ต๋๋ค.
์ด ๊ธ์์๋ eslint-config-airbnb
ํจํค์ง์ ๋์์ผ๋ก @rushstack/eslint-config
๋ฅผ ์ ํํ๊ฒ ๋ ๊ณผ์ ์ ๊ณต์ ํ๋ ค๊ณ ํฉ๋๋ค. ๋ํ, ๊ณต์ ESLint ์ปจํผ๊ทธ ํจํค์ง๋ฅผ ๋ง๋๋ ๊ณผ์ ๊ณผ ์ปจํผ๊ทธ์ ๋ํ ์ค๋ช
, ๊ทธ๋ฆฌ๊ณ ์ถ์ฒํ ๋งํ ๊ท์น ๋ช ๊ฐ์ง์ ๋ํด์๋ ๋ค๋ฃจ์ด๋ณด๊ฒ ์ต๋๋ค.
Airbnb ๊ท์น์ ๋์
.Airbnb์ eslint-config-airbnb
ํจํค์ง๋ ๋ง์ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๊ฐ ์ฌ์ฉํ๋ ๋ํ์ ์ธ ์ฝ๋ฉ ์คํ์ผ ๊ฐ์ด๋์
๋๋ค. ๋ค์ํ JavaScript์ React ๊ด๋ จ ๊ท์น๋ค์ ํฌํจํ๊ณ ์๋ Airbnb ๊ท์น์ 2015๋
5์์ ์คํ์์ค๋ก ์ฒ์ ๊ณต๊ฐ๋์ด ํ์ฌ๊น์ง๋ ๋ง์ ํ๋ก์ ํธ์์ ์ฌ์ฉ๋๊ณ ์์ต๋๋ค.
.Airbnb ๊ท์น์ ์ฌ์ฉํ๋ฉด ESLint ์ค์ ์๊ฐ์ ์ค์ฌ์ค๋ค๋ ์ฅ์ ์ด ์์ง๋ง, Airbnb ์กฐ์ง์ด ์ ํ ์ฝ๋ฉ ์คํ์ผ ๊ฐ์ด๋๋ฅผ ๋ฐ๋ผ์ผํ๋ ๋จ์ ์ด ์์ต๋๋ค. ์ฝ๋์ ์ธ์ธํ ๋ถ๋ถ๊น์ง ๊ท์น์ด ์ ์ฉ๋์ด์๊ธฐ ๋๋ฌธ์, ๊ฐ๋ฐ ๊ณผ์ ์์ ESLint ๊ฒฝ๊ณ ๋ฅผ ์ ๊ฑฐํ๊ธฐ ์ํ ์ฌํฌ๊ฐ ํผ์ณ์ง๊ณค ํฉ๋๋ค. Airbnb ๊ท์น์ ์ฌ์ฉํ๋ฉด์ ๋ถํธํจ์ ๋๊ผ๋ ์ฌ๋ก๋ ๊ธ ๋ง์ง๋ง์ ๋ถ๋ก์ ์ ์ด๋์์ต๋๋ค.
.Airbnb ๊ท์น์ด ์์ฐ์ฑ์ ๋จ์ด๋จ๋ฆฐ๋ค๊ณ ๋๊ผ๋ ์ ๋, ํ์ ์ฐ๋ฆฌ๋ง์ ESLint ์ปจํผ๊ทธ๋ฅผ ๋ง๋ค์๋ ์๊ฒฌ์ ์ ์ํ์ต๋๋ค. ์ฒ์์ ๋ฐฑ์ง๋ถํฐ ์์ํด์ ํ์ํ ๋๋ง๋ค ๊ท์น์ ํฉ์ํด์ ๋ง๋ค์ด๋๊ฐ์๋ ์๊ฒฌ๋ ์ ์ํ์์ง๋ง, ๋ฒ ์ด์ค ์ปจํผ๊ทธ๊ฐ ์์์ผ๋ฉด ์ข๊ฒ ๋ค๋ ํ์ ์๊ฒฌ์ ์์ฉํด์ ๋์ฒด ํจํค์ง๋ฅผ ์กฐ์ฌํ๊ฒ ๋์์ต๋๋ค.
์ ๊ฐ ์ฐพ์ Airbnb ์ปจํผ๊ทธ์ ๋์์ Microsoft์์ ๊ด๋ฆฌํ๋ @rushstack/eslint-config
ํจํค์ง์
๋๋ค. microsoft/rushstack
์ ๋ชจ๋
ธ๋ ํฌ ๊ด๋ฆฌ ๋๊ตฌ์ด์ง๋ง, ๋ฒ์ฉ์ ์ผ๋ก ์ฌ์ฉํ ์ ์๋ ESLint ๊ณต์ ์ปจํผ๊ทธ ํจํค์ง๋ ์ ๊ณตํฉ๋๋ค. ์ด ํจํค์ง๋ฅผ ๋์์ผ๋ก ์ ํํ ์ด์ ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- Microsoft๊ฐ ๊ด๋ฆฌํจ
- ์ต๊ทผ์ ๋ง๋ค์ด์ง (2021๋ 12์ ์ฒซ ์ปค๋ฐ)
- ๋ ๋จ์ ์ด์ง ์์ ๊ท์น
- ์ฃผ์์ ๊ฐ ๊ท์น์ ๋ํ ๋ช
ํํ ๊ทผ๊ฑฐ๊ฐ ๋ช
์๋์ด์์ (์์ (
microsoft/rushstack
)) - ๋์ ์ฝ๋ ํ๋ฆฌํฐ์ ์์ธํ ๋ฌธ์
.Github์์ ์ค์ ์ฝ๋์ ๋ฌธ์๋ฅผ ์ดํด๋ณธ ๊ฒฐ๊ณผ, ํ์ง์ด ์๋นํ ์ข๋ค๊ณ ๋๊ผ์ต๋๋ค. Prettier์์ ์ถฉ๋ ๋ฐฉ์ง๋ฅผ ๊ณ ๋ คํ ๋ถ๋ถ์ด๋, ์ฐ์ ์์ ์ด์๋ฅผ ๊ณ ๋ คํด recommended ํ ํ๋ฆฟ ์์ด ์ปจํผ๊ทธ๋ฅผ ๊ตฌ์ฑํ ๋ถ๋ถ, ๊ฐ ๊ท์น์ ๋ํด ๋ช ํํ ๊ทผ๊ฑฐ๋ฅผ ์ ์ํ ์ ๋ฑ ๋ํ ์ผํ ๋ถ๋ถ์ด ๋ง์์ ๋ค์์ต๋๋ค. ๋ฐ๋ผ์ ์ด ํจํค์ง๋ฅผ ๊ธฐ๋ณธ ๋ฒ ์ด์ค ์ปจํผ๊ทธ๋ก ์ก๊ณ , ํ์ํ ๊ท์น๊ณผ ํ๋ฌ๊ทธ์ธ๋ค์ ์ถ๊ฐํ๊ธฐ๋ก ๊ฒฐ์ ํ์ต๋๋ค. ๊ณต์ ์ปจํผ๊ทธ ํจํค์ง๋ฅผ ํ ๋ด์์ ๊ณตํต์ผ๋ก ์ฌ์ฉํ๊ธฐ ์ํด ํ์ ๋ถ๋ค์ ์ค๋ํ๊ธฐ ์ํ ๋ฐํ ์๋ฃ๋ฅผ ๋ง๋ค์๊ณ , ๊ฒฐ๊ณผ์ ์ผ๋ก ํ์ ๋ถ๋ค์ ๊ณต๊ฐ์ ์ด๋์ด๋ด๋ ๋ฐ ์ฑ๊ณตํ์ฌ ๋ณธ๊ฒฉ์ ์ผ๋ก ํจํค์ง ๊ฐ๋ฐ์ ์ฐฉ์ํ๊ฒ ๋ฉ๋๋ค.
๊ณต์ ์ปจํผ๊ทธ๋ฅผ ์ํ ๋ชจ๋ ธ๋ ํฌ ๊ตฌ์ฑํ๊ธฐ
๊ณต์ ์ปจํผ๊ทธ ํจํค์ง๋ฅผ ๊ด๋ฆฌํ ์ ์ฅ์๋ฅผ ํ๋ ๋ง๋ญ๋๋ค. ESLint์ Prettier ๊ณต์ ์ปจํผ๊ทธ๋ฅผ ๊ฐ๊ฐ์ npm ํจํค์ง๋ก ๋ฐฐํฌํ๊ธฐ ์ํด ๋ชจ๋ ธ๋ ํฌ๋ฅผ ๊ตฌ์ฑํ์ต๋๋ค. ๋ชจ๋ ธ๋ ํฌ๋ฅผ ๊ตฌ์ฑํ ์ด์ ๋ ์ฌ๋ฌ ๊ฐ์ ๊ด๋ จ ํจํค์ง๋ฅผ ํ๋์ ์ ์ฅ์์์ ๊ด๋ฆฌํ์ฌ ๊ฐ๋ฐ์ ํจ์จ์ฑ์ ๋์ด๋ ค๋ ๋ชฉ์ ์ ๋๋ค. ๋ชจ๋ ธ๋ ํฌ๋ฅผ ์ฌ์ฉํ๋ฉด ๊ณต์ ์ปจํผ๊ทธ ํจํค์ง ๊ฐ์ ์์กด์ฑ ๊ด๋ฆฌ๊ฐ ์ฌ์์ง๋ฉฐ, ๋ณ๊ฒฝ์ด ํ์ํ ๋ ํ ๊ณณ์์ ํ ์คํธ๊ฐ ๊ฐ๋ฅํ ์ฅ์ ์ด ์์ต๋๋ค.
์ฝ์ด์นํ๋ก ํธ๊ฐ๋ฐํ์ ๋ชจ๋ ํ๋ก์ ํธ์ ์ ์ฅ์์์ ์ผ๊ด๋ ํจํค์ง ๋งค๋์ ์ฌ์ฉ์ ์ํด ํ ๋ด ํฉ์๋ฅผ ํตํด pnpm์ ์ ํํ๊ฒ ๋์์ผ๋ฉฐ, ์ด์ ๋ฐ๋ผ pnpm๊ณผ pnpm workspace๋ฅผ ํ์ฉํ์ฌ ๋ชจ๋ ธ๋ ํฌ ํ๊ฒฝ์ ๊ตฌ์ฑํ๊ฒ ๋์์ต๋๋ค.
์ ์ฅ์ ๊ตฌ์กฐ๋ฅผ ๊ฐ๋จํ๊ฒ ๋์ํํด๋ณด์์ต๋๋ค.
๐ example // ๋ก์ปฌ ํ๊ฒฝ ๋ฐ CI์์ ์ปจํผ๊ทธ๋ฅผ ํ
์คํธํ๊ธฐ ์ํ Vite + React ํ๋ก์ ํธ
๐ package.json
๐ packages
๐ eslint-config
๐ mixins
๐ react.js // React๋ฅผ ์ฌ์ฉํ๋ ํ๋ก์ ํธ๋ฅผ ์ํ ์ปจํผ๊ทธ ํ์ผ
๐ index.js // ๊ณตํต ์ปจํผ๊ทธ ํ์ผ
๐ package.json
๐ prettier-config
๐ index.js
๐ package.json
๐ pnpm-workspace.yaml
๋ฃจํธ์ package.json
์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
{
"name": "shared-config-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"example": "pnpm --filter example"
},
"devDependencies": {
"prettier": "^3.2.5"
},
"pnpm": {
"overrides": {
"eslint": "8.57.0",
"@typescript-eslint/eslint-plugin": "7.5.0",
"@typescript-eslint/parser": "7.5.0"
}
}
}
scripts
์๋ ์์ ์ฑ์ ๋น ๋ฅด๊ฒ ์คํํ๊ธฐ ์ํ example
๋ช
๋ น์ด๋ฅผ ๋ฃ์ด๋์์ต๋๋ค. ๋ง์ฝ ์์ ์ฑ์ ๋ฆฐํธ๋ฅผ ์คํํด๋ณด๊ณ ์ถ๋ค๋ฉด pnpm --filter example lint
๋์ ์ pnpm example lint
๋ก ๊ฐ๋จํ๊ฒ ๋ฆฐํธ๋ฅผ ์คํํ ์ ์์ต๋๋ค. ๋ค๋ง, ํ์ ํ ๋ด์ฉ์์๋ ์ดํด๋ฅผ ๋๊ธฐ ์ํด --filter
์ต์
์ ํฌํจํ ์์๋ฅผ ์ ๊ณตํ ์์ ์
๋๋ค.
pnpm.overrides
ํ๋๋ ์์กด์ฑ ๋ฒ์ ์ ์ฌ์ ์ํฉ๋๋ค. ๊ตฌ์ฑ ๊ณผ์ ์์ TypeScript ๋ฒ์ ๊ฒฝ๊ณ ๊ฐ ๋ฐ์ํ์ฌ ๋ฃ์์ผ๋ฉฐ, ๋ฒ์ ๊ฒฝ๊ณ ๊ฐ ๋ฐ์ํ์ง ์๋๋ค๋ฉด ์๋ตํด๋ ๋ฌด๋ฐฉํฉ๋๋ค.
.pnpm-workspace.yaml
ํ์ผ์ ๋ชจ๋
ธ๋ ํฌ ๋ด์์ ๊ด๋ฆฌ๋๋ ํจํค์ง๋ค์ ์์น๋ฅผ pnpm์๊ฒ ์๋ ค์ฃผ๋ ์ญํ ์ ํฉ๋๋ค. ์ด๋ ์ปจํผ๊ทธ ํ
์คํธ๋ฅผ ์ํ example
๋๋ ํฐ๋ฆฌ์ packages/*
์๋์ ๋ชจ๋ ํจํค์ง๊ฐ ๋ชจ๋
ธ๋ ํฌ์ ํฌํจ๋๋ ๊ฒ์ pnpm์๊ฒ ์๋ ค์ฃผ๋ ์์
์
๋๋ค. pnpm-workspace.yaml
ํ์ผ์ ์๋์ ๊ฐ์ด ์์ฑํ์ต๋๋ค.
packages:
- 'example'
- 'packages/*'
๋ค์์ผ๋ก ESLint ๊ณต์ ์ปจํผ๊ทธ ํจํค์ง๋ฅผ ๊ตฌ์ฑํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
ESLint ๊ณต์ ์ปจํผ๊ทธ ํจํค์ง ๋ง๋ค๊ธฐ
package.json
๋ง๋ค๊ธฐ
๋จผ์ packages/eslint-config
ํ์์ package.json
์ ๋ง๋ญ๋๋ค.
// packages/eslint-config/package.json
{
"name": "@org/eslint-config",
"main": "index.js",
"version": "1.0.0",
"dependencies": {
"@rushstack/eslint-config": "3.6.8",
"@rushstack/eslint-patch": "1.10.1",
"@tanstack/eslint-plugin-query": "4.38.0",
"eslint-plugin-cypress": "2.15.1",
"eslint-plugin-jsx-a11y": "6.8.0",
"eslint-plugin-no-relative-import-paths": "1.5.3",
"eslint-plugin-react": "7.34.1",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-react-refresh": "0.4.6",
"eslint-plugin-storybook": "0.8.0",
"eslint-plugin-testing-library": "6.2.0"
},
"peerDependencies": {
"eslint": ">= 8",
"typescript": ">= 5"
}
}
์ฃผ์ ๋ด์ฉ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
main
ํ๋๋ ์ด ํจํค์ง๊ฐ ๋ฉ์ธ์ผ๋ก ์ฌ์ฉํ ํ์ผ์
๋๋ค. ์ฌ๊ธฐ์๋ index.js
๋ฅผ ์ง์ ํ๋๋ฐ, ์ด ํ์ผ์ ESLint์ ์ปจํผ๊ทธ๋ฅผ ๋ด๋ณด๋ด๋ ์ญํ ์ ํฉ๋๋ค.
dependencies
ํ๋์ Rush Stack์ ๊ท์น์ ์ฌ์ฉํ๊ธฐ ์ํ ํจํค์ง์, ๊ณตํต ๊ท์น ์ ์์ ํ์ํ ESLint ํ๋ฌ๊ทธ์ธ๋ค์ ๋ํ ์์กด์ฑ๋ค์ ๋ช
์ํ์ต๋๋ค. @rushstack/eslint-config
ํจํค์ง๋ ํ์์ด๋ฉฐ, ๋๋จธ์ง ํจํค์ง๋ ํ์์ ๋ฐ๋ผ ์ค์นํฉ๋๋ค.
@rushstack/eslint-config
: Rush Stack์ ๊ท์น์ ์ฌ์ฉํ๋ ค๋ฉด ๋ฐ๋์ ์ค์นํด์ผ ํ๋ ํจํค์ง์ ๋๋ค.@rushstack/eslint-patch
: ์ฌ์ฉ์ฒ์์ ESLint ํ๋ฌ๊ทธ์ธ์ ์์กด์ฑ์ผ๋ก ์ค์นํ์ง ์๊ณ ๋ ์ฌ์ฉํ ์ ์๊ฒ ํด์ค๋๋ค.@tanstack/eslint-plugin-query
: React Query๋ฅผ ํ ํ์ค ๊ธฐ์ ์คํ์ผ๋ก ํฉ์ํ์๊ธฐ ๋๋ฌธ์ ํจํค์ง์ ํฌํจํ์ต๋๋ค. Cypress, Storybook, Testing Library ํ๋ฌ๊ทธ์ธ๋ ๊ฐ์ ์ด์ ๋ก ํฌํจ๋์์ต๋๋ค.eslint-plugin-jsx-a11y
: ๊ฐ๋ฐ์๊ฐ ๋์น๊ธฐ ์ฌ์ด ์ ๊ทผ์ฑ ๊ท์น๋ค์ ๋ฃ๊ธฐ ์ํด ํฌํจํ์์ต๋๋ค.eslint-plugin-no-relative-import-paths
: ํ์์ import ์ฌ์ฉ ์ ์ ๋ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ๊ธฐ๋ก ํฉ์ํ์ฌ ๊ท์น์ผ๋ก ์ถ๊ฐํ์์ต๋๋ค.
peerDependencies
ํ๋๋ ์ฌ์ฉ์ฒ์์ ESLint >= 8 ๋ฒ์ , TypeScript >= 5 ๋ฒ์ ์ค์น๊ฐ ํ์ํ๋ค๊ณ ๋ช
์ํ์ต๋๋ค. ๋ฉ์ด์ ๋ฒ์ ์ด ๋ณ๊ฒฝ๋๋ฉด Breaking Changes๊ฐ ๋ฐ์ํ๊ธฐ ๋๋ฌธ์, ๋ฉ์ด์ ๋ฒ์ ์ ๊ธฐ์ค์ผ๋ก ์ต์ ์ค์น ๋ฒ์ ์ ๋ช
์ํ์ต๋๋ค.
์ปจํผ๊ทธ ํ์ผ ๋ง๋ค๊ธฐ
ESLint ๊ณต์ ์ปจํผ๊ทธ ํจํค์ง๋ ์ผ๋ฐ์ ์ธ ์๋ฐ์คํฌ๋ฆฝํธ ํ๋ก์ ํธ์ ์ฌ์ฉํ ๊ธฐ๋ณธ ์ปจํผ๊ทธ ํ์ผ๊ณผ, React ํ๋ก์ ํธ์ ์ฌ์ฉํ ์ปจํผ๊ทธ ํ์ผ ๋ ๊ฐ์ง๋ฅผ ๊ตฌ๋ถํ์ต๋๋ค. React๋ฅผ ์ฌ์ฉํ์ง ์๋๋ค๋ฉด React ๊ด๋ จ ๊ท์น์ด ํ์ํ์ง ์๊ธฐ ๋๋ฌธ์ ๋๋ค. React๋ฅผ ์ฌ์ฉํ๋ ํ๋ก์ ํธ์์๋ ๋ ๊ฐ์ง ์ปจํผ๊ทธ๋ฅผ ๋ชจ๋ ๋ถ๋ฌ์ค๊ณ , React๋ฅผ ์ฌ์ฉํ์ง ์๋ ํ๋ก์ ํธ์์๋ ์๋ฐ์คํฌ๋ฆฝํธ ์ปจํผ๊ทธ ํ์ผ๋ง ๋ถ๋ฌ์ค๋ฉด ๋ฉ๋๋ค.
๊ธฐ๋ณธ ์ปจํผ๊ทธ
.eslint-config/
index.js
๊ฒฝ๋ก์ ๊ธฐ๋ณธ ์ปจํผ๊ทธ๋ฅผ ์ ์ํฉ๋๋ค. extends
ํ๋๋ Rush Stack์ ์ปจํผ๊ทธ๋ฅผ ํฌํจํ๊ณ , ๋๋จธ์ง๋ ๊ณตํต์ผ๋ก ์ฌ์ฉํ ํ๋ฌ๊ทธ์ธ๊ณผ ๊ท์น๋ค์ ๋ช
์ํฉ๋๋ค.
module.exports = {
// ํ์ํ ํ๋ฌ๊ทธ์ธ์ ์ฌ๊ธฐ์ ์ ์ํฉ๋๋ค.
plugins: ['no-relative-import-paths'],
extends: [
// <img draggable="false" role="img" class="emoji" alt="โ
" src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg"> (ํ์) rushstack ์ปจํผ๊ทธ๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
'@rushstack/eslint-config/profile/web-app',
],
rules: {
// ํ์ํ ์ปค์คํ
๊ท์น์ ์ฌ๊ธฐ์ ์ ์ํฉ๋๋ค.
'@typescript-eslint/explicit-function-return-type': 'off',
},
settings: {
// ๊ณตํต์ผ๋ก ๋ฃ๊ณ ์ถ์ ์ค์ ์ด ์์ผ๋ฉด ์ถ๊ฐํฉ๋๋ค.
},
};
React ์ปจํผ๊ทธ
.eslint-config/mixins/
react.js
๊ฒฝ๋ก์ React ํ๋ก์ ํธ์ ์ฌ์ฉํ ์ปจํผ๊ทธ๋ฅผ ์ ์ํฉ๋๋ค.
module.exports = {
// ํ๋ฌ๊ทธ์ธ ๋ฌธ์:
// https://www.npmjs.com/package/eslint-plugin-react
// https://github.com/ArnaudBarre/eslint-plugin-react-refresh
// https://www.npmjs.com/package/eslint-plugin-jsx-a11y
plugins: ["react", "react-refresh", "jsx-a11y"],
extends: [
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:react/jsx-runtime",
"plugin:@tanstack/eslint-plugin-query/recommended",
],
settings: {
react: {
// ํ์ฌ React ๋ฒ์ ์ ๋ช
์ํฉ๋๋ค.
// ๋ช
์ํ์ง ์์ ๊ฒฝ์ฐ(๊ธฐ๋ณธ๊ฐ 'detect') React ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ์ฒด๋ฅผ ๋ถ๋ฌ์ค๋ฏ๋ก
// ๋ฆฐํธ ๊ณผ์ ์์ ์๋๊ฐ ๋๋ ค์ง ์ ์์ต๋๋ค.
version: "detect",
},
},
overrides: [
{
files: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],
extends: ['plugin:testing-library/react'],
rules: {
'react-refresh/only-export-components': 'off',
},
},
],
rules: {
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
// <img> ์๋ฆฌ๋จผํธ์ ์ ์๋ฏธํ ๋์ฒด ํ
์คํธ๊ฐ ์๋์ง ์ฒดํฌ
"jsx-a11y/alt-text": [
"warn",
{
elements: ["img"],
},
],
// ์ ํจํ aria-* ์์ฑ๋ง ์ฌ์ฉ
"jsx-a11y/aria-props": "warn",
// ์ ํจํ aria-* ์ํ/๊ฐ๋ง ์ฌ์ฉ
"jsx-a11y/aria-proptypes": "warn",
// DOM์์ ์ง์๋๋ role, ARIA๋ง ์ฌ์ฉ
"jsx-a11y/aria-unsupported-elements": "warn",
// ํ์ ARIA ์์ฑ์ด ๋น ์ ธ์๋์ง ์ฒดํฌ
"jsx-a11y/role-has-required-aria-props": "warn",
// ARIA ์์ฑ์ ์ง์๋๋ role์์๋ง ์ฌ์ฉ
"jsx-a11y/role-supports-aria-props": "warn",
// DOM์ ์ ์๋์ง ์์ ์์ฑ์ ์ฌ์ฉํ๋์ง ์ฒดํฌ (emotion css ์์ฑ ๋ฑ ์์ธ ์ผ์ด์ค๊ฐ ์์ผ๋ฏ๋ก ๊ธฐ๋ณธ์ off)
"react/no-unknown-property": "off",
// ์ ์ํ props ์ค์ ๋น ์ง๊ฒ ์๋์ง ์ฒดํฌ (NextPage ๋ฑ ์ผ๋ถ ์ถ์ํ ์ปดํฌ๋ํธ์์ ๋ณต์กํด์ง๋ฏ๋ก ๊ธฐ๋ณธ์ off)
"react/prop-types": "off",
},
};
extends
ํ๋๋ ํ๋ฌ๊ทธ์ธ์์ ์ ๊ณตํ๋ ์ถ์ฒ ๊ท์น๋ค์ ์ ์ํ์ต๋๋ค.
extends: [
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:react/jsx-runtime",
"plugin:@tanstack/eslint-plugin-query/recommended",
],
settings
ํ๋๋ React ๋ฒ์ ์ ๋ํ ๋ด์ฉ์ ๋ช
์ํด๋์์ต๋๋ค. eslint-plugin-react ๋ฌธ์ (jsx-eslint/eslint-plugin-react
) ๋ฅผ ๋ณด๋ฉด React ๋ฒ์ ์ ์๋์ผ๋ก ๊ฐ์งํ๋ detect
๊ฐ ๊ธฐ๋ณธ๊ฐ์ด๊ธฐ ๋๋ฌธ์ ๋ช
์ํ์ง ์์๋ ๋ฌธ์ ๋ ์์ง๋ง, React ๋ฒ์ ์ ๋ช
์ํ์ง ์์ ๊ฒฝ์ฐ React ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ์ฒด๋ฅผ ๋ถ๋ฌ์ค๊ธฐ ๋๋ฌธ์ ๋ฆฐํธ(์ํํธ์จ์ด ๊ฐ๋ฐ์์ ์ฝ๋์ ๋ฌธ์ ๋ฅผ ์๋ณํ๊ณ ๊ฒ์ฌํ๋ ๋๊ตฌ) ์๋๊ฐ ์ ํ๋ฉ๋๋ค. ๋ชจ๋ ์ ์ฅ์์์ React ์ต์ ๋ฒ์ ์ ์ฌ์ฉํ๋ ๊ฑด ์๋๊ธฐ ๋๋ฌธ์ detect
๋ก ์ค์ ์ ๋ฃ์ด๋์๊ณ , ์ค์ React ํ๋ก์ ํธ์์ version: '18.2'
์ ๊ฐ์ด ํ๋ก์ ํธ์์ ์ฌ์ฉ ์ค์ธ ๋ฒ์ ์ ๋ช
์ํด์ผ ํฉ๋๋ค.
settings: {
react: {
// ํ์ฌ React ๋ฒ์ ์ ๋ช
์ํฉ๋๋ค.
// ๋ช
์ํ์ง ์์ ๊ฒฝ์ฐ(๊ธฐ๋ณธ๊ฐ 'detect') React ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ์ฒด๋ฅผ ๋ถ๋ฌ์ค๋ฏ๋ก
// ๋ฆฐํธ ๊ณผ์ ์์ ์๋๊ฐ ๋๋ ค์ง ์ ์์ต๋๋ค.
version: "detect",
},
},
overrides
ํ๋๋ ESLint ์ค์ ์ ํน์ ํ์ผ์ด๋ ํด๋์ ๋ํด ๋ค๋ฅด๊ฒ ์ ์ฉํ๋ ค๊ณ ํ ๋ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ ๋ ํ
์คํธ ํ์ผ๋ค(__tests__
ํด๋ ๋ด ํ์ผ๊ณผ ์ด๋ฆ์ spec
๋๋ test
๋ฅผ ํฌํจํ๋ ํ์ผ๋ค)์ ๋ํด ํน๋ณํ ๊ท์น์ ์ค์ ํ์ต๋๋ค. ์ฌ๊ธฐ์๋ plugin:testing-library/react
๋ฅผ ํ์ฅํ์ฌ ํ
์คํธ ๊ด๋ จ ์ถ์ฒ ์ค์ ์ ์ ์ฉํ๊ณ , react-refresh/only-export-components
๊ท์น์ ๋นํ์ฑํ(off
)ํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ก๋์
๊ณผ ํ
์คํธ ํ์ผ์ ์ ์ฉํ ๊ท์น์ ๊ตฌ๋ถํด๋๋ฉด ๊ฐ๋ฐํ ๋ ๋ถํ์ํ ๋ฆฐํธ ๊ฒฝ๊ณ ๊ฐ ๋ฐ์ํ๋ ๊ฒ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
overrides: [
{
files: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],
extends: ['plugin:testing-library/react'],
rules: {
'react-refresh/only-export-components': 'off',
},
},
],
rules
ํ๋๋ Vite์ Next.js ํ๋ก์ ํธ ์์ฑ ์ ๊ธฐ๋ณธ์ ์ผ๋ก ํ์ฑํ๋ ๊ท์น๋ค์ ์ฐธ๊ณ ํด์ ์ปค์คํ
๊ท์น๋ค์ ์ ์ํด๋์์ต๋๋ค. ์ด๋ค ๊ท์น๋ค์ด ์ ์๋์ด ์๋์ง ํ๋์ฉ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
react-refresh/only-export-components
๊ท์น์ ํ์ผ์์ React ์ปดํฌ๋ํธ๋ง์ export
ํ๋๋ก ์ ํํจ์ผ๋ก์จ, Fast Refresh๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์๋ํ ์ ์๋๋ก ๋์ต๋๋ค. create-vite (vitejs/vite
) ๋ก ํ๋ก์ ํธ๋ฅผ ์์ฑํ๋ฉด ๊ธฐ๋ณธ์ ์ผ๋ก ์ ์ฉ๋ผ์๋ ๊ท์น์ด๊ธฐ๋ ํฉ๋๋ค.
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }]
allowConstantExport
(ArnaudBarre/eslint-plugin-react-refresh
) ์ต์
{ allowConstantExport: true }
์ ์ปดํฌ๋ํธ ํ์ผ์์ ์ปดํฌ๋ํธ ์ธ์ ๋ค๋ฅธ ๋ณ์๋ ํจ์๋ฅผ ๋ด๋ณด๋ด๋ ๊ฒ์ ํ์ฉํ ์ง ๊ฒฐ์ ํ๋ ์ต์
์
๋๋ค. Vite์ ๊ฒฝ์ฐ true
๋ก ์ค์ ํ๋๋ผ๋ Fast Refresh ๊ธฐ๋ฅ์ด ์ ์๋ํ๋๋ก ์ง์ํ๊ธฐ ๋๋ฌธ์ true
๋ก ์ค์ ํ์ต๋๋ค.
jsx-a11y
ํ๋ฌ๊ทธ์ธ์์ ์ ๊ณตํ๋ ๊ท์น๋ค์ create-next-app
(vercel/next.js
)์ผ๋ก ํ๋ก์ ํธ๋ฅผ ์์ฑํ๋ฉด ๊ธฐ๋ณธ์ ์ผ๋ก ์ ์ฉ๋์ด ์๋ eslint-config-next์ ๊ท์น (vercel/next.js
)์ ์ฐธ๊ณ ํ์ต๋๋ค. ์ ๋ ๊ท์น์ ๋ํ ์ค๋ช
์ ๊ณต์ ๋ฌธ์ (jsx-eslint/eslint-plugin-jsx-a11y
))๋ฅผ ๋ณด๊ณ ํ๊ธ ์ฃผ์์ผ๋ก ์ ์ด๋์์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ๋ฌธ์๋ฅผ ํ์ธํ๋ ๋ฒ๊ฑฐ๋ก์์ด ์ค์ด๋ค๊ธฐ ๋๋ฌธ์, ๊ฐ๋ฅํ๋ฉด ์ฃผ์์ ์ค๋ช
์ ์ ๋ ๊ฒ์ ์ถ์ฒ๋๋ฆฝ๋๋ค.
// <img> ์๋ฆฌ๋จผํธ์ ์ ์๋ฏธํ ๋์ฒด ํ
์คํธ๊ฐ ์๋์ง ์ฒดํฌ
"jsx-a11y/alt-text": [
"warn",
{
elements: ["img"],
},
],
// ์ ํจํ aria-* ์์ฑ๋ง ์ฌ์ฉ
"jsx-a11y/aria-props": "warn",
// ์ ํจํ aria-* ์ํ/๊ฐ๋ง ์ฌ์ฉ
"jsx-a11y/aria-proptypes": "warn",
// DOM์์ ์ง์๋๋ role, ARIA๋ง ์ฌ์ฉ
"jsx-a11y/aria-unsupported-elements": "warn",
// ํ์ ARIA ์์ฑ์ด ๋น ์ ธ์๋์ง ์ฒดํฌ
"jsx-a11y/role-has-required-aria-props": "warn",
// ARIA ์์ฑ์ ์ง์๋๋ role์์๋ง ์ฌ์ฉ
"jsx-a11y/role-supports-aria-props": "warn",
no-unknown-property
๊ท์น์ react
ํ๋ฌ๊ทธ์ธ์ ์ถ์ฒ ๊ท์น์ ํฌํจ๋ ๊ท์น์ผ๋ก, DOM์ ์ ์๋์ง ์์ ์์ฑ ์ฌ์ฉ์ ๊ธ์งํ๋ ๊ท์น์
๋๋ค. ํ๋ก์ ํธ ์ค์ emotion-js/emotion
๋ผ์ด๋ธ๋ฌ๋ฆฌ์ css ์์ฑ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ์์ด์ ๋นํ์ฑํํ์ต๋๋ค.
// DOM์ ์ ์๋์ง ์์ ์์ฑ์ ์ฌ์ฉํ๋์ง ์ฒดํฌ (Emotion css ์์ฑ ๋ฑ ์์ธ ์ผ์ด์ค๊ฐ ์์ผ๋ฏ๋ก ๊ธฐ๋ณธ์ off)
"react/no-unknown-property": "off",
ํ๋ฌ๊ทธ์ธ ํจ์น ํ์ผ ๋ง๋ค๊ธฐ
ESLint๋ 8.5.7 ๋ฒ์ ๊ธฐ์ค, ESLint ํ๋ฌ๊ทธ์ธ๋ค์ ํจํค์ง์ ํฌํจํ ์ ์๋๋ฐ @rushstack/eslint-patch
ํจํค์ง๋ ์ ์ฅ์์์ ํด๋น ํจํค์ง๋ฅผ ์ค์นํ ํ์ ์์ด, ํจ์น๋ฅผ ๋ฐ๋ก ์ ์ฉํ ์ ์๋๋ก ํจ์น ํ์ผ์ ๋ง๋ค์ด ์ค๋๋ค. ์ฌ๊ธฐ์ ๋ง๋ ํจ์น ํ์ผ์ ์ฌ์ฉ์ฒ์์ ๋ถ๋ฌ์ค๊ธฐ๋ง ํด์ฃผ๋ฉด ๋ฐ๋ก ์ฌ์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค. ์ค์ ์์๋ "ESLint/Prettier ๋์ ๊ฒ์ฆํ๊ธฐ" ๋ชฉ์ฐจ์์ ํ์ธํ ์ ์์ต๋๋ค.
.eslint-config/
patch.js
๋ฅผ ๋ง๋ค๊ณ ์๋์ ๊ฐ์ด ์์ฑํด ์ค๋๋ค.
/*
* @rushstack/eslint-patch๋ ๊ณต์ ์ปจํผ๊ทธ ํจํค์ง์ ESLint ํ๋ฌ๊ทธ์ธ์ ํฌํจ์์ผ์ค๋๋ค.
*
* https://www.npmjs.com/package/@rushstack/eslint-patch
*/
require("@rushstack/eslint-patch/modern-module-resolution");
eslint-patch ์์ด ESLint ํ๋ฌ๊ทธ์ธ์ ํฌํจํ๋ ๋ฐฉ๋ฒ์ ์๋์?
์ด๋ฏธ 2015๋
8์์ ESLint Github์ Support having plugins as dependencies in shareable config (eslint/eslint
)๋ผ๋ ์ ๋ชฉ์ผ๋ก ํ๋ฌ๊ทธ์ธ์ peer dependencies๊ฐ ์๋ ์ง์ ์ ์ธ ์ข
์์ฑ์ผ๋ก ํฌํจํ ์ ์๊ฒ ํด๋ฌ๋ผ๋ ์์ฒญ์ด ์์์ต๋๋ค. 2022๋
8์์ ESLint ์ฐฝ์์์ธ Nicholas C. Zakas (nzakas
)๊ฐ ๋จ๊ธด ๋๊ธ์ ์ํ๋ฉด, ESLint์ ์ ์ปจํผ๊ทธ(Flat config)์์๋ ํ๋ฌ๊ทธ์ธ์ ์ง์ ์ข
์์ฑ์ผ๋ก ์ง์ ํ ์ ์๋ค๊ณ ์ธ๊ธํ์ต๋๋ค.
2023๋ 11์ 7์ผ์ ESLint ๋ธ๋ก๊ทธ์ ์ฌ๋ผ์จ What's coming in ESLint v9.0.0์ ์ํ๋ฉด, 9.0.0 ๋ถํฐ๋ Flat config๊ฐ ๋ํดํธ๋ก ์ฑํ๋ ์์ ์ผ๋ก, ์ถํ์๋ patch ํจํค์ง ์์ด๋ ํ๋ฌ๊ทธ์ธ์ ๊ณต์ ์ปจํผ๊ทธ์ ํฌํจํ ์ ์์ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค.
๋ค์์ผ๋ก, Prettier ๊ณต์ ์ปจํผ๊ทธ ํจํค์ง๋ฅผ ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค.
Prettier ๊ณต์ ์ปจํผ๊ทธ ํจํค์ง ๋ง๋ค๊ธฐ
packages
ํ์์ prettier-config
ํด๋๋ฅผ ์ถ๊ฐํ๊ณ , ํด๋ ํ์์ package.json
๊ณผ index.js
ํ์ผ์ ์์ฑํฉ๋๋ค.
๐ example
๐ package.json
๐ packages
๐ eslint-config
๐ mixins
๐ react.js
๐ index.js
๐ package.json
๐ prettier-config // ์ฌ๊ธฐ์ ํจํค์ง๋ฅผ ๊ตฌ์ฑํฉ๋๋ค.
๐ index.js
๐ package.json
๐ pnpm-workspace.yaml
.package.json
์ ์ดํด๋ด
์๋ค.
// packages/prettier-config/package.json
{
"name": "@org/prettier-config",
"main": "index.js",
"version": "1.0.0",
"peerDependencies": {
"prettier": ">= 3"
}
}
ESLint ํจํค์ง์ ๋์ผํ๊ฒ, Prettier ์ปจํผ๊ทธ๋ฅผ ๋ด๋ณด๋ด๊ธฐ ์ํด index.js
๋ฅผ main
ํ๋์ ์ง์ ํ์ต๋๋ค.
peerDependencies
ํ๋๋ ์ด ๊ณต์ ์ปจํผ๊ทธ๋ฅผ ์ฌ์ฉํ๊ณ ์ ํ๋ ํ๋ก์ ํธ๊ฐ ๋ฐ๋์ Prettier ๋ฒ์ 3 ์ด์์ ์ค์นํ๊ณ ์์ด์ผ ํจ์ ๋ช
์ํ๊ณ ์์ต๋๋ค. 3 ๋ฏธ๋ง์ ๋ฒ์ ์ ์ฌ์ฉํด๋ ์คํ์ ๋ฌธ์ ๋ ์์ง๋ง, ๋ชจ๋ ์ ์ฅ์์์ ๋์ผํ ๋ฒ์ ์ ์ฌ์ฉํ์ฌ ๋์ผํ ์คํ ๊ฒฐ๊ณผ๋ฅผ ์ป๊ธฐ ์ํด ์ต์ ์ค์น ๋ฒ์ ์ ์๊ฒฉํ๊ฒ ์ค์ ํ์์ต๋๋ค.
.index.js
ํ์ผ์ ๊ณตํต์ผ๋ก ์ฌ์ฉํ Prettier ์ค์ ์ ์ ์ํฉ๋๋ค.
// packages/prettier-config/index.js
module.exports = {
printWidth: 100,
trailingComma: 'all', // ๊ธฐ๋ณธ๊ฐ
tabWidth: 2, // ๊ธฐ๋ณธ๊ฐ
semi: true, // ์ผ๋ถ ์ฝ๋์์ ๋ผ์ธ์ ์์ ๋ถ๋ถ์ ์ธ๋ฏธ ์ฝ๋ก ์ถ๊ฐ
singleQuote: true,
bracketSpacing: true, // ๊ธฐ๋ณธ๊ฐ. true์ธ ๊ฒฝ์ฐ {foo:bar}๋ { foo: bar }๋ก ๋ณํ๋จ
arrowParens: 'always', // ๊ธฐ๋ณธ๊ฐ
useTabs: false, // ๊ธฐ๋ณธ๊ฐ
};
๋๋ถ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๊ธฐ๋ณธ๊ฐ์ ๋ฐ๋ฅด์ง๋ง ๋ช๋ช ์ค์ ๊ฐ์ ํ ๋ด ์ ํธ๋์ ํ ๋ก ์ ๊ธฐ๋ฐ์ผ๋ก ํฉ์๋์์ต๋๋ค.
printWidth
๋ ์ฝ๋ ํ ๋ผ์ธ์ ๋๋ต์ ์ผ๋ก ๋ช ๊ธ์๊ฐ ๋ค์ด๊ฐ์ง Prettier์๊ฒ ์๋ ค์ฃผ๋ ์ญํ ์ ํฉ๋๋ค. ๊ณต์ ๋ฌธ์ ๊ถ์ฅ ์ค์ ๊ฐ์ธ 80
์ผ๋ก ํ๋ ค๊ณ ํ์ง๋ง, ํฐ ํ๋ฉด์์ ๋ณด๊ธฐ ํธํ๊ฒ 120
๋๋ 100
์ผ๋ก ์ค์ ํ๋ฉด ์ข๊ฒ ๋ค๋ ์๊ฒฌ์ด ์์์ต๋๋ค. 120
์ ๊ฒฝ์ฐ 14์ธ์น ๋งฅ๋ถ์์ ๋ณด๊ธฐ์ ๋๋ฌด ๊ธธ๊ณ , 80
์ ๋๋ฌด ์งง๋ค๋ ์๊ฒฌ์ด ์์ด์ ๊ทธ ์ค๊ฐ๊ฐ์ธ 100
์ผ๋ก ํฉ์๊ฐ ๋์์ต๋๋ค.
semi
๋ ๋ผ์ธ์ ๋์ ์ธ๋ฏธ์ฝ๋ก ์ ์๋์ผ๋ก ๋ถ์ฌ์ค์ง๋ฅผ ๊ฒฐ์ ํฉ๋๋ค. ์ด ๋ถ๋ถ์ ํธ๋ถํธ๊ฐ ๋ง์ด ๊ฐ๋ฆฌ๋ ์์ญ์ด์๊ธฐ ๋๋ฌธ์, ํฌํ๋ฅผ ์งํํด๋ณด์์ต๋๋ค.
๋ค์์ผ๋ก, ์ง๊ธ๊น์ง ๋ง๋ ๊ณต์ ์ปจํผ๊ทธ ํจํค์ง๋ค์ด ์ค์ ๋ก ์ ๋์ํ๋์ง ํ์ธํ๊ธฐ ์ํด React ์์ ์ฑ์ ๋ง๋ค๊ณ ์ค์ ์ ์งํํ ๋ค ๋ช ๋ น์ด๋ฅผ ์คํํ์ฌ ๊ฒ์ฆํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
ESLint/Prettier ๋์ ๊ฒ์ฆํ๊ธฐ
์์ ์ฑ ๋ง๋ค๊ธฐ
์ค์ ๋ก ESLint์ Prettier ๊ท์น์ด ์ ๋์ํ๋์ง ๊ฒ์ฆํ๊ธฐ ์ํด Vite ๊ธฐ๋ฐ์ React ์์ ์ฑ์ ์์ฑํ๊ฒ ์ต๋๋ค. ์ฌ๊ธฐ์ ๋ง๋ ์์ ์ฑ์ ๊ณต์ ์ปจํผ๊ทธ์ ๋ณ๊ฒฝ ์ฌํญ์ด ์๊ฒผ์ ๋์ CI์์ ์ค๋ฅ๋ฅผ ๊ฒ์ฆํ๋ ๋ชฉ์ ์ผ๋ก๋ ์ฌ์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค.
pnpm create vite example --template react-swc-ts
.eslintrc.cjs
ํ๋ก์ ํธ ์์ฑ์ด ์๋ฃ๋ฌ์ผ๋ฉด ์์ฑ๋ ํด๋ ํ์์ .eslintrc.cjs
ํ์ผ์ ๋ง๋ค๊ณ ์๋์ ๊ฐ์ด ESLint ์ปจํผ๊ทธ๋ฅผ ์ค์ ํฉ๋๋ค.
// <img draggable="false" role="img" class="emoji" alt="โ
" src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg"> ์์ ์ ์ํ patch ํ์ผ์ ๋ถ๋ฌ์ต๋๋ค.
// ์ด๋ ๊ฒ ํ๋ฉด ESLint ํ๋ฌ๊ทธ์ธ๋ค์ ํ๋ก์ ํธ์์ ์ผ์ผ์ด ์ค์นํ ํ์๊ฐ ์์ด์ง๋๋ค.
require("@org/eslint-config/patch");
module.exports = {
env: { browser: true, es2020: true },
extends: [
"@org/eslint-config", // ๊ณตํต ESLint ์ปจํผ๊ทธ ๋ถ๋ฌ์ค๊ธฐ
"@org/eslint-config/mixins/react", // React์ฉ ESLint ์ปจํผ๊ทธ ๋ถ๋ฌ์ค๊ธฐ
],
settings: {
react: {
// ํ์ฌ React ๋ฒ์ ์ ๋ช
์ํฉ๋๋ค.
// ๋ช
์ํ์ง ์์ ๊ฒฝ์ฐ(๊ธฐ๋ณธ๊ฐ 'detect') React ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ์ฒด๋ฅผ ๋ถ๋ฌ์ค๋ฏ๋ก
// ๋ฆฐํธ ๊ณผ์ ์์ ์๋๊ฐ ๋๋ ค์ง ์ ์์ต๋๋ค.
// ์: '16.9', '17.0', '18.0' ๋ฑ
version: "18.2",
},
},
// Rush Stack์ @typescript-eslint ํ๋ฌ๊ทธ์ธ์ ๋ด์ฅํ๊ณ ์์ผ๋ฏ๋ก
// ํ์
์คํฌ๋ฆฝํธ ํ์์ ๋ํ ์ค์ ์ด ํ์ํฉ๋๋ค.
parserOptions: {
project: true,
tsconfigRootDir: __dirname,
},
};
์ปจํผ๊ทธ ํ์ผ์ ์ต์๋จ์๋ ๊ณต์ ์ปจํผ๊ทธ ํจํค์ง์์ ๋ง๋ ํจ์น ํ์ผ์ ๋ถ๋ฌ์ต๋๋ค. ํจ์น ํ์ผ์ ๋ถ๋ฌ์์ผ ESLint ํ๋ฌ๊ทธ์ธ์ ์ค์น ์์ด ์ฌ์ฉํ ์ ์์ต๋๋ค.
require("@org/eslint-config/patch");
extends
ํ๋์๋ ๊ณต์ ์ปจํผ๊ทธ๋ฅผ ๋ถ๋ฌ์ต๋๋ค. React๋ฅผ ์ฌ์ฉํ๊ณ ์์ผ๋ฏ๋ก React ์ปจํผ๊ทธ๋ ๊ฐ์ด ํฌํจํฉ๋๋ค.
extends: [
"@org/eslint-config", // ๊ธฐ๋ณธ ESLint ์ปจํผ๊ทธ ๋ถ๋ฌ์ค๊ธฐ
"@org/eslint-config/mixins/react", // React์ฉ ESLint ์ปจํผ๊ทธ ๋ถ๋ฌ์ค๊ธฐ
],
settings.react.version
ํ๋์๋ React ๋ฒ์ ์ ๋ช
์ํฉ๋๋ค. React ๋ฒ์ ์ ๋ช
์ํ์ง ์์ผ๋ฉด React ๋ฒ์ ์ ๊ฐ์งํ๊ธฐ ์ํด React ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ์ฒด๋ฅผ ๋ถ๋ฌ์ค๋ฏ๋ก ๋ฆฐํธ ์คํ ์๋๊ฐ ๋๋ ค์ง ์ ์์ต๋๋ค.
settings: {
react: {
version: "18.2",
},
},
ESLint ์ค์ ์์ parserOptions
๋ ESLint๊ฐ ์ฝ๋๋ฅผ ๋ถ์ํ ๋ ์ฌ์ฉํ๋ ํ์์ ์ต์
์ ์ค์ ํฉ๋๋ค. Rush Stack์ ๊ท์น์ typescript-eslint๋ฅผ ์์กด์ฑ์ผ๋ก ๊ฐ๊ณ ์๊ธฐ ๋๋ฌธ์, tsconfig.json
์ ๊ฒฝ๋ก๋ฅผ ์ค์ ํ๋ ์์
์ด ํ์ํฉ๋๋ค.
parserOptions: {
project: true,
tsconfigRootDir: __dirname,
},
project
๋ true
๋ก ์ค์ ํ๋ ๊ฒ์ด ์ข์ต๋๋ค. true
์ต์
์ typescript-eslint 5.52.0 ๋ฒ์ ์์ ์ถ๊ฐ๋ ์ค์ ์ผ๋ก, ๋ฆฐํ
๋๋ ์์ค ํ์ผ์ด ํด๋น ๊ฒฝ๋ก์์ ๊ฐ์ฅ ๊ฐ๊น์ด tsconfig.json
๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํด์๋๋๋ก ์ค์ ํฉ๋๋ค. ์ด๋ ํนํ ์ ์ฅ์ ์์ ์ฌ๋ฌ ๊ฐ์ tsconfig.json
ํ์ผ์ด ์กด์ฌํ๋ ๋ชจ๋
ธ๋ ํฌ ๊ตฌ์กฐ์์ ์ ์ฉํฉ๋๋ค.
tsconfigRootDir
๋ ํ๋ก์ ํธ์ ๋ฃจํธ ๋๋ ํฐ๋ฆฌ(๊ฐ์ฅ ์ผ๋ฐ์ ์ผ๋ก __dirname
)๋ก ์ค์ ํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ค์๋ก ๋ฃจํธ์ tsconfig.json
ํ์ผ์ ์ญ์ ํ๊ฑฐ๋ ์ด๋ฆ์ ๋ณ๊ฒฝํ๋ ๊ฒฝ์ฐ @typescript-eslint/parser
๊ฐ ์์ ๊ฒฝ๋ก์์ ์์ tsconfig.json
ํ์ผ์ ์ฐพ๋ ๊ฒ์ ๋ง์์ค๋๋ค.
package.json
์์ ํ๊ธฐ
.packages/example/
package.json
ํ์ผ์ ์์ ํ์ฌ, ๊ณต์ ์ปจํผ๊ทธ ํจํค์ง ์์กด์ฑ ๋ฐ Prettier ์ปจํผ๊ทธ ์ค์ ์ ์ถ๊ฐํด๋ณด๊ฒ ์ต๋๋ค.
{
"name": "example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"prettier": "prettier --write \"**/*.{js,jsx,ts,tsx,css,html}\""
},
"dependencies": {
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
// <img draggable="false" role="img" class="emoji" alt="โ
" src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg"> ESLint, Prettier ์ปจํผ๊ทธ์ ๋ํ ์์กด์ฑ์ ๋ช
์ํด์ค๋๋ค.
"@org/eslint-config": "workspace:*",
"@org/prettier-config": "workspace:*",
"@types/react": "18.2.74",
"@types/react-dom": "18.2.24",
// <img draggable="false" role="img" class="emoji" alt="โ
" src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg"> @typescript-eslint/* ํจํค์ง๋ rushstack์ ํฌํจ๋์ด์์ผ๋ฏ๋ก ํฌํจํ์ง ์์ต๋๋ค.
"@vitejs/plugin-react-swc": "3.6.0",
"eslint": "8.57.0",
"typescript": "5.4.4",
"vite": "5.2.8"
},
"prettier": "@org/prettier-config"
}
๋จผ์ , devDependencies
์ ์๋์ ๊ฐ์ด ๊ณต์ ์ปจํผ๊ทธ ํจํค์ง๋ฅผ ๊ฐ๊ฐ ์ถ๊ฐํด์ค๋๋ค.
"devDependencies": {
"@org/eslint-config": "workspace:*",
"@org/prettier-config": "workspace:*",
},
ํน์ ๋ฃจํธ์์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ฌ ํจํค์ง๋ฅผ ์ถ๊ฐํ ์๋ ์์ต๋๋ค.
pnpm add -D @org/eslint-config@workspace:* @org/prettier-config@workspace:* --filter example
์์กด์ฑ ์ค์ ๊ณต์ ์ปจํผ๊ทธ์ ์ด๋ฏธ ํฌํจ๋ ์์กด์ฑ๋ค์ ์ ๊ฑฐํ์ต๋๋ค.
eslint-plugin-react-hooks
: ๊ณต์ ์ปจํผ๊ทธ ์์กด์ฑ์ ํฌํจ๋ผ ์์ผ๋ฏ๋ก ์ ์ธํ์ต๋๋ค.eslint-plugin-react-refresh
: ๊ณต์ ์ปจํผ๊ทธ ์์กด์ฑ์ ํฌํจ๋ผ ์์ผ๋ฏ๋ก ์ ์ธํ์ต๋๋ค.@typescript-eslint/*
: Rush Stack์ ํฌํจ๋์ด์์ผ๋ฏ๋ก ์ ์ธํ์ต๋๋ค.
prettier
ํ๋์ Prettier ์ปจํผ๊ทธ ํจํค์ง ์ด๋ฆ์ ๋ช
์ํฉ๋๋ค.
"prettier": "@org/prettier-config"
๊ฒ์ฆํ๊ธฐ
๊ฒ์ฆ ์ , ๋ฃจํธ ๊ฒฝ๋ก์์ ํจํค์ง ์ค์น๋ฅผ ์งํํฉ๋๋ค.
pnpm install
.example/src/
App.tsx
์์ ์๋์ ์ผ๋ก ๋ฆฐํธ ์๋ฌ๊ฐ ๋ฐ์ํ๋ ์ํฉ์ ๋ง๋ญ๋๋ค. ์ ๋ useEffect
์ ๋ค์ด๊ฐ์ผ ํ ์ข
์์ฑ์ ์ผ๋ถ๋ฌ ๋๋ฝ์์ผ ๋ณด์์ต๋๋ค.
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(count);
}, []);
// ...
ํฐ๋ฏธ๋์์ example
์ํฌ์คํ์ด์ค์ lint
๋ช
๋ น์ด๋ฅผ ์คํํ์ฌ ESLint๊ฐ ๋์ํ๋์ง ํ์ธํด ๋ด
๋๋ค.
pnpm --filter example lint
#
# > shared-config-example@0.0.0 example /shared-config-example
# > pnpm --filter example "lint"
#
# > example@0.0.0 lint /shared-config-example/example
# > eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0
#
# /shared-config-example/example/src/App.tsx
# 11:6 warning React Hook useEffect has a missing dependency: 'count'. Either include it or remove the dependency array react-hooks/exhaustive-deps
#
# <img draggable="false" role="img" class="emoji" alt="โ" src="https://s.w.org/images/core/emoji/14.0.0/svg/2716.svg"> 1 problem (0 errors, 1 warning)
#
# ESLint found too many warnings (maximum: 0).
# /shared-config-example/example:
# โERR_PNPM_RECURSIVE_RUN_FIRST_FAILโ example@0.0.0 lint: <span class="token variable"><span class="token variable">`eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0<span class="token variable">`
# Exit status 1
# โELIFECYCLEโ Command failed with exit code 1.<span>
์ค์ ํ ๊ท์น์ ๋ง๊ฒ ๊ฒฝ๊ณ ๋ฅผ ์ถ๋ ฅํ๋ ๊ฒ์ผ๋ก ๋ณด์ ESLint๊ฐ ์ ์์ ์ผ๋ก ๋์ํ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
TypeScript ๋ฒ์ ๊ฒฝ๊ณ ํด๊ฒฐ ๋ฐฉ๋ฒ
WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree.
SUPPORTED TYPESCRIPT VERSIONS: `>=3.3.1 > YOUR TYPESCRIPT VERSION: `5.2.2`
์์ ๊ฐ์ ๊ฒฝ๊ณ ๊ฐ ๋ฐ์ํ๋ค๋ฉด ๋ฃจํธ์ package.json
์์ typescript-eslint
ํจํค์ง ๋ฒ์ ๋ค์ ์ต์ ๋ฒ์ ์ผ๋ก ์ค๋ฒ๋ผ์ด๋ ํ๋ ๋ฐฉ์์ผ๋ก ํด๊ฒฐํ ์ ์์ต๋๋ค.
"pnpm": {
"overrides": {
"@typescript-eslint/eslint-plugin": "7.5.0",
"@typescript-eslint/parser": "7.5.0"
}
}
ํฐ๋ฏธ๋์์ example
์ํฌ์คํ์ด์ค์ prettier
๋ช
๋ น์ด๋ฅผ ์คํํ์ฌ Prettier๋ ์ ๋๋์ง ํ์ธํด๋ด
๋๋ค.
pnpm example prettier
#
# > example@0.0.0 prettier /shared-config-example/example
# > prettier --write "**/*.{js,jsx,ts,tsx,css,html}"
#
# index.html 19ms (unchanged)
# src/App.css 18ms (unchanged)
# src/App.tsx 114ms
# src/index.css 5ms (unchanged)
# src/main.tsx 3ms (unchanged)
# src/vite-env.d.ts 2ms (unchanged)
# vite.config.ts 3ms (unchanged)
Prettier ์ญ์ ์ ์ ๋์ํ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
๋ค์์ผ๋ก, ์ฝ์ด์นํ๋ก ํธ๊ฐ๋ฐํ์์ ์ฌ์ฉํ๋ ๊ท์น ์ค ์ถ์ฒํ๋ ๊ท์น๊ณผ ํ๋ฌ๊ทธ์ธ๋ค์ ์๊ฐํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
์ถ์ฒ ๊ท์น ๋ฐ ํ๋ฌ๊ทธ์ธ
๋ค์ด๋ฐ ์ปจ๋ฒค์ ๊ท์น
.ํ๋ฌ๊ทธ์ธ:
@typescript-eslint/eslint-plugin
(typescript-eslint/typescript-eslint
) (@rushstack/eslint-config
์ ์ค์นํ๋ค๋ฉด ๋ณ๋ ์ค์น ํ์ ์์)
@typescript-eslint/naming-convention
๊ท์น์ ์ด๋ฆ(๋ณ์, ํจ์, ํด๋์ค, ํ์
๋ฑ)์ ์ปจ๋ฒค์
์ ์ ์ํ ์ ์๋ ๊ท์น์
๋๋ค. ๋ง์ฝ ํ์
์ด๋ฆ์ ํ๊ฐ๋ฆฌ์ ํ๊ธฐ๋ฒ์ ์ฐ์ง ์๊ธฐ๋ก ํ์์ ํฉ์ํ๋ค๋ฉด, ์ด ๊ท์น์ ํตํด ๋๊ตฐ๊ฐ ํ๊ฐ๋ฆฌ์ ํ๊ธฐ๋ฒ์ ์ค์๋ก ์ฌ์ฉํ๋ ๊ฒ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
์ฐธ๊ณ ๋ก, ํ๊ฐ๋ฆฌ์ ํ๊ธฐ๋ฒ์ ๋ณ์๋ ํจ์์ ์ธ์ ์ด๋ฆ ์์ ๋ฐ์ดํฐ ํ์
์ ์ ๋์ด๋ก ๋ช
์ํ๋ ํ๊ธฐ๋ฒ์
๋๋ค. ์๋ฅผ ๋ค์ด, TypeScript์ Interface๋ I
๋ฅผ ์ ๋์ด๋ก ์ฌ์ฉํด์ IVariable
, Type์ T
๋ฅผ ์ ๋์ด๋ก ์ฌ์ฉํด์ TVariable
๋ก ํํํ ์ ์์ต๋๋ค.
์ฌ์ฉ๋ฒ์ ๋จผ์ ์
๋ ํฐ (typescript-eslint/typescript-eslint
)(ex: ๋ณ์, ํ์
..)๋ฅผ ์ ํ๊ณ , ์
๋ ํฐ์ ํด๋น๋๋ ํฌ๋งท(ex: camelCase, ์ ๊ท์..)์ ์ง์ ํฉ๋๋ค. ์๋๋ ์ฝ์ด์นํ๋ก ํธ๊ฐ๋ฐํ์์ ์ฐ๊ณ ์๋ ๊ท์น๋ค์ ์์์
๋๋ค.
{
"rules": {
"@typescript-eslint/naming-convention": [
"warn",
// camelCase ๋ณ์, PascalCase ๋ณ์, UPPER_CASE ๋ณ์ ํ์ฉ
{
"selector": "variable",
"format": ["camelCase", "PascalCase", "UPPER_CASE"]
},
// camelCase ํจ์, PascalCase ํจ์ ํ์ฉ
{
"selector": "function",
"format": ["camelCase", "PascalCase"]
},
// PascalCase ํด๋์ค, interfaces, type aliases, enums ํ์ฉ
{
"selector": "typeLike",
"format": ["PascalCase"]
},
// interface ์์ I ์ฌ์ฉ ๋ถ๊ฐ
{
"selector": "interface",
"format": ["PascalCase"],
"custom": {
"regex": "^I[A-Z]",
"match": false
}
},
// typeAlias ์์ T ์ฌ์ฉ ๋ถ๊ฐ
{
"selector": "typeAlias",
"format": ["PascalCase"],
"custom": {
"regex": "^T[A-Z]",
"match": false
}
},
// typeParameter ์์ T ์ฌ์ฉ ๋ถ๊ฐ
{
"selector": "typeParameter",
"format": ["PascalCase"],
"custom": {
"regex": "^T[A-Z]",
"match": false
}
}
]
}
}
์ ๋ ๊ฒฝ๋ก ๊ฐ์ ๊ท์น
.ํ๋ฌ๊ทธ์ธ:
eslint-plugin-no-relative-import-paths
(MelvinVermeer/eslint-plugin-no-relative-import-paths
)
import
๊ฒฝ๋ก๋ฅผ ์ ๋ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ๊ธฐ๋ก ํฉ์ํ์ ๋ ์ฌ์ฉํ๊ธฐ ์ข์ ๊ท์น์
๋๋ค. ์ด ๊ท์น์ ๋์
ํ๊ธฐ๋ก ํ์ ๋ ํ์๋ถ๋ค๋ก๋ถํฐ ๊ฐ์ฅ ๋ง์ด ๋ฐ์๋ ์ง๋ฌธ์ด '๊ฐ์ ํด๋์์ import
ํ ๋๋ ์๋ ๊ฒฝ๋ก๋ฅผ ์ธ ์ ์๋์?'์๋๋ฐ, allowSameFolder
๋ผ๋ ์์ฑ์ true
๋ก ํ๋ฉด ๊ฐ๋ฅํฉ๋๋ค. ์๋๋ ์์์
๋๋ค.
{
"rules": {
// ๊ฐ์ ํด๋์ธ ๊ฒฝ์ฐ๋ฅผ ์ ์ธํ๊ณ import ๊ฒฝ๋ก๋ ํญ์ ์ ๋ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉ
"no-relative-import-paths/no-relative-import-paths": [
"warn",
{ "allowSameFolder": true, "rootDir": "src", "prefix": "@" }
]
}
}
prefix
๋ฅผ ์ง์ ํ๋ฉด, fix ์คํ ์ eslint๊ฐ ์๋์ผ๋ก prefix๋ฅผ ๋ฃ์ด์ import ๊ฒฝ๋ก๋ฅผ ๊ณ ์ณ์ค๋๋ค.
// { "prefix": "@" } ์ต์
์ ์ง์ ํ๋ฉด
import Something from "../../components/something";
// ์๋์ ๊ฐ์ด ๊ณ ์ณ์ค๋๋ค.
import Something from "@/components/something";
๊ตฌ์กฐ ๋ถํด ํ ๋น ๊ฐ์ ๊ท์น
์ด ๊ท์น์ ํ์ ๋ถ์ด ์ ์ ์ฃผ์ ์ ๋์ ํ๊ฒ ๋ ๊ท์น์ ๋๋ค. ESLint ๋ด์ฅ ๊ท์น์ธ prefer-destructuring์ ํตํด ๊ตฌ์กฐ ๋ถํด ํ ๋น์ ๊ฐ์ ํ ์ ์์ต๋๋ค. ๊ตฌ์กฐ ๋ถํด ํ ๋น์ ๋ํ ์ปจ๋ฒค์ ์ ๋ง์ถ๋ฉด, ์ผ๊ด์ ์ธ ์ฝ๋๋ฅผ ์ ์งํ ์ ์์ด์ ๊ฐ๋ ์ฑ์ ๋์์ด ๋ฉ๋๋ค.
ESLint์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํ๋ ๊ท์น์ด๊ธฐ ๋๋ฌธ์ ๋ณ๋ ํ๋ฌ๊ทธ์ธ ์ค์น๋ ํ์ํ์ง ์์ต๋๋ค. ์ฝ์ด์นํ๋ก ํธ๊ฐ๋ฐํ์ ๋ณ์ ์ ์ธ์์์ ๊ฐ์ฒด์ ๋ํด์๋ง ๊ตฌ์กฐ ๋ถํด ํ ๋น ๊ท์น์ ๊ฐ์ ํ๋๋ก ์ค์ ํ์ต๋๋ค.
{
"prefer-destructuring": [
"error",
{
"VariableDeclarator": {
"array": false,
"object": true
},
"AssignmentExpression": {
"array": false,
"object": false
}
}
]
}
์ฐธ๊ณ ๋ก, VariableDeclarator.object
์ต์
์ ๊ฒฝ์ฐ ESLint์ --fix
์ต์
์ ๋ฃ์ด์ ์คํํ๋ฉด ๊ตฌ์กฐ ๋ถํด ํ ๋น์ ์ ์ฉํ๋ ์ฝ๋๋ก ๊ณ ์ณ์ค๋๋ค.
์ด ๊ท์น์ ์ ์ฉํ๋ฉด, ์๋ ์ํฉ์์ ์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค.
const user = {
name: 'john',
age: 25
};
// <img draggable="false" role="img" class="emoji" alt="๐จ" src="https://s.w.org/images/core/emoji/14.0.0/svg/1f6a8.svg"> Error: Use object destructuring.
const name = user.name;
์ฌ๋ฐ๋ฅธ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
const user = {
name: 'john',
age: 25
};
// <img draggable="false" role="img" class="emoji" alt="โ
" src="https://s.w.org/images/core/emoji/14.0.0/svg/2705.svg">
const { name } = user.name;
Tailwind CSS ํด๋์ค ์๋ ์ ๋ ฌ ํ๋ฌ๊ทธ์ธ
2022๋
1์ Tailwind CSS์์ ๊ณต์์ผ๋ก ๋ฐํํ prettier-plugin-tailwindcss
(tailwindlabs/prettier-plugin-tailwindcss
)์ Tailwind CSS์ ํด๋์ค ๋ค์์ ๊ถ์ฅ ํด๋์ค ์์์ ๋ง๊ฒ ์๋์ผ๋ก ์ ๋ ฌํด์ฃผ๋ ์ ์ฉํ ๋๊ตฌ์
๋๋ค. ์ ์ฉํ๋ฉด ํด๋์ค ๋ค์์ ๊ฐ๋
์ฑ์ ๋์ฌ์ฃผ๊ธฐ ๋๋ฌธ์ ์ถ์ฒ๋๋ฆฌ๋ ํ๋ฌ๊ทธ์ธ์
๋๋ค.
์ ์ฉํ๋ ค๋ฉด ๋จผ์ prettier-plugin-tailwindcss
ํจํค์ง๋ฅผ ์ค์นํฉ๋๋ค.
pnpm add -D prettier prettier-plugin-tailwindcss
๋ค์์ผ๋ก, Prettier ์ค์ ํ์ผ์ plugins
ํ๋์ ํจํค์ง ์ด๋ฆ์ ๋ช
์ํฉ๋๋ค.
{
plugins: ['prettier-plugin-tailwindcss']
}
์์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
<!-- ์ ์ฉ ์ -->
<button
class="text-white px-4 sm:px-8 py-2 sm:py-3 bg-sky-700 hover:bg-sky-800"
>
...
</button>
<!-- ์ ์ฉ ํ -->
<button
class="bg-sky-700 px-4 py-2 text-white hover:bg-sky-800 sm:px-8 sm:py-3"
>
...
</button>
ํ๋ฌ๊ทธ์ธ์ ๊ณต์ฉ์ผ๋ก ์ฌ์ฉํ๊ณ ์ถ๋ค๋ฉด, Prettier ๊ณต์ ์ปจํผ๊ทธ ํจํค์ง์ ํฌํจํ๋ฉด ๋ฉ๋๋ค. Prettier ๊ณต์ ์ปจํผ๊ทธ ํจํค์ง์ ํ๋ฌ๊ทธ์ธ์ ์ค์นํ๊ณ plugins
ํ๋์ ํจํค์ง ์ด๋ฆ์ ๋ช
์ํ๋ฉด, ์ฌ์ฉ์ฒ์์ ๋ณ๋ ์ค์ ์์ด ํ๋ฌ๊ทธ์ธ์ด ์ ์ฉ๋ฉ๋๋ค.
๊ฒฐ๋ก
ESLint/Prettier ๊ณต์ ์ปจํผ๊ทธ ํจํค์ง๋ฅผ ๋ง๋ค์ด ๋ฐฐํฌํ๊ณ , ์ฝ์ด์นํ๋ก ํธ๊ฐ๋ฐํ์ ๋ชจ๋ ์ ์ฅ์์ ์ ์ฉํ์ฌ ์ผ๊ด๋ ๊ฐ๋ฐ ๊ฒฝํ์ ์ป์ ์ ์์์ต๋๋ค. ์ด ๊ณผ์ ์์ ํ์๋ค๋ผ๋ฆฌ ๋๋ด๋ ๋ํ์ ํฉ์๋ฅผ ํตํด ํต์ผ๋ ๊ฐ๋ฐ ๋ฌธํ๋ฅผ ํ์ฑํ ์ ์์๊ณ , ํ ๋ด ์์ฐ์ฑ์ด ํฅ์๋๋ ํจ๊ณผ๋ฅผ ๋๋ฆด ์ ์์์ต๋๋ค.
ํฉ์ํ๋ ๊ณผ์ ์์ ๋ค์ ์๊ฐ์ด ์์๋์ง๋ง, ํจํค์ง๋ฅผ ์ ์ฉํ๊ณ ๋๋ ๋ค๋ฅธ ์ ์ฅ์์์ ์์ ํ ๋๋ ์๋ฌธ์ ESLint ์๋ฌ๊ฐ ๋ฐ๋ชฉ์ ์ก๋ ์ผ์ ์์ด์ก์ต๋๋ค. ๊ทธ๋ฟ๋ง ์๋๋ผ, ์ฝ๋ ๋ฆฌ๋ทฐ ๊ณผ์ ์์๋ ๋ถํ์ํ ๋ ผ์์ ์ค์ด๊ณ , ์ปค๋ฎค๋์ผ์ด์ ๋น์ฉ์ ์๋ผ๋ ํจ๊ณผ๋ ๋ค์ผ๋ก ์ป์ ์ ์์์ต๋๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก, Airbnb์ ESLint ๊ท์น๋ค์ด ์์ฐ์ฑ์ ์ ํดํ๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ณ , ์ฝ์ด์นํ๋ก ํธ๊ฐ๋ฐํ๋ง์ ๊ท์น๋ค๋ก ์ฑ์์ง ๊ณต์ ์ปจํผ๊ทธ ํจํค์ง๋ฅผ ๋์ ํจ์ผ๋ก์จ ๊ฐ๋ฐ ํจ์จ์ฑ์ ํฌ๊ฒ ๊ฐ์ ํ ์ ์์์ต๋๋ค. ์ด ๊ณผ์ ์ ํตํด, ์ข์ ๋๊ตฌ ์ ํ๊ณผ ํ์ ๊ฐ์ ์์ฌ์ํต์ ์ค์์ฑ์ ๋ค์ ํ๋ฒ ๋๋ ์ ์์์ต๋๋ค.
๋ถ๋ก: ์์ฐ์ฑ์ ์ ํดํ๋ Airbnb ๊ท์น๋ค
์ฌ์ํ๋ค๊ณ ์ฌ๊ฒจ์ง ์ ์์ง๋ง, ESLint์ ๊ฒฝ๊ณ ๋ฅผ ํผํ๊ธฐ ์ํด ์ฝ๋๋ฅผ ์ฌ๋ฌ ๋ฒ ๋ค์ ์์ฑํ๋ค ๋ณด๋ฉด ์์ ํ๋ฆ์ด ๋๊ธฐ๊ณ ๊ฝค๋ ๋ง์ ์๊ฐ์ด ์์๋ฉ๋๋ค. ์์ฐ์ฑ์ ์ ํดํ๋ค๊ณ ๋๊ผ๋ Airbnb ๊ท์น์ ์ ๋ฆฌํด๋ณด์์ต๋๋ค.
for..of
์ฌ์ฉ ๊ธ์ง ๊ท์น
for (const key of obj) {
// ~~~
// ESLint: iterators/generators require regenerator-runtime, which is
// too heavyweight for this guide to allow them. Separately, loops should
// be avoided in favor of array iterations. (no-restricted-syntax)
}
for..of
๋ ์ธ๋ฑ์ค๋ ํค ๊ฐ์ ๊ด๊ณ์์ด ๋จ์ํ ๋ฐ๋ณต๋ฌธ์ ์์ฑํ ๋ ์ ์ฉํ ๋ฌธ๋ฒ์
๋๋ค. Airbnb์ ์ ์๋ no-restricted-syntax
๊ท์น์ ํน์ ๊ตฌ๋ฌธ(ex: for..of
) ์ฌ์ฉ ์ ์๋ฌ๋ฅผ ํ์ํ๋๋ก ์ค์ (airbnb/javascript
)ํ๊ณ ์์ต๋๋ค. "๊ตฌํ ๋ธ๋ผ์ฐ์ ์์ ํธํ์ฑ์ด ๋จ์ด์ง๊ณ regenerator-runtime ํด๋ฆฌํ์ด ๋ฌด๊ฑฐ์ฐ๋ ํธํ์ฑ ์ข์ forEach
๋ฅผ ์ฐ๋๊ฒ ๋ซ๋ค"๋ ๋ฐฐ๊ฒฝ์์ ์ ํ๋์๋ค๊ณ ํฉ๋๋ค.
forEach
์ ๊ฒฝ์ฐ break
, continue
, await
์ ๊ฐ์ ํค์๋ ์ฌ์ฉ์ ์ ์ฝ์ด ์์ด ๋ถํธํจ์ ๊ฒช์์ต๋๋ค.
for..of ์ ํ ๊ท์น์ด ๋ถํ์ํ ์ด์
2017๋
1์์ Github ์ด์ (airbnb/javascript
)์ ์ฌ๋ผ์์ ์ด ๊ท์น์ด ์ ๋ง ํ์ํ์ง์ ๋ํด์ ๊ต์ฅํ ๋ง์ ์๊ฒฌ์ด ์์์ต๋๋ค. ์ฌ๊ธฐ์ ์๊ธฐํ๋ ๊ตฌํ ๋ธ๋ผ์ฐ์ ์ ๊ธฐ์ค์ async ํจ์๋ฅผ ์ฌ์ฉํ ์ ์๋ ํ๊ฒฝ, ์ฆ Safari 11 ๋ฒ์ ๋ฏธ๋ง๊ณผ IE ํ๊ฒฝ ์๊ธฐ์
๋๋ค. 2023๋
8์ ๊ธฐ์ค์ผ๋ก ํ๊ตญ์์์ iOS ๋ฒ์ 11 ๋ฏธ๋ง ์ฌ์ฉ์ ๋น์จ์ด 0.06%์ธ ๊ฒ์ ๊ณ ๋ คํ๋ฉด(์ถ์ฒ: StatCounter) ํ์ฌ ์์ ์์๋ ์ด ๊ท์น์ ์ค์์ฑ์ด ์๋์ ์ผ๋ก ์ค์ด๋ค์๋ค๊ณ ๋ณผ ์ ์์ต๋๋ค. ์ด ๊ท์น์ ์ด์ ๋ ๊ณ ๋ คํ์ง ์์๋ ๋๋ ๊ตฌํ ๋ธ๋ผ์ฐ์ ๋ฅผ ์ํ ๊ฒ์ด๋ฏ๋ก, ์ง๊ธ ์์ ์์๋ ๋ถํ์ํ๋ค๊ณ ๋ณผ ์ ์์ต๋๋ค.
ํ์ดํ ํจ์์ ์ค๊ดํธ ๊ฐ์ ์๋ต ๊ท์น
const <span class="token function-variable function">bar = () => {
// ~
// ESLint: Unexpected block statement surrounding arrow body;
// move the returned value immediately after the '=>'. (arrow-body-style)
return 0;
}
arrow-body-style
๊ท์น์ ํ์ดํ ํจ์์์ ์ค๊ดํธ๋ฅผ ์๋ตํ ์ ์๋ ๊ฒฝ์ฐ์ ์๋ตํ๋๋ก ๊ฐ์ ํ๋ ๊ท์น์
๋๋ค. ์ด ๊ท์น์ ์ฝ๋๋ฅผ ๊ฐ๊ฒฐํ๊ฒ ๋ง๋ค์ด์ฃผ์ง๋ง, ํธ๋ถํธ๊ฐ ๊ฐ๋ฆฌ๋ ๊ท์น์ด๊ธฐ๋ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด ๋น ํจ์๋ฅผ ๋ง๋ค์ด๋๊ณ ๋์ค์ ๋ก์ง์ ์ถ๊ฐํ๋ ค๋ ๊ฒฝ์ฐ, ์ฝ๋๋ฅผ ์ง์ฐ๊ณ ๋ค์ ์ค๊ดํธ๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค.
์ค๊ดํธ๋ฅผ ์ฒ์๋ถํฐ ์์ฑํด๋๋ฉด ๋์ค์ ์ถ๊ฐ์ ์ธ ๋ก์ง์ด ํ์ํ ๊ฒฝ์ฐ ๋ฐ๋ก ์ฝ๋๋ฅผ ์์ฑํ ์ ์์ด ํธ๋ฆฌํฉ๋๋ค. ๋น์ทํ ์ฌ๋ก๋ก Prettier 2.0.0์์๋ ์ด๋ฌํ ์ฅ์ ์ ์ป๊ธฐ ์ํด ๊ธฐ๋ณธ ์ค์ ์ ํ์ดํ ํจ์ ํ๋ผ๋ฏธํฐ์ ๊ดํธ๋ฅผ ํญ์ ํฌํจํ๋๋ก ๋ณ๊ฒฝ๋ ํ์คํ ๋ฆฌ๊ฐ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, x => x
๋ฅผ ์์ฑํ๋ฉด (x) => x
๋ก ๋ณํํด์ค๋๋ค.