Single-Spa Application 구성
기업 분석 마이크로 프론트엔드는 모듈화된 마이크로 프론트엔드 아키텍처를 기반으로, 사용자가 스스로 기업이나 산업을 분석하고 정리하며, 이를 기록으로 남길 수 있도록 돕습니다.
이를 통해 면접이나 취업 준비 과정에서 보다 체계적으로 정보를 정리하고 활용할 수 있으며, 개인적인 인사이트를 쌓아가는 데에도 유용합니다.
기능 | 설명 |
---|---|
기업 분석 페이지 관리 | 새로운 분석 페이지를 생성, 수정, 삭제할 수 있으며, 목록에서 검색 및 페이지네이션 기능을 지원합니다. |
강력한 폼 유효성 검사 | VeeValidate와 Zod를 활용하여 입력 폼의 유효성을 검증하고, 사용자 입력 오류를 방지합니다. |
직관적인 리치 텍스트 에디터 | Editor.js를 사용하여 블록 기반 편집이 가능한 리치 텍스트 입력 기능을 제공합니다. |
최적화된 데이터 관리 | Vue Query를 활용한 쿼리 캐싱을 통해 API 요청을 최적화하고, 빠른 데이터 로딩을 지원합니다. |
상태 관리 | Pinia를 활용한 프록시 기반 상태 관리로 효율적이고 직관적인 상태 관리가 가능합니다. |
폴더 구조 최적화 | Feature-Sliced Design(FSD) 방식의 폴더 구조 설계를 적용하여, 명시적인 비즈니스 로직과 도메인 스코프 덕분에 필요한 로직을 쉽게 찾을 수 있습니다. |
마이크로 프론트엔드 아키텍처 적용 | Module Federation을 활용한 스타일 가이드 통합 및 Single-SPA 기반의 마이크로 프론트엔드 빌드를 지원합니다. |
graph TD
direction TB
App --> report-page --> side-feature & report-feature
subgraph side-feature
direction TB
summary-store[(pinia store)]
load-summaries -.-> summary-store -.-> drawer-layout & display-summaries & search-summaries
side --> load-summaries & drawer-layout & display-summaries
drawer-layout --> search-summaries
display-summaries --> add-report & section-tab
end
subgraph report-feature
report-store[(pinia store)]
remove-report & load-report -.-> report-store -.-> navigation & report-form & save-report
report-store -.-> form-layout
report --> navigation & load-report & display-report
display-report --> form-layout
form-layout --> report-form & actions-report
report-form --> section-field --> field-content & field-title
actions-report --> save-report & remove-report
end
click App "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/app/App.vue"
click report-page "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/pages/index.vue"
click side "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/side/index.vue"
click load-summaries "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/report/ui/load-report.vue"
click summary-store "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/side/store/summary.ts"
click drawer-layout "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/side/ui/drawer-layout.vue"
click display-summaries "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/side/ui/display-summaries.vue"
click search-summaries "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/side/ui/search-summaries.vue"
click add-report "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/side/ui/add-report.vue"
click section-tab "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/side/ui/section-tab.vue"
click report "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/report/index.vue"
click load-report "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/report/ui/load-report.vue"
click report-store "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/report/store/report.ts"
click navigation "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/report/ui/navigation.vue"
click report-form "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/report/ui/report-form.vue"
click save-report "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/report/ui/save-report.vue"
click form-layout "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/report/ui/form-layout.vue"
click display-report "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/report/ui/display-report.vue"
click actions-report "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/report/ui/actions-report.vue"
click section-field "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/report/ui/section-field.vue"
click field-content "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/report/ui/field-content.vue"
click field-title "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/report/ui/field-title.vue"
click remove-report "https://github.com/Daily1Hour/PickMe-Report-Application/blob/main/src/features/report/ui/remove-report.vue"
stateDiagram-v2
state report {
state "load" as load {
state load/if_exist_id <<choice>>
state load/if_fetch <<choice>>
state load/join_state <<join>>
load/Prepare: Prepare
load/Fetching: Fetching
load/Empty: Empty
load/Loaded: Loaded
[*] --> load/Prepare: 페이지 진입 시
load/Prepare --> load/if_exist_id: 경로 확인<br>useRoute()
load/if_exist_id --> load/Empty: new
load/if_exist_id --> load/Fetching: 경로 존재
load/Fetching --> load/if_fetch: 데이터 패칭<br>useQuery()
load/if_fetch --> load/Empty: fetch 실패
load/if_fetch --> load/Loaded: fetch 성공
load/Empty --> load/join_state
load/Loaded --> load/join_state
}
state report/is_valid <<choice>>
report/Ready: Ready
report/Editing: Editing
report/Edited: Edited
report/Validating: Validating
report/Submitting: Submitting
report/Ready --> valid/SchemaInit: 폼모델 & 스키마 구성
report/Ready --> report/Editing: 사용자 입력 시작
report/Editing --> report/Validating: 유효성 검사 트리거
report/Validating --> valid/WatchFields: 필드 유효성 검사 시작
report/is_valid --> report/Editing: 실패
report/is_valid --> report/Edited: 성공
report/Edited --> report/Submitting: 저장/삭제
report/Submitting --> [*]: 페이지 이동
}
state "Pinia Store" as store {
store/Raw: Raw
store/Reactive: Reactive
store/Schema: Schema
[*] --> store/Raw: store 정의
store/Raw --> store/Reactive: proxy
store/Reactive --> store/Raw: proxy
store/Reactive --> store/Schema: zod 스키마 변환
}
note left of store
Proxy 방식 중앙상태저장소
상태 객체를 직접 관찰하고 변경 사항을 자동으로 감지
end note
state "vee-validate" as vlid {
valid/SchemaInit: SchemaInit
valid/FormInit: FormInit
valid/WatchFields: WatchFields
valid/ValidateField: ValidateField
valid/ErrorUpdate: ErrorUpdate
valid/AllValid: AllValid
valid/SchemaInit --> valid/FormInit: formData 초기화
valid/FormInit --> valid/WatchFields: v-model 감지
valid/WatchFields --> valid/ValidateField: 필드 검사
valid/ValidateField --> valid/ErrorUpdate: errorMessage 반영
valid/ErrorUpdate --> valid/WatchFields: 입력 대기
valid/WatchFields --> valid/AllValid: 전체 유효성 통과
}
load/join_state --> store: Setter
store --> report/Ready: Getter
valid/AllValid --> report/is_valid: report로 결과 전달
graph LR
subgraph CD[🚀 CD 영역]
direction LR
Tag[태그 푸시]
Tag --> DeployGH[gh-pages에 배포] --> |자동 워크플로 실행|pages-build-deployment[GitHub Pages 배포 완료]
Tag --> DeployAWS[Amazon S3에 배포] --> |콘텐츠 서빙|CloudFront[Amazon CloudFront]
end
Build & Build_Single_SPA -.-> |📦 아티팩트|Tag
subgraph CI[🧪 CI 영역]
direction LR
Push[브랜치 푸시] --> Lint[린트] --> |🟢 통과|Test[테스트]
Test --> |🟢 통과|Build[빌드] & Build_Single_SPA[빌드 For single-spa] --> |🟢 통과|Review[리뷰]
Review -->|✔️ 승인|Merge[머지]
end
click Build "https://github.com/Daily1Hour/PickMe-Report-Application/actions/workflows/vite-build.yml"
click Build_Single_SPA "https://github.com/Daily1Hour/PickMe-Report-Application/actions/workflows/vite-build.yml"
click Review "https://github.com/Daily1Hour/PickMe-Report-Application/actions/workflows/auto-assign.yml"
click DeployGH "https://github.com/Daily1Hour/PickMe-Report-Application/actions/workflows/deploy-gh-pages.yml"
click pages-build-deployment "https://github.com/Daily1Hour/PickMe-Report-Application/actions/workflows/pages/pages-build-deployment"
click DeployAWS "https://github.com/Daily1Hour/PickMe-Report-Application/actions/workflows/deploy-aws-s3.yml"
열기
PickMe-Report-Application
├─ .github
│ └─ workflows # 깃헙 액션 워크플로어 프로세스
│ ├─ deploy-aws-s3.yml # AWS S3로 페이지로 배포
│ ├─ deploy-gh-pages.yml # 깃헙 페이지로 배포
│ └─ vite-build.yml # Vite로 빌드
├─ index.html
├─ src
│ ├─ app
│ │ ├─ App.vue
│ │ ├─ application.ts # single-spa 애플리케이션 진입점
│ │ ├─ main.ts # 프로바이더 스택
│ │ └─ router.ts # 라우터
│ ├─ entities # 비즈니스 엔터티 레이어
│ │ ├─ report
│ │ │ └─ model
│ │ │ ├─ index.ts
│ │ │ ├─ Report.ts # 인터페이스
│ │ │ ├─ CompanyReport.ts
│ │ │ └─ IndustryReport.ts
│ │ └─ summary
│ │ └─ model
│ │ ├─ index.ts
│ │ └─ Summary.ts
│ ├─ features # 기능 구현체 레이어
│ │ ├─ report
│ │ │ ├─ index.vue
│ │ │ ├─ api
│ │ │ │ ├─ index.ts
│ │ │ │ ├─ dto.ts # 데이터전송객체
│ │ │ │ ├─ getHealthy.ts # 헬스 체크
│ │ │ │ ├─ getReport.ts # 리포트 가져오기
│ │ │ │ ├─ setReport.ts # 리포트 내보내기
│ │ │ │ └─ deleteReport.ts # 레포트 삭제
│ │ │ ├─ model # 폼 스키마
│ │ │ │ ├─ index.ts
│ │ │ │ ├─ CompanySchema.ts
│ │ │ │ └─ IndustrySchema.ts
│ │ │ ├─ service # 엔터티 ↔ dto/form 헬퍼 메서드
│ │ │ │ ├─ index.ts
│ │ │ │ ├─ map_dto_to_report.ts
│ │ │ │ ├─ map_form_to_report.ts
│ │ │ │ ├─ map_report_to_dto.ts
│ │ │ │ └─ map_report_to_form.ts
│ │ │ ├─ store # 중앙상태저장소
│ │ │ │ ├─ index.ts
│ │ │ │ └─ report.ts
│ │ │ └─ ui
│ │ │ ├─ index.ts
│ │ │ ├─ load-report.vue # 리포트 데이터 로딩
│ │ │ ├─ display-report.vue # 리포트 뷰 컴포넌트
│ │ │ │ └─ display-report-skeleton.vue
│ │ │ ├─ form-layout.vue # 리포트 뷰 폼 레이아웃
│ │ │ ├─ report-form.vue # 리포트 뷰 폼 컨텐츠
│ │ │ ├─ section-field.vue # 리포트 뷰 필드 레이아웃
│ │ │ ├─ field-content.vue # 리포트 컨텐츠 필드
│ │ │ │ └─ field-content-skeleton.vue
│ │ │ ├─ field-title.vue # 리포트 타이틀 필드
│ │ │ │ └─ field-title-skeleton.vue
│ │ │ ├─ actions-report.vue # 삭제&저장 액션 버튼
│ │ │ │ └─ actions-report-skeleton.vue
│ │ │ ├─ save-report.vue # 리포트 저장 버튼
│ │ │ ├─ remove-report.vue # 리포트 삭제 버튼
│ │ │ └─ navigation.vue # 페이지 앵커 컴포넌트
│ │ │ └─ navigation-skeleton.vue
│ │ └─ side
│ │ ├─ index.vue
│ │ ├─ api
│ │ │ ├─ index.ts
│ │ │ ├─ dto.ts
│ │ │ └─ getSummaries.ts
│ │ ├─ service
│ │ │ ├─ index.ts
│ │ │ └─ mapper.ts
│ │ ├─ store
│ │ │ ├─ index.ts
│ │ │ └─ summary.ts
│ │ └─ ui
│ │ ├─ index.ts
│ │ ├─ load-summaries.vue # 요약 데이터 로딩
│ │ ├─ drawer-layout.vue # 서랍형 레이아웃
│ │ ├─ search-summaries.vue # 요약 검색
│ │ ├─ display-summaries.vue # 요약 뷰 컴포넌트
│ │ │ └─ display-summaries-skeleton.vue
│ │ ├─ add-report.vue # 리포트 생성 페이지 라우팅
│ │ │ └─ add-report-skeleton.vue
│ │ └─ section-tab.vue # 탭 컴포넌트
│ │ └─ section-tab-skeleton.vue
│ ├─ pages # 페이지 레이어
│ │ └─ index.vue
│ ├─ shared # 공유 레이어
│ │ ├─ api
│ │ │ ├─ index.ts
│ │ │ ├─ client.ts # Axios 인스턴스
│ │ │ ├─ ServerError.ts # 서버 에러 상태 관리
│ │ │ └─ tokens.ts # 토큰 3종종
│ │ ├─ hook # 커스텀 훅
│ │ │ ├─ index.ts
│ │ │ └─ usePagination.ts # 페이지네이션 훅
│ │ ├─ model # 열거형 객체
│ │ │ ├─ index.ts
│ │ │ ├─ Category.ts
│ │ │ ├─ QueryKey.ts
│ │ │ └─ RouteName.ts
│ │ ├─ trans # 다국어 처리
│ │ │ └─ ko.ts
│ │ ├─ type
│ │ │ └─ QSkeletonAnimation.ts
│ │ └─ ui
│ │ ├─ index.ts
│ │ ├─ pagination-skeleton.vue
│ │ └─ skeleton-transition.vue
│ └─ widgets
│ └─ editorjs.vue # editor.js의 Vue3 컴포넌트 래퍼
├─ package.json # 의존성 설정
│ ├─ .prettierrc # 포맷터 설정
│ ├─ eslint.config.js # 린트 설정
│ └─ steiger.config.ts # FSD 린트 설정
└─ vite.config.ts # Vite 설정 파일
├─ shims-vue.d.ts # *.vue 타입 정의
├─ styleguide-types.d.ts # 스타일 가이드의 타입
└─ vite-env.d.ts # 환경변수 타입 정의
$ npm install
$ npm run dev
$ npm install
$ npm run build:single-spa
$ npm install
$ npm run start