wip: prep to fix bug in create

This commit is contained in:
Jonathan Kelley committed 2022-11-15 23:22:41 -08:00
1 parent e09b6bf813
commit 04bc635ac5
14 files changed
+539 -57

No files matched your search

+8 -6
View File
@@ -16,15 +16,17 @@ fn main() {
}
fn app(cx: Scope) -> Element {
let nf = NodeFactory::new(&cx);
// let nf = NodeFactory::new(&cx);
let mut attrs = dioxus::core::exports::bumpalo::collections::Vec::new_in(nf.bump());
// let mut attrs = dioxus::core::exports::bumpalo::collections::Vec::new_in(nf.bump());
attrs.push(nf.attr("client-id", format_args!("abc123"), None, false));
// attrs.push(nf.attr("client-id", format_args!("abc123"), None, false));
attrs.push(nf.attr("name", format_args!("bob"), None, false));
// attrs.push(nf.attr("name", format_args!("bob"), None, false));
attrs.push(nf.attr("age", format_args!("47"), None, false));
// attrs.push(nf.attr("age", format_args!("47"), None, false));
Some(nf.raw_element("my-element", None, &[], attrs.into_bump_slice(), &[], None))
// Some(nf.raw_element("my-element", None, &[], attrs.into_bump_slice(), &[], None))
todo!()
}
+1 -1
View File
@@ -15,7 +15,7 @@ fn app(cx: Scope) -> Element {
cx.render(rsx! {
div { "hello {name}!" }
Child {}
ChildWithRef{}
ChildWithRef {}
})
}
+2 -1
View File
@@ -2,12 +2,13 @@
//! It also proves that lifetimes work properly, especially when used with use_ref
use dioxus::prelude::*;
use dioxus_ssr::config::SsrConfig;
fn main() {
let mut vdom = VirtualDom::new(example);
vdom.rebuild();
let out = dioxus_ssr::render_vdom_cfg(&vdom, |c| c.newline(true).indent(true));
let out = dioxus_ssr::render_vdom_cfg(&vdom, SsrConfig::default().newline(true).indent(true));
println!("{}", out);
}
+18 -10
View File
@@ -6,15 +6,23 @@ fn main() {
fn app(cx: Scope) -> Element {
cx.render(rsx!(
// Use Map directly to lazily pull elements
(0..10).map(|f| rsx! { "{f}" }),
// Collect into an intermediate collection if necessary, and call into_iter
["a", "b", "c"]
.into_iter()
.map(|f| rsx! { "{f}" })
.collect::<Vec<_>>()
.into_iter(),
// Use optionals
Some(rsx! { "Some" }),
div { id: "123123123",
// Use Map directly to lazily pull elements
// (0..3).map(|f| rsx! { "{f}" }),
// Collect into an intermediate collection if necessary, and call into_iter
["a", "b", "c", "x", "y", "z"]
.into_iter()
.map(|f| rsx! { "{f}" })
.collect::<Vec<_>>()
.into_iter(),
["d", "e", "f"]
.into_iter()
.map(|f| rsx! { "{f}" })
.collect::<Vec<_>>()
.into_iter(),
// Use optionals
// Some(rsx! { "Some" }),
}
))
}
+3 -2
View File
@@ -5,6 +5,7 @@
use std::fmt::Write;
use dioxus::prelude::*;
use dioxus_ssr::config::SsrConfig;
fn main() {
// We can render VirtualDoms
@@ -25,14 +26,14 @@ fn main() {
// We can configure the SSR rendering to add ids for rehydration
println!(
"{}",
dioxus_ssr::render_vdom_cfg(&vdom, |c| c.pre_render(true))
dioxus_ssr::render_vdom_cfg(&vdom, SsrConfig::default().pre_render(true))
);
// We can even render as a writer
let mut file = String::new();
let _ = file.write_fmt(format_args!(
"{}",
dioxus_ssr::TextRenderer::from_vdom(&vdom, Default::default())
dioxus_ssr::SsrRender::default().render_vdom(&vdom)
));
println!("{}", file);
}
+409
View File
@@ -96,3 +96,412 @@ set_node(el, [1,2,3,4])
set_attribute("class", "foo")
append_child(1)
*/
//! Instructions returned by the VirtualDOM on how to modify the Real DOM.
//!
//! This module contains an internal API to generate these instructions.
//!
//! Beware that changing code in this module will break compatibility with
//! interpreters for these types of DomEdits.
use crate::innerlude::*;
use std::{any::Any, fmt::Debug};
/// ## Mutations
///
/// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
/// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
/// applied the edits.
///
/// Mutations are the only link between the RealDOM and the VirtualDOM.
pub struct Mutations<'a> {
/// The list of edits that need to be applied for the RealDOM to match the VirtualDOM.
pub edits: Vec<DomEdit<'a>>,
/// The list of Scopes that were diffed, created, and removed during the Diff process.
pub dirty_scopes: FxHashSet<ScopeId>,
/// The list of nodes to connect to the RealDOM.
pub refs: Vec<NodeRefMutation<'a>>,
}
impl Debug for Mutations<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Mutations")
.field("edits", &self.edits)
.field("noderefs", &self.refs)
.finish()
}
}
/// A `DomEdit` represents a serialized form of the VirtualDom's trait-based API. This allows streaming edits across the
/// network or through FFI boundaries.
#[derive(Debug, PartialEq)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type")
)]
pub enum DomEdit<'bump> {
/// Pop the topmost node from our stack and append them to the node
/// at the top of the stack.
AppendChildren {
/// The parent to append nodes to.
root: Option<u64>,
/// The ids of the children to append.
children: Vec<u64>,
},
/// Replace a given (single) node with a handful of nodes currently on the stack.
ReplaceWith {
/// The ID of the node to be replaced.
root: Option<u64>,
/// The ids of the nodes to replace the root with.
nodes: Vec<u64>,
},
/// Insert a number of nodes after a given node.
InsertAfter {
/// The ID of the node to insert after.
root: Option<u64>,
/// The ids of the nodes to insert after the target node.
nodes: Vec<u64>,
},
/// Insert a number of nodes before a given node.
InsertBefore {
/// The ID of the node to insert before.
root: Option<u64>,
/// The ids of the nodes to insert before the target node.
nodes: Vec<u64>,
},
/// Remove a particular node from the DOM
Remove {
/// The ID of the node to remove.
root: Option<u64>,
},
/// Create a new purely-text node
CreateTextNode {
/// The ID the new node should have.
root: Option<u64>,
/// The textcontent of the node
text: &'bump str,
},
/// Create a new purely-element node
CreateElement {
/// The ID the new node should have.
root: Option<u64>,
/// The tagname of the node
tag: &'bump str,
/// The number of children nodes that will follow this message.
children: u32,
},
/// Create a new purely-comment node with a given namespace
CreateElementNs {
/// The ID the new node should have.
root: Option<u64>,
/// The namespace of the node
tag: &'bump str,
/// The namespace of the node (like `SVG`)
ns: &'static str,
/// The number of children nodes that will follow this message.
children: u32,
},
/// Create a new placeholder node.
/// In most implementations, this will either be a hidden div or a comment node.
CreatePlaceholder {
/// The ID the new node should have.
root: Option<u64>,
},
/// Create a new Event Listener.
NewEventListener {
/// The name of the event to listen for.
event_name: &'static str,
/// The ID of the node to attach the listener to.
scope: ScopeId,
/// The ID of the node to attach the listener to.
root: Option<u64>,
},
/// Remove an existing Event Listener.
RemoveEventListener {
/// The ID of the node to remove.
root: Option<u64>,
/// The name of the event to remove.
event: &'static str,
},
/// Set the textcontent of a node.
SetText {
/// The ID of the node to set the textcontent of.
root: Option<u64>,
/// The textcontent of the node
text: &'bump str,
},
/// Set the value of a node's attribute.
SetAttribute {
/// The ID of the node to set the attribute of.
root: Option<u64>,
/// The name of the attribute to set.
field: &'static str,
/// The value of the attribute.
value: AttributeValue<'bump>,
// value: &'bump str,
/// The (optional) namespace of the attribute.
/// For instance, "style" is in the "style" namespace.
ns: Option<&'bump str>,
},
/// Remove an attribute from a node.
RemoveAttribute {
/// The ID of the node to remove.
root: Option<u64>,
/// The name of the attribute to remove.
name: &'static str,
/// The namespace of the attribute.
ns: Option<&'bump str>,
},
/// Clones a node.
CloneNode {
/// The ID of the node to clone.
id: Option<u64>,
/// The ID of the new node.
new_id: u64,
},
/// Clones the children of a node. (allows cloning fragments)
CloneNodeChildren {
/// The ID of the node to clone.
id: Option<u64>,
/// The ID of the new node.
new_ids: Vec<u64>,
},
/// Navigates to the last node to the first child of the current node.
FirstChild {},
/// Navigates to the last node to the last child of the current node.
NextSibling {},
/// Navigates to the last node to the parent of the current node.
ParentNode {},
/// Stores the last node with a new id.
StoreWithId {
/// The ID of the node to store.
id: u64,
},
/// Manually set the last node.
SetLastNode {
/// The ID to set the last node to.
id: u64,
},
}
use rustc_hash::FxHashSet;
use DomEdit::*;
#[allow(unused)]
impl<'a> Mutations<'a> {
pub(crate) fn new() -> Self {
Self {
edits: Vec::new(),
refs: Vec::new(),
dirty_scopes: Default::default(),
}
}
pub(crate) fn replace_with(&mut self, root: Option<u64>, nodes: Vec<u64>) {
self.edits.push(ReplaceWith { nodes, root });
}
pub(crate) fn insert_after(&mut self, root: Option<u64>, nodes: Vec<u64>) {
self.edits.push(InsertAfter { nodes, root });
}
pub(crate) fn insert_before(&mut self, root: Option<u64>, nodes: Vec<u64>) {
self.edits.push(InsertBefore { nodes, root });
}
pub(crate) fn append_children(&mut self, root: Option<u64>, children: Vec<u64>) {
self.edits.push(AppendChildren { root, children });
}
// Remove Nodes from the dom
pub(crate) fn remove(&mut self, id: Option<u64>) {
self.edits.push(Remove { root: id });
}
// Create
pub(crate) fn create_text_node(&mut self, text: &'a str, id: Option<u64>) {
self.edits.push(CreateTextNode { text, root: id });
}
pub(crate) fn create_element(
&mut self,
tag: &'static str,
ns: Option<&'static str>,
id: Option<u64>,
children: u32,
) {
match ns {
Some(ns) => self.edits.push(CreateElementNs {
root: id,
ns,
tag,
children,
}),
None => self.edits.push(CreateElement {
root: id,
tag,
children,
}),
}
}
// placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
pub(crate) fn create_placeholder(&mut self, id: Option<u64>) {
self.edits.push(CreatePlaceholder { root: id });
}
// events
pub(crate) fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId) {
let Listener {
event,
mounted_node,
..
} = listener;
let element_id = Some(mounted_node.get().unwrap().into());
self.edits.push(NewEventListener {
scope,
event_name: event,
root: element_id,
});
}
pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: Option<u64>) {
self.edits.push(RemoveEventListener { event, root });
}
// modify
pub(crate) fn set_text(&mut self, text: &'a str, root: Option<u64>) {
self.edits.push(SetText { text, root });
}
pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: Option<u64>) {
let Attribute {
value, attribute, ..
} = attribute;
self.edits.push(SetAttribute {
field: attribute.name,
value: value.clone(),
ns: attribute.namespace,
root,
});
}
pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: Option<u64>) {
let Attribute { attribute, .. } = attribute;
self.edits.push(RemoveAttribute {
name: attribute.name,
ns: attribute.namespace,
root,
});
}
pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) {
self.dirty_scopes.insert(scope);
}
pub(crate) fn clone_node(&mut self, id: Option<u64>, new_id: u64) {
self.edits.push(CloneNode { id, new_id });
}
pub(crate) fn clone_node_children(&mut self, id: Option<u64>, new_ids: Vec<u64>) {
self.edits.push(CloneNodeChildren { id, new_ids });
}
pub(crate) fn first_child(&mut self) {
self.edits.push(FirstChild {});
}
pub(crate) fn next_sibling(&mut self) {
self.edits.push(NextSibling {});
}
pub(crate) fn parent_node(&mut self) {
self.edits.push(ParentNode {});
}
pub(crate) fn store_with_id(&mut self, id: u64) {
self.edits.push(StoreWithId { id });
}
pub(crate) fn set_last_node(&mut self, id: u64) {
self.edits.push(SetLastNode { id });
}
}
// refs are only assigned once
pub struct NodeRefMutation<'a> {
pub element: &'a mut Option<once_cell::sync::OnceCell<Box<dyn Any>>>,
pub element_id: ElementId,
}
impl<'a> std::fmt::Debug for NodeRefMutation<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NodeRefMutation")
.field("element_id", &self.element_id)
.finish()
}
}
impl<'a> NodeRefMutation<'a> {
pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
self.element
.as_ref()
.and_then(|f| f.get())
.and_then(|f| f.downcast_ref::<T>())
}
pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.element
.as_mut()
.and_then(|f| f.get_mut())
.and_then(|f| f.downcast_mut::<T>())
}
}
+16 -14
View File
@@ -49,20 +49,19 @@ impl VirtualDom {
let mut dynamic_nodes = template.template.node_paths.iter().enumerate().peekable();
let cur_scope = self.scope_stack.last().copied().unwrap();
println!("creating template: {:#?}", template);
let mut on_stack = 0;
for (root_idx, root) in template.template.roots.iter().enumerate() {
on_stack += match root {
TemplateNode::Element { .. }
| TemplateNode::Text(_)
| TemplateNode::DynamicText { .. } => {
mutations.push(LoadTemplate {
name: template.template.id,
index: root_idx,
});
1
}
mutations.push(LoadTemplate {
name: template.template.id,
index: root_idx,
});
TemplateNode::Dynamic(id) => {
on_stack += match root {
TemplateNode::Element { .. } | TemplateNode::Text(_) => 1,
TemplateNode::DynamicText(id) | TemplateNode::Dynamic(id) => {
self.create_dynamic_node(mutations, template, &template.dynamic_nodes[*id], *id)
}
};
@@ -268,9 +267,12 @@ impl VirtualDom {
}
}
DynamicNode::Fragment { nodes, .. } => nodes
.iter()
.fold(0, |acc, child| acc + self.create(mutations, child)),
DynamicNode::Fragment { nodes, .. } => {
//
nodes
.iter()
.fold(0, |acc, child| acc + self.create(mutations, child))
}
DynamicNode::Placeholder(_) => {
let id = self.next_element(template);
+4 -2
View File
@@ -160,8 +160,10 @@ impl<'a, 'b> IntoDynNode<'a> for VNode<'a> {
impl<'a, 'b, T: IntoDynNode<'a>> IntoDynNode<'a> for Option<T> {
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
// DynamicNode::Fragment { nodes: cx., inner: () }
todo!()
match self {
Some(val) => val.into_vnode(_cx),
None => DynamicNode::Placeholder(Default::default()),
}
}
}
+3
View File
@@ -9,6 +9,7 @@ use std::{
pub type TemplateId = &'static str;
/// A reference to a template along with any context needed to hydrate it
#[derive(Debug)]
pub struct VNode<'a> {
// The ID assigned for the root of this template
pub node_id: Cell<ElementId>,
@@ -106,6 +107,7 @@ pub enum TemplateNode<'a> {
DynamicText(usize),
}
#[derive(Debug)]
pub enum DynamicNode<'a> {
Component {
name: &'static str,
@@ -137,6 +139,7 @@ pub enum TemplateAttribute<'a> {
Dynamic(usize),
}
#[derive(Debug)]
pub struct Attribute<'a> {
pub name: &'a str,
pub value: AttributeValue<'a>,
+2
View File
@@ -50,6 +50,8 @@ impl DesktopController {
let edits = dom.rebuild();
println!("got muts: {:#?}", edits);
{
let mut queue = edit_queue.lock().unwrap();
queue.push(serde_json::to_string(&edits.template_mutations).unwrap());
+1 -1
View File
@@ -7,7 +7,7 @@ use wry::{
const MODULE_LOADER: &str = r#"
<script>
import("./index.js").then(function (module) {
module.main();
module.main();
});
</script>
"#;
+25 -17
View File
@@ -1,7 +1,9 @@
export function main() {
let root = window.document.getElementById("main");
console.log("loading!");
if (root != null) {
window.interpreter = new Interpreter(root);
console.log("properly loaded!");
window.ipc.postMessage(serializeIpcMessage("initialize"));
}
}
@@ -62,13 +64,15 @@ class ListenerMap {
export class Interpreter {
constructor(root) {
console.log("interpreter created", root);
this.root = root;
this.stack = [root];
this.listeners = new ListenerMap(root);
this.nodes = [root];
this.stack = [root];
this.handlers = {};
this.lastNodeWasText = false;
this.nodes = [root];
this.templates = {};
console.log(this);
}
top() {
return this.stack[this.stack.length - 1];
@@ -208,14 +212,11 @@ export class Interpreter {
}
}
handleEdits(edits) {
console.log("handling edits", edits);
console.log("handling edits", edits, this.stack.length);
for (let edit of edits) {
this.handleEdit(edit);
console.log(this.stack);
}
console.log(this.stack);
}
AssignId(path, id) {
this.nodes[id] = this.LoadChild(path);
@@ -235,16 +236,20 @@ export class Interpreter {
node.textContent = value;
this.nodes[id] = node;
}
ReplacePlaceholder(path, m) {
let els = this.stack.splice(this.stack.length - m);
let node = this.LoadChild(path);
node.replaceWith(...els);
}
LoadTemplate(name, index) {
console.log("loading template", name, index);
let node = this.templates[name][index].cloneNode(true);
this.stack.push(node);
}
SaveTemplate(name, m) {
this.templates[name] = this.stack.splice(-m);
this.templates[name] = this.stack.splice(this.stack.length - m);
}
handleEdit(edit) {
console.log("handling edit", edit);
switch (edit.type) {
case "AppendChildren":
this.AppendChildren(edit.m);
@@ -270,6 +275,9 @@ export class Interpreter {
case "ReplaceWith":
this.ReplaceWith(edit.id, edit.m);
break;
case "ReplacePlaceholder":
this.ReplacePlaceholder(edit.path, edit.m);
break;
case "InsertAfter":
this.InsertAfter(edit.id, edit.n);
break;
@@ -288,6 +296,15 @@ export class Interpreter {
case "CreateElementNs":
this.CreateElementNs(edit.tag, edit.id, edit.ns);
break;
case "SetText":
this.SetText(edit.id, edit.text);
break;
case "SetAttribute":
this.SetAttribute(edit.id, edit.name, edit.value, edit.ns);
break;
case "RemoveAttribute":
this.RemoveAttribute(edit.id, edit.name, edit.ns);
break;
case "RemoveEventListener":
this.RemoveEventListener(edit.id, edit.event_name);
break;
@@ -385,15 +402,6 @@ export class Interpreter {
};
this.NewEventListener(edit.event_name, edit.id, handler, event_bubbles(edit.event_name));
break;
case "SetText":
this.SetText(edit.id, edit.text);
break;
case "SetAttribute":
this.SetAttribute(edit.id, edit.field, edit.value, edit.ns);
break;
case "RemoveAttribute":
this.RemoveAttribute(edit.id, edit.name, edit.ns);
break;
}
}
+7 -2
View File
@@ -245,11 +245,16 @@ impl<'a> DynamicContext<'a> {
quote! { ::dioxus::core::TemplateNode::Text(#text) }
}
BodyNode::RawExpr(_) | BodyNode::Text(_) | BodyNode::Component(_) => {
BodyNode::Text(_) | BodyNode::RawExpr(_) | BodyNode::Component(_) => {
let ct = self.dynamic_nodes.len();
self.dynamic_nodes.push(root);
self.node_paths.push(self.current_path.clone());
quote! { ::dioxus::core::TemplateNode::Dynamic(#ct) }
if let BodyNode::Text(_) = root {
quote! { ::dioxus::core::TemplateNode::DynamicText(#ct) }
} else {
quote! { ::dioxus::core::TemplateNode::Dynamic(#ct) }
}
}
}
}
+40 -1
View File
@@ -1,6 +1,8 @@
use std::fmt::Write;
use dioxus_core::VirtualDom;
use dioxus_core::{LazyNodes, ScopeId, VirtualDom};
use crate::config::SsrConfig;
pub fn pre_render(dom: &VirtualDom) -> String {
todo!()
@@ -9,3 +11,40 @@ pub fn pre_render(dom: &VirtualDom) -> String {
pub fn pre_render_to(dom: &VirtualDom, write: impl Write) {
todo!()
}
pub fn render_vdom(dom: &VirtualDom) -> String {
todo!()
// format!("{:}", TextRenderer::from_vdom(dom, SsrConfig::default()))
}
pub fn pre_render_vdom(dom: &VirtualDom) -> String {
todo!()
// format!(
// "{:}",
// TextRenderer::from_vdom(dom, SsrConfig::default().pre_render(true))
// )
}
pub fn render_vdom_cfg(dom: &VirtualDom, cfg: SsrConfig) -> String {
todo!()
// format!(
// "{:}",
// TextRenderer::from_vdom(dom, cfg(SsrConfig::default()))
// )
}
pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
todo!()
// Some(format!(
// "{:}",
// TextRenderer {
// cfg: SsrConfig::default(),
// root: vdom.get_scope(scope).unwrap().root_node(),
// vdom: Some(vdom),
// }
// ))
}
pub fn render_lazy<'a, 'b>(f: LazyNodes<'a, 'b>) -> String {
todo!()
}