dioxus_devtools/
lib.rs

1use dioxus_core::internal::HotReloadedTemplate;
2use dioxus_core::{ScopeId, VirtualDom};
3use dioxus_signals::{GlobalKey, Signal, Writable};
4
5pub use dioxus_devtools_types::*;
6pub use subsecond;
7use subsecond::PatchError;
8
9/// Applies template and literal changes to the VirtualDom
10///
11/// Assets need to be handled by the renderer.
12pub fn apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) {
13    try_apply_changes(dom, msg).unwrap()
14}
15
16/// Applies template and literal changes to the VirtualDom, but doesn't panic if patching fails.
17///
18/// Assets need to be handled by the renderer.
19pub fn try_apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) -> Result<(), PatchError> {
20    dom.runtime().on_scope(ScopeId::ROOT, || {
21        // 1. Update signals...
22        let ctx = dioxus_signals::get_global_context();
23        for template in &msg.templates {
24            let value = template.template.clone();
25            let key = GlobalKey::File {
26                file: template.key.file.as_str(),
27                line: template.key.line as _,
28                column: template.key.column as _,
29                index: template.key.index as _,
30            };
31            if let Some(mut signal) = ctx.get_signal_with_key(key.clone()) {
32                signal.set(Some(value));
33            }
34        }
35
36        // 2. Attempt to hotpatch
37        if let Some(jump_table) = msg.jump_table.as_ref().cloned() {
38            if msg.for_build_id == Some(dioxus_cli_config::build_id()) {
39                unsafe { subsecond::apply_patch(jump_table) }?;
40                dioxus_core::prelude::force_all_dirty();
41                ctx.clear::<Signal<Option<HotReloadedTemplate>>>();
42            }
43        }
44
45        Ok(())
46    })
47}
48
49/// Connect to the devserver and handle its messages with a callback.
50///
51/// This doesn't use any form of security or protocol, so it's not safe to expose to the internet.
52#[cfg(not(target_arch = "wasm32"))]
53pub fn connect(callback: impl FnMut(DevserverMsg) + Send + 'static) {
54    let Some(endpoint) = dioxus_cli_config::devserver_ws_endpoint() else {
55        return;
56    };
57
58    connect_at(endpoint, callback);
59}
60
61/// Connect to the devserver and handle hot-patch messages only, implementing the subsecond hotpatch
62/// protocol.
63///
64/// This is intended to be used by non-dioxus projects that want to use hotpatching.
65///
66/// To handle the full devserver protocol, use `connect` instead.
67#[cfg(not(target_arch = "wasm32"))]
68pub fn connect_subsecond() {
69    connect(|msg| {
70        if let DevserverMsg::HotReload(hot_reload_msg) = msg {
71            if let Some(jumptable) = hot_reload_msg.jump_table {
72                unsafe { subsecond::apply_patch(jumptable).unwrap() };
73            }
74        }
75    });
76}
77
78#[cfg(not(target_arch = "wasm32"))]
79pub fn connect_at(endpoint: String, mut callback: impl FnMut(DevserverMsg) + Send + 'static) {
80    std::thread::spawn(move || {
81        let uri = format!(
82            "{endpoint}?aslr_reference={}&build_id={}",
83            subsecond::__aslr_reference(),
84            dioxus_cli_config::build_id()
85        );
86
87        let (mut websocket, _req) = match tungstenite::connect(uri) {
88            Ok((websocket, req)) => (websocket, req),
89            Err(_) => return,
90        };
91
92        while let Ok(msg) = websocket.read() {
93            if let tungstenite::Message::Text(text) = msg {
94                if let Ok(msg) = serde_json::from_str(&text) {
95                    callback(msg);
96                }
97            }
98        }
99    });
100}