Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
795505e
feat: add line & format & remove unused files
geonhwiii Aug 9, 2025
0b98fb4
feat: 공통컴포넌트 정리
geonhwiii Aug 9, 2025
ddb49ec
feat: dialog & select ui 분리
geonhwiii Aug 9, 2025
def18b3
feat: table ui 분리
geonhwiii Aug 9, 2025
7f0ac84
feat: widgets 디렉토리 컨벤션 통일
geonhwiii Aug 14, 2025
19a1cc5
feat: entities & features 폴더링 설계
geonhwiii Aug 14, 2025
b514b2d
feat: add nested features folder
geonhwiii Aug 14, 2025
379572b
feat: entities 모델 및 api 정의
geonhwiii Aug 14, 2025
d8ef7d2
feat: features내 모델 정의
geonhwiii Aug 14, 2025
40444d9
feat: 추가 훅 분리, post-table 컴포넌트 정의
geonhwiii Aug 14, 2025
a46cc01
feat: post 내 feature 정의
geonhwiii Aug 14, 2025
b0aca74
feat: comments & dialog 분리
geonhwiii Aug 14, 2025
513a185
feat: pages내 features 추가
geonhwiii Aug 14, 2025
636827a
feat: pages내 home 정의
geonhwiii Aug 14, 2025
38b423b
feat: home 폴더 정리
geonhwiii Aug 14, 2025
fe9f6da
feat: comment feature 분리
geonhwiii Aug 14, 2025
e8c1493
feat: pages 내 로직 분리
geonhwiii Aug 14, 2025
2b6d26d
feat: app 구조 변경
geonhwiii Aug 14, 2025
9fdc334
feat: add dialog host
geonhwiii Aug 14, 2025
f0549c1
feat: dialog 전역으로 이동
geonhwiii Aug 15, 2025
1cd7b14
feat: post detail dialog 분리
geonhwiii Aug 15, 2025
149d9ab
feat: add react-query
geonhwiii Aug 15, 2025
547ca1d
feat: 요청 훅 react-query 적용
geonhwiii Aug 15, 2025
47087f4
feat: dialog handler 분리
geonhwiii Aug 15, 2025
7c8b98b
feat: url 기본 파라미터 개선
geonhwiii Aug 15, 2025
5c89ba8
feat: add gh-pages
geonhwiii Aug 15, 2025
45b49c0
feat: add api client
geonhwiii Aug 15, 2025
a3cb6ae
fix: tsconfig
geonhwiii Aug 15, 2025
0addd50
feat: add author info
geonhwiii Aug 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
{
"semi": false,
"printWidth": 120,
"semi": true,
"singleQuote": true,
"jsxSingleQuote": true,
"tabWidth": 2,
"singleQuote": false,
"quoteProps": "consistent",
"trailingComma": "all",
"singleAttributePerLine": false
}
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always",
"printWidth": 100,
"endOfLine": "auto"
}
181 changes: 181 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
### FSD 리팩토링 TODO

- **목표**: `src/`를 FSD 아키텍처(Feature-Sliced Design)에 맞추어 `apps`, `shared`, `widgets`, `features`, `entities` 계층으로 재구성하고, 각 계층의 Public API를 `index.ts`로만 노출한다.

### 전역 원칙

- **Public API 규칙**: 외부 접근은 항상 각 슬라이스의 `index.ts`만 사용한다. 예) `@/features/add-cart`에서만 가져오고, `@/features/add-cart/ui/AddCart.tsx` 같은 딥 임포트 금지.
- **의존성 규칙**:
- `shared`: 누구나 의존 가능. 외부 의존 금지.
- `entities`: `shared`에만 의존 가능.
- `features`: `entities`, `shared`에 의존 가능. 서로 간 직접 의존 지양.
- `widgets`: `features`, `entities`, `shared`에 의존 가능. 비즈니스 로직 금지.
- `apps`: 상위 조립 레이어. 모든 하위 레이어에 의존 가능하나 반대 방향 금지.
- **코드 스타일**: 선언형 프로그래밍, Clean Code 지향, 의미 있는 네이밍, 얕은 중첩, 가드 절 적극 사용.
- **파일 노출**: 각 슬라이스 루트의 `index.ts`에서만 export. 하위 파일은 외부 노출 금지.
- **그룹핑 디렉토리 규칙**: `features` 내부 도메인 그룹은 괄호 표기 폴더로 작성한다. 예) `(post)`, `(comment)`, `(user)`. 외부 임포트는 항상 하위 feature의 `index.ts`를 통해서만 한다.

### 경로 별칭(plan)

- 단일 별칭 사용: `@/*` → `src/*`.
- TS: `tsconfig.json`의 `compilerOptions.paths`에 `"@/*": ["./src/*"]` 설정 추가/정합화.
- Vite: `vite.config.ts`의 `resolve.alias`에 `'@': path.resolve(__dirname, './src')` 설정 확인.
- ESLint: 딥 임포트 방지를 위한 제한 규칙 유지(예: `import/no-restricted-paths` or `eslint-plugin-boundaries`).

### 목표 폴더 구조(스캐폴딩)

```
src/
apps/
routes/
index.ts
pages/
home/
ui/
entities/
post/
model/
ui/
api/
lib/
index.ts
features/
(post)/
add-post/
model/
ui/
index.ts
edit-post/
model/
ui/
index.ts
delete-post/
model/
ui/
index.ts
list-posts/
model/
ui/
lib/
index.ts
filter-by-tag/
model/
ui/
index.ts
search-posts/
model/
ui/
index.ts
sort-posts/
model/
ui/
index.ts
paginate-posts/
model/
ui/
index.ts
view-post-detail/
model/
ui/
index.ts
(comment)/
add-comment/
model/
ui/
index.ts
edit-comment/
model/
ui/
index.ts
delete-comment/
model/
ui/
index.ts
like-comment/
model/
ui/
index.ts
list-comments/
model/
ui/
index.ts
(user)/
view-user-modal/
model/
ui/
index.ts
widgets/
header/
ui/
index.ts
footer/
ui/
index.ts
shared/
api/
config/
lib/
styles/
ui/
button/
button.tsx
index.ts
select/
select.tsx
index.ts
...
index.ts
```

### 마이그레이션 체크리스트(순서)

1. [x] FSD 글 숙지 및 용어 정리: slice, segment(`model`, `ui`, `lib`, `api`), public API, 계층 의존성 규칙.
2. [x] 경로 별칭 점검: Vite `@` → `src` 설정 확인, TS `paths`에 `@/*` → `src/*` 동기화.
3. [x] ESLint 규칙 추가
4. [ ] 디렉토리 스캐폴딩 생성: 위 구조대로 빈 폴더 및 `index.ts` 파일 틀 준비.
5. [ ] 기존 `shared/ui/*` 점검: 각 컴포넌트 폴더에 `index.ts` 존재 확인. 없으면 생성하고 필요한 export만 노출.
6. [ ] 기존 `widgets/header`, `widgets/footer` 정리: 각 위젯 루트에 `index.ts` 생성 후 `ui/*`만 노출. 현재 파일(`widgets/header/header.tsx`, `widgets/footer/footer.tsx`)은 `ui/` 하위로 이동.
7. [ ] `pages/` 구조 FSD화: 괄호 그룹으로 도메인 구분. 예) `src/pages/(post)/posts-manager`.
8. [ ] 라우트 어댑터 유지: `apps/routes/*`는 `pages`의 public index만 import.
9. [ ] 모든 임포트 경로를 별칭 + Public API로 변경: 딥 임포트 제거. 예) `@/shared/ui/select`(index를 통한 노출)만 허용, `@/shared/ui/select/select.tsx` 금지.
10. [ ] 빌드/실행 점검: 타입 에러 및 런타임 이슈 수정.
11. [ ] 린트/포맷 통과 확인: import 정리, 순환 참조 없는지 확인.

### 사용 예시(공개 API만 노출)

- `features/ui/index.ts`를 만들 경우, 내부 구현 파일은 `features/ui/add-cart.tsx` 등으로 두고 외부에는 `index.ts`로만 노출.

```ts
// features/ui/index.ts
export { AddCart } from './add-cart';
```

```ts
// features/ui/add-cart.tsx
export function AddCart() {
/* ... */
}
```

```ts
// apps/routes/ProductsPage.tsx
import { AddCart } from '@/features/ui'; // 내부 파일로의 직접 경로 사용 금지
```

### 자동화/검증

- ESLint: `import/no-restricted-paths` 또는 `eslint-plugin-boundaries`로 레이어 규칙과 딥 임포트 금지.
- depcruise(optional): 의존성 그래프 검증 및 순환 참조 탐지.
- ts-prune(optional): 미사용 export 정리.

### Done 정의

- 모든 공개 API가 `index.ts`를 통해서만 노출되고, 외부 딥 임포트가 없다.
- 레이어 의존성 규칙을 위반하는 임포트가 없다.
- 기존 기능은 동일하게 동작하며 빌드/테스트/린트가 모두 통과한다.

### 백로그(후순위)

- 슬라이스 생성 템플릿 추가(예: plop/hygen)로 `model/ui/lib/api/index.ts` 골격 자동 생성.
- 스토리북 도입 시 공개 API 기준으로만 스토리 노출.
- 라우트 스플리팅 및 코드 스플리팅 전략 수립.
130 changes: 130 additions & 0 deletions eslint.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import typescript from '@typescript-eslint/eslint-plugin';
import typescriptParser from '@typescript-eslint/parser';
import { defineConfig } from 'eslint/config';
import prettier from 'eslint-config-prettier';
import compat from 'eslint-plugin-compat';
import importPlugin from 'eslint-plugin-import';
import eslintPluginPrettier from 'eslint-plugin-prettier';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';

export default defineConfig([
{
ignores: ['**/node_modules/**', 'dist/**', '.eslintrc.cjs'],
},
{
files: ['**/*.{js,jsx,ts,tsx}'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
parser: typescriptParser,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
tsconfigRootDir: '.',
},
},
plugins: {
prettier: eslintPluginPrettier,
react,
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
'@typescript-eslint': typescript,
compat,
import: importPlugin,
},
settings: {
react: {
version: 'detect',
},
browsers: '> 0.5%, last 2 versions, not op_mini all, Firefox ESR, not dead',
},
rules: {
// 기존 규칙 유지
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],

// Prettier 통합 규칙
'comma-dangle': [
'error',
{
arrays: 'always-multiline',
objects: 'always-multiline',
imports: 'always-multiline',
exports: 'always-multiline',
functions: 'never',
},
],

// React 관련 규칙
'react/prop-types': 'off',
'react/react-in-jsx-scope': 'off',
'react-hooks/rules-of-hooks': 'error',

// TypeScript 관련 규칙
'@typescript-eslint/no-explicit-any': 'warn',

// 팀 컨벤션 - var 사용 금지
'no-var': 'error',
'@typescript-eslint/no-unused-vars': 'error',

// 팀 컨벤션 - 동등 연산자 (==, !=) 금지
eqeqeq: ['error', 'always', { null: 'ignore' }],

// 팀 컨벤션 - 얼리 리턴 권장
'consistent-return': 'error',
'no-else-return': ['error', { allowElseIf: false }],

// 팀 컨벤션 - 템플릿 리터럴 규칙
'prefer-template': 'error',

// 팀 컨벤션 - 상수는 대문자
camelcase: [
'error',
{
properties: 'never',
ignoreDestructuring: false,
ignoreImports: false,
ignoreGlobals: false,
allow: ['^[A-Z][A-Z0-9_]*$'],
},
],

// 팀 컨벤션 - 구조분해할당 권장
'prefer-destructuring': [
'error',
{
array: true,
object: true,
},
{
enforceForRenamedProperties: false,
},
],

// 기본 코드 품질 규칙
'prefer-const': 'error',
'object-shorthand': 'error',
'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }],
'no-console': ['warn', { allow: ['warn', 'error'] }],
'no-debugger': 'warn',
'no-undef': 'off',

// import 순서 규칙
'import/order': [
'error',
{
groups: ['builtin', 'external', ['parent', 'sibling'], 'index'],
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
'newlines-between': 'always',
},
],
'import/extensions': 'off',
},
},
prettier,
]);
28 changes: 0 additions & 28 deletions eslint.config.js

This file was deleted.

Loading