플러그인 모델
This content is not available in your language yet.
Suji의 plugin은 사용자가 만든 cross-cutting 기능을 5 SDK (Zig/Rust/Go/Node + frontend JS)에서 일관되게 호출하기 위한 구조. OS native API (clipboard/fs/dialog 등)는 plugin이 아니라 코어 API로 제공되며, 이 부분은 Electron/Tauri와 동일한 결정.
두 종류 비교
섹션 제목: “두 종류 비교”| OS native API | 사용자 plugin | |
|---|---|---|
| 빌드 | suji 코어와 함께 | 별도 dylib (또는 source 포함) |
| 5 SDK 노출 | ✅ 자동 | ✅ wrapper 직접 작성 |
| 예시 | clipboard, fs, dialog, tray, menu, notification, globalShortcut | state, sqlite, log, store, http, notification-rich, 사용자 DB / 인증 / telemetry 등 |
| Mac App Sandbox 호환 | ✅ | 🟡 dylib + entitlements 분리 작업 필요 |
OS native API를 plugin으로 만들지 않는 이유:
- Cocoa/CoreFoundation 등 OS framework 링크가 필요해 dylib 분리 시 복잡도가 크게 올라간다
- CEF Helper 프로세스와 entitlements 격리가 충돌한다
- Electron/Tauri도 이 부분은 코어 API로 둔다
Tauri 모델 비교
섹션 제목: “Tauri 모델 비교”Tauri의 “plugin install”은 build-time Cargo 의존성이지 런타임 plugin 아님:
[dependencies]tauri-plugin-fs = "2"tauri-plugin-dialog = "2"cargo build # fetch + compile + link → 같은 binary→ 사용자가 필요한 것만 install = build-time choice. 결국 같은 binary에 들어감. 진짜 dylib plugin이 아님 (Tauri도 안 함 — OS Sandbox 충돌이 동일).
| 모델 | 사용자 경험 | binary 크기 | 진짜 외부 plugin? |
|---|---|---|---|
| Tauri | cargo add tauri-plugin-fs | feature만큼만 | ❌ build-time dep |
| Electron | (없음, 모든 API 코어) | 전체 포함 | ❌ |
| Suji 현재 | (없음, native API 코어) | 전체 포함 | ❌ for native API |
Suji plugins/<name>/ 패턴 | plugins/<my-plugin>/ 디렉토리 + Zig dylib | 사용자 plugin만 | ✅ 진짜 외부 install |
진짜 외부 install 형태 plugin은 Suji가 이미 더 강함 — 사용자가 임의 plugin 만들고
dylib으로 빌드, suji.json plugins: ["my-plugin"]에 등록. 5 SDK wrapper도 직접 작성.
사용자 plugin 만들기
섹션 제목: “사용자 plugin 만들기”디렉토리 구조
섹션 제목: “디렉토리 구조”plugins/my-plugin/├── zig/ # plugin 본체 (Zig)│ ├── build.zig│ └── src/main.zig # backend_init, backend_handle_ipc, backend_free, backend_destroy├── rust/ # 5 SDK wrapper (선택)├── go/├── node/└── ts/ # frontend SDK (@suji/<plugin> 또는 inline)plugins/state/가 첫 공식 — 패턴 참고.
suji.json 등록
섹션 제목: “suji.json 등록”{ "plugins": [ "state", { "name": "my-plugin", "source": "./plugins/my-plugin", "permissions": ["state:get", "state:set"] } ]}문자열은 로컬/내장 plugins/<name>/을 찾는다. 객체 form의 source는 로컬 경로
또는 GitHub source(github.com/owner/repo, https://github.com/owner/repo)를
받는다. GitHub source는 ~/.suji/plugins/ 아래에 shallow clone/pull 후 빌드한다.
permissions는 플러그인이 init/handler 실행 중 다른 채널로 core.invoke할 때의 outbound
allowlist다. 생략하면 기존 호환을 위해 unrestricted, 빈 배열은 deny-all, "*"와
"state:*" 같은 prefix wildcard를 지원한다.
→ suji 시작 시 plugins/my-plugin/zig/zig-out/lib/libbackend.dylib 같은 backend ABI
dylib가 자동 빌드/로드된다.
Plugin C ABI
섹션 제목: “Plugin C ABI”backend dlopen과 같은 entry:
export fn backend_init(core: ?*const SujiCore) callconv(.c) void { ... }export fn backend_handle_ipc(request: [*:0]const u8) callconv(.c) ?[*:0]u8 { ... }export fn backend_free(ptr: ?[*:0]u8) callconv(.c) void { ... }export fn backend_destroy() callconv(.c) void { ... }→ 5 SDK가 suji.invoke('my-channel', request) 같이 호출.
C/C++ 플러그인은 include/suji.h를 include하면 동일 ABI와 WindowApi v1 raw
dispatcher 선언을 사용할 수 있다.
notification-rich는 OS notification primitive를 사용하지만 코어 notification
API 표면을 넓히지 않기 위해 공식 plugin으로 제공된다. Windows는 WinRT toast,
macOS는 UNUserNotificationCenter action category, Linux는 Freedesktop
Notifications D-Bus action signal을 사용하고, action click은
notification:click {notificationId, actionId} 채널로 라우팅된다.
플러그인이 창이나 WebContentsView를 직접 조작해야 할 때는 SujiCore.get_window_api
로 window 전용 C ABI table을 받을 수 있다. 이 table은 window cmd JSON을 받는
request_json dispatcher와 free_response를 제공하며, 주입되지 않은 호스트에서는
invoke("__core__", ...) 경로로 자동 폴백한다.
향후 계획
섹션 제목: “향후 계획”- Build-time feature flag:
zig build -Dfeatures=fs,clipboard또는suji.jsonfeatures: [...]로 미사용 native API를 binary에서 제외해 크기를 줄일 수 있다. 단 CEF 자체가 binary 크기의 대부분을 차지하므로 절약 효과는 제한적이다.