3-3. CEF를 떼어내고 모바일로 (5/16 ~ 5/19)
두 번째 공백(5/3~5/15) 뒤 돌아온 나흘은 이 프로젝트에서 가장 방향이 크게 바뀐 구간이다. 데스크톱 프레임워크가 모바일까지 간다.
embed C ABI 분리 (5/16, 28커밋)
첫 커밋이 feat(core): CEF 무관 embed C ABI 분리 (Phase 1+2)다. 코어에서 CEF에 의존하는 부분을 걷어내 libsuji_core로 잘라냈다. 창도 브라우저도 모르는, IPC·이벤트·핸들러 레지스트리·플러그인만 가진 라이브러리다.
방향은 zero-native(vercel-labs)를 벤치마킹하며 정했다. 레퍼런스를 들이는 방식은 늘 같다 — 클론해 두고, 무엇을 가져올지 먼저 묻는다.
"네 해주시고 거기에 https://github.com/vercel-labs/zero-native 이거 클론해주세요" — 2026-05-16
"여기서 우리가 따라하면 좋을만한점이 뭐강 ㅣㅆ을까요?" — 2026-05-16
"타우리나 지그 네이티브는 어떻게 구현했는데? 그거 맞춰서 구현해줘 X로 표시한거" — 2026-05-17
그리고 동기는 모바일이 아니었다. 당시 창과 IPC는 e2e로만 검증할 수 있어 테스트가 어려웠고, 결합도와 빌드 속도 문제도 있었다. CEF에서 코어를 떼면 그게 풀리고, 시스템 WebView 옵션과 모바일은 그 부수 효과로 따라온다는 판단이었다.
Phase 1은 로직을 옮기지 않고 순수하게 추가하는 방식이었다. src/embed.zig에 BackendRegistry + EventBus를 감싸는 C ABI shim(suji_core_init/destroy/invoke/free/emit/emit_to/on/off)을 두고 zig build lib로 libsuji_core.a를 냈다 — CEF·Cocoa·Node 링크가 0인 라이브러리다. 헤드리스 테스트 3개를 zig build test에 연결했다.
작업하면서 확인된 사실이 하나 있다. src/core/*와 src/backends/loader.zig에는 이미 CEF import가 한 건도 없었다. 분리선은 새로 긋는 게 아니라 이미 있던 것을 드러내는 일이었다.
그 위에 하루 만에 다음이 올라갔다.
- 모바일 크로스 컴파일 지원과 CI 회귀 방지
- iOS 호스트 스켈레톤 + JS 브릿지(Phase 3), Android 호스트 스켈레톤 + JNI/JS 브릿지
suji_core_register_handler— 모바일 네이티브 핸들러suji_core_last_error()진단 게터- iOS·Android 정적 백엔드 — Rust·Go·Zig를 고유 심볼 진입점으로 링크
- 언어별 예제 구조 —
_shared호스트 + multi/rust/go/zig 변형 - Windows
dlopen백엔드 지원 (kernel32 직접 래핑)
데스크톱은 dylib을 dlopen하지만 모바일은 동적 로딩이 제한되므로, 같은 백엔드 코드를 정적 링크로 붙이는 경로를 따로 만들었다. 질문 자체가 그 지점을 겨눴다.
"그리고 안드로이드, ios에도 고나 러스트 노드 코드가 들어가서 실행이 될 수 있는건지?" — 2026-05-16
"실기기에서 실제로 잘 도는지 빌드해서 확인해줘 그리고 다 푸시 한거야?" — 2026-05-16
문서상 "지원"으로 끝내지 않고 시뮬레이터·에뮬레이터 e2e를 붙인 것도 이 요구 때문이다.
릴리스 파이프라인 (5/17)
- macOS 서명 모드(none/adhoc/identity) + 공증(notarytool/stapler) +
.dmg - Windows/Linux 패키징(zip / tar.gz +
.desktop, 선택 signtool) - 모바일 서명 — Android keystore signingConfig, iOS exportOptions +
archive-ios.sh - SDK 배포 워크플로(기본은 검증만, 발행은 하지 않음)
__core__ — 모바일에서도 같은 API (5/17, 56커밋)
모바일 호스트에서 네이티브 기능을 쓰려면 데스크톱과 다른 길이 필요하다. 이걸 __core__ 채널 하나로 통일하고 슬라이스 11개로 채웠다.
여기에 http.fetch를 모바일 임베드 경로까지 배선하고, iOS 시뮬레이터와 Android 에뮬레이터에서 실제로 띄워 확인하는 e2e를 붙였다. 예제 앱에는 모바일 네이티브 @suji/api 인터랙티브 데모 8종을 넣었다.
Phase 6·7 — OO 래퍼와 격리 (5/17)
BrowserWindowOO 래퍼를@suji/api·@suji/node·Rust·Go에 모두 (Phase 6 완료)set_user_agent/get_user_agent와capture_page— CDP 기반, 코어 + 4 SDK- contextIsolation —
window.__suji__frozen + 슬롯 봉인 (Phase 7) npx @suji/cli init— 의존성 0의 순수 Node 스캐폴더- Programmatic context menu —
Menu.popup(items, {x?, y?})
플러그인과 권한 게이트 (5/18, 25커밋)
- SQLite 플러그인 — 두 번째 공식 플러그인(벤더 SQLite 3.51), 그리고 같은 기능의 모바일 백엔드와 iOS/Android 예제 변형
@suji/plugin-{state,sqlite}-node— Node 래퍼로 4언어 SDK 매트릭스 완성- macOS App Sandbox 인프라와 security-scoped bookmarks(샌드박스에서 영속 파일 접근)
session.clearStorageData,desktopCapturer.getSources,capturePage(rect),powerMonitoridle "locked", deep-link 자동화(CFBundleURLTypes), 클립보드 TIFF,webRequesttimeout fallback- 모바일 권한 게이트 — C ABI Stage 1(코어) + Stage 2(iOS/Android 호스트 글루), 실 e2e 검증
CLI 정비 (5/19)
suji init --frontend=react|vue|svelte|solid|preact|vanilla로 프론트엔드 템플릿을 고를 수 있게 하고, create-vite 위임을 폐기하고 번들 템플릿(Tauri식)으로 바꿨다. 미디어 키 전용 globalShortcut API도 이날 들어갔다(Carbon → NSEvent 분기).