Mac의 기본 텍스트 에디터인 TextEdit을 떠올려보자. 그걸 마크다운에만 바친 4MB짜리 네이티브 앱이 MarkEdit이다. 그런데 신기한 점은 — 에디터 코어가 Swift가 아니라 CodeMirror 6, 즉 웹 기술이라는 사실이다. 왜 그렇게 만들었는지, 코드는 어떻게 엮여 있는지 한 번 풀어본다.
한 줄로 — Mac을 위한 가볍고 빠른 오픈소스 마크다운 에디터.
MarkEdit은 macOS 전용 오픈소스 마크다운 에디터다. MIT 라이선스, 별 약 3,800개, 1,100여 커밋. 만든 사람은 Ying Zhong이라는 개발자고, 직접 만든 이유는 README에 이렇게 적혀 있다 — "We make it mostly because we need it, and we ship it just in case you need it too." (자기가 필요해서 만들었고, 혹시 당신도 필요할까 싶어 풀어둔다는 뜻.)
핵심 캐릭터를 짚어보자.
brew install --cask markedit · 데이터 수집 0 (앱이 외부로 보내는 요청은 GitHub Releases API 1개뿐, 업데이트 체크용).비유하자면 — 워드 프로세서가 한글이나 MS Word라면, MarkEdit은 '메모장에 가까운, 그러나 마크다운 문법만 예쁘게 입혀주는' 도구다. WYSIWYG(보이는 그대로 결과가 나오는 편집 방식)도 아니고, 미리보기 패널도 기본 탑재가 아니다. 별도 확장으로 켤 수는 있지만, 기본은 어디까지나 "원본 마크다운 문법을 그대로 보면서 쓰는" 에디터다.
# 제목이라는 원본 마크다운 문법을 그대로 보여주고, 그 위에 색·굵기만 살짝 입혀준다. "키 한 번 누르면 무슨 문자가 들어갔는지 정확히 보고 싶다"는 철학.다른 마크다운 에디터들과 위치를 비교하면 이렇게 그려진다.
네이티브 ◄─────────────── 크로스플랫폼/Electron
| |
TextEdit MarkEdit ───────► Typora ─► Obsidian ─► Notion
(간단) (간단, (WYSIWYG) (지식관리) (모든 것)
마크다운
전용)
──────────────────────────────────────────────────────►
기능 풍부
왼쪽으로 갈수록 "한 가지만 잘하기", 오른쪽으로 갈수록 "이것저것 다 하기". MarkEdit은 의도적으로 왼쪽 끝 가까이에 자리잡았다.
한 줄 요약 — 가볍고, 사용자 데이터를 만지지 않고, 그래도 진짜 빠르다.
MarkEdit이 트렌드에 오른 이유는 새로운 기능이 있어서가 아니라, 거꾸로 최신 마크다운 앱들이 당연하게 넣는 것을 일부러 빼고 있어서다. 미리보기 패널, WYSIWYG, 동기화 서버, 플러그인 마켓플레이스, AI 기능 — 전부 없다. 그 결과 설치 크기 4MB, 10MB짜리 파일도 깜빡임 없이 편집되는 성능, 그리고 "내 데이터는 그냥 디스크의 .md 파일일 뿐"이라는 안도감이 남았다.
경쟁 제품들과 비교하면 자리가 더 분명해진다.
Obsidian과 Notion은 "지식 관리 시스템"이다. 노트끼리 링크하고, 그래프 뷰를 그리고, 데이터베이스 뷰까지 지원한다. MarkEdit은 그런 야망이 없다. 그냥 .md 파일 하나 열어서 쓰고 닫는 일을 한다. "이걸로 두 번째 뇌를 만들고 싶다"가 아니라 "그냥 마크다운 파일 좀 빨리 편집하고 싶다"인 사람에게 맞다.
Typora는 유료(약 $15)고 WYSIWYG 위주다. MarkEdit은 무료/오픈소스고 정반대로 마크다운 문법을 그대로 보여준다. 또 Typora는 Electron 기반이라 설치 시 100MB 이상인데, MarkEdit은 4MB. "마크다운 문법을 매번 보고 싶다"는 취향에 정확히 맞물린다.
VSCode로도 마크다운을 쓸 수 있지만, 개발자 도구라 메뉴가 너무 많고 설치 크기도 350MB+. MarkEdit은 "Mac 사용자가 TextEdit에서 마크다운만 쓰고 싶을 때 쓰는 앱"으로 포지셔닝이 다르다. 키 바인딩이나 메뉴 구조가 macOS 표준 그대로라 학습 곡선이 거의 없다.
여기까지가 "어디에 있는 앱인가". 다음 섹션부터는 본격적으로 코드 속으로 들어간다. 정말 흥미로운 건 이 작은 Mac 앱 안에 웹 기술이 어떻게 박혀 있는지다.
Swift + AppKit + WebKit + CodeMirror — Mac과 웹이 한 앱 안에 공존한다.
레포 언어 비율부터 보자. Swift 60.5%, TypeScript 37.2%, 나머지는 Mustache/HTML/Objective-C/JS/CSS 소량. 이 두 언어가 거의 5:3 비율로 섞여 있다는 점이 이미 힌트다 — 절반은 macOS 네이티브, 절반은 웹 에디터다.
Swift 코드는 크게 세 영역에 살고 있다.
EditorWindow, EditorWindowController, 메뉴/툴바, 설정 화면(SwiftUI), Shortcuts/AppleScript 연동 등 "macOS 앱 그 자체"를 담당.MarkEditCoreTests)도 여기에 있다.이런 식의 "UI 계층 ◄ Kit 계층 ◄ Core 계층" 3단 분리는 Apple의 표준 권장 아키텍처다. 가장 안쪽 Core는 UIKit/AppKit 안 쓰니 어디서든 재사용·테스트가 가능하다.
UI 프레임워크 선택도 흥미롭다. README에 직접 적힌 원칙은 — "WebKit for text editing, SwiftUI for settings, AppKit for all the rest." 즉 텍스트 편집은 웹, 설정 화면은 SwiftUI, 나머지 메뉴·창·툴바는 AppKit. 한 가지 도구를 모든 곳에 강요하지 않고, 시나리오마다 가장 잘 맞는 도구를 골랐다.
WKWebView라는 컴포넌트로 들어 있어 앱 안에 작은 웹뷰를 띄울 수 있다. MarkEdit의 편집 영역은 사실 이 WKWebView다.편집기 코어는 CoreEditor/ 폴더 안에 통째로 들어 있다. package.json을 까보면 의존성 윤곽이 한눈에 들어온다.
// CoreEditor/package.json — 핵심 dependencies (요약)
"dependencies": {
"@codemirror/autocomplete": "^6.0.0", // 자동완성
"@codemirror/commands": "^6.0.0", // 기본 키 명령
"@codemirror/lang-markdown": "^6.0.0", // 마크다운 파서
"@codemirror/lang-yaml": "^6.0.0", // YAML front-matter
"@codemirror/language": "^6.0.0", // 언어 추상화
"@codemirror/search": "^6.0.0", // 찾기/바꾸기
"@codemirror/state": "^6.0.0", // 에디터 상태
"@codemirror/view": "^6.0.0", // 렌더링
"@lezer/markdown": "^1.0.0", // Lezer 파서 (마크다운)
"@lezer/highlight": "^1.0.0" // 구문 강조
}
핵심은 CodeMirror 6. 그리고 그 안에서 마크다운을 파싱하는 건 Lezer라는 또 다른 라이브러리(CodeMirror와 같은 저자 Marijn Haverbeke). Lezer는 "에러에 강한 LR 파서 제너레이터"로, 코드 한 글자 칠 때마다 트리 일부만 다시 파싱해서 실시간 신택스 하이라이트가 가능하게 한다.
@codemirror/view, @codemirror/state, ...)으로 잘게 쪼개진 v6는 트리 셰이킹과 커스터마이즈가 쉽다. VSCode가 쓰는 Monaco와 함께 웹 에디터 두 거장 중 하나. Replit, Sourcegraph, Jupyter, 그리고 MarkEdit이 이걸 코어로 쓴다. 비유하자면 — 자동차로 치면 "엔진+변속기" 같은 동력 모듈만 따로 사다가 자기 차체에 올린 셈.웹 코어를 빌드하는 도구도 모던하다. Vite 7 + vite-plugin-singlefile + esbuild + rollup. 빌드의 결과물은 단일 HTML 파일이다 — 자바스크립트와 CSS가 전부 한 .html 안에 인라인으로 박힌다. 왜? 그래야 macOS 앱 번들 안에 그대로 넣고 WKWebView가 loadFileURL로 한 번에 불러올 수 있어서다.
그리고 가장 흥미로운 도구가 하나 더 있다 — Microsoft가 만든 ts-gyb다.
여기서 처음 보이는 큰 그림: 웹 기술(TS+CodeMirror)이 텍스트 편집을, 네이티브 기술(Swift+AppKit)이 윈도우·메뉴·파일 시스템을 맡고, 둘 사이는 ts-gyb로 자동 생성된 JSON 메시지로 통신한다. 다음 섹션에서 이 그림을 더 자세히 본다.
WKWebView 브릿지 + 양방향 메시지 — Mac과 웹이 만나는 지점.
먼저 시스템 전체 구조를 한 장으로.
┌─────────────────────────────────────────────────────────────────┐ │ MarkEdit.app (macOS) │ │ │ │ ┌──────────── MarkEditMac ─────────────┐ │ │ │ EditorWindowController │ AppKit │ │ │ ├─ NSToolbar / NSMenuItem │ (창, 메뉴, │ │ │ ├─ Settings (SwiftUI) │ 툴바) │ │ │ └─ Shortcuts / AppleScript 핸들러 │ │ │ └──────────────┬────────────────────────┘ │ │ │ 메서드 호출 │ │ ┌──────────────▼─ MarkEditKit ─────────┐ │ │ │ EditorWebView : WKWebView │ WebKit 브릿지 │ │ │ ├─ ScriptMessageHandler │ (Swift ↔ JS │ │ │ ├─ Bridge / Bridge+Generated.swift │ 메시지 통로) │ │ │ └─ MarkEditCore (모델) │ │ │ └──────────────┬────────────────────────┘ │ │ │ postMessage / evaluateJavaScript │ │ ┌──────────────▼─ WKWebView 내부 (CoreEditor) ──┐ │ │ │ index.html (Vite로 한 파일에 인라인됨) │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ │ │ CodeMirror 6 (EditorView, State) │ │ │ │ │ │ + @codemirror/lang-markdown │ │ │ │ │ │ + Lezer 파서 트리 │ │ │ │ │ │ + ~30개 자체 modules/ (commands, │ │ │ │ │ │ completion, frontMatter, ...) │ │ │ │ │ └──────────────────────────────────────┘ │ │ │ │ window.nativeModules.core.notify…() │ │ │ │ ▲ ▲ ▲ │ │ │ └──┼──┼──┼─────────────────────────────────────┘ │ │ │ │ └─ notifyContentHeightDidChange │ │ │ └──── notifyViewDidUpdate (선택·포커스 변경) │ │ └─────── notifyContentOffsetDidChange (스크롤) │ │ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ FinderExtension (NSFinderSync / FinderSyncExtension) │ │ │ │ PreviewExtension (QuickLook Preview / com.apple.quicklook.preview) │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ │ ts-gyb 자동 생성 ──► Swift/TS 양쪽에 동일 인터페이스 보장 │ └─────────────────────────────────────────────────────────────────┘
이 앱의 가장 큰 디자인 결정은 "편집기를 웹뷰 안에 둔다"였다. 그러면 macOS 메뉴에서 'Bold' 버튼을 눌렀을 때 그 메시지가 어떻게 웹뷰 안의 CodeMirror까지 전달될까? 정답은 두 방향이다.
Swift 측에서 WKWebView.evaluateJavaScript("MarkEdit.replaceText(...)")를 실행. WebKit이 그 JS를 웹뷰 안의 글로벌 컨텍스트에서 평가하고 결과를 콜백으로 돌려준다. 예: 사용자가 메뉴에서 "Bold" 클릭 → Swift가 insertText('**bold**', ...)을 JS로 평가 → CodeMirror가 텍스트 삽입.
JS 측에서 window.webkit.messageHandlers.<name>.postMessage(payload)를 호출. Swift 측 WKScriptMessageHandler가 그 메시지를 받는다. core.ts를 보면 실제로 window.nativeModules.core.notifyContentHeightDidChange(...) 같은 호출이 곳곳에 있는데, 이게 전부 이 통로다.
이 두 방향을 손으로 짜면 양쪽 시그니처가 어긋나기 쉽다. 그래서 등장하는 게 앞서 본 ts-gyb. TypeScript에서 인터페이스 한 번 적으면 Swift 쪽 호출 코드를 자동 생성해준다.
국제 회의에서 동시통역사가 양옆에 한 명씩 앉아 있는 그림. 한국 대표가 한국어로 발화하면 영어로 즉시 번역되고, 영어권 대표가 영어로 답하면 한국어로 즉시 돌아온다. WKWebView 브릿지가 통역사고, ts-gyb는 그 통역사들에게 "오늘 회의에서 쓸 단어집"을 사전에 양쪽 언어로 만들어주는 도구다. 그래서 통역 중 단어가 헷갈릴 일이 없다.
사용자가 설정에서 '줄 번호 표시'를 켜고 끌 때, 일반적인 에디터라면 전체를 다시 생성해야 한다. CodeMirror 6는 Compartment라는 메커니즘으로 일부 확장만 런타임에 갈아끼울 수 있다. MarkEdit은 이걸 적극 활용한다.
// CoreEditor/src/extensions.ts에서 발췌
const theme = new Compartment;
const readOnly = new Compartment;
const gutters = new Compartment;
const invisibles = new Compartment;
const activeLine = new Compartment;
const selectedLines = new Compartment;
const lineWrapping = new Compartment;
// ... 12개 컴파트먼트
window.dynamics = {
theme, readOnly, gutters, invisibles, activeLine, selectedLines,
lineWrapping, lineEndings, indentUnit, selectionHighlight,
extensionConfigurator, markdownConfigurator,
};
window.dynamics에 다 박아둔 게 인상적. Swift 쪽에서 "테마를 dark로 바꿔라"라는 메시지가 오면 window.dynamics.theme.reconfigure(newThemeExtension)만 호출하면 끝. 전체를 다시 만들 필요가 없다.
웹뷰 안에서 동작하는 모든 코드(JS + CSS + 모듈)는 빌드 후 HTML 파일 하나에 인라인으로 박힌다. 이걸 가능하게 하는 게 vite-plugin-singlefile. macOS 앱 번들에 .js 파일 수십 개를 넣고 경로 관리하는 일이 없어지고, WKWebView가 loadFileURL로 한 번에 띄울 수 있다.
① CodeMirror는 트리 셰이킹이 잘 되도록 모듈 분리가 잘 되어 있어, 안 쓰는 기능은 번들에 안 들어감. ② Vite + esbuild로 압축된 단일 HTML. ③ Swift 코드도 사용 안 하는 거대 프레임워크(예: 자체 마크다운 파서)를 안 들고 있음. ④ 미리보기·동기화·플러그인 마켓 같은 무거운 기능을 의도적으로 빼서 4MB 안에 들어맞은 결과.
레포 루트의 폴더가 무슨 역할을 하는지.
MarkEdit/ ├── CoreEditor/ ← 웹 에디터 (TypeScript + CodeMirror) │ ├── package.json ← Yarn 4, Vite 7, CodeMirror 6 │ └── src/ │ ├── core.ts ← 에디터 진입점, resetEditor() │ ├── extensions.ts ← 12개 Compartment 정의 │ ├── config.ts ← 사용자 설정 타입 │ ├── api/ ← MarkEdit-api (외부 스크립트용) │ ├── bridge/ ← Swift와 주고받는 메시지 인터페이스 │ ├── common/ ← 유틸·스토어 │ ├── modules/ ← selection, commands, completion, │ │ │ events, frontMatter, lezer, │ │ │ lineEndings, textChecker, toc, ... │ │ └── ... (30+ 서브모듈) │ ├── styling/ ← 테마·노드 스타일 (gutter, line, link) │ ├── @codegen/ ← ts-gyb 설정 │ ├── @vendor/ ← 직접 패치한 외부 코드 (예: history) │ ├── @res/ ← 정적 리소스 │ └── @types/ ← 글로벌 타입 선언 │ ├── MarkEditMac/ ← macOS 앱 본체 (Swift + AppKit + SwiftUI) │ ├── Info.plist ← 번들 메타데이터 │ ├── Info.entitlements ← 샌드박스 권한 │ ├── AppShortcuts.xcstrings ← Shortcuts 앱 통합 │ ├── Sources/ │ │ ├── Main/ ← AppDelegate, 앱 라이프사이클 │ │ ├── Editor/ ← EditorWindow / WindowController / │ │ │ ChunkLoader / ImageLoader … │ │ ├── Extensions/ ← Swift 표준 타입 확장 │ │ ├── Panels/ ← 다이얼로그·검색 패널 │ │ ├── Settings/ ← SwiftUI 설정 화면 │ │ ├── Shortcuts/ ← Shortcuts 인텐트 │ │ ├── Scripting/ ← AppleScript 핸들러 │ │ └── Updater/ ← 업데이트 체크 │ ├── Modules/ ← Previewer, DiffKit 등 보조 모듈 │ ├── Base.lproj / mul.lproj ← 다국어 리소스 │ └── Resources/ ← 아이콘, 사운드 등 │ ├── MarkEditCore/ ← Foundation만 의존하는 모델·로직 ├── MarkEditKit/ ← WebView 호스팅 + 공유 UI 로직 ├── MarkEditTools/ ← SwiftLint 등 빌드 도구 플러그인 ├── FinderExtension/ ← Finder 통합 (.md 미리보기 등) ├── PreviewExtension/ ← QuickLook 썸네일 ├── MarkEdit.xcodeproj/ ← Xcode 프로젝트 정의 ├── Build.xcconfig ← 버전 번호 등 빌드 설정 ├── .swiftlint.yml ← Swift 코드 스타일 규칙 └── README.md / LICENSE (MIT)
핵심 포인트 몇 가지.
웹 개발자가 cd CoreEditor && yarn dev로 에디터만 따로 띄워서 브라우저에서 개발할 수 있다. macOS 앱을 빌드하지 않고도 CodeMirror 부분만 만지는 게 가능. 개발 자전거(웹 dev server) + 배포 자동차(macOS .app) 두 가지 모드를 한 레포 안에 보유.
macOS에서 Finder 안에서 .md 파일을 미리보거나 컨텍스트 메뉴를 띄우려면 "앱 본체와 분리된 작은 번들"을 따로 만들어야 한다(샌드박스 격리). 이 두 폴더가 각각 그 작은 번들이다. 작은 앱이지만 macOS 시스템 확장 점까지 챙긴 점이 인상적.
CoreEditor/src/@vendor/commands/history처럼 @vendor/ 안엔 npm에서 받아온 코드를 손으로 수정한 버전들이 들어 있다. 외부 라이브러리에 버그가 있거나 동작이 미세하게 다르면, fork 대신 vendor 디렉토리에 박제해서 직접 패치하는 패턴. "의존성을 fork하지 말고 vendor에 박아라"는 작은 팀에 잘 맞는 전략.
이 레포에서 무엇을 배울 수 있는가, 그리고 어디부터 손대볼 수 있는가.
대부분의 Swift 튜토리얼은 iOS+SwiftUI에 치우쳐 있는데, MarkEdit은 macOS + AppKit + SwiftUI 혼용을 실제로 어떻게 하는지 보여주는 살아 있는 표본이다. 메뉴·툴바·창은 AppKit, 설정 화면은 SwiftUI — "필요한 자리에 맞는 도구를 쓴다"의 본보기.
MarkEditMac/Sources/Settings/ 안 SwiftUI 코드를 보고, 거기에 새로운 토글 옵션 1개를 추가해보기. 예: "포커스 모드(현재 줄만 진하게)" 옵션을 SwiftUI에 추가하고, 그 값을 CoreEditor 쪽으로 메시지로 보내는 흐름을 따라가보기.
CodeMirror 6는 v5까지의 "꽉 차고 모놀리식"에서 벗어나 잘게 쪼개진 모듈식이 됐다. 이걸 실전에서 어떻게 조립하는지 알고 싶다면 CoreEditor/src/extensions.ts가 거의 교과서. 12개 Compartment 패턴, keymap.of([...])의 우선순위, EditorState.transactionFilter로 입력 가로채기 등이 한 파일에 다 있다.
CodeMirror 6 단독으로 npm create vite한 빈 페이지에 마크다운 에디터를 만들어보기. @codemirror/state + @codemirror/view + @codemirror/lang-markdown 3개만 install 하면 시작할 수 있다. 그러고 나서 MarkEdit이 어떤 옵션을 추가로 어떻게 켰는지 비교해보면 학습 효과가 배.
Mac/iOS에서 웹뷰를 앱에 박고 native ↔ JS 통신을 만들어야 하는 경우는 의외로 많다. (예: 인앱 결제 페이지, 차트 라이브러리, 코드 에디터…) MarkEdit은 그 양방향 흐름을 깔끔히 정리한 예. MarkEditKit의 Bridge+Generated.swift와 CoreEditor/src/bridge/를 같이 펴놓고 보면 한 함수가 양쪽에 어떻게 대응되는지 한눈에 들어온다.
아주 작은 Mac 앱(SwiftUI WebView) 안에 HTML 파일을 띄우고, 버튼 클릭 → JS 함수 호출, JS의 fetch 결과 → Swift로 알림 받기를 손으로 한 번 구현해보기. ts-gyb 없이 손으로 짠 뒤, 이후 ts-gyb 도입을 시도하면 그 가치가 체감된다.
요즘 웹앱은 분할 번들이 표준인데, 임베디드 환경(WKWebView, Electron, 시놀로지 NAS 앱 등)에서는 거꾸로 "단일 HTML 한 파일"이 더 깔끔할 때가 많다. vite-plugin-singlefile이 그 정확한 도구. MarkEdit의 vite.config.mts(여러 개로 분기되어 있음 — 다크/라이트 빌드)는 좋은 레퍼런스다.
요즘은 잘 다뤄지지 않는 영역이지만 Mac 네이티브 앱에서 강력한 차별화 포인트. MarkEdit은 이 네 가지를 모두 챙긴다. MarkEditMac/Sources/Scripting(AppleScript), Shortcuts(인텐트), FinderExtension(파일 매니저 통합), PreviewExtension(QuickLook 미리보기) 각각이 독립 학습 단원이 될 수 있다.
기술적인 학습 외에도 이 레포가 보여주는 큰 교훈이 하나 있다 — 제품을 단순하게 유지하기 위한 의사결정 그 자체. README와 Wiki의 "Philosophy", "Why MarkEdit" 페이지를 읽어보면 "왜 이 기능을 안 넣는지"를 거의 모든 항목마다 설명하고 있다. 신규 기능 PR은 거부하면서 버그 수정 PR은 환영하는 정책도 명문화. 작은 앱을 작게 유지하기 위해 필요한 사회·문서 측면의 장치까지 챙긴 점이 학습할 만하다.
Mac 사용자 한정, 그것도 최신 두 버전만.
MarkEdit의 시스템 요구사항은 단순하지만 매우 엄격하다.
yarn dev로 브라우저에서 작업 가능)."최신 2개 메이저 macOS만 지원"은 큰 결정이다. 보통 OSS는 호환성 유지를 더 길게 가져가는데, MarkEdit은 일부러 잘라낸다. 이유는 Philosophy 페이지에 적혀 있다 — "To be focused, we only support the latest two major macOS releases." 핵심에 집중하기 위해 호환성 부담을 줄인 것.
예전엔 App Store에 있었지만 2023년 10월 이후 GitHub Releases로만 배포(이슈 #368). 자동 업데이트는 앱 내장 기능이 처리하지만, App Store 대비 약간의 추가 신뢰 단계(개발자 인증 + 노터라이즈)를 거친다. 처음 실행 시 "확인되지 않은 개발자" 같은 경고가 안 뜨도록 notarytool로 사전 노터라이즈된 상태.
퍼포먼스 측면 — README 본인 주장으로 8년 된 MacBook Pro에서 10MB 마크다운 파일이 깜빡임 없이 열린다. 이건 CodeMirror 6의 가상화 렌더링(화면 밖 줄은 DOM에 안 그림) 덕분이고, 일반적인 마크다운 앱이 2MB 파일조차 못 여는 경우와 큰 격차.
난이도별로 5단계.
Mac 없어도 가능. 레포 클론 후 cd CoreEditor && yarn install && yarn dev. Vite dev server가 뜨고, 브라우저에서 CodeMirror 기반 마크다운 에디터가 그대로 동작한다. window.webModules로 노출된 내부 API를 콘솔에서 호출해보면 에디터 내부가 어떻게 짜였는지 빨리 감 잡을 수 있다.
빈 Vite 프로젝트에 @codemirror/state, @codemirror/view, @codemirror/lang-markdown을 설치하고 30줄 정도로 동작하는 에디터를 만들기. 그다음 MarkEdit의 extensions.ts에서 마음에 드는 옵션 하나(예: highlightSelectionMatches)를 골라 내 에디터에도 적용해보기.
v1.19.0부터 도입된 MarkEdit-api를 사용하면 macOS 앱 빌드 없이도 사용자 스크립트(JavaScript)로 기능을 추가할 수 있다. 예: "선택된 텍스트의 마크다운 표를 정렬해주는 명령"을 1~2시간 작업으로 만들어보기. 이미 있는 MarkEdit-preview나 MarkEdit-theming이 코드 참고용 좋은 본보기.
Xcode 열고 MarkEditMac 스킴 빌드 → 본인 머신에서 동작하는 .app 생성. 그다음 메뉴에 "Reverse text" 같은 작은 기능 1개를 추가. AppKit 메뉴 등록 → Swift 핸들러 → WKWebView로 메시지 → CodeMirror에서 텍스트 변형까지 한 번 손으로 끝까지 따라가는 게 핵심 학습. 새 기능 PR은 받지 않으니 본인 fork에만 적용.
같은 발상(웹 에디터를 네이티브 셸 안에 박기)을 다른 OS에 가져가본다. Windows라면 WebView2(Edge의 임베디드 컴포넌트) + WinUI 3 또는 .NET MAUI. Linux라면 GTK + WebKitGTK. 핵심 학습: 네이티브 ↔ JS 메시지 브릿지가 플랫폼마다 어떻게 다른지, 그리고 단일 HTML 번들 전략이 어디서도 통하는지. 완성된 결과물보다 "왜 어렵나"를 체득하는 게 목적.
주차별 4주 코스 — 가볍게 한 주 한 분야씩.
공식 가이드 codemirror.net/docs의 "Examples"부터. 특히 State / View / Extensions의 분리 모델을 이해. 목표: 빈 페이지에 마크다운 신택스 하이라이트 + 기본 키 바인딩 + 검색 기능까지 동작하는 ~80줄 에디터. 옆에서 MarkEdit의 extensions.ts를 같이 펴두고 "내가 만든 것과 뭐가 다른가" 대조.
Apple 공식 문서의 "Communicating with Your Webpage" 챕터. 목표: SwiftUI macOS 앱에 WKWebView를 박고, 버튼 한 번에 JS 함수를 호출(Swift→JS), JS의 fetch 결과를 Swift로 받기(JS→Swift). 처음엔 손으로 짜고, 두 번째 단계에서 ts-gyb 도입을 시도. ts-gyb 없는 버전 → 있는 버전으로 진화 과정에서 "왜 자동 생성이 필요한가"가 몸으로 체감됨.
Vite의 plugin-singlefile 또는 esbuild의 인라인 옵션. 목표: React 또는 Svelte 미니 앱을 한 .html 파일로 빌드해서 더블클릭으로 동작하게. 이 기술은 MarkEdit 외에도 임베디드 화면(전자종이, 키오스크), 이메일 첨부 가능한 작은 도구 등에 광범위하게 쓸 수 있다.
Apple 공식 App Intents 문서 + AppleScript Language Guide. 목표: 본인이 자주 쓰는 작은 Mac 앱(또는 직접 만든 앱)에 Shortcuts 인텐트 1개 + QuickLook 미리보기를 붙여보기. 이건 학습 곡선이 가파른 분야지만 일단 익혀두면 Mac 앱의 "체감 완성도"가 단숨에 올라간다.
4주 후엔 "내가 만드는 Mac 앱"이 더 이상 SwiftUI 튜토리얼 수준에 머물지 않고, MarkEdit 정도의 통합 깊이를 흉내낼 수 있는 상태가 된다.
레포 곳곳에 등장하는 용어 한 번씩 정리.
messageHandlers / evaluateJavaScript로 양방향 통신.xcrun notarytool로 사전 처리 후 .dmg 배포.---로 둘러싸인 YAML 메타데이터 영역. 정적 사이트 생성기(Jekyll, Hugo)와 Obsidian 같은 도구에서 흔히 사용. MarkEdit은 @codemirror/lang-yaml의 yamlFrontmatter 확장으로 이 부분만 YAML로 파싱.더 깊이 파고들 때 갈 곳들.
· MarkEdit 본 레포 (MIT, ~3,800 stars)
· Wiki — Philosophy / Why MarkEdit / Development / Customization / Manual
· MarkEdit-api — 사용자 스크립트용 API
· MarkEdit-preview · MarkEdit-theming · MarkEdit-ai-writer
· MarkEdit-skill — AI 에이전트로 MarkEdit 관리
· CodeMirror 6 공식
· Lezer 파서 가이드
· ts-gyb (Microsoft)
· Apple WKWebView 공식 문서
· GitHub Flavored Markdown 사양
· Vite 공식 · vite-plugin-singlefile
· libremac.github.io — 같은 팀의 다른 무료 Mac 앱들
· Daring Fireball — WYSIWYG가 아닌 마크다운 에디터 논의 (Philosophy 페이지에서 인용한 글)
· TrendShift의 MarkEdit 페이지
· @MarkEditApp on Mastodon — 공식 업데이트 채널
cd CoreEditor && yarn install && yarn dev로 5분 안에 브라우저에서 CodeMirror 기반 마크다운 에디터가 동작하는 걸 확인. 콘솔에서 window.dynamics나 window.webModules를 찍어보며 내부 구조 탐색.@codemirror/state, @codemirror/view, @codemirror/lang-markdown만 설치해서 30줄짜리 에디터. 그다음 MarkEdit의 extensions.ts에서 마음에 드는 옵션을 하나씩 가져와 비교.WebView에 HTML 1장을 띄우고, Swift→JS 호출과 JS→Swift 알림을 둘 다 동작시키기. 이 패턴은 MarkEdit 같은 풀앱뿐 아니라 인앱 결제·차트·코드 뷰어 등에도 쓰인다.