mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +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/rsx-rosetta",
|
||||
"packages/signals",
|
||||
"packages/hot-reload",
|
||||
"packages/hot-reload-macro",
|
||||
"docs/guide",
|
||||
]
|
||||
|
||||
|
@ -43,6 +45,7 @@ publish = false
|
|||
dioxus = { path = "./packages/dioxus" }
|
||||
dioxus-desktop = { path = "./packages/desktop", features = ["transparent"] }
|
||||
dioxus-ssr = { path = "./packages/ssr" }
|
||||
dioxus-hot-reload = { path = "./packages/hot-reload" }
|
||||
dioxus-router = { path = "./packages/router" }
|
||||
dioxus-signals = { path = "./packages/signals" }
|
||||
fermi = { path = "./packages/fermi" }
|
||||
|
|
|
@ -33,7 +33,7 @@ webbrowser = "0.8.0"
|
|||
infer = "0.11.0"
|
||||
dunce = "1.0.2"
|
||||
|
||||
interprocess = { version = "1.1.1", optional = true }
|
||||
interprocess = { version = "1.2.1", optional = true }
|
||||
futures-util = "0.3.25"
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies]
|
||||
|
|
|
@ -8,6 +8,7 @@ use crate::events::IpcMessage;
|
|||
use crate::Config;
|
||||
use crate::WebviewHandler;
|
||||
use dioxus_core::ScopeState;
|
||||
use dioxus_core::Template;
|
||||
use dioxus_core::VirtualDom;
|
||||
use serde_json::Value;
|
||||
use wry::application::event_loop::EventLoopProxy;
|
||||
|
@ -262,6 +263,8 @@ pub enum EventData {
|
|||
|
||||
Ipc(IpcMessage),
|
||||
|
||||
TemplateUpdated(Template<'static>),
|
||||
|
||||
NewWindow,
|
||||
|
||||
CloseWindow,
|
||||
|
|
|
@ -2,43 +2,29 @@
|
|||
|
||||
use dioxus_core::Template;
|
||||
|
||||
use interprocess::local_socket::{LocalSocketListener, LocalSocketStream};
|
||||
use interprocess::local_socket::LocalSocketStream;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::time::Duration;
|
||||
use std::{sync::Arc, sync::Mutex};
|
||||
use wry::application::{event_loop::EventLoopProxy, window::WindowId};
|
||||
|
||||
fn handle_error(connection: std::io::Result<LocalSocketStream>) -> Option<LocalSocketStream> {
|
||||
connection
|
||||
.map_err(|error| eprintln!("Incoming connection failed: {}", error))
|
||||
.ok()
|
||||
}
|
||||
use crate::desktop_context::{EventData, UserWindowEvent};
|
||||
|
||||
pub(crate) fn init(proxy: futures_channel::mpsc::UnboundedSender<Template<'static>>) {
|
||||
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
|
||||
pub(crate) fn init(proxy: EventLoopProxy<UserWindowEvent>) {
|
||||
std::thread::spawn(move || {
|
||||
let temp_file = std::env::temp_dir().join("@dioxusin");
|
||||
|
||||
if let Ok(listener) = LocalSocketListener::bind(temp_file) {
|
||||
for conn in listener.incoming().filter_map(handle_error) {
|
||||
*latest_in_connection_handle.lock().unwrap() = Some(BufReader::new(conn));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
if let Some(conn) = &mut *latest_in_connection.lock().unwrap() {
|
||||
if let Ok(socket) = LocalSocketStream::connect(temp_file.as_path()) {
|
||||
let mut buf_reader = BufReader::new(socket);
|
||||
loop {
|
||||
let mut buf = String::new();
|
||||
match conn.read_line(&mut buf) {
|
||||
match buf_reader.read_line(&mut buf) {
|
||||
Ok(_) => {
|
||||
let msg: Template<'static> =
|
||||
let template: Template<'static> =
|
||||
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) => {
|
||||
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();
|
||||
|
||||
// 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*
|
||||
// Any future we poll later will use this runtime to spawn tasks and for IO
|
||||
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 {
|
||||
EventData::TemplateUpdated(template) => {
|
||||
for webview in webviews.values_mut() {
|
||||
webview.dom.replace_template(template);
|
||||
|
||||
poll_vdom(webview);
|
||||
}
|
||||
}
|
||||
|
||||
EventData::CloseWindow => {
|
||||
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