출시·고도화 중
Tauri 안내서 · 3/6
이 장에서는 Tauri 앱이 어떻게 움직이는지 다룹니다. Rust 코어와 WebView의 관계, 프런트엔드에서 Rust를 부르는 명령(command), 양방향 알림인 이벤트, 권한 기반 보안 모델, 기능을 더하는 플러그인을 차례로 살펴봅니다.
Tauri 앱은 Rust로 작성한 코어 프로세스 하나와, 화면을 그리는 WebView로 이루어집니다. Tauri는 브라우저 엔진을 앱에 넣지 않고 운영체제의 WebView를 씁니다. Windows는 WebView2(Microsoft Edge 기반), macOS와 iOS는 WKWebView, Linux는 WebKitGTK, Android는 시스템 WebView입니다. 덕분에 설치 파일이 작지만, 플랫폼마다 렌더링 엔진이 다르므로 화면은 여러 운영체제에서 확인해야 합니다.
창 관리는 TAO, WebView 연결은 WRY라는 Tauri 팀의 라이브러리가 맡습니다. 코어는 창과 메뉴, 트레이, 운영체제 기능에 접근할 수 있고, WebView 안의 코드는 IPC(프로세스 간 통신)로 코어에 요청을 보냅니다. 이 경계에서 권한을 검사하는 것이 Tauri 보안의 중심입니다.
Rust 함수에 #[tauri::command]를 붙이고 generate_handler!에 등록하면 프런트엔드에서 이름으로 부를 수 있습니다. invoke_handler는 한 번만 호출해야 하므로 모든 명령을 한 목록에 적습니다.
#[tauri::command]
fn greet(name: &str) -> String {
format!("안녕하세요, {name}님!")
}
#[tauri::command]
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
return Err("0으로 나눌 수 없습니다".into());
}
Ok(a / b)
}
#[tauri::command]
async fn count_lines(path: String) -> Result<usize, String> {
let text = std::fs::read_to_string(&path).map_err(|e| e.to_string())?;
Ok(text.lines().count())
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet, divide, count_lines])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}인자와 반환값은 JSON으로 오가므로 serde로 직렬화할 수 있어야 합니다. JavaScript에서는 인자를 camelCase 키로 넘기고, Rust의 snake_case 인자 이름과 자동으로 짝지어집니다. Result의 Err는 프런트엔드에서 거부된 Promise가 됩니다. async가 없는 명령은 메인 스레드에서 실행되므로, 오래 걸리는 작업은 async 명령으로 만들어 화면이 멈추지 않게 합니다.
import { invoke } from "@tauri-apps/api/core";
const message = await invoke<string>("greet", { name: "Tauri" });
try {
const result = await invoke<number>("divide", { a: 10, b: 0 });
console.log(result);
} catch (error) {
console.error(error); // "0으로 나눌 수 없습니다"
}명령이 요청과 응답이라면, 이벤트는 어느 쪽에서든 보낼 수 있는 일방향 알림입니다. 진행률이나 상태 변화처럼 Rust 쪽에서 먼저 알려야 할 때 씁니다. Rust에서는 Emitter 트레이트의 emit, 프런트엔드에서는 listen을 씁니다.
use tauri::{AppHandle, Emitter};
#[tauri::command]
fn start_download(app: AppHandle) {
std::thread::spawn(move || {
for progress in [25, 50, 75, 100] {
app.emit("download-progress", progress).unwrap();
std::thread::sleep(std::time::Duration::from_millis(300));
}
});
}import { listen } from "@tauri-apps/api/event";
const unlisten = await listen<number>("download-progress", (event) => {
console.log(`진행률 ${event.payload}%`);
});
// 화면을 떠날 때는 구독을 해제합니다
unlisten();특정 창에만 보내려면 emit_to를 씁니다. 많은 데이터를 순서대로 흘려보내야 한다면 이벤트보다 빠른 tauri::ipc::Channel을 명령 인자로 받아 쓰는 편이 좋습니다.
Tauri 2는 WebView에서 쓸 수 있는 기능을 기본적으로 막아 두고, 허용할 것만 capability 파일에 적게 합니다.
fs:allow-read-text-file처럼 씁니다.{
"identifier": "main-capability",
"windows": ["main"],
"permissions": [
"core:default",
"dialog:allow-open",
{
"identifier": "fs:allow-read-text-file",
"allow": [{ "path": "$APPDATA/**" }]
}
]
}앱이 직접 등록한 명령은 기본적으로 모든 창에서 부를 수 있으며, 필요하면 앱 매니페스트로 이 명령들에도 권한을 정의할 수 있습니다. 여기에 더해 app.security.csp로 콘텐츠 보안 정책을 걸어, 외부 스크립트가 실행되는 일을 막습니다.
파일 시스템, 대화 상자, HTTP, 알림, 셸, 저장소 같은 기능은 플러그인으로 제공됩니다. 공식 플러그인은 CLI로 추가합니다. 명령이 Rust 크레이트와 JavaScript 패키지를 설치하고 lib.rs에 등록 코드를 넣어 주며, 남은 일은 capability에 필요한 권한이 들어갔는지 확인하는 것입니다.
npm run tauri add dialogimport { open } from "@tauri-apps/plugin-dialog";
const path = await open({
multiple: false,
filters: [{ name: "Text", extensions: ["txt", "md"] }],
});#[tauri::command]와 invoke로 프런트엔드에서 Rust를 부르고, 이벤트로 양쪽에 알림을 보냅니다.tauri add로 추가해 씁니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.