mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-14 00:17:17 +00:00
implement hot reloading for desktop
This commit is contained in:
parent
0e40619b52
commit
1073574896
9 changed files with 155 additions and 32 deletions
|
@ -20,6 +20,8 @@ members = [
|
||||||
"packages/native-core-macro",
|
"packages/native-core-macro",
|
||||||
"packages/rsx-rosetta",
|
"packages/rsx-rosetta",
|
||||||
"packages/signals",
|
"packages/signals",
|
||||||
|
"packages/hot-reload",
|
||||||
|
"packages/hot-reload-macro",
|
||||||
"docs/guide",
|
"docs/guide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -43,6 +45,7 @@ publish = false
|
||||||
dioxus = { path = "./packages/dioxus" }
|
dioxus = { path = "./packages/dioxus" }
|
||||||
dioxus-desktop = { path = "./packages/desktop", features = ["transparent"] }
|
dioxus-desktop = { path = "./packages/desktop", features = ["transparent"] }
|
||||||
dioxus-ssr = { path = "./packages/ssr" }
|
dioxus-ssr = { path = "./packages/ssr" }
|
||||||
|
dioxus-hot-reload = { path = "./packages/hot-reload" }
|
||||||
dioxus-router = { path = "./packages/router" }
|
dioxus-router = { path = "./packages/router" }
|
||||||
dioxus-signals = { path = "./packages/signals" }
|
dioxus-signals = { path = "./packages/signals" }
|
||||||
fermi = { path = "./packages/fermi" }
|
fermi = { path = "./packages/fermi" }
|
||||||
|
|
|
@ -33,7 +33,7 @@ webbrowser = "0.8.0"
|
||||||
infer = "0.11.0"
|
infer = "0.11.0"
|
||||||
dunce = "1.0.2"
|
dunce = "1.0.2"
|
||||||
|
|
||||||
interprocess = { version = "1.1.1", optional = true }
|
interprocess = { version = "1.2.1", optional = true }
|
||||||
futures-util = "0.3.25"
|
futures-util = "0.3.25"
|
||||||
|
|
||||||
[target.'cfg(target_os = "ios")'.dependencies]
|
[target.'cfg(target_os = "ios")'.dependencies]
|
||||||
|
|
|
@ -8,6 +8,7 @@ use crate::events::IpcMessage;
|
||||||
use crate::Config;
|
use crate::Config;
|
||||||
use crate::WebviewHandler;
|
use crate::WebviewHandler;
|
||||||
use dioxus_core::ScopeState;
|
use dioxus_core::ScopeState;
|
||||||
|
use dioxus_core::Template;
|
||||||
use dioxus_core::VirtualDom;
|
use dioxus_core::VirtualDom;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use wry::application::event_loop::EventLoopProxy;
|
use wry::application::event_loop::EventLoopProxy;
|
||||||
|
@ -262,6 +263,8 @@ pub enum EventData {
|
||||||
|
|
||||||
Ipc(IpcMessage),
|
Ipc(IpcMessage),
|
||||||
|
|
||||||
|
TemplateUpdated(Template<'static>),
|
||||||
|
|
||||||
NewWindow,
|
NewWindow,
|
||||||
|
|
||||||
CloseWindow,
|
CloseWindow,
|
||||||
|
|
|
@ -2,43 +2,29 @@
|
||||||
|
|
||||||
use dioxus_core::Template;
|
use dioxus_core::Template;
|
||||||
|
|
||||||
use interprocess::local_socket::{LocalSocketListener, LocalSocketStream};
|
use interprocess::local_socket::LocalSocketStream;
|
||||||
use std::io::{BufRead, BufReader};
|
use std::io::{BufRead, BufReader};
|
||||||
use std::time::Duration;
|
use wry::application::{event_loop::EventLoopProxy, window::WindowId};
|
||||||
use std::{sync::Arc, sync::Mutex};
|
|
||||||
|
|
||||||
fn handle_error(connection: std::io::Result<LocalSocketStream>) -> Option<LocalSocketStream> {
|
use crate::desktop_context::{EventData, UserWindowEvent};
|
||||||
connection
|
|
||||||
.map_err(|error| eprintln!("Incoming connection failed: {}", error))
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn init(proxy: futures_channel::mpsc::UnboundedSender<Template<'static>>) {
|
pub(crate) fn init(proxy: EventLoopProxy<UserWindowEvent>) {
|
||||||
let latest_in_connection: Arc<Mutex<Option<BufReader<LocalSocketStream>>>> =
|
|
||||||
Arc::new(Mutex::new(None));
|
|
||||||
|
|
||||||
let latest_in_connection_handle = latest_in_connection.clone();
|
|
||||||
|
|
||||||
// connect to processes for incoming data
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let temp_file = std::env::temp_dir().join("@dioxusin");
|
let temp_file = std::env::temp_dir().join("@dioxusin");
|
||||||
|
if let Ok(socket) = LocalSocketStream::connect(temp_file.as_path()) {
|
||||||
if let Ok(listener) = LocalSocketListener::bind(temp_file) {
|
let mut buf_reader = BufReader::new(socket);
|
||||||
for conn in listener.incoming().filter_map(handle_error) {
|
|
||||||
*latest_in_connection_handle.lock().unwrap() = Some(BufReader::new(conn));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
loop {
|
loop {
|
||||||
if let Some(conn) = &mut *latest_in_connection.lock().unwrap() {
|
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
match conn.read_line(&mut buf) {
|
match buf_reader.read_line(&mut buf) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let msg: Template<'static> =
|
let template: Template<'static> =
|
||||||
serde_json::from_str(Box::leak(buf.into_boxed_str())).unwrap();
|
serde_json::from_str(Box::leak(buf.into_boxed_str())).unwrap();
|
||||||
proxy.unbounded_send(msg).unwrap();
|
proxy
|
||||||
|
.send_event(UserWindowEvent(
|
||||||
|
EventData::TemplateUpdated(template),
|
||||||
|
unsafe { WindowId::dummy() },
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if err.kind() != std::io::ErrorKind::WouldBlock {
|
if err.kind() != std::io::ErrorKind::WouldBlock {
|
||||||
|
@ -47,8 +33,6 @@ pub(crate) fn init(proxy: futures_channel::mpsc::UnboundedSender<Template<'stati
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// give the error handler time to take the mutex
|
|
||||||
std::thread::sleep(Duration::from_millis(100));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,6 +109,10 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
|
||||||
|
|
||||||
let proxy = event_loop.create_proxy();
|
let proxy = event_loop.create_proxy();
|
||||||
|
|
||||||
|
// Intialize hot reloading if it is enabled
|
||||||
|
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
||||||
|
hot_reload::init(proxy.clone());
|
||||||
|
|
||||||
// We start the tokio runtime *on this thread*
|
// We start the tokio runtime *on this thread*
|
||||||
// Any future we poll later will use this runtime to spawn tasks and for IO
|
// Any future we poll later will use this runtime to spawn tasks and for IO
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
@ -168,6 +172,14 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::UserEvent(event) => match event.0 {
|
Event::UserEvent(event) => match event.0 {
|
||||||
|
EventData::TemplateUpdated(template) => {
|
||||||
|
for webview in webviews.values_mut() {
|
||||||
|
webview.dom.replace_template(template);
|
||||||
|
|
||||||
|
poll_vdom(webview);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
EventData::CloseWindow => {
|
EventData::CloseWindow => {
|
||||||
webviews.remove(&event.1);
|
webviews.remove(&event.1);
|
||||||
|
|
||||||
|
|
13
packages/hot-reload-macro/Cargo.toml
Normal file
13
packages/hot-reload-macro/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "dioxus-hot-reload-macro"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syn = { version = "1.0.107", features = ["full"] }
|
||||||
|
proc-macro2 = { version = "1.0" }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
7
packages/hot-reload-macro/src/lib.rs
Normal file
7
packages/hot-reload-macro/src/lib.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use syn::__private::quote::quote;
|
||||||
|
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn hot_reload(_: TokenStream) -> TokenStream {
|
||||||
|
quote!(dioxus_hot_reload::init(core::env!("CARGO_MANIFEST_DIR"))).into()
|
||||||
|
}
|
17
packages/hot-reload/Cargo.toml
Normal file
17
packages/hot-reload/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "dioxus-hot-reload"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
dioxus-hot-reload-macro = { path = "../hot-reload-macro" }
|
||||||
|
dioxus-rsx = { path = "../rsx" }
|
||||||
|
dioxus-core = { path = "../core", features = ["serialize"] }
|
||||||
|
dioxus-html = { path = "../html", features = ["hot-reload-context"] }
|
||||||
|
|
||||||
|
interprocess = { version = "1.2.1" }
|
||||||
|
notify = "5.0.0"
|
||||||
|
chrono = "0.4.23"
|
||||||
|
serde_json = "1.0.91"
|
84
packages/hot-reload/src/lib.rs
Normal file
84
packages/hot-reload/src/lib.rs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
use std::{
|
||||||
|
io::Write,
|
||||||
|
path::PathBuf,
|
||||||
|
str::FromStr,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub use dioxus_hot_reload_macro::hot_reload;
|
||||||
|
use dioxus_html::HtmlCtx;
|
||||||
|
use dioxus_rsx::hot_reload::{FileMap, UpdateResult};
|
||||||
|
use interprocess::local_socket::LocalSocketListener;
|
||||||
|
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
|
|
||||||
|
pub fn init(path: &'static str) {
|
||||||
|
if let Ok(crate_dir) = PathBuf::from_str(path) {
|
||||||
|
let temp_file = std::env::temp_dir().join("@dioxusin");
|
||||||
|
let channels = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
if let Ok(local_socket_stream) = LocalSocketListener::bind(temp_file.as_path()) {
|
||||||
|
// listen for connections
|
||||||
|
std::thread::spawn({
|
||||||
|
let channels = channels.clone();
|
||||||
|
move || {
|
||||||
|
for connection in local_socket_stream.incoming() {
|
||||||
|
if let Ok(connection) = connection {
|
||||||
|
channels.lock().unwrap().push(connection);
|
||||||
|
println!("Connected to hot reloading 🚀");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// watch for changes
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let mut last_update_time = chrono::Local::now().timestamp();
|
||||||
|
let mut file_map = FileMap::<HtmlCtx>::new(crate_dir.clone());
|
||||||
|
|
||||||
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
|
|
||||||
|
let mut watcher = RecommendedWatcher::new(tx, notify::Config::default()).unwrap();
|
||||||
|
|
||||||
|
let mut examples_path = crate_dir.clone();
|
||||||
|
examples_path.push("examples");
|
||||||
|
let _ = watcher.watch(&examples_path, RecursiveMode::Recursive);
|
||||||
|
let mut src_path = crate_dir.clone();
|
||||||
|
src_path.push("src");
|
||||||
|
let _ = watcher.watch(&src_path, RecursiveMode::Recursive);
|
||||||
|
|
||||||
|
for evt in rx {
|
||||||
|
// Give time for the change to take effect before reading the file
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
|
if chrono::Local::now().timestamp() > last_update_time {
|
||||||
|
if let Ok(evt) = evt {
|
||||||
|
let mut channels = channels.lock().unwrap();
|
||||||
|
for path in &evt.paths {
|
||||||
|
// skip non rust files
|
||||||
|
if path.extension().and_then(|p| p.to_str()) != Some("rs") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find changes to the rsx in the file
|
||||||
|
match file_map.update_rsx(&path, crate_dir.as_path()) {
|
||||||
|
UpdateResult::UpdatedRsx(msgs) => {
|
||||||
|
for msg in msgs {
|
||||||
|
for channel in channels.iter_mut() {
|
||||||
|
let msg = serde_json::to_string(&msg).unwrap();
|
||||||
|
channel.write_all(msg.as_bytes()).unwrap();
|
||||||
|
channel.write_all(&[b'\n']).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UpdateResult::NeedsRebuild => {
|
||||||
|
println!("Rebuild needed... shutting down hot reloading");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last_update_time = chrono::Local::now().timestamp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue