Something went wrong. Try again.
A monorepo of young ideas
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106//! Development-mode support: hot-swapping the render pool when the server//! bundle is rebuilt, and pointing the page at Vite's dev server for the client.
use std::path::{Path, PathBuf};use std::sync::{Arc, RwLock};use std::time::Duration;
use anyhow::{Context as _, Result};use notify_debouncer_mini::notify::{RecommendedWatcher, RecursiveMode};use notify_debouncer_mini::{DebounceEventResult, Debouncer, new_debouncer};
use crate::pool::RenderPool;
/// A render pool that can be replaced wholesale at runtime.////// Reloading is done by building a brand-new pool and swapping the `Arc`./// In-flight renders keep using the old pool; once they finish and drop their/// handle, the old workers see their channel close and exit.pub type SharedPool = Arc<RwLock<Arc<RenderPool>>>;
pub struct BundleWatcher { _debouncer: Debouncer<RecommendedWatcher>,}
/// Watches `bundle_path` and rebuilds `pool` whenever it changes.////// If the new bundle fails to load (e.g. a syntax error mid-edit), the error is/// logged and the previous pool stays in service.pub fn watch_server_bundle( bundle_path: PathBuf, pool: SharedPool, workers: usize,) -> Result<BundleWatcher> { let dir = bundle_path .parent() .context("bundle path has no parent directory")? .to_path_buf(); let file_name = bundle_path .file_name() .context("bundle path has no file name")? .to_os_string();
let mut debouncer = new_debouncer( Duration::from_millis(200), move |events: DebounceEventResult| { let events = match events { Ok(events) => events, Err(err) => { eprintln!("bundle watcher error: {err}"); return; } }; // Vite also writes a `.map` next to the bundle; only react to the // bundle itself. if !events .iter() .any(|event| event.path.file_name() == Some(file_name.as_os_str())) { return; }
match reload(&bundle_path, workers) { Ok(new_pool) => { *pool .write() .unwrap_or_else(|poisoned| poisoned.into_inner()) = Arc::new(new_pool); println!("server bundle changed; reloaded {workers} render workers"); } Err(err) => eprintln!("failed to reload server bundle, keeping old one:\n{err:#}"), } }, )?; debouncer .watcher() .watch(&dir, RecursiveMode::NonRecursive) .with_context(|| format!("failed to watch {}", dir.display()))?;
Ok(BundleWatcher { _debouncer: debouncer, })}
fn reload(bundle_path: &Path, workers: usize) -> Result<RenderPool> { let bundle = std::fs::read_to_string(bundle_path) .with_context(|| format!("failed to read {}", bundle_path.display()))?; RenderPool::new(Arc::from(bundle), workers)}
/// Script tags that load the client through Vite's dev server with HMR.////// The React Refresh preamble is what `@vitejs/plugin-react` would normally/// inject into `index.html`; since Rust owns the HTML we emit it ourselves.pub fn vite_client_scripts(origin: &str) -> String { format!( r#"<script type="module">import RefreshRuntime from "{origin}/@react-refresh";RefreshRuntime.injectIntoGlobalHook(window);window.$RefreshReg$ = () => {{}};window.$RefreshSig$ = () => (type) => type;window.__vite_plugin_react_preamble_installed__ = true;</script><script type="module" src="{origin}/@vite/client"></script><script type="module" src="{origin}/src/client.jsx"></script>"# )}