Skip to content

Step 1 — 스캐폴딩

상태: ✅ 완료 (2026-04-14) 커밋: af2d700목표: 빌드/개발/타입/Git 흐름이 모두 동작하는 최소 프로젝트 뼈대 구축


목차

  1. 결정 사항
  2. 생성된 파일 구조
  3. 파일별 설명
  4. 검증 결과
  5. 알려진 제한 / TODO

결정 사항

초기 설정에서 내린 기술적 선택과 그 근거.

결정채택 값근거
빌드 도구tsup제로 설정, TS 네이티브, Vite를 라이브러리용으로 쓰면 설정이 길어짐
개발 서버Vite데모 HMR에 최적. 라이브러리 빌드는 tsup, 데모 서빙은 Vite로 역할 분리
언어TypeScript strictProseMirror 스키마 타이핑, 사용자 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: trueimport "./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);

현재 동작:

  • 단순 contentEditable div
  • Markdown 파싱/직렬화 없음 — Step 3에서 ProseMirror로 대체 예정

src/styles.css

최소 스타일만. .newtil-editor, .newtil-editor__content BEM 훅 기초.

index.html + demo.ts

  • Vite가 루트 index.html 을 진입점으로 자동 인식
  • demo.tssrc/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 commitaf2d700
git push origin mainnewlecture-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.markdown getter + 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 / .themeStep 5
4. all: revert 격리 모드 (옵션)data-isolation="strict" 속성Step 6 이후
5. Shadow DOM 옵트인 (옵션)shadow 속성Step 6 이후

Step 1 시점 상태:

  • 계층 1 (BEM) 은 src/styles.csssrc/index.ts 에 이미 적용됨
  • 계층 2~5 는 Step 5 및 Step 6 이후에 순차 도입

관련 문서: design.md §9.5, roadmap.md Step 5