개요
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, Button | dioxus_compose::prelude::* | 구현 지금 렌더러가 그림 |
대부분의 Dioxus 앱이 HTML 로 쓰이므로 이 가이드의 중심은 HTML 경로입니다. 위젯 경로는
Compose 위젯 아래에 따로 있습니다. 두 prelude 가 각자 자기 요소
이름을 들여오므로 rsx! 블록 하나는 한쪽 어휘만 씁니다. 한 화면에 둘을 함께 두는
것은 계획 단계입니다.
앱의 모양
아래가 samples/hello 의 전부입니다. 컴포넌트 하나와 스타일시트 하나이고, import
한 줄 말고는 이 크레이트에만 있는 것이 없습니다.
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 가 그리는 데 필요한 것은 그것뿐입니다.
- 앱 실행.
HtmlDom이 컴포넌트를 DioxusVirtualDom에서 실행하고 그 결과를 blitz-dom 문서에 적용합니다. 루트는<body>안에 그려집니다. - 스타일 계산. Firefox 의 CSS 엔진인 Stylo 가 스타일시트와 캐스케이드를 풉니다.
- 배치. Taffy 가 블록, flex 와 grid 컨테이너, 표를 배치합니다. 텍스트 크기는
직접 넘길 수 있는
TextMeasurer가 잽니다(텍스트 참고). - 그릴 목록. 결과는
DisplayList입니다. 상자마다 항목 하나가 그리는 순서대로 있고, 사각형, 배경, 테두리, 모서리, 그림자, 클립, 불투명도, 텍스트 조각, 입력란, 이미지, 스크롤 컨테이너를 담습니다. - 그리기 계획.
plan_from이 목록을 그리기 원소의 트리(컨테이너 안에 놓인 상자, 스크롤 컨테이너, 텍스트, 입력란, 이미지)로 바꾸고,diff가 두 계획 사이에서 바뀐 것을 노드 단위로 알려 줍니다. - 그리기. 렌더러가 Compose 로 계획을 그립니다. 계획 이 단계가 아직 없는 다리입니다 (#43).
이벤트는 반대 방향으로 갑니다. 한 점을 누르면 그 아래 요소의 Dioxus 핸들러로 가고, 입력란에서
확정된 글자는 그 요소의 oninput 으로 갑니다. 지금은 테스트나 임베더가
HtmlDom 의 메서드를 직접 부르고, 렌더러가 그리게 되면 렌더러가 같은 메서드를
부릅니다.
HTML 경로가 지키는 규칙
- JavaScript 는 실행하지 않습니다.
<script>는 사용자 에이전트 스타일시트가 숨기는 보통 요소입니다. 그래서 Rust 로 쓰지 않은 코드가 페이지에서 실행될 수 없습니다. - 아무것도 내려받지 않습니다.
<img src>와 CSSurl()은 쓴 그대로 기록됩니다. 무엇으로 그릴지는 앱의 이미지 리졸버가 정하므로, 무엇을 어디서 읽을지는 앱이 결정합니다. - 입력란은 비제어입니다. 사용자가 고치는 동안 글자는 렌더러가 갖고, 확정된 글자만 Rust 로 옵니다. 왕복하면 조합 중인 음절이 초기화되므로 한글, 일본어, 중국어 조합은 왕복하지 않습니다.
- 스크롤 위치, 포커스, 선택은 렌더러의 것입니다. 매 프레임 바뀌는 값이라 Rust 가 가지면 한 프레임씩 늦습니다.
브라우저와의 차이
기준은 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 에서 측정).