๋ฆฌ์กํธ๋ก ํ๋ก ํธ์๋ ๊ฐ๋ฐ ์ ์ ์ฉํ ์ปดํฌ๋ํธ ๊ด๋ฆฌ ๋๊ตฌ, ์คํ ๋ฆฌ๋ถ์ ๋์ ํด๋ณด๊ธฐ
(CRA + Prettier + ESLint + TypeScript + Storybook + chromatic)
Prettier
yarn add -D prettier
# ๋๋
npm install --save-dev prettier
์ด์ ๋ฏธ๋ฆฌ ์ด๋ค ํฌ๋งท์ ์ฌ์ฉํ ๊ฒ์ธ์ง ์ ์ํ๊ธฐ ์ํด ํ๋ก์ ํธ์ root์์ .prettierrc
ํ์ผ์ ์์ฑํ๋ค.
{
"singleQuote": false,
"semi": true,
"useTabs": false,
"tabWidth": 2,
"printWidth": 80,
"arrowParens": "always",
"trailingComma": "all",
"bracketSpacing": true,
"proseWrap": "preserve",
"requirePragma": false,
"overrides": [
{
"files": "*.css",
"options": {
"parser": "css"
}
},
{
"files": ["*.mts", "*.cts", "*.ts"],
"options": {
"parser": "typescript"
}
}
]
}
- css ํ์ผ์ prettier๊ฐ ์๋ฌ๋ผ๊ณ ์ก์๋ด๋ ๊ฒฝ์ฐ๊ฐ ์์ด์ override๋ก ์ค์ ์ ์ถ๊ฐํด์ค๋ค.
- "parset": "typescript"๋ฅผ override ์์ ๋ฃ์ด์ฃผ์ด์ผ ์ธ๋ฐ์๋ ์๋ฌ๋ฅผ ํผํ ์ ์๋ค.
์ด์ npx prettier --check ./src
๋ช
๋ น์ด๋ฅผ ์คํํด๋ณธ๋ค.
์ผ๋ถ๋ฌ ๊ธฐํธ ํ๋๋ฅผ ๋ฃ์ด๋์๋๋ฐ, ์ฌ์ค ์์ ์์ฑํ ๊ฒ๊ณผ๋ ํฌ๊ฒ ๊ด๋ จ ์๊ธด ํ์ง๋ง, ์ด๋ฐ ์์ผ๋ก ํฌ๋งท ์คํ ์ ์๋ฌ๋ฅผ ๋ณด์ฌ์ค๋ค.
๋ง์ผ prettier๋ก ํฌ๋งทํ ํ์๊ฐ ์๋ค๋ฉด, .prettierignore
ํ์ผ์ ํ๋ก์ ํธ ๋ฃจํธ์ ์์ฑํด์ gitignore
์ฒ๋ผ ๋ด๋ฒ๋ ค ๋๊ณ ์ ํ๋ ํ์ผ์ ์ถ๊ฐํ๋ค. ๋๋ MDX ํ์ผ์ ๊ณ์ ์๋ฌ๋ก ์ก๊ธธ๋ ์๋์ฒ๋ผ ํด์ฃผ์๋ค.
# .prettierignore
# Ignore all MDX files:
*.mdx
ํ์ง๋ง ๋งค๋ฒ ์ด๋ ๊ฒ ๋ช ๋ น์ด๋ฅผ ์คํํ๋ ๊ฒ์ ๊ท์ฐฎ์ผ๋, vscode ์๋ํฐ๋ฅผ ๊ธฐ์ค์ผ๋ก, extension์ ๋์์ ๋ฐ๊ณ ์ ํ๋ค.
command + Shift + P -> settings.json
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
์ ์ฝ๋๋ฅผ ์ค๊ฐ์ ์ฝ์
ํ๋ฉด, ์ด์ ์ฝ๋๋ฅผ ์ ์ฅํ ๋๋ง๋ค ์์์ prettier๊ฐ ์ฝ๋๋ฅผ .prettierrc
์ ์ง์ ํ ๋๋ก ๊ณ ์ณ์ค ๊ฒ์ด๋ค.
ESLint
ESLint๋ EcmaSript + Lint์ ํฉ์ฑ์ด์ธ๋ฐ, Lint๋ ์ ์ฌ์ ์ผ๋ก ๋ฒ๊ทธ๋ ์๋ฌ๋ฅผ ๋ผ ๊ฐ๋ฅ์ฑ์ด ์๋ ์ฝ๋๋ฅผ ๋ถ์ํด ๋ณด์ฌ์ค๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก
npm install --save-dev eslint
# ๋๋
yarn add -D eslint
๋ํ๋์๋ฅผ ์ค์นํด์ค๋ค.
์ด์ npx eslint --init
To check syntax and find problems
JavaScript modules (import/export)
React
Yes
๋ธ๋ผ์ฐ์ ์ ๋ ธ๋ ์์์ ๋ชจ๋ ์ฝ๋๋ฅผ ๋๋ฆฐ๋ค๊ณ ํ๋ฉด ์คํ์ด์ค ๋ฐ๋ฅผ ๋๋ฌ์ ๋๋ค ์ ํํด์ฃผ๊ณ ์ํฐ๋ฅผ ์น๋ค.
JSON์ผ๋ก ์ ํํ์ง๋ง, ๋์ค์ ts๋ก ๋ฐ๊พธ๊ฒ ๋์๋ค.
๋ง์ง๋ง์ eslint-plugin-react@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest
๋ฅผ npm์ผ๋ก ๋ค์ด ๋ฐ๊ฒ ๋๊ณ ํ๋๋ฐ, ๋๋ yarn์ ์ฐ๊ณ ์์ด์ No๋ฅผ ํํ๊ณ ,
yarn add -D eslint-plugin-react@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest
์ ๋ช ๋ น์ด๋ก ๋ํ๋์๋ฅผ ๊น์์ฃผ์๋ค. npm์ผ๋ก ๋ชจ๋์ ๋ค์ด๋ฐ๊ณ ์์๋ค๋ฉด ๊ทธ๋ฅ yes๋ฅผ ํด๋ฆญํ๋ฉด ๋๋ค.
// .eslintrc.ts
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: ["eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended"],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: "latest",
sourceType: "module",
project: "./tsconfig.json",
},
plugins: ["react", "@typescript-eslint", "prettier"],
settings: {
react: {
version: "detect",
},
},
rules: {
// General
"no-console": "error",
// TypeScript
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-member-accessibility": "off",
"@typescript-eslint/indent": "off",
"@typescript-eslint/member-delimiter-style": "off",
"@typescript-eslint/no-confusing-void-expression": [
"error",
{ ignoreArrowShorthand: true, ignoreVoidOperator: true },
],
"no-duplicate-imports": "off",
"@typescript-eslint/no-duplicate-imports": "error",
"@typescript-eslint/no-implicit-any-catch": "error",
"no-invalid-this": "off",
"@typescript-eslint/no-invalid-this": "error",
"@typescript-eslint/no-invalid-void-type": "error",
"no-loop-func": "off",
"@typescript-eslint/no-loop-func": "error",
"no-loss-of-precision": "off",
"@typescript-eslint/no-loss-of-precision": "error",
"@typescript-eslint/no-parameter-properties": "off",
"no-redeclare": "off",
"@typescript-eslint/no-redeclare": "error",
"no-shadow": "off",
"@typescript-eslint/no-shadow": "error",
"no-throw-literal": "off",
"@typescript-eslint/no-throw-literal": "error",
"@typescript-eslint/no-unnecessary-boolean-literal-compare": "error",
"@typescript-eslint/no-unnecessary-condition": "error",
"@typescript-eslint/no-unnecessary-type-arguments": "error",
"no-unused-expressions": "off",
"@typescript-eslint/no-unused-expressions": "error",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-use-before-define": ["error", { variables: false }],
"@typescript-eslint/prefer-enum-initializers": "error",
"@typescript-eslint/prefer-for-of": "error",
"@typescript-eslint/prefer-includes": "error",
"@typescript-eslint/prefer-nullish-coalescing": "error",
"@typescript-eslint/prefer-optional-chain": "error",
"@typescript-eslint/prefer-reduce-type-parameter": "error",
"@typescript-eslint/prefer-string-starts-ends-with": "error",
"@typescript-eslint/prefer-ts-expect-error": "error",
"@typescript-eslint/promise-function-async": "error",
"no-return-await": "off",
"@typescript-eslint/return-await": "error",
"@typescript-eslint/strict-boolean-expressions": "error",
"@typescript-eslint/switch-exhaustiveness-check": "error",
// React
"react/jsx-boolean-value": "warn",
"react/jsx-curly-brace-presence": "warn",
"react/jsx-fragments": "warn",
"react/jsx-no-useless-fragment": "warn",
"react/jsx-uses-react": "off",
"react/prefer-stateless-function": "warn",
"react/prop-types": "off",
"react/react-in-jsx-scope": "off",
// Functional
"functional/prefer-readonly-type": [
"warn",
{
allowLocalMutation: true,
allowMutableReturnType: true,
ignoreClass: true,
},
],
},
};
๋ค์ ๋ฒ์๋ ์ฝ๋ husky์ lint-staged๋ฅผ ํตํด์ ์ฝ๋ ํฌ๋งท ์๋ํ๋ฅผ ํ๋ ๊ฒ๊น์ง ๋ค๋ฃจ๊ณ ์ ํ๋ค.