나는 개발 경험이 많지 않은 주니어 개발자이다.
그래서 자연스럽게 누군가 설계한 프로젝트에 투입이 되어 개발을 하고 있고 앞으로도 그럴 것이다.
그런 환경에서 프로젝트를 잘 마무리할수록 내가 잘 알고있다고 착각하기 쉽다.
아무것도 없는 상태에서 새로운 프로젝트를 나혼자 바닥부터 설계를 해보라고 하면 못하지 않겠는가.
그러니 나만의 레시피가 필요하고 정리하고 공부해야한다.
vite기반 타입스크립트 리액트 기반의 프로젝트를 견고하게 설계해보자
corepack enable
pnpm i
tsconfig.json (공통)
tsconfig.app.json (애플리케이션 코드용)
tsconfig.node.json (개발 환경용)
필요한 경우 분리 가능
// tsconfig.json
{
"files":[],
"references":[
{"path" : "./tsconfig.app.json"},
{"path" : "./tsconfig.node.json"}
]
}
prettier.config.mjs 설정
eslint.config.js 설정
협업에 있어서 엄격한 코드작성 규칙을 적어보자.
선언적 프로그래밍 방식의 철학을 최대한 반영하고, 명확하고 가독성있는 코드 작성 스타일을 지향한다.
1. interface대신 type사용. // 타입 안정성 보장
2. let,var 대신 const사용 // 그냥 const만 써라 엄격한 안정성
3. switch 대신 if사용 // 복잡한 로직 평가 가능, 다양한 타입 조건검사 가능
4. 삼항연산자 대신 if 사용 // 복잡한 로직 명확히 표기, 가독성, 유지보수성 향상
5. else 대신 easy return 사용 // 복잡도 감소, 가독성 향상
설명이 필요한데, 먼저 아토믹 디자인 패턴은 atoms, molecules, organisms, templates, pages단위로 5계층의 디렉토리 구조를 가진다.
장단점이 있지만 결론부터 말하자면 나는 organisms계층은 제외한 4계층의 디렉토리 구조로 설계할것이다.
antd와 같은 UI라이브러리를 사용할것이기도 하고(atoms단위가 사실상 UI라이브러리를 포함하는 기존의 molecules과 유사해짐), 프로젝트의 복잡도가 올라갈수록 organisms의 위치는 molecules계층과 경계가 불명확해진다고 느꼈다.
결과적으로 하이브리드 패턴의 구조는 다음과 같다.
src - |
components |
- atoms
- molcules
- templates
- pages
| api
| assets
| utils
| main.tsx index.scss ...
atoms - UI라이브러리의 기본컴포넌트를 포함한 재사용성 있는 컴포넌트
molecules - atoms 계층의 조합으로 비즈니스로직 일부 포함 가능
templates - molecules계층의 확장, 복잡한 비즈니스로직 포함
pages - templates계층과 여러 레이어의 조합으로 최종 페이지를 나타냄
플러그인, 서버(프록시),빌드 등 설정
pnpm create vite@latest --> react-typescript
git init
git flow init -d
4-1. 절대경로 설정 ( ./App.css -> @/app.css )
// tsconfig.app.json
{
"compilerOptions": {
...
"paths": {
"@/*": ["src/*"]
},
"baseUrl": ".",
},
}
4-2. vite 절대경로 의존성 설치
pnpm i -D vite-tsconfig-paths
4-3. vite plugins 설정
// vite.config.ts
export default defineConfig({
plugins: [react(), tsconfigPaths()], // tsconfigPaths추가
})
5-1. 스토리북 설치
pnpm dlx storybook@latest init
.storybook/main.ts
add온에서 onboarding 제거
5-2. 스토리북 반응형 뷰포트 addon설치
pnpm i -D @storybook/addon-viewport
5-3. 뷰포트 적용
./storybook/preview.ts
const preview: Preview = {
parameters: {
viewport: {
viewports: INITIAL_VIEWPORTS, // INITIAL_VIEWPORTS 추가
defaultViewport: 'ipad', // 기본 뷰포트 단위 설정
},
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
6-1. sass(scss)설치
pnpm i sass -D
6-2. scss세팅
// index.css -> index.scss
:root {
//TODO 여기에 디자인 토큰 작성
}
7-1. eslint 설정
기본 vite기반 프로젝트 README.md 파일 eslint설정 참고
7-2. eslint 스토리북 파일도 타입체크 처리
//tsconfig.app.json
...
"include": ["src", ".storybook/*"]
7-3. eslint defaultCodeStyle 추가
const defaultCodeStyle = {
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 'latest',
globals: {
...globals.browser,
...globals.node
}
},
plugins: {
'unused-imports': unusedImports // pnpm install 필요
},
rules: {
'max-depth': ['error', 2], // 블록 중첩을 2단계로 제한
'padding-line-between-statements': [
'error',
{ blankLine: 'always', prev: '*', next: 'return' }, // return 문 앞에 항상 공백 줄을 추가
{ blankLine: 'always', prev: '*', next: 'if' }, // if 문 앞에 항상 공백 줄을 추가
{ blankLine: 'always', prev: 'function', next: '*' }, // 함수 선언 후에 공백 줄 추가
{ blankLine: 'always', prev: '*', next: 'function' } // 함수 선언 전에 공백 줄 추가
],
'no-restricted-syntax': [
'error',
{
selector: 'TSInterfaceDeclaration',
message: 'Interface 대신 type 을 사용하세요.'
},
{
selector: 'VariableDeclaration[kind="let"]',
message: 'let 대신 const 를 사용하세요.'
},
{
selector: 'VariableDeclaration[kind="var"]',
message: 'var 대신 const 를 사용하세요.'
},
{
selector: 'SwitchStatement',
message: 'switch 대신 if 를 사용하세요.'
},
{
selector: 'ConditionalExpression',
message: '삼항 연산자 대신 if 를 사용하세요.'
},
{
selector: 'IfStatement[alternate]',
message: 'else 대신 early return 을 사용하세요.'
}
]
}
}
export default tseslint.config(
...
defaultCodeStyle
)
7-4. 커맨드라인 "lint" 수정
// package.json
"script": {
...
"lint": "eslint --fix --ignore-pattern .gitignore --cache --cache-location ./node_modules/.cache/eslint .",
}
7-5. 스토리북 eslint 설정
pnpm i -D eslint-plugin-storybook
// eslint.config.js
export default tseslint.config(
...
...storybook.configs['flat/recommended'],
defaultCodeStyle
)
8-1. prettier 설치
pnpm i prettier -D
8-2. prettier 설정
root경로 prettier.config.mjs 생성
// prettier.config.mjs
/** @type {import('prettier').Config} */
export default {
endOfLine: 'lf',
semi: false,
singleQuote: true,
tabWidth: 2,
trailingComma: 'none',
// import sort[s]
plugins: ['@ianvs/prettier-plugin-sort-imports'],
importOrder: [
'^react',
'',
'<BUILTIN_MODULES>',
'<THIRD_PARTY_MODULES>',
'',
'.css$',
'.scss$',
'^[.]'
],
importOrderParserPlugins: ['typescript', 'jsx', 'decorators-legacy']
// import sort[e]
}
8-3. prettier 커맨드라인
// package.json
"script": {
...
"prettier": "prettier --write \"**/*.{ts,tsx,cjs,mjs,json,html,scss,css,js,jsx}\" --cache --config prettier.config.mjs",
}
8-4. IDE 설정
intelliJ, vscode에서 prettier설정과, eslint 설정 모두 자동 적용으로 설정
9-1. husky 설치
pnpm add --save-dev husky
pnpm exec husky init
이후 .husky의 pre-commit 파일을 git 변경사항에 올려야함
9-2. husky의 타입체크를 위한 커맨드라인 설정
// package.json
"script": {
...
"type:check": "tsc --skipLibCheck --noEmit -p tsconfig.app.json",
}
9-3. run-all 설치
husky의 순차적 커맨드라인 실행에 있어서 윈도우는 &&연산자를 읽지 못해서, run-all 이라는 의존성 설치 필요
pnpm install npm-run-all --save-dev
이후 실행 명령어를 run-s로 구동
9-4. husky 커맨드라인 설정
// package.json
"script": {
...
"format": "run-s type:check prettier lint"
}
9-5. husky pre-commit 수정
// pre-commit
pnpm format
9-6. (참고) 커맨드라인에서의 .env적용
"dev": "dotenv -e env/.env.local -- vite",
"storybook": "dotenv -e env/.env.storybook -- storybook dev -p 6006"
// 빌드 명령어도 env파일이 필요할경우 위처럼 작성