Hook
파일 하나가 346KB, 7,600줄이 되었습니다. HTML, CSS, JavaScript가 전부 한 파일 안에 있습니다. Git diff는 읽을 수 없고, AI 에이전트는 매 편집마다 이 거대한 파일을 통째로 읽어야 하고, 캐싱은 당연히 효율이 없습니다.
AI 디자인 도구는 기본적으로 모놀리식 단일 파일을 생성하는 경향이 있습니다. 이 글에서는 빌드 도구를 도입하지 않고 4-Phase 점진적 분할로 이 문제를 해결한 과정을 공유합니다.
TL;DR
- AI 도구의 모놀리식 경향이 기술 부채의 원인 — 단일 파일은 머지 충돌, 리뷰 어려움, 캐싱 비효율, AI 컨텍스트 폭발을 유발합니다
- 4-Phase 점진적 분할로 해결: 정적 데이터 → CSS 분리 → core 로직 → screens 분할
- 빌드 도구는 도입하지 않습니다 — 순수 파일 분할만으로 AI 컨텍스트를 절약합니다
- 매 Phase마다 회귀 검증 스크립트로 안전하게 마이그레이션합니다
Background: 왜 단일 파일이 문제인가
모놀리식 HTML이 만드는 4가지 부채
| 부채 | 설명 | 영향 |
|---|---|---|
| 머지 충돌 | 여러 변경이 같은 파일에 집중 | 병합 지연, 수동 해결 |
| 리뷰 어려움 | PR diff가 수백 줄 | 코드 리뷰 품질 저하 |
| 캐싱 비효율 | 전체 파일을 재다운로드 | 성능 저하 |
| AI 컨텍스트 폭발 | AI가 전체 파일을 읽어야 함 | 응답 품질 저하, 토큰 낭비 |
AI 컨텍스트 문제가 가장 심각합니다
단일 파일 (Before):
AI가 "운동 저장 버튼 수정" 요청을 받음
→ 346KB / 7,600줄 파일 전체를 읽음
→ 컨텍스트 대부분을 소비
→ 정작 수정할 부분의 맥락이 흐려짐
→ 잘못된 수정 발생
분할 후 (After):
AI가 같은 요청을 받음
→ workout-form.js (200줄)만 읽음
→ 전체 컨텍스트를 이 파일에 집중
→ 정확한 수정, 빠른 응답
Solution: 4-Phase 점진적 분할
핵심 원칙
빌드 도구를 도입하지 않습니다. 순수 파일 분할만으로 해결합니다. 이는 “단일 파일 정적 PWA”라는 원래 아키텍처 결정의 정신을 유지하면서, 발생한 부채만 해결하는 것입니다.
Phase별 마이그레이션
Phase 1: 정적 데이터 분할 ✅
│ 운동 정의, 설정값, 상수를 별도 JS 파일로 추출
│
Phase 2: CSS 분리 ✅
│ 인라인 <style>을 별도 .css 파일로 추출
│
Phase 3: core 로직 분할 ✅
│ 비즈니스 로직(오버로드 계산, 상태 관리)을 별도 모듈로
│
Phase 4: screens 분할 (진행 중)
각 화면(루틴, 운동, 통계, 설정)을 독립 파일로
4가지 엄격 규칙
분할 과정에서 지켜야 할 규칙입니다:
| 규칙 | 금지 | 유일한 예외 |
|---|---|---|
| 인라인 CSS | <style>, style="" | 크리티컬 CSS (압축 ≤14KB) |
| 인라인 JS | <script> 코드 바디 | 환경 변수 주입 (window.__ENV__) |
| 인라인 이벤트 | onclick, onload | 없음 — addEventListener 사용 |
| CSS-in-JS | 인라인 style 객체 | 없음 — CSS Modules 사용 |
관심사 분리 구조
┌─────────────┐ 구조 & 시맨틱 ┌──────────────┐
│ .html 파일 │ │ 마크업만 │
└─────────────┘ └──────────────┘
┌─────────────┐ 표현 & 레이아웃 ┌──────────────┐
│ .css 파일 │ │ 1컴포넌트당 │
└─────────────┘ │ 1 module.css │
└─────────────┘ └──────────────┘
┌─────────────┐ 동작 & 상호작용 ┌──────────────┐
│ .js 파일 │ │ 기능별 모듈 │
└─────────────┘ └──────────────┘
네이밍 컨벤션
| 대상 | 규칙 | 예시 |
|---|---|---|
| CSS 클래스 | BEM | .flow-card, .flow-card__header, .flow-card--active |
| JS 모듈 | kebab-case | flow-builder.js |
| 컴포넌트 | PascalCase | FlowBuilder |
| 변수 | camelCase | calculateProgressiveOverload |
| 타입/인터페이스 | PascalCase | FlowStep |
| 상수 | UPPER_SNAKE_CASE | MAX_SETS |
점진적 마이그레이션 정책 (기존 코드베이스)
새 프로젝트라면 처음부터 규칙을 적용하면 됩니다. 하지만 이미 모놀리식인 코드베이스에는 점진적 접근이 필요합니다:
- 패턴 동결 — 채택일 이후 새 인라인 코드 금지
- 점진적 추출 — 다른 이유로 파일 수정 시 해당 부분만 추출
- “빅뱅” 리팩터 금지 — 한 번에 전체 재작성은 부채를 숨길 뿐
- 부채 추적 — 미추출 부분에 주석 마킹
<!-- FIXME: extract inline styles -->
<div style="display:flex;gap:8px">...</div>
회귀 검증
매 Phase 완료 후 검증 스크립트(tools/check.js)를 실행합니다:
검증 스크립트 9개 카테고리
| # | 카테고리 | 검증 내용 |
|---|---|---|
| 1 | 파일 크기 | 개별 파일 400KB 하드 실패 |
| 2 | manifest 스키마 | 필드 누락/타입 오류 |
| 3 | HTML 구조 | 필수 요소 존재 여부 |
| 4 | 데드 버튼 | 빈 onclick/href=”#” 정적 탐지 |
| 5 | 목 데이터 | mock/dummy/fake/sample 변수 탐지 |
| 6 | SW precache | 모든 자산이 캐시 대상에 포함 |
| 7 | 회귀 검증 | 기능별 핵심 데이터 구조 보존 |
| 8 | i18n 구조 | 번역 파일 구조 정합성 |
| 9 | 사용자 데이터 | LocalStorage 키 보존 |
이 스크립트는 의존성 없는 Node 표준 모듈만 사용합니다.
npm install없이 CI/로컬/hook 3곳에서 동일하게 실행됩니다.
Result: 분할 전후 비교
| 지표 | Before (단일 파일) | After (분할) |
|---|---|---|
| 최대 파일 크기 | 346KB / 7,600줄 | 개별 파일 평균 300줄 |
| AI 컨텍스트 소비 | 전체 파일 읽기 | 관련 파일만 (평균 80% 절약) |
| Git diff 가독성 | 수백 줄 변경 | 기능별로 분산 |
| 캐싱 | 전체 재다운로드 | 변경된 파일만 (immutable 캐싱) |
| 머지 충돌 | 빈번 | 드묾 |
Takeaway
AI 도구의 모놀리식 경향은 단일 파일 선택과 다릅니다 — “단일 파일 PWA”는 의도적 아키텍처 결정일 수 있지만, AI 도구가 만드는 모놀리식은 비의도적 부채입니다. 의도적 단일 파일(오프라인 보장, 호스팅 최소화)과 비의도적 모놀리식(머지 충돌, 컨텍스트 폭발)을 구분해야 합니다. 빌드 도구 없이도 파일 분할은 가능하며, 분할 후에도 원래 아키텍처의 이점(오프라인, 무료 호스팅)을 유지할 수 있습니다
점진적 마이그레이션의 핵심은 “빅뱅 금지”입니다 — 한 번에 전체를 재작성하면 반드시 무언가 망가집니다. 대신 “다른 이유로 이 파일을 수정할 때, 그 부분만 추출한다”는 전략을 취합니다. 매 Phase마다 회귀 검증을 실행하여 기능이 무너지지 않았음을 확인합니다. 이 방식은 느려 보이지만, 실제로는 재작업이 없어서 더 빠릅니다
파일 분할의 가장 큰 수혜자는 AI 에이전트입니다 — 파일이 작아지면 AI가 전체 파일을 읽고도 컨텍스트에 여유가 남습니다. 이 여유가 수정 품질로 직결됩니다. “AI 컨텍스트 절약”을 파일 분할의 1순위 동기로 삼으세요. 346KB 파일을 매번 읽던 AI와 200줄 파일만 읽는 AI의 출력 품질 차이는 극적입니다
| ← 이전 | 시리즈: AI 주도 개발 거버넌스 | 다음 → |
|---|---|---|
| (2) 4단계 디자인 게이트 | (3) 모놀리식 HTML 분할 | (4) gettext 원문 키 |
게이트웨이 On-promise 제품 팀에서 시스템 모니터링 및 관리를 쉽게 다가갈 수 있도록 하기 위한 업무를 하고 있습니다.
Contact: lhjnano@gmail.com