lib.rs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. //! Dioxus WebSys
  2. //!
  3. //! This crate implements a renderer of the Dioxus Virtual DOM for the web browser.
  4. //!
  5. //! While it is possible to render a single component directly, it is not possible to render component trees. For these,
  6. //! an external renderer is needed to progress the component lifecycles. The `WebsysRenderer` shows how to use the Virtual DOM
  7. //! API to progress these lifecycle events to generate a fully-mounted Virtual DOM instance which can be renderer in the
  8. //! `render` method.
  9. //!
  10. //! ```ignore
  11. //! fn main() {
  12. //! let renderer = WebsysRenderer::<()>::new(|_| html! {<div> "Hello world" </div>});
  13. //! let output = renderer.render();
  14. //! assert_eq!(output, "<div>Hello World</div>");
  15. //! }
  16. //! ```
  17. //!
  18. //! The `WebsysRenderer` is particularly useful when needing to cache a Virtual DOM in between requests
  19. use dioxus::{patch::Patch, prelude::VText};
  20. pub use dioxus_core as dioxus;
  21. use dioxus_core::{
  22. events::EventTrigger,
  23. prelude::{bumpalo::Bump, html, DiffMachine, Properties, VNode, VirtualDom, FC},
  24. };
  25. use futures::{channel::mpsc, future, SinkExt, StreamExt};
  26. use mpsc::UnboundedSender;
  27. pub mod interpreter;
  28. /// The `WebsysRenderer` provides a way of rendering a Dioxus Virtual DOM to the browser's DOM.
  29. /// Under the hood, we leverage WebSys and interact directly with the DOM
  30. ///
  31. pub struct WebsysRenderer {
  32. internal_dom: VirtualDom,
  33. }
  34. impl WebsysRenderer {
  35. /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
  36. ///
  37. /// This means that the root component must either consumes its own context, or statics are used to generate the page.
  38. /// The root component can access things like routing in its context.
  39. pub fn new(root: FC<()>) -> Self {
  40. Self::new_with_props(root, ())
  41. }
  42. /// Create a new text-renderer instance from a functional component root.
  43. /// Automatically progresses the creation of the VNode tree to completion.
  44. ///
  45. /// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
  46. pub fn new_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) -> Self {
  47. Self::from_vdom(VirtualDom::new_with_props(root, root_props))
  48. }
  49. /// Create a new text renderer from an existing Virtual DOM.
  50. /// This will progress the existing VDom's events to completion.
  51. pub fn from_vdom(dom: VirtualDom) -> Self {
  52. Self { internal_dom: dom }
  53. }
  54. /// Run the renderer, progressing any events that crop up
  55. /// Yield on event handlers
  56. /// If the dom errors out, self is consumed and the dom is torn down
  57. pub async fn run(self) -> dioxus_core::error::Result<()> {
  58. let WebsysRenderer { mut internal_dom } = self;
  59. // Progress the mount of the root component
  60. internal_dom
  61. .progress()
  62. .expect("Progressing the root failed :(");
  63. // set up the channels to connect listeners to the event loop
  64. let (sender, mut receiver) = mpsc::unbounded::<EventTrigger>();
  65. // Iterate through the nodes, attaching the closure and sender to the listener
  66. {
  67. let mut remote_sender = sender.clone();
  68. let listener = move || {
  69. let event = EventTrigger::new();
  70. wasm_bindgen_futures::spawn_local(async move {
  71. remote_sender
  72. .send(event)
  73. .await
  74. .expect("Updating receiver failed");
  75. })
  76. };
  77. }
  78. // Event loop waits for the receiver to finish up
  79. // TODO! Connect the sender to the virtual dom's suspense system
  80. // Suspense is basically an external event that can force renders to specific nodes
  81. while let Some(event) = receiver.next().await {
  82. // event is triggered
  83. // relevant listeners are ran
  84. // internal state is modified, components are tagged for changes
  85. match internal_dom.progress_with_event(event).await {
  86. Err(_) => {}
  87. Ok(_) => {} // Ok(_) => render_diffs(),
  88. }
  89. // waiting for next event to arrive from the external triggers
  90. }
  91. Ok(())
  92. }
  93. pub fn simple_render(tree: impl for<'a> Fn(&'a Bump) -> VNode<'a>) {
  94. let bump = Bump::new();
  95. let old = html! { <div> </div> }(&bump);
  96. let created = create_dom_node(&old);
  97. let root_node = created.node;
  98. let new = tree(&bump);
  99. let mut machine = DiffMachine::new();
  100. let patches = machine.diff(&old, &new);
  101. // log::info!("There are {:?} patches", patches.len());
  102. let root2 = root_node.clone();
  103. patch(root_node, &patches).expect("Failed to simple render");
  104. let document = web_sys::window().unwrap().document().unwrap();
  105. document.body().unwrap().append_child(&root2);
  106. // log::info!("Succesfully patched the dom");
  107. }
  108. }
  109. use std::collections::HashMap;
  110. use std::collections::HashSet;
  111. use std::{cmp::min, rc::Rc};
  112. use wasm_bindgen::JsCast;
  113. use wasm_bindgen::JsValue;
  114. use web_sys::{Element, Node, Text};
  115. /// Apply all of the patches to our old root node in order to create the new root node
  116. /// that we desire.
  117. /// This is usually used after diffing two virtual nodes.
  118. pub fn patch<N: Into<Node>>(root_node: N, patches: &Vec<Patch>) -> Result<(), JsValue> {
  119. // pub fn patch<N: Into<Node>>(root_node: N, patches: &Vec<Patch>) -> Result<ActiveClosures, JsValue> {
  120. let root_node: Node = root_node.into();
  121. let mut cur_node_idx = 0;
  122. let mut nodes_to_find = HashSet::new();
  123. for patch in patches {
  124. nodes_to_find.insert(patch.node_idx());
  125. }
  126. let mut element_nodes_to_patch = HashMap::new();
  127. let mut text_nodes_to_patch = HashMap::new();
  128. // Closures that were added to the DOM during this patch operation.
  129. // let mut active_closures = HashMap::new();
  130. find_nodes(
  131. root_node,
  132. &mut cur_node_idx,
  133. &mut nodes_to_find,
  134. &mut element_nodes_to_patch,
  135. &mut text_nodes_to_patch,
  136. );
  137. for patch in patches {
  138. let patch_node_idx = patch.node_idx();
  139. if let Some(element) = element_nodes_to_patch.get(&patch_node_idx) {
  140. let new_closures = apply_element_patch(&element, &patch)?;
  141. // active_closures.extend(new_closures);
  142. continue;
  143. }
  144. if let Some(text_node) = text_nodes_to_patch.get(&patch_node_idx) {
  145. apply_text_patch(&text_node, &patch)?;
  146. continue;
  147. }
  148. unreachable!("Getting here means we didn't find the element or next node that we were supposed to patch.")
  149. }
  150. // Ok(active_closures)
  151. Ok(())
  152. }
  153. fn find_nodes(
  154. root_node: Node,
  155. cur_node_idx: &mut usize,
  156. nodes_to_find: &mut HashSet<usize>,
  157. element_nodes_to_patch: &mut HashMap<usize, Element>,
  158. text_nodes_to_patch: &mut HashMap<usize, Text>,
  159. ) {
  160. if nodes_to_find.len() == 0 {
  161. return;
  162. }
  163. // We use child_nodes() instead of children() because children() ignores text nodes
  164. let children = root_node.child_nodes();
  165. let child_node_count = children.length();
  166. // If the root node matches, mark it for patching
  167. if nodes_to_find.get(&cur_node_idx).is_some() {
  168. match root_node.node_type() {
  169. Node::ELEMENT_NODE => {
  170. element_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
  171. }
  172. Node::TEXT_NODE => {
  173. text_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
  174. }
  175. other => unimplemented!("Unsupported root node type: {}", other),
  176. }
  177. nodes_to_find.remove(&cur_node_idx);
  178. }
  179. *cur_node_idx += 1;
  180. for i in 0..child_node_count {
  181. let node = children.item(i).unwrap();
  182. match node.node_type() {
  183. Node::ELEMENT_NODE => {
  184. find_nodes(
  185. node,
  186. cur_node_idx,
  187. nodes_to_find,
  188. element_nodes_to_patch,
  189. text_nodes_to_patch,
  190. );
  191. }
  192. Node::TEXT_NODE => {
  193. if nodes_to_find.get(&cur_node_idx).is_some() {
  194. text_nodes_to_patch.insert(*cur_node_idx, node.unchecked_into());
  195. }
  196. *cur_node_idx += 1;
  197. }
  198. Node::COMMENT_NODE => {
  199. // At this time we do not support user entered comment nodes, so if we see a comment
  200. // then it was a delimiter created by virtual-dom-rs in order to ensure that two
  201. // neighboring text nodes did not get merged into one by the browser. So we skip
  202. // over this virtual-dom-rs generated comment node.
  203. }
  204. _other => {
  205. // Ignoring unsupported child node type
  206. // TODO: What do we do with this situation? Log a warning?
  207. }
  208. }
  209. }
  210. }
  211. // pub type ActiveClosures = HashMap<u32, Vec<DynClosure>>;
  212. // fn apply_element_patch(node: &Element, patch: &Patch) -> Result<ActiveClosures, JsValue> {
  213. fn apply_element_patch(node: &Element, patch: &Patch) -> Result<(), JsValue> {
  214. // let active_closures = HashMap::new();
  215. match patch {
  216. Patch::AddAttributes(_node_idx, attributes) => {
  217. for (attrib_name, attrib_val) in attributes.iter() {
  218. node.set_attribute(attrib_name, attrib_val)?;
  219. }
  220. // Ok(active_closures)
  221. Ok(())
  222. }
  223. Patch::RemoveAttributes(_node_idx, attributes) => {
  224. for attrib_name in attributes.iter() {
  225. node.remove_attribute(attrib_name)?;
  226. }
  227. // Ok(active_closures)
  228. Ok(())
  229. }
  230. Patch::Replace(_node_idx, new_node) => {
  231. let created_node = create_dom_node(&new_node);
  232. node.replace_with_with_node_1(&created_node.node)?;
  233. Ok(())
  234. // Ok(created_node.closures)
  235. }
  236. Patch::TruncateChildren(_node_idx, num_children_remaining) => {
  237. let children = node.child_nodes();
  238. let mut child_count = children.length();
  239. // We skip over any separators that we placed between two text nodes
  240. // -> `<!--ptns-->`
  241. // and trim all children that come after our new desired `num_children_remaining`
  242. let mut non_separator_children_found = 0;
  243. for index in 0 as u32..child_count {
  244. let child = children
  245. .get(min(index, child_count - 1))
  246. .expect("Potential child to truncate");
  247. // If this is a comment node then we know that it is a `<!--ptns-->`
  248. // text node separator that was created in virtual_node/mod.rs.
  249. if child.node_type() == Node::COMMENT_NODE {
  250. continue;
  251. }
  252. non_separator_children_found += 1;
  253. if non_separator_children_found <= *num_children_remaining as u32 {
  254. continue;
  255. }
  256. node.remove_child(&child).expect("Truncated children");
  257. child_count -= 1;
  258. }
  259. Ok(())
  260. // Ok(active_closures)
  261. }
  262. Patch::AppendChildren(_node_idx, new_nodes) => {
  263. let parent = &node;
  264. let mut active_closures = HashMap::new();
  265. for new_node in new_nodes {
  266. let created_node = create_dom_node(&new_node);
  267. // let created_node = new_node.create_dom_node();
  268. parent.append_child(&created_node.node)?;
  269. active_closures.extend(created_node.closures);
  270. }
  271. Ok(())
  272. // Ok(active_closures)
  273. }
  274. Patch::ChangeText(_node_idx, _new_node) => {
  275. unreachable!("Elements should not receive ChangeText patches.")
  276. }
  277. }
  278. }
  279. fn apply_text_patch(node: &Text, patch: &Patch) -> Result<(), JsValue> {
  280. match patch {
  281. Patch::ChangeText(_node_idx, new_node) => {
  282. node.set_node_value(Some(&new_node.text));
  283. }
  284. Patch::Replace(_node_idx, new_node) => {
  285. node.replace_with_with_node_1(&create_dom_node(&new_node).node)?;
  286. // node.replace_with_with_node_1(&new_node.create_dom_node().node)?;
  287. }
  288. other => unreachable!(
  289. "Text nodes should only receive ChangeText or Replace patches, not ",
  290. // other,
  291. // "Text nodes should only receive ChangeText or Replace patches, not {:?}.",
  292. // other,
  293. ),
  294. };
  295. Ok(())
  296. }
  297. /// A node along with all of the closures that were created for that
  298. /// node's events and all of it's child node's events.
  299. pub struct CreatedNode<T> {
  300. /// A `Node` or `Element` that was created from a `VirtualNode`
  301. pub node: T,
  302. /// A map of a node's unique identifier along with all of the Closures for that node.
  303. ///
  304. /// The DomUpdater uses this to look up nodes and see if they're still in the page. If not
  305. /// the reference that we maintain to their closure will be dropped, thus freeing the Closure's
  306. /// memory.
  307. pub closures: HashMap<u32, Vec<DynClosure>>,
  308. }
  309. /// Box<dyn AsRef<JsValue>>> is our js_sys::Closure. Stored this way to allow us to store
  310. /// any Closure regardless of the arguments.
  311. pub type DynClosure = Rc<dyn AsRef<JsValue>>;
  312. impl<T> CreatedNode<T> {
  313. pub fn without_closures<N: Into<T>>(node: N) -> Self {
  314. CreatedNode {
  315. node: node.into(),
  316. closures: HashMap::with_capacity(0),
  317. }
  318. }
  319. }
  320. impl<T> std::ops::Deref for CreatedNode<T> {
  321. type Target = T;
  322. fn deref(&self) -> &Self::Target {
  323. &self.node
  324. }
  325. }
  326. impl From<CreatedNode<Element>> for CreatedNode<Node> {
  327. fn from(other: CreatedNode<Element>) -> CreatedNode<Node> {
  328. CreatedNode {
  329. node: other.node.into(),
  330. closures: other.closures,
  331. }
  332. }
  333. }
  334. fn create_dom_node(node: &VNode<'_>) -> CreatedNode<Node> {
  335. match node {
  336. VNode::Text(text_node) => CreatedNode::without_closures(create_text_node(text_node)),
  337. VNode::Element(element_node) => create_element_node(element_node).into(),
  338. // VNode::Element(element_node) => element_node.create_element_node().into(),
  339. VNode::Suspended => todo!(" not iimplemented yet"),
  340. VNode::Component(_) => todo!(" not iimplemented yet"),
  341. }
  342. }
  343. /// Build a DOM element by recursively creating DOM nodes for this element and it's
  344. /// children, it's children's children, etc.
  345. pub fn create_element_node(node: &dioxus_core::nodes::VElement) -> CreatedNode<Element> {
  346. let document = web_sys::window().unwrap().document().unwrap();
  347. // TODO: enable svg again
  348. // let element = if html_validation::is_svg_namespace(&node.tag_name) {
  349. // document
  350. // .create_element_ns(Some("http://www.w3.org/2000/svg"), &node.tag_name)
  351. // .unwrap()
  352. // } else {
  353. let element = document.create_element(&node.tag_name).unwrap();
  354. // };
  355. let mut closures = HashMap::new();
  356. node.attributes
  357. .iter()
  358. .map(|f| (f.name, f.value))
  359. .for_each(|(name, value)| {
  360. if name == "unsafe_inner_html" {
  361. element.set_inner_html(value);
  362. return;
  363. }
  364. element
  365. .set_attribute(name, value)
  366. .expect("Set element attribute in create element");
  367. });
  368. // if node.events.0.len() > 0 {
  369. // let unique_id = create_unique_identifier();
  370. // element
  371. // .set_attribute("data-vdom-id".into(), &unique_id.to_string())
  372. // .expect("Could not set attribute on element");
  373. // closures.insert(unique_id, vec![]);
  374. // node.events.0.iter().for_each(|(onevent, callback)| {
  375. // // onclick -> click
  376. // let event = &onevent[2..];
  377. // let current_elem: &EventTarget = element.dyn_ref().unwrap();
  378. // current_elem
  379. // .add_event_listener_with_callback(event, callback.as_ref().as_ref().unchecked_ref())
  380. // .unwrap();
  381. // closures
  382. // .get_mut(&unique_id)
  383. // .unwrap()
  384. // .push(Rc::clone(callback));
  385. // });
  386. // }
  387. let mut previous_node_was_text = false;
  388. node.children.iter().for_each(|child| {
  389. // log::info!("Patching child");
  390. match child {
  391. VNode::Text(text_node) => {
  392. let current_node = element.as_ref() as &web_sys::Node;
  393. // We ensure that the text siblings are patched by preventing the browser from merging
  394. // neighboring text nodes. Originally inspired by some of React's work from 2016.
  395. // -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
  396. // -> https://github.com/facebook/react/pull/5753
  397. //
  398. // `ptns` = Percy text node separator
  399. if previous_node_was_text {
  400. let separator = document.create_comment("ptns");
  401. current_node
  402. .append_child(separator.as_ref() as &web_sys::Node)
  403. .unwrap();
  404. }
  405. current_node
  406. .append_child(&create_text_node(&text_node))
  407. .unwrap();
  408. previous_node_was_text = true;
  409. }
  410. VNode::Element(element_node) => {
  411. previous_node_was_text = false;
  412. let child = create_element_node(element_node);
  413. // let child = element_node.create_element_node();
  414. let child_elem: Element = child.node;
  415. closures.extend(child.closures);
  416. element.append_child(&child_elem).unwrap();
  417. }
  418. VNode::Suspended => {
  419. todo!("Not yet supported")
  420. }
  421. VNode::Component(_) => {
  422. todo!("Not yet supported")
  423. }
  424. }
  425. });
  426. // TODO: connect on mount to the event system somehow
  427. // if let Some(on_create_elem) = node.events.0.get("on_create_elem") {
  428. // let on_create_elem: &js_sys::Function = on_create_elem.as_ref().as_ref().unchecked_ref();
  429. // on_create_elem
  430. // .call1(&wasm_bindgen::JsValue::NULL, &element)
  431. // .unwrap();
  432. // }
  433. CreatedNode {
  434. node: element,
  435. closures,
  436. }
  437. }
  438. /// Return a `Text` element from a `VirtualNode`, typically right before adding it
  439. /// into the DOM.
  440. pub fn create_text_node(node: &VText) -> Text {
  441. let document = web_sys::window().unwrap().document().unwrap();
  442. document.create_text_node(&node.text)
  443. }
  444. // /// For any listeners in the tree, attach the sender closure.
  445. // /// When a event is triggered, we convert it into the synthetic event type and dump it back in the Virtual Dom's queu
  446. // fn attach_listeners(sender: &UnboundedSender<EventTrigger>, dom: &VirtualDom) {}
  447. // fn render_diffs() {}