dioxus-compose 가이드

개요

HTML 과 CSS 를 Compose 로 그리는 Dioxus 렌더러

dioxus-compose 는 웹에서 쓰듯 div, h1, flexbox, 스타일시트로 쓴 Dioxus 앱을 실행하고, compose-rust 를 거쳐 Compose Multiplatform 으로 네이티브하게 그립니다. 배포하는 프로그램에는 웹뷰도 JVM 도 없습니다.

지금 상태

HTML 경로는 지금 화면을 배치하고 그리기 계획을 만들지만, 아직 화면에 그리지는 않습니다. 그 계획을 렌더러에 넘기는 다리가 아직 없습니다(부분, #43). 이 가이드의 모든 설명은 Host 에서 테스트로 확인한 것이고, 구현 / 부분 / 계획의 구분은 현황 페이지에 있습니다.

화면을 쓰는 두 가지 방법

이 크레이트의 rsx! 는 두 가지 어휘를 받고, 둘 다 늘 들어 있습니다. 한 크레이트가 두 종류의 앱을 모두 맡기로 소유자가 정했으므로, 어느 쪽도 기능 플래그로 끄지 않습니다.

쓰는 것import지금
HTML 요소와 CSS: div, span, input, 스타일시트dioxus_compose::html::prelude::*부분 Host 에서 배치하고 계획까지 만듦, 아직 그리지 않음
Compose 위젯 이름: Column, Text, Buttondioxus_compose::prelude::*구현 지금 렌더러가 그림

대부분의 Dioxus 앱이 HTML 로 쓰이므로 이 가이드의 중심은 HTML 경로입니다. 위젯 경로는 Compose 위젯 아래에 따로 있습니다. 두 prelude 가 각자 자기 요소 이름을 들여오므로 rsx! 블록 하나는 한쪽 어휘만 씁니다. 한 화면에 둘을 함께 두는 것은 계획 단계입니다.

앱의 모양

아래가 samples/hello 의 전부입니다. 컴포넌트 하나와 스타일시트 하나이고, import 한 줄 말고는 이 크레이트에만 있는 것이 없습니다.

samples/hello/src/lib.rs, 줄임
use dioxus_compose::html::prelude::*;
use dioxus_hooks::use_signal;

pub const STYLE: &str = r#"
body { margin: 0; font-family: system-ui, sans-serif; font-size: 16px; line-height: 1.5; }
.page { padding: 40px 48px; }
h1 { margin: 0 0 16px; font-size: 32px; line-height: 40px; }
.link { color: #0969da; text-decoration: underline; cursor: pointer; }
"#;

pub fn app() -> Element {
    let mut hellos = use_signal(|| 0u32);

    rsx! {
        main { class: "page",
            h1 { id: "title", "Hello, Dioxus" }
            p { id: "action",
                a {
                    id: "greet",
                    class: "link",
                    href: "#",
                    onclick: move |event| {
                        event.prevent_default();
                        hellos += 1;
                    },
                    "Say hello"
                }
            }
        }
    }
}

HTML 경로의 동작

스타일과 레이아웃은 Rust 쪽인 Host 안에서 창 없이 도는 blitz-dom 이 계산합니다. 렌더러는 CSS 를 보지 않습니다. 렌더러가 받는 것은 사각형, 색, 텍스트 조각이고, Compose 가 그리는 데 필요한 것은 그것뿐입니다.

  1. 앱 실행. HtmlDom 이 컴포넌트를 Dioxus VirtualDom 에서 실행하고 그 결과를 blitz-dom 문서에 적용합니다. 루트는 <body> 안에 그려집니다.
  2. 스타일 계산. Firefox 의 CSS 엔진인 Stylo 가 스타일시트와 캐스케이드를 풉니다.
  3. 배치. Taffy 가 블록, flex 와 grid 컨테이너, 표를 배치합니다. 텍스트 크기는 직접 넘길 수 있는 TextMeasurer 가 잽니다(텍스트 참고).
  4. 그릴 목록. 결과는 DisplayList 입니다. 상자마다 항목 하나가 그리는 순서대로 있고, 사각형, 배경, 테두리, 모서리, 그림자, 클립, 불투명도, 텍스트 조각, 입력란, 이미지, 스크롤 컨테이너를 담습니다.
  5. 그리기 계획. plan_from 이 목록을 그리기 원소의 트리(컨테이너 안에 놓인 상자, 스크롤 컨테이너, 텍스트, 입력란, 이미지)로 바꾸고, diff 가 두 계획 사이에서 바뀐 것을 노드 단위로 알려 줍니다.
  6. 그리기. 렌더러가 Compose 로 계획을 그립니다. 계획 이 단계가 아직 없는 다리입니다 (#43).

이벤트는 반대 방향으로 갑니다. 한 점을 누르면 그 아래 요소의 Dioxus 핸들러로 가고, 입력란에서 확정된 글자는 그 요소의 oninput 으로 갑니다. 지금은 테스트나 임베더가 HtmlDom 의 메서드를 직접 부르고, 렌더러가 그리게 되면 렌더러가 같은 메서드를 부릅니다.

HTML 경로가 지키는 규칙

브라우저와의 차이

기준은 VS Code 가 쓰는 Chromium 입니다. 2026-10-03 에 VS Code 워크벤치의 세 구역(액티비티 바, 사이드바 트리, 에디터 탭)을 Code-OSS CSS 로 macOS, 배율 1 에서 배치했습니다. 텍스트 크기를 VS Code 가 잰 값으로 넣었을 때 상자는 다음과 같았습니다.

구역VS Code 와 1px 이내인 상자가장 큰 차이
액티비티 바29 개 중 29 개0px
사이드바 트리203 개 중 201 개(99.0%)4.00px
에디터 탭94 개 중 94 개0px

테스트는 dioxus-compose/tests/fr34_box_tolerance.rs 입니다. Host 자체의 텍스트 엔진인 Parley 로 잰 텍스트는 맞지 않았습니다. 줄바꿈 22 곳 중 6 곳이 다른 자리에서 끊겼습니다. 그래서 텍스트는 그것을 그리는 엔진인 Compose 가 재게 됩니다 (부분, #43).

왜 만드는가

브라우저 엔진 위의 데스크톱 앱은 무겁고, 하루 종일 켜 두는 앱은 그 무게를 매시간 치릅니다. Rust UI 툴킷은 가볍지만 텍스트 입력이 아직 Compose 수준에 이르지 못했고, 90% 만 맞는 입력기는 한글을 칠 수 없는 앱입니다. dioxus-compose 는 앱이 이미 쓰고 있는 Dioxus 와 CSS 를 그대로 두고, 픽셀과 텍스트와 입력기는 Compose 에서 빌려 옵니다.

Compose 쪽은 미리 네이티브 라이브러리로 컴파일되므로 배포하는 프로그램에 JVM 이 없습니다. 위젯 경로로 만든 notepad 예제는 macOS 에서 파일 하나, 28.76 MB 입니다(M1 에서 측정).

다음으로