wip: get diff compiling

Currently working through the normalization process. Occasionally, we want to iterate through all the nodes that we know have a real image in the dom. However, fragments and components don't directly have a mirror in the dom. This commit is exploring the concept of a custom iterator that explores every node in an array of nodes, returning only valid nodes which may be mounted to the dom. A big issue we're working through is heavily nested rootless nodes - something not terribly common but important nonetheless.

Inferno, React, and Preact all perform a mutative-form of normalization which alter the children list before comparing to the previous. Mostly, we're concerned about fragments in lists and heavily nested components that do not render real elements.
This commit is contained in:
Jonathan Kelley committed 2021-06-21 00:52:37 -04:00
1 parent 79127ea6cd
commit cff0547f1a
6 files changed
+644 -482

No files matched your search

+482 -456
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -54,8 +54,8 @@ pub mod on {
use crate::{
builder::ElementBuilder,
builder::NodeCtx,
innerlude::{Attribute, Listener, VNode},
virtual_dom::NodeCtx,
};
use super::VirtualEvent;
+1 -1
View File
@@ -58,7 +58,7 @@ pub mod prelude {
pub use crate::nodebuilder::LazyNodes;
pub use crate::nodebuilder::ChildrenList;
pub use crate::virtual_dom::NodeCtx;
pub use crate::nodebuilder::NodeCtx;
// pub use nodes::iterables::IterableNodes;
/// This type alias is an internal way of abstracting over the static functions that represent components.
pub use crate::innerlude::FC;
+43 -2
View File
@@ -1,13 +1,15 @@
//! Helpers for building virtual DOM VNodes.
use std::{any::Any, borrow::BorrowMut, fmt::Arguments, intrinsics::transmute, u128};
use std::{
any::Any, borrow::BorrowMut, cell::RefCell, fmt::Arguments, intrinsics::transmute, u128,
};
use crate::{
events::VirtualEvent,
innerlude::{Properties, VComponent, FC},
nodes::{Attribute, Listener, NodeKey, VNode},
prelude::{VElement, VFragment},
virtual_dom::NodeCtx,
virtual_dom::Scope,
};
/// A virtual DOM element builder.
@@ -715,3 +717,42 @@ impl<'a, 'b> ChildrenList<'a, 'b> {
self.children.into_bump_slice()
}
}
// NodeCtx is used to build VNodes in the component's memory space.
// This struct adds metadata to the final VNode about listeners, attributes, and children
#[derive(Clone)]
pub struct NodeCtx<'a> {
pub scope_ref: &'a Scope,
pub listener_id: RefCell<usize>,
}
impl<'a> NodeCtx<'a> {
#[inline]
pub fn bump(&self) -> &'a bumpalo::Bump {
&self.scope_ref.cur_frame().bump
}
fn text(&self, args: Arguments) -> VNode<'a> {
text3(self.bump(), args)
}
fn element<'b, Listeners, Attributes, Children>(
&'b self,
tag_name: &'static str,
) -> ElementBuilder<
'a,
'b,
bumpalo::collections::Vec<'a, Listener<'a>>,
bumpalo::collections::Vec<'a, Attribute<'a>>,
bumpalo::collections::Vec<'a, VNode<'a>>,
> {
ElementBuilder::new(self, tag_name)
}
}
use std::fmt::Debug;
impl Debug for NodeCtx<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
+117 -2
View File
@@ -6,8 +6,8 @@
use crate::{
events::VirtualEvent,
innerlude::{Context, Properties, Scope, ScopeIdx, FC},
nodebuilder::text3,
virtual_dom::{NodeCtx, RealDomNode},
nodebuilder::{text3, NodeCtx},
virtual_dom::RealDomNode,
};
use bumpalo::Bump;
use std::{
@@ -110,6 +110,10 @@ impl<'a> VNode<'a> {
VNode::Suspended => NodeKey::NONE,
}
}
fn get_child(&self, id: u32) -> Option<VNode<'a>> {
todo!()
}
}
#[derive(Clone)]
@@ -379,3 +383,114 @@ impl<'a> VFragment<'a> {
Self { key, children }
}
}
/// This method converts a list of nested real/virtual nodes into a stream of nodes that are definitely associated
/// with the real dom.
///
/// Why?
/// ---
/// Fragments are seen as virtual nodes but are actually a list of possibly-real nodes.
/// JS implementations normalize their node lists when fragments are present. Here, we just create a new iterator
/// that iterates through the recursive nesting of fragments.
///
/// Fragments are stupid and I wish we didn't need to support them.
///
/// This iterator only supports 3 levels of nested fragments
///
pub fn iterate_real_nodes<'a>(nodes: &'a [VNode<'a>]) -> RealNodeIterator<'a> {
RealNodeIterator::new(nodes)
}
struct RealNodeIterator<'a> {
nodes: &'a [VNode<'a>],
// an idx for each level of nesting
// it's highly highly unlikely to hit 4 levels of nested fragments
// so... we just don't support it
nesting_idxs: [Option<u32>; 3],
}
impl<'a> RealNodeIterator<'a> {
fn new(nodes: &'a [VNode<'a>]) -> Self {
Self {
nodes,
nesting_idxs: [None, None, None],
}
}
// advances the cursor to the next element, panicing if we're on the 3rd level and still finding fragments
fn advance_cursor(&mut self) {
match self.nesting_idxs {
[None, ..] => {}
}
}
fn get_current_node(&self) -> Option<&VNode<'a>> {
match self.nesting_idxs {
[None, None, None] => None,
[Some(a), None, None] => Some(&self.nodes[a as usize]),
[Some(a), Some(b), None] => {
//
*&self.nodes[a as usize].get_child(b).as_ref()
}
[Some(a), Some(b), Some(c)] => {
//
*&self.nodes[a as usize]
.get_child(b)
.unwrap()
.get_child(c)
.as_ref()
}
}
}
}
impl<'a> Iterator for RealNodeIterator<'a> {
type Item = &'a VNode<'a>;
fn next(&mut self) -> Option<Self::Item> {
todo!()
// let top_idx = self.nesting_idxs.get_mut(0).unwrap();
// let node = &self.nodes.get_mut(*top_idx as usize);
// if node.is_none() {
// return None;
// }
// let node = node.unwrap();
// match node {
// VNode::Element(_) | VNode::Text(_) => {
// *top_idx += 1;
// return Some(node);
// }
// VNode::Suspended => todo!(),
// // we need access over the scope map
// VNode::Component(_) => todo!(),
// VNode::Fragment(frag) => {
// let nest_idx = self.nesting_idxs.get_mut(1).unwrap();
// let node = &frag.children.get_mut(*nest_idx as usize);
// match node {
// VNode::Element(_) | VNode::Text(_) => {
// *nest_idx += 1;
// return Some(node);
// }
// VNode::Fragment(_) => todo!(),
// VNode::Suspended => todo!(),
// VNode::Component(_) => todo!(),
// }
// }
// }
}
}
mod tests {
use crate::nodebuilder::LazyNodes;
#[test]
fn iterate_nodes() {
// let t1 = LazyNodes::new(|b| {
// //
// });
}
}
-20
View File
@@ -881,26 +881,6 @@ impl PartialOrd for HeightMarker {
}
}
// NodeCtx is used to build VNodes in the component's memory space.
// This struct adds metadata to the final VNode about listeners, attributes, and children
#[derive(Clone)]
pub struct NodeCtx<'a> {
pub scope_ref: &'a Scope,
pub listener_id: RefCell<usize>,
}
impl<'a> NodeCtx<'a> {
pub fn bump(&self) -> &'a Bump {
&self.scope_ref.cur_frame().bump
}
}
impl Debug for NodeCtx<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
#[derive(Debug, PartialEq, Hash)]
pub struct ContextId {
// Which component is the scope in