์ฌ์ฉํ spotify api์ ๋ํ ์ค๋น๊ฐ ๋๋ฌ์ผ๋ ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ํ๋ก์ ํธ ์ค์ ์ ํด๋ณด์.
์ค๋์ eslint์ prettier๊ฐ ๊ฐ๊ฐ ๋ญ์ง, ์ ํ์ํ์ง ๊ทธ๋ฆฌ๊ณ ์ค์ ๋ฐฉ๋ฒ์ ๋ํด ์ดํด๋ณด์.
๊ทธ๋ฆฌ๊ณ ์ปค๋ฐ ๋ฉ์์ง๊ฐ ๋ฆฐํธ๊ฐ ๋๋๋ก commitlint ๋ ์ค์ ํด ์ฃผ์.
โ ๏ธ ํด๋น ํ๋ก์ ํธ๋ vite+react+yarn์ ๊ธฐ์ค์ผ๋ก ๋ค๋ฃน๋๋ค! ๋ชจ๋ ์ค์ ํ์ผ๋ค์ root์ ์์ฑํฉ๋๋ค.
Prettier
๐ค prettier๊ฐ ๋ญ๊ณ ์ ํ์ํ ๊น?
prettier์ ๊ณต์ ํ์ด์ง์ ๋ค์ด๊ฐ๋ฉด prettier๋ "opinionated" ์ฝ๋ ํฌ๋ฉงํฐ๋ผ๊ณ ์๊ฐํ๋ค.
prettier๊ฐ opinionated(prettier๊ฐ ์ ๋ฆฝํ ์คํ์ผ์ ๊ณ ์ํ๋) ์ด์ ๋ ์ฝ๋ฉ ์คํ์ผ์ ๋ํ ๋ ผ์์ ๋ฉ์ถ๊ธฐ ์ํด์๋ผ๊ณ ํ๋ค.
์คํ์ผ์ ๋ํ ์ต์ ์ด ์๊ธฐ๋ฉด "์ด๋ค ์ต์ ์ด ๋ ์ข์๊ฐ?", "์?", "์ฐ๋ฆฌ๊ฐ ํ ์ ํ์ด ์ต์ ์ธ๊ฐ?"๋ผ๋ ๋ ผ์์ด ๋ฐ์ํ๋ค. ์ฝ๋ ๋ฆฌ๋ทฐ ๋ ์ผ์ผ์ด "์ฌ๊ธฐ๋ ์์๋ฐ์ดํ๋ก ์ฐ์ จ๋ค์, ํฐ ๋ฐ์ดํ๋ก ๋ชจ๋ ๋ณ๊ฒฝํด ์ฃผ์ธ์."๋ผ๋ ๋ง์ ๋ฃ๊ฑฐ๋ ํ๋ ๊ฒ ์ ๋ง ํ์ํ ๊ณผ์ ์ผ๊น ์ถ๋ค. ์ฝ๋๋ฅผ ์ง๊ธฐ๋ ์ ์ ์ด๋ฐ ๋ ผ์๋ค์ ํ๋ค๋ณด๋ฉด ์ ๋ง ์ค์ํ ์ฝ๋ ์ง๊ธฐ์ ์ฐ๋ ์๋์ง๋ฅผ ๋ญ๋นํ๋ ์ผ๋ฟ์ด๋ค.
๋ค๋ฅธ ์คํ์ผ ๊ฐ์ด๋์ ๋๊ณ ์ prettier๋ฅผ ์ฌ์ฉํด์ผ ์ถ๊ฒ ์ง๋ง, prettier์๊ฒ๋ 100% ์์ ํ ์๋ํ๋ผ๋ ์ต๊ฐ์ ๋ฌด๊ธฐ๊ฐ ์๋ค.
๋ถ๋ช prettier๊ฐ ์ ํ ์ฝ๋ ํฌ๋งทํ ์ค์ ๋ง์์ ์๋ค์ง ์๋ ๋ถ๋ถ๋ ์๊ธฐ ๋ง๋ จ์ด์ง๋ง ํ๋์ ์คํ์ผ๋ก ์๋์ผ๋ก ํต์ผํด ์ฃผ๋ ์ด์ ๊ณผ ์ ์ฅ์ผ๋ก ๋ฐ๋ก ํฌ๋งท์ด ์ ์ฉ๋๋ ์ด์ ์ด ๋ ๋งค๋ ฅ์ ์ผ๋ก ๋ค๊ฐ์จ๋ค.
prettier๋ ์ผ๊ด์ฑ์๋ ์ฝ๋ ์คํ์ผ๋ก ํฌ๋งท์ ํด์ฃผ๋ ๋๊ตฌ๋ผ๋ ์ ์ ์๊ฒ ๋์๋ค.
์ด์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ณด์. ๋จผ์ prettier ์ค์ ์ ๋ํด ์ดํด๋ณด๊ณ , ๊ทธ ํ git hooks ์ค์ ๋ ์ถ๊ฐํด ๋ณด์.
prettier ์ค์
1. ์ค์น
yarn add --dev --exact prettier
2. ์ค์ ํ์ผ
- . prettierrc ํ์ผ์ ๋ง๋ค๊ณ , ์ค๋ฒ๋ผ์ด๋ ํ๊ณ ์ ํ๋ ๊ท์น์ด ์๋ค๋ฉด ์ฌ๊ธฐ์ ์ ์ผ๋ฉด ๋๋ค.
{
"singleQuote": true,
"trailingComma": "all",
"arrowParens": "avoid"
}
- .prettierignore ํ์ผ์ ๋ง๋ค๊ณ , ํฌ๋งท์ ํ๊ณ ์ถ์ง ์์ ํ์ผ๋ค์ ์ถ๊ฐํด ์ฃผ๋ฉด ๋๋ค.
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
.next
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# changelog
CHANGELOG.md
Git hooks ์ค์
1. ์ค์น
yarn add --dev husky lint-staged
npx husky init
2. ์ค์ ํ์ผ
- .husky/pre-commit ํ์ผ์ ์์ฑํ๊ณ ์๋์ ๋ช ๋ น์ด๋ฅผ ์ถ๊ฐํ๋ค.
npx lint-staged
- package.json์ ์๋์ ๊ฐ์ด ์ถ๊ฐ๋ฅผ ํด์ค๋ค.
{
"lint-staged": {
"**/*": "prettier --write --ignore-unknown"
}
}
์คํ
ํฐ๋ฏธ๋์ yarn prettier . --write ๋ช ๋ น์ด๋ก ์คํํด ๋ณด์.
(...์๋ต...)
ํด๋น ๋ช ๋ น์ด๋ก ํญ์ ์คํํ๋ ๋ฒ๊ฑฐ๋ก์์ ๋๊ผ์ ์๋ ์๋ค. package.json scripts์ ๋ช ๋ น์ด๋ก ์คํํ ์ ์๋๋ก ์ถ๊ฐํด ๋ณด์.
{
"name": "vinylify",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"prepare": "husky",
"format": "yarn prettier . --write", // ์ถ๊ฐ
"format:check": "yarn prettier . --check", // ์ถ๊ฐ
"typecheck": "tsc --noEmit --incremental false"
},
// ... ์๋ต
}
Eslint
๐ค eslint๊ฐ ๋ญ๊ณ ์ ํ์ํ ๊น?
Eslint๋ ECMAScript์ JavaScript ์ฝ๋๋ฅผ ์ ์ ์ผ๋ก ๋ถ์ํด์
์ฝ๋ ์๋ฌ๋ฅผ ์ฐพ์์ค์ ์ฝ๋ ํ๋ฆฌํฐ๊ฐ ์ผ์ ํ๊ณ ๋ฒ๊ทธ๋ฅผ ํผํ ์ ์๋๋ก ํ๋ ๋๊ตฌ๋ค.
eslint ์ค์
1. ์ค์น
yarn create @eslint/config@latest
2. ์ค์
yarn init @eslint/config
์์ ๋ช ๋ น์ด๋ฅผ ์ ๋ ฅํ๋ฉด eslint.config.js ๋ eslint.config.mjs ๋ฑ์ ํ์ผ์ด ์์ฑ๋๋ค.
๊ทธ ์์๋ ๊ท์น์ ์ค์ ํด ์ค ์ ์๋ค.
์๋ฅผ ๋ค๋ฉด, ๋ค์๊ณผ ๊ฐ์ด "off"๋ ๊ท์น์ ๋๊ณ , "warn" ๋๋ 1๋ก ์ค์ ์ ๊ฒฝ๊ณ ๋ง ์ผ๊ณ exit code๋ ์ํฅ์ ์ฃผ์ง ์๊ณ , "error" ๋๋ 2๋ก ์ค์ ํ๋ฉด ์๋ฌ๋ฅผ ํค๊ณ exit code๋ฅผ 1๋ก ์ค์ ํ๋ ๊ฒ์ด๋ค.
// eslint.config.js
import js from "@eslint/js";
export default [
js.configs.recommended,
{
rules: {
"no-unused-vars": "warn",
"no-undef": "warn"
}
}
];
eslint์ ํฐ ํน์ง์ ์ ์ฉํ ๊ท์น๋ค์ ํ๋ฌ๊ทธ์ธ ํ ์ ์๋ค๋ ๊ฒ์ด๋ค.
์ํ๋ ์ค์ ๋ค์ devDependencies๋ก ์ถ๊ฐํ๊ณ config ํ์ผ์๋ ์ถ๊ฐ๋ฅผ ํด์ฃผ์.
yarn add @typescript-eslint/parser eslint-plugin-react-hooks eslint-plugin-react-refresh
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
};
Prettier vs. Linters
prettier์ eslint๋ ํฌ๋งท์ ํด์ฃผ๋ ๋๊ตฌ๋ผ๋ ๊ฑด ๊ฐ์๋ฐ, ๋ด๋น ์์ญ์ด ์๋ก ๋ค๋ฅด๋ค๋ ๊ฑธ ์ ์ ์๋ค.
linter๋ ๋ค์์ ๋ ์นดํ ๊ณ ๋ฆฌ๋ก ๊ท์น์ ๋๋ ์ ์๋ค.
ํฌ๋ฉง ๊ท์น : eg: max-len, no-mixed-spaces-and-tabs, keyword-spacing, comma-style…
prettier๋ ์ด๋ ๊ฒ ๋ค์ํ ๊ท์น๋ค์ ๋ํ ๊ณ ๋ฏผ์ ํ๋ฐฉ์ ์์ ์ค๋ค.
์ฐ๋ฆฌ๊ฐ ์ด๋ป๊ฒ ์ฝ๋๋ฅผ ์ง๋๊ฐ์ prettier๋ ์์ ์ ํฌ๋งท ๊ท์น์ผ๋ก ์ฝ๋๋ฅผ ์๋ก ํฌ๋งทํด ์ฃผ๊ธฐ ๋๋ฌธ์ด๋ค.
์ฝ๋ ํ๋ฆฌํฐ ๊ท์น: eg no-unused-vars, no-extra-bind, no-implicit-globals, prefer-promise-reject-errors…
prettier๋ ์ฝ๋ ํ๋ฆฌํฐ์ ๊ด์ฌํ์ง ์๋๋ค.
์ด ์ผ์ linter (eslint, tslint, stylelint ๋ฑ)์ด ๋ด๋นํ ์์ญ์ด๋ผ๋ ๊ฒ์ด๋ค.
์ฝ๋์ ์๋ ๋ฒ๊ทธ๋ค์ ์ก๋ ์ญํ ์ linter๊ฐ, ์ฝ๋ ์ธ๊ด(?)์ ์ผ๊ด๋๊ฒ ๊พธ๋ฉฐ์ฃผ๋ ์ญํ ์ prettier๊ฐ ๋ด๋นํ๋ค๋ ์ฐจ์ด๊ฐ ์๋ค.
Commitlint
๋ง์ง๋ง์ผ๋ก commitlint๋ฅผ ์ค์ ํด ์ฃผ์.
commitlint๋ ์ปค๋ฐ ์ปจ๋ฒค์ ์ ์ค์ํ๋๋ก ๋์์ฃผ๋ ๋ฆฐํฐ๋ค.
์ค์น
yarn add --dev @commitlint/{cli,config-conventional}
์ค์
- commitlint.config.js ํ์ผ์ ์์ฑํ๊ณ , ์ค์ ์ ์ถ๊ฐํด ๋ณด์.
- ์๋์ ์ค์ ์ ์ปค๋ฐ ๋ฉ์์ง ์์ฑ ์ ๋ฐฐ์ด ์์ ํค์๋๋ก ์์ํด์ผ ํ๋ค๋ ๊ท์น์ด๋ค.
export default {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'feat',
'fix',
'docs',
'chore',
'style',
'refactor',
'ci',
'test',
'perf',
'revert',
'init',
],
],
},
};
์ด์ ์ปค๋ฐ ๋ฉ์์ง ์์ฑํ ๊ฒฝ์ฐ ์๋์ผ๋ก ๋ฆฐํธ๋ฅผ ํด์ค ์ค์ ์ ์ถ๊ฐํด ๋ณด์.
- github/workflows/commitlint.yml ํ์ผ ์์ฑ
name: Lint Commit Messages
on: [pull_request, push]
jobs:
commitlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: wagoid/commitlint-github-action@v4
- .husky/_/commit-msg ํ์ผ์ ์๋์ ๋ด์ฉ ์ถ๊ฐ
npx --no -- commitlint --edit $1
์คํ
๐ฉ๐ป๐ป ์ค๋์ ์ฝ๋๋ฅผ ๋ณธ๊ฒฉ์ ์ผ๋ก ์ง๊ธฐ ์ ์ฝ๋ ํฌ๋งท์ ๋ํ ์ค์ ์ ๋ชจ๋ ํ๋ค.
์ฝ๋ ์คํ์ผ, ์ฝ๋ ๋ฒ๊ทธ ๋ถ์, ์ปค๋ฐ ๋ฉ์์ง ๋ฆฐํธ ๋ฑ์ ๊ท์น์ ๋ ผํ๋ ค๋ฉด ๋ง์ ์๋์ง๊ฐ ์ฐ์ผ ์ ์๋ค.
ํ์ง๋ง ๋ฏธ๋ฆฌ ์ฝ์ํ ๊ท์น์ ์ ํ๊ณ , ์๋ํ๊น์ง ๊ฐ๋ฅํ ๋๊ตฌ๋ค์ ๋์ ํด ์ด๋ฐ ๋ ผ์์ผ๋ก ์ฐ์ผ ์๋์ง๋ฅผ ์ ์ฝํด์
๋ ํจ์จ์ ์ผ๋ก ์์ ์ ์งํํ ์ ์๊ธฐ์ ๊ฐ๋ฐ์ ๊ฒฝํ (DX)๋ฅผ ๋์ผ ์ ์๋ค.
๐ ์ค๋์ ์ ์ฉํ ๊ฑฐ๋ฅผ ๊ธฐ๋กํด ๋ณด๋ ๊ฐ ๋๊ตฌ๋ค์ ๋ ์ดํด๋ณผ ๊ธฐํ๊ฐ ๋ ๊ฑฐ ๊ฐ์์ ํฅ๋ฏธ๋ก์ ๋ค.
๐ References
https://prettier.io/docs/en/comparison