Step 1 — 스캐폴딩
상태: ✅ 완료 (2026-04-14) 커밋: af2d700목표: 빌드/개발/타입/Git 흐름이 모두 동작하는 최소 프로젝트 뼈대 구축
목차
결정 사항
초기 설정에서 내린 기술적 선택과 그 근거.
| 결정 | 채택 값 | 근거 |
|---|---|---|
| 빌드 도구 | tsup | 제로 설정, TS 네이티브, Vite를 라이브러리용으로 쓰면 설정이 길어짐 |
| 개발 서버 | Vite | 데모 HMR에 최적. 라이브러리 빌드는 tsup, 데모 서빙은 Vite로 역할 분리 |
| 언어 | TypeScript strict | ProseMirror 스키마 타이핑, 사용자 IDE 자동완성 필수 |
| CSS 전달 방식 | JS에 자동 주입 (injectStyle: true) | import "@newtil/editor" 한 번으로 스타일까지 적용 — Web Component UX 간소화 |
| 패키지 포맷 | ESM + CJS + .d.ts | 모던 번들러(ESM) + Node 호환(CJS) + 타입 추론 |
| UMD 제외 | — | CDN 사용자는 jsdelivr가 ESM을 자동 변환. UMD 복잡도 불필요 |
| Shadow DOM | 미사용 예정 | IME/Selection 안정성 (design.md §9.5 참조) |
| 모듈 해석 | moduleResolution: "bundler" | TS 5.x 권장, 현대 번들러 호환 |
| 최초 버전 | 0.1.0 | 개발 중임을 명시, major bump 여유 확보 |
선택하지 않은 것들
- Rollup 직접 설정: tsup가 내부적으로 Rollup을 쓰지만 설정 없이 충분
- Parcel: 커뮤니티 규모/TS 지원 면에서 tsup 대비 약함
- Bun 빌드: 생태계 준비 덜 됨, 사용자 환경 호환성 우려
- Storybook: 이 단계에서는 오버엔지니어링. 단일
index.html데모로 충분
생성된 파일 구조
newtil-editor/
├── .gitignore
├── LICENSE # MIT
├── README.md # 사용자 대상 소개
├── package.json # @newtil/editor@0.1.0
├── package-lock.json # (gitignore 하지 않음 — 재현성)
├── tsconfig.json # strict TS
├── tsup.config.ts # 라이브러리 빌드
├── index.html # Vite 데모 진입점
├── demo.ts # 데모 스크립트
├── src/
│ ├── index.ts # 라이브러리 진입점 + Web Component 등록
│ └── styles.css # 기본 스타일 (빌드 시 JS에 주입)
├── docs/ # 이 문서 폴더
└── dist/ # 빌드 산출물 (gitignored)
├── index.js # ESM 번들
├── index.cjs # CJS 번들
├── index.d.ts # 타입 정의
└── *.map # 소스맵파일별 설명
package.json
핵심 필드:
json
{
"name": "@newtil/editor",
"version": "0.1.0",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"publishConfig": { "access": "public" },
"files": ["dist", "README.md", "LICENSE"]
}type: "module"— ESM 우선. CJS는.cjs확장자로 명시publishConfig.access: "public"— 스코프 패키지의 기본값은 private이라 필수files— 배포 시dist/,README.md,LICENSE만 포함.src/,docs/, 테스트 등 제외
tsup.config.ts
ts
{
entry: ["src/index.ts"],
format: ["esm", "cjs"],
dts: true,
sourcemap: true,
clean: true,
treeshake: true,
target: "es2020",
injectStyle: true,
outExtension({ format }) {
return { js: format === "cjs" ? ".cjs" : ".js" };
},
}injectStyle: true—import "./styles.css"를 런타임에<style>태그로 주입outExtension— ESM은.js, CJS는.cjs로 확장자 분리treeshake— 사용되지 않는 export 제거 (번들 크기 최소화)
tsconfig.json
중요 옵션:
"strict": true— 모든 strict 플래그 활성화"moduleResolution": "bundler"— TS 5 권장, 현대 번들러와 일치"target": "ES2020"— 크롬 94+, Safari 15+ 지원. 더 보수적인 타깃은 번들 크기 증가
src/index.ts — 현재 Web Component
ts
export class NewtilEditor extends HTMLElement {
private contentEl: HTMLDivElement | null = null;
static get observedAttributes(): string[] { return ["value"]; }
connectedCallback(): void { this.render(); }
attributeChangedCallback(name: string): void {
if (name === "value") this.syncValue();
}
get value(): string { return this.contentEl?.textContent ?? ""; }
set value(v: string) { this.setAttribute("value", v); }
private render(): void { /* contentEditable div 생성 */ }
private syncValue(): void { /* attribute ↔ textContent 동기화 */ }
}
customElements.define("newtil-editor", NewtilEditor);현재 동작:
- 단순
contentEditablediv - Markdown 파싱/직렬화 없음 — Step 3에서 ProseMirror로 대체 예정
src/styles.css
최소 스타일만. .newtil-editor, .newtil-editor__content BEM 훅 기초.
index.html + demo.ts
- Vite가 루트
index.html을 진입점으로 자동 인식 demo.ts는src/index.ts를 import 하여 Custom Element 등록- 입력 시 하단에 실시간 값 표시 (동작 확인용)
검증 결과
| 명령 | 결과 |
|---|---|
npm install | ✅ 54 패키지 설치, 정상 |
npm run typecheck | ✅ 오류 0건 |
npm run build | ✅ ESM 2.3KB / CJS 2.3KB / DTS 생성 |
npm run dev | ✅ Vite 5173 포트, HTTP 200 |
git init + git commit | ✅ af2d700 |
git push origin main | ✅ newlecture-corp/newtil-editor 원격 반영 |
알려진 제한 / TODO
다음 Step에서 해결할 것들:
| 제한 | 해결 예정 Step |
|---|---|
| Markdown 파싱 없음 | Step 3 (ProseMirror 통합) |
change 이벤트 미발행 | Step 2 또는 Step 3 |
| 툴바 없음 | Step 4 |
| 단축키/InputRules 없음 | Step 4 |
@newtil/css 테마 없음 | Step 5 |
| React/Vue 래퍼 없음 | Step 6 |
설계 변경 이력
2026-04-14 — 편집 모델 확정 (WYSIWYG 전용)
배경: source 모드 토글을 지원할지 검토 중 round-trip 정규화 문제(강조 기호 통일, 제목 스타일 통일 등)가 발견됨.
결정:
- source/WYSIWYG 토글은 제공하지 않음
- Markdown은 읽기 전용으로만 노출 (
editor.markdowngetter +change이벤트) - 과거 Step 2 (API 보강) 을 Step 3 (ProseMirror 통합) 에 병합 → 새 번호 체계로 Step 2 가 됨 (기존 Step 3 → Step 2, 기존 Step 4 → Step 3, ...)
mode속성은 제거. 대신 Step 이후 추가 기능으로preview="markdown"(읽기 전용 분할 뷰) 도입 검토
영향:
- 복잡도 감소: CodeMirror 통합 불필요, 양방향 동기화 로직 불필요
- 사용자 가치: MVP 수준의 저장/전송/미리보기 시나리오는 읽기 전용 API로 충분히 커버
- 추후 사용자 요구가 강하면 별도 패키지(
@newtil/editor-source-mode)로 도입 검토
관련 문서: design.md §12, roadmap.md
2026-04-14 — CSS 격리 전략 확장
배경: Light DOM 사용 시 호스트 페이지 CSS 영향이 어디까지 미치는지 검토.
결정: 기존 "Light DOM + BEM" 1단 방어에서 5계층 다층 전략으로 설계 확장.
| 계층 | 내용 | 적용 Step |
|---|---|---|
| 1. BEM 네임스페이스 | .newtil-editor__* 접두 | Step 1 (완료) |
| 2. 기본 스타일 명시 재정의 | 내부 h1~h6, ul, code 등 기본값 고정 | Step 5 |
3. @layer 래핑 | @layer newtil-editor.base / .theme | Step 5 |
4. all: revert 격리 모드 (옵션) | data-isolation="strict" 속성 | Step 6 이후 |
| 5. Shadow DOM 옵트인 (옵션) | shadow 속성 | Step 6 이후 |
Step 1 시점 상태:
- 계층 1 (BEM) 은
src/styles.css와src/index.ts에 이미 적용됨 - 계층 2~5 는 Step 5 및 Step 6 이후에 순차 도입
관련 문서: design.md §9.5, roadmap.md Step 5