From 089cf24ab3638449abb56d9594ff87bb18ec7f5c Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Sun, 7 Apr 2024 12:13:02 +0800 Subject: [PATCH 1/6] add readme --- readme.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 readme.md diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..00ec16e --- /dev/null +++ b/readme.md @@ -0,0 +1,15 @@ +# How to start? + +* install wasm-pack +* `npm run build` +* cd `examples/hello-world` +* `pnpm install` +* `npm run dev` + +# Articles + +[从零实现 React v18,但 WASM 版 - [1] 项目框架搭建](https://www.paradeto.com/2024/04/03/big-react-wasm-1/) + +[从零实现 React v18,但 WASM 版 - [2] 实现 ReactElement](https://www.paradeto.com/2024/04/04/big-react-wasm-2/) + +[从零实现 React v18,但 WASM 版 - [3] Renderer 和 Reconciler 架构设计](https://www.paradeto.com/2024/04/07/big-react-wasm-3/) From c27745778ef95d3ef5faa10d4a21ebca1fcbc51f Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Tue, 9 Apr 2024 18:57:36 +0800 Subject: [PATCH 2/6] blog-4 --- Cargo.lock | 11 ++ packages/react-dom/src/lib.rs | 4 +- packages/react-reconciler/Cargo.toml | 1 + packages/react-reconciler/src/begin_work.rs | 70 +++++++ packages/react-reconciler/src/child_fiber.rs | 105 +++++++++++ packages/react-reconciler/src/fiber.rs | 172 +++++++++++++++++- packages/react-reconciler/src/fiber_flags.rs | 15 ++ packages/react-reconciler/src/lib.rs | 44 +++-- packages/react-reconciler/src/update_queue.rs | 73 ++++++++ packages/react-reconciler/src/work_loop.rs | 129 +++++++++++++ packages/react-reconciler/src/work_tags.rs | 7 + packages/react/src/lib.rs | 4 +- packages/shared/Cargo.toml | 2 + packages/shared/src/lib.rs | 18 +- 14 files changed, 634 insertions(+), 21 deletions(-) create mode 100644 packages/react-reconciler/src/begin_work.rs create mode 100644 packages/react-reconciler/src/child_fiber.rs create mode 100644 packages/react-reconciler/src/fiber_flags.rs create mode 100644 packages/react-reconciler/src/update_queue.rs create mode 100644 packages/react-reconciler/src/work_loop.rs create mode 100644 packages/react-reconciler/src/work_tags.rs diff --git a/Cargo.lock b/Cargo.lock index 9d49099..a2210d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + [[package]] name = "bumpalo" version = "3.15.4" @@ -92,6 +98,7 @@ dependencies = [ name = "react-reconciler" version = "0.1.0" dependencies = [ + "bitflags", "console_error_panic_hook", "react", "shared", @@ -109,6 +116,10 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "shared" version = "0.1.0" +dependencies = [ + "log", + "web-sys", +] [[package]] name = "syn" diff --git a/packages/react-dom/src/lib.rs b/packages/react-dom/src/lib.rs index 4487e37..a5c3771 100644 --- a/packages/react-dom/src/lib.rs +++ b/packages/react-dom/src/lib.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use wasm_bindgen::prelude::*; use react_reconciler::Reconciler; @@ -13,7 +15,7 @@ mod host_config; #[wasm_bindgen(js_name = createRoot)] pub fn create_root(container: &JsValue) -> Renderer { set_panic_hook(); - let reconciler = Reconciler::new(Box::new(ReactDomHostConfig)); + let reconciler = Reconciler::new(Rc::new(ReactDomHostConfig)); let root = reconciler.create_container(container); let renderer = Renderer::new(root, reconciler); renderer diff --git a/packages/react-reconciler/Cargo.toml b/packages/react-reconciler/Cargo.toml index e30daae..183aaf8 100644 --- a/packages/react-reconciler/Cargo.toml +++ b/packages/react-reconciler/Cargo.toml @@ -20,6 +20,7 @@ shared = { path = "../shared" } # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for # code size when deploying. console_error_panic_hook = { version = "0.1.7", optional = true } +bitflags = "2.5.0" [dev-dependencies] diff --git a/packages/react-reconciler/src/begin_work.rs b/packages/react-reconciler/src/begin_work.rs new file mode 100644 index 0000000..3a21d3b --- /dev/null +++ b/packages/react-reconciler/src/begin_work.rs @@ -0,0 +1,70 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use wasm_bindgen::JsValue; + +use shared::{derive_from_js_value, log}; + +use crate::child_fiber::{mount_child_fibers, reconcile_child_fibers}; +use crate::fiber::FiberNode; +use crate::update_queue::process_update_queue; +use crate::work_tags::WorkTag; + +pub fn begin_work(work_in_progress: Rc>) -> Option>> { + let tag = work_in_progress.clone().borrow().tag.clone(); + return match tag { + WorkTag::FunctionComponent => None, + WorkTag::HostRoot => update_host_root(work_in_progress.clone()), + WorkTag::HostComponent => update_host_component(work_in_progress.clone()), + WorkTag::HostText => None + }; +} + +pub fn update_host_root( + work_in_progress: Rc>, +) -> Option>> { + process_update_queue(work_in_progress.clone()); + let next_children = work_in_progress.clone().borrow().memoized_state.clone(); + log!("tag {:?}", next_children); + reconcile_children(work_in_progress.clone(), next_children); + log!("tag {:?}", work_in_progress.clone().borrow().child.clone()); + + work_in_progress.clone().borrow().child.clone() +} + + +pub fn update_host_component( + work_in_progress: Rc>, +) -> Option>> { + let work_in_progress = Rc::clone(&work_in_progress); + + let next_children = { + let ref_fiber_node = work_in_progress.borrow(); + derive_from_js_value(ref_fiber_node.pending_props.clone().unwrap(), "children") + }; + + { + reconcile_children(work_in_progress.clone(), next_children); + } + work_in_progress.clone().borrow().child.clone() +} + +pub fn reconcile_children(work_in_progress: Rc>, children: Option>) { + let work_in_progress = Rc::clone(&work_in_progress); + let current = { work_in_progress.borrow().alternate.clone() }; + if current.is_some() { + // update + work_in_progress.borrow_mut().child = reconcile_child_fibers( + work_in_progress.clone(), + current.clone(), + children, + ) + } else { + // mount + work_in_progress.borrow_mut().child = mount_child_fibers( + work_in_progress.clone(), + None, + children, + ) + } +} \ No newline at end of file diff --git a/packages/react-reconciler/src/child_fiber.rs b/packages/react-reconciler/src/child_fiber.rs new file mode 100644 index 0000000..4ce2e7f --- /dev/null +++ b/packages/react-reconciler/src/child_fiber.rs @@ -0,0 +1,105 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use wasm_bindgen::JsValue; +use web_sys::js_sys::{Object, Reflect}; + +use shared::{derive_from_js_value, log, REACT_ELEMENT_TYPE}; + +use crate::fiber::FiberNode; +use crate::fiber_flags::Flags; +use crate::work_tags::WorkTag; + +fn place_single_child( + fiber: Rc>, + should_track_effect: bool, +) -> Rc> { + if should_track_effect { + let fiber = fiber.clone(); + let mut fiber = fiber.borrow_mut(); + fiber.flags |= Flags::Placement; + } + return fiber; +} + +fn reconcile_single_element( + return_fiber: Rc>, + current_first_child: Option>>, + element: Option>, +) -> Rc> { + let mut fiber = FiberNode::create_fiber_from_element(element.unwrap()); + fiber._return = Some(Rc::downgrade(&return_fiber)); + Rc::new(RefCell::new(fiber)) +} + +fn reconcile_single_text_node( + return_fiber: Rc>, + current_first_child: Option>>, + content: Option>, +) -> Rc> { + let props = Object::new(); + Reflect::set( + &props, + &JsValue::from("content"), + &content.unwrap().clone(), + ) + .expect("props panic"); + let mut created = FiberNode::new(WorkTag::HostText, Some(Rc::new(Object::into(props))), None); + created._return = Some(Rc::downgrade(&return_fiber.clone())); + Rc::new(RefCell::new(created)) +} + +fn _reconcile_child_fibers( + return_fiber: Rc>, + current_first_child: Option>>, + new_child: Option>, + should_track_effect: bool, +) -> Option>> { + if new_child.is_some() { + let new_child = Rc::clone(&new_child.unwrap()); + + if new_child.is_string() { + return Some(place_single_child( + reconcile_single_text_node( + return_fiber, + current_first_child, + Some(new_child.clone()), + ), + should_track_effect, + )); + } else if new_child.is_object() { + log!("{:?}", new_child); + let _typeof = Rc::clone(&derive_from_js_value(new_child.clone(), "$$typeof").unwrap()) + .as_string() + .unwrap(); + if _typeof == REACT_ELEMENT_TYPE { + return Some(place_single_child( + reconcile_single_element( + return_fiber, + current_first_child, + Some(new_child.clone()), + ), + should_track_effect, + )); + } + } + } + log!("Unsupported child type when reconcile"); + return None; +} + +pub fn reconcile_child_fibers( + return_fiber: Rc>, + current_first_child: Option>>, + new_child: Option>, +) -> Option>> { + _reconcile_child_fibers(return_fiber, current_first_child, new_child, true) +} + +pub fn mount_child_fibers( + return_fiber: Rc>, + current_first_child: Option>>, + new_child: Option>, +) -> Option>> { + _reconcile_child_fibers(return_fiber, current_first_child, new_child, false) +} \ No newline at end of file diff --git a/packages/react-reconciler/src/fiber.rs b/packages/react-reconciler/src/fiber.rs index 96c2bc7..379da72 100644 --- a/packages/react-reconciler/src/fiber.rs +++ b/packages/react-reconciler/src/fiber.rs @@ -1,2 +1,172 @@ +use std::any::Any; +use std::cell::RefCell; +use std::ops::Deref; +use std::rc::{Rc, Weak}; + +use wasm_bindgen::JsValue; + +use shared::derive_from_js_value; + +use crate::fiber_flags::Flags; +use crate::update_queue::{Update, UpdateQueue, UpdateType}; +use crate::work_tags::WorkTag; + +trait Node {} + #[derive(Debug)] -pub struct FiberRootNode {} \ No newline at end of file +pub enum StateNode { + FiberRootNode(Rc>), + Element(Rc), +} + +#[derive(Debug)] +pub struct FiberNode { + pub tag: WorkTag, + pub pending_props: Option>, + key: Option, + pub state_node: Option>, + pub update_queue: Option>>, + pub _return: Option>>, + pub sibling: Option>>, + pub child: Option>>, + pub alternate: Option>>, + pub _type: Option>, + pub flags: Flags, + pub subtree_flags: Flags, + pub memoized_props: JsValue, + pub memoized_state: Option>, +} + +impl Node for FiberNode {} + +impl FiberNode { + pub fn new(tag: WorkTag, pending_props: Option>, key: Option) -> Self { + Self { + tag, + pending_props, + key, + state_node: None, + update_queue: None, + _return: None, + sibling: None, + child: None, + alternate: None, + _type: None, + memoized_props: JsValue::null(), + memoized_state: None, + flags: Flags::NoFlags, + subtree_flags: Flags::NoFlags, + } + } + + pub fn create_fiber_from_element(ele: Rc) -> Self { + let _type = derive_from_js_value(ele.clone(), "type"); + let key = match derive_from_js_value(ele.clone(), "key") { + None => None, + Some(k) => k.as_string() + }; + let props = derive_from_js_value(ele.clone(), "props"); + + let mut fiber_tag = WorkTag::FunctionComponent; + if _type.is_some() && (*_type.as_ref().unwrap()).is_string() { + fiber_tag = WorkTag::HostComponent + } + let mut fiber = FiberNode::new(fiber_tag, props, key); + fiber._type = _type; + fiber + } + + pub fn enqueue_update(&mut self, update: Update) { + let mut update_queue = match &self.update_queue { + None => { + return; + } + Some(a) => a.clone() + }; + + let mut u = update_queue.borrow_mut(); + u.shared.pending = Some(update); + } + + pub fn initialize_update_queue(&mut self) { + self.update_queue = Some(Rc::new(RefCell::new(UpdateQueue { + shared: UpdateType { + pending: Some(Update { action: None }), + }, + }))); + } + + pub fn create_work_in_progress( + current: Rc>, + pending_props: Rc, + ) -> Rc> { + let c_rc = Rc::clone(¤t); + let w = { + let c = c_rc.borrow(); + c.deref().alternate.clone() + }; + + return if w.is_none() { + let mut wip = { + let c = c_rc.borrow(); + let mut wip = FiberNode::new(c.tag.clone(), c.pending_props.clone(), c.key.clone()); + wip.update_queue = Some(c.update_queue.as_ref().unwrap().clone()); + wip.flags = c.flags.clone(); + wip.child = c.child.clone(); + wip.memoized_props = c.memoized_props.clone(); + wip.memoized_state = c.memoized_state.clone(); + wip + }; + wip._type = c_rc.borrow()._type.clone(); + wip.state_node = c_rc.borrow().state_node.clone(); + wip.alternate = Some(current); + let wip_rc = Rc::new(RefCell::new(wip)); + let mut fibler_node = c_rc.borrow_mut(); + fibler_node.alternate = Some(wip_rc.clone()); + wip_rc + } else { + let c = c_rc.borrow(); + let a = w.clone().unwrap(); + let mut wip = a.borrow_mut(); + + wip.pending_props = Some(pending_props.clone()); + wip.update_queue = Some(c.update_queue.as_ref().unwrap().clone()); + wip.flags = c.flags.clone(); + wip.child = Some(Rc::clone(c.child.as_ref().unwrap())); + wip.memoized_props = c.memoized_props.clone(); + wip.memoized_state = c.memoized_state.clone(); + w.clone().unwrap() + }; + } + + pub fn derive_state_node(fiber: Rc>) -> Option> { + let state_node = fiber.clone().borrow().state_node.clone(); + if state_node.is_none() { + return None; + } + + Some(match &*state_node.unwrap().clone() { + StateNode::FiberRootNode(root) => root.clone().borrow().container.clone(), + StateNode::Element(ele) => ele.clone(), + }) + } +} + +#[derive(Debug)] +pub struct FiberRootNode { + pub container: Rc, + pub current: Rc>, + pub finished_work: Option>>, +} + +impl Node for FiberRootNode {} + +impl FiberRootNode { + pub fn new(container: Rc, host_root_fiber: Rc>) -> Self { + Self { + container, + current: host_root_fiber, + finished_work: None, + } + } +} \ No newline at end of file diff --git a/packages/react-reconciler/src/fiber_flags.rs b/packages/react-reconciler/src/fiber_flags.rs new file mode 100644 index 0000000..8c7ced2 --- /dev/null +++ b/packages/react-reconciler/src/fiber_flags.rs @@ -0,0 +1,15 @@ +use bitflags::bitflags; + +bitflags! { + #[derive(Debug, Clone)] + pub struct Flags: u8 { + const NoFlags = 0b00000000; + const Placement = 0b00000010; + const Update = 0b00000100; + const ChildDeletion = 0b00010000; + } +} + +pub fn get_mutation_mask() -> Flags { + Flags::Placement | Flags::Update | Flags::ChildDeletion +} \ No newline at end of file diff --git a/packages/react-reconciler/src/lib.rs b/packages/react-reconciler/src/lib.rs index 31ddbf6..c54e31d 100644 --- a/packages/react-reconciler/src/lib.rs +++ b/packages/react-reconciler/src/lib.rs @@ -3,12 +3,21 @@ use std::cell::RefCell; use std::rc::Rc; use wasm_bindgen::JsValue; -use web_sys::{Element, window}; -use web_sys::js_sys::Reflect; -use crate::fiber::FiberRootNode; +use shared::log; + +use crate::fiber::{FiberNode, FiberRootNode, StateNode}; +use crate::update_queue::{create_update, enqueue_update}; +use crate::work_loop::WorkLoop; +use crate::work_tags::WorkTag; pub mod fiber; +mod fiber_flags; +mod work_tags; +mod update_queue; +mod work_loop; +mod begin_work; +mod child_fiber; pub trait HostConfig { fn create_text_instance(&self, content: String) -> Rc; @@ -18,28 +27,31 @@ pub trait HostConfig { } pub struct Reconciler { - host_config: Box, + host_config: Rc, } impl Reconciler { - pub fn new(host_config: Box) -> Self { + pub fn new(host_config: Rc) -> Self { Reconciler { host_config } } pub fn create_container(&self, container: &JsValue) -> Rc> { - Rc::new(RefCell::new(FiberRootNode {})) + let host_root_fiber = Rc::new(RefCell::new(FiberNode::new(WorkTag::HostRoot, None, None))); + host_root_fiber.clone().borrow_mut().initialize_update_queue(); + let root = Rc::new(RefCell::new(FiberRootNode::new(Rc::new(container.clone()), host_root_fiber.clone()))); + let r1 = root.clone(); + host_root_fiber.borrow_mut().state_node = Some(Rc::new(StateNode::FiberRootNode(r1))); + log!("create_container, state_node {:?}", FiberNode::derive_state_node(host_root_fiber)); + root.clone() } pub fn update_container(&self, element: Rc, root: Rc>) { - let props = Reflect::get(&*element, &JsValue::from_str("props")).unwrap(); - let _type = Reflect::get(&*element, &JsValue::from_str("type")).unwrap(); - let children = Reflect::get(&props, &JsValue::from_str("children")).unwrap(); - let text_instance = self.host_config.create_text_instance(children.as_string().unwrap()); - let div_instance = self.host_config.create_instance(_type.as_string().unwrap()); - self.host_config.append_initial_child(div_instance.clone(), text_instance); - let window = window().unwrap(); - let document = window.document().unwrap(); - let body = document.body().expect("document should have a body"); - body.append_child(&*div_instance.clone().downcast::().unwrap()); + let host_root_fiber = Rc::clone(&root).borrow().current.clone(); + let update = create_update(element); + enqueue_update(host_root_fiber.borrow(), update); + log!("update_container, state_node {:?}", FiberNode::derive_state_node(host_root_fiber.clone())); + + let mut work_loop = WorkLoop::new(self.host_config.clone()); + work_loop.schedule_update_on_fiber(host_root_fiber); } } diff --git a/packages/react-reconciler/src/update_queue.rs b/packages/react-reconciler/src/update_queue.rs new file mode 100644 index 0000000..39694cc --- /dev/null +++ b/packages/react-reconciler/src/update_queue.rs @@ -0,0 +1,73 @@ +use std::cell::{Ref, RefCell}; +use std::rc::Rc; + +use wasm_bindgen::{JsCast, JsValue}; +use web_sys::js_sys::Function; + +use shared::log; + +use crate::fiber::FiberNode; + +#[derive(Clone, Debug)] +pub struct UpdateAction; + + +#[derive(Clone, Debug)] +pub struct Update { + pub action: Option>, +} + +#[derive(Clone, Debug)] +pub struct UpdateType { + pub pending: Option, +} + + +#[derive(Clone, Debug)] +pub struct UpdateQueue { + pub shared: UpdateType, +} + + +pub fn create_update(action: Rc) -> Update { + Update { action: Some(action) } +} + +pub fn enqueue_update(fiber: Ref, update: Update) { + if fiber.update_queue.is_some() { + let uq = fiber.update_queue.clone().unwrap(); + let mut update_queue = uq.borrow_mut(); + update_queue.shared.pending = Some(update); + } +} + +pub fn process_update_queue(fiber: Rc>) { + let mut rc_fiber = fiber.clone(); + let mut fiber = rc_fiber.borrow_mut(); + let mut new_state = None; + match fiber.update_queue.clone() { + None => { + log!("{:?} process_update_queue, update_queue is empty", fiber) + } + Some(q) => { + let update_queue = q.clone(); + let pending = update_queue.clone().borrow().shared.pending.clone(); + update_queue.borrow_mut().shared.pending = None; + if pending.is_some() { + let action = pending.unwrap().action; + match action { + None => {} + Some(action) => { + let f = action.dyn_ref::(); + new_state = match f { + None => Some(action.clone()), + Some(f) => Some(Rc::new(f.call0(&JsValue::null()).unwrap())), + } + } + } + } + } + } + + fiber.memoized_state = new_state +} \ No newline at end of file diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs new file mode 100644 index 0000000..9fb3801 --- /dev/null +++ b/packages/react-reconciler/src/work_loop.rs @@ -0,0 +1,129 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use wasm_bindgen::JsValue; + +use shared::log; + +use crate::begin_work::begin_work; +use crate::fiber::{FiberNode, FiberRootNode, StateNode}; +use crate::HostConfig; +use crate::work_tags::WorkTag; + +static mut WORK_IN_PROGRESS: Option>> = None; + +pub struct WorkLoop { + work_in_progress: Option>>, +} + +impl WorkLoop { + pub fn new(host_config: Rc) -> Self { + Self { + work_in_progress: None, + } + } + pub fn schedule_update_on_fiber(&mut self, fiber: Rc>) { + let root = self.mark_update_lane_from_fiber_to_root(fiber); + if root.is_none() { + return; + } + log!( + "schedule_update_on_fiber - root container: {:?}", + root.clone() + .unwrap() + .clone() + .borrow() + .container + ); + + self.ensure_root_is_scheduled(root.unwrap()) + } + + pub fn mark_update_lane_from_fiber_to_root( + &self, + fiber: Rc>, + ) -> Option>> { + let mut node = Rc::clone(&fiber); + let mut parent = Rc::clone(&fiber).borrow()._return.clone(); + + while parent.is_some() { + node = parent.clone().unwrap().upgrade().unwrap(); + let rc = Rc::clone(&parent.unwrap().upgrade().unwrap()); + let rc_ref = rc.borrow(); + let next = match rc_ref._return.as_ref() { + None => None, + Some(node) => { + let a = Rc::downgrade(&node.upgrade().unwrap()); + Some(a) + } + }; + parent = next; + } + + let fiber_node_rc = Rc::clone(&node); + let fiber_node = fiber_node_rc.borrow(); + if fiber_node.tag == WorkTag::HostRoot { + match fiber_node.state_node.clone() { + None => {} + Some(state_node) => { + let state_node = state_node; + return match &*state_node { + StateNode::FiberRootNode(fiber_root_node) => { + Some(Rc::clone(&fiber_root_node)) + } + StateNode::Element(_) => todo!(), + }; + } + } + } + + None + } + + fn ensure_root_is_scheduled(&mut self, root: Rc>) { + self.perform_sync_work_on_root(root); + } + + fn perform_sync_work_on_root(&mut self, root: Rc>) { + self.prepare_fresh_stack(Rc::clone(&root)); + + loop { + self.work_loop(); + break; + } + + // commit + } + + fn prepare_fresh_stack(&mut self, root: Rc>) { + let root = Rc::clone(&root); + self.work_in_progress = Some(FiberNode::create_work_in_progress( + root.borrow().current.clone(), + Rc::new(JsValue::null()), + )); + } + + fn work_loop(&mut self) { + while self.work_in_progress.is_some() { + log!( + "work_loop - work_in_progress {:?}", + self.work_in_progress.clone().unwrap().clone().borrow().tag + ); + self.perform_unit_of_work(self.work_in_progress.clone().unwrap()); + } + } + + fn perform_unit_of_work(&mut self, fiber: Rc>) { + let next = begin_work(fiber.clone()); + + if next.is_none() { + // self.complete_unit_of_work(fiber.clone()) + } else { + log!( + "perform_unit_of_work - next {:?}", + next.clone().unwrap().clone().borrow().tag + ); + self.work_in_progress = Some(next.unwrap()); + } + } +} \ No newline at end of file diff --git a/packages/react-reconciler/src/work_tags.rs b/packages/react-reconciler/src/work_tags.rs new file mode 100644 index 0000000..187a6ce --- /dev/null +++ b/packages/react-reconciler/src/work_tags.rs @@ -0,0 +1,7 @@ +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum WorkTag { + FunctionComponent = 0, + HostRoot = 3, + HostComponent = 5, + HostText = 6, +} \ No newline at end of file diff --git a/packages/react/src/lib.rs b/packages/react/src/lib.rs index 03d41f8..5bdaaab 100644 --- a/packages/react/src/lib.rs +++ b/packages/react/src/lib.rs @@ -8,11 +8,11 @@ pub fn jsx_dev(_type: &JsValue, config: &JsValue, key: &JsValue) -> JsValue { let react_element = Object::new(); Reflect::set( &react_element, - &"&&typeof".into(), + &"$$typeof".into(), &JsValue::from_str(REACT_ELEMENT_TYPE), ) .expect("$$typeof panic"); - Reflect::set(&react_element, &"type".into(), _type).expect("_type panic"); + Reflect::set(&react_element, &"type".into(), _type).expect("type panic"); Reflect::set(&react_element, &"key".into(), key).expect("key panic"); let conf = config.dyn_ref::().unwrap(); diff --git a/packages/shared/Cargo.toml b/packages/shared/Cargo.toml index 886aebd..e6f849b 100644 --- a/packages/shared/Cargo.toml +++ b/packages/shared/Cargo.toml @@ -6,3 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +web-sys = "0.3.69" +log = "0.4.21" diff --git a/packages/shared/src/lib.rs b/packages/shared/src/lib.rs index 3fd8ffe..aa6123a 100644 --- a/packages/shared/src/lib.rs +++ b/packages/shared/src/lib.rs @@ -1,3 +1,8 @@ +use std::rc::Rc; + +use web_sys::js_sys::Reflect; +use web_sys::wasm_bindgen::JsValue; + pub static REACT_ELEMENT_TYPE: &str = "react.element"; #[macro_export] @@ -5,4 +10,15 @@ macro_rules! log { ( $( $t:tt )* ) => { web_sys::console::log_1(&format!( $( $t )* ).into()); } -} \ No newline at end of file +} + +pub fn derive_from_js_value(js_value: Rc, str: &str) -> Option> { + match Reflect::get(&js_value, &JsValue::from_str(str)) { + Ok(v) => Some(Rc::new(v)), + Err(_) => { + log!("derive {} from {:?} error", str, js_value); + None + } + } +} + From b6309a14ad47ce13c8f913f8dad8647e96298aee Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Wed, 10 Apr 2024 21:14:31 +0800 Subject: [PATCH 3/6] blog-4 print FiberRootNode --- Cargo.lock | 1 - packages/react-reconciler/src/fiber.rs | 30 ++++++++++++++++++---- packages/react-reconciler/src/work_loop.rs | 2 ++ packages/shared/Cargo.toml | 2 +- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a2210d5..ec39ddf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,7 +117,6 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" name = "shared" version = "0.1.0" dependencies = [ - "log", "web-sys", ] diff --git a/packages/react-reconciler/src/fiber.rs b/packages/react-reconciler/src/fiber.rs index 379da72..29ea5d1 100644 --- a/packages/react-reconciler/src/fiber.rs +++ b/packages/react-reconciler/src/fiber.rs @@ -1,5 +1,7 @@ use std::any::Any; use std::cell::RefCell; +use std::collections::VecDeque; +use std::fmt::{Debug, Formatter}; use std::ops::Deref; use std::rc::{Rc, Weak}; @@ -11,8 +13,6 @@ use crate::fiber_flags::Flags; use crate::update_queue::{Update, UpdateQueue, UpdateType}; use crate::work_tags::WorkTag; -trait Node {} - #[derive(Debug)] pub enum StateNode { FiberRootNode(Rc>), @@ -37,7 +37,6 @@ pub struct FiberNode { pub memoized_state: Option>, } -impl Node for FiberNode {} impl FiberNode { pub fn new(tag: WorkTag, pending_props: Option>, key: Option) -> Self { @@ -152,14 +151,12 @@ impl FiberNode { } } -#[derive(Debug)] pub struct FiberRootNode { pub container: Rc, pub current: Rc>, pub finished_work: Option>>, } -impl Node for FiberRootNode {} impl FiberRootNode { pub fn new(container: Rc, host_root_fiber: Rc>) -> Self { @@ -169,4 +166,27 @@ impl FiberRootNode { finished_work: None, } } +} + +impl Debug for FiberRootNode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut root = self.current.clone().borrow().alternate.clone(); + Ok(if let Some(node) = root { + let mut queue = VecDeque::new(); + queue.push_back(Rc::clone(&node)); + + while let Some(current) = queue.pop_front() { + let current_ref = current.borrow(); + writeln!(f, "{:?} -> ", current_ref.tag); + if let Some(ref child) = current_ref.child { + queue.push_back(Rc::clone(child)); + let mut sibling = child.clone().borrow().sibling.clone(); + while sibling.is_some() { + queue.push_back(Rc::clone(sibling.as_ref().unwrap())); + sibling = sibling.as_ref().unwrap().clone().borrow().sibling.clone(); + } + } + } + }) + } } \ No newline at end of file diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index 9fb3801..74e063d 100644 --- a/packages/react-reconciler/src/work_loop.rs +++ b/packages/react-reconciler/src/work_loop.rs @@ -92,6 +92,7 @@ impl WorkLoop { break; } + log!("{:?}", *root) // commit } @@ -118,6 +119,7 @@ impl WorkLoop { if next.is_none() { // self.complete_unit_of_work(fiber.clone()) + self.work_in_progress = None; } else { log!( "perform_unit_of_work - next {:?}", diff --git a/packages/shared/Cargo.toml b/packages/shared/Cargo.toml index e6f849b..1d3b016 100644 --- a/packages/shared/Cargo.toml +++ b/packages/shared/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" [dependencies] web-sys = "0.3.69" -log = "0.4.21" + From 886b68b0cccdf86425e77bc9836e38c0d5805bbb Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Thu, 11 Apr 2024 10:40:42 +0800 Subject: [PATCH 4/6] blog-4 change Weak to Rc --- examples/hello-world/src/main.tsx | 4 +- packages/react-reconciler/src/begin_work.rs | 4 +- packages/react-reconciler/src/child_fiber.rs | 4 +- packages/react-reconciler/src/fiber.rs | 59 +++++++++++++++++--- packages/react-reconciler/src/work_loop.rs | 8 +-- pnpm-lock.yaml | 5 ++ 6 files changed, 66 insertions(+), 18 deletions(-) create mode 100644 pnpm-lock.yaml diff --git a/examples/hello-world/src/main.tsx b/examples/hello-world/src/main.tsx index e70c233..661991b 100644 --- a/examples/hello-world/src/main.tsx +++ b/examples/hello-world/src/main.tsx @@ -1,7 +1,9 @@ import {createRoot} from 'react-dom' -const comp =
hello world
+const comp =
+

good

+
const root = createRoot(document.getElementById("root")) root.render(comp) diff --git a/packages/react-reconciler/src/begin_work.rs b/packages/react-reconciler/src/begin_work.rs index 3a21d3b..5a3765a 100644 --- a/packages/react-reconciler/src/begin_work.rs +++ b/packages/react-reconciler/src/begin_work.rs @@ -27,8 +27,6 @@ pub fn update_host_root( let next_children = work_in_progress.clone().borrow().memoized_state.clone(); log!("tag {:?}", next_children); reconcile_children(work_in_progress.clone(), next_children); - log!("tag {:?}", work_in_progress.clone().borrow().child.clone()); - work_in_progress.clone().borrow().child.clone() } @@ -42,7 +40,7 @@ pub fn update_host_component( let ref_fiber_node = work_in_progress.borrow(); derive_from_js_value(ref_fiber_node.pending_props.clone().unwrap(), "children") }; - + { reconcile_children(work_in_progress.clone(), next_children); } diff --git a/packages/react-reconciler/src/child_fiber.rs b/packages/react-reconciler/src/child_fiber.rs index 4ce2e7f..3469df1 100644 --- a/packages/react-reconciler/src/child_fiber.rs +++ b/packages/react-reconciler/src/child_fiber.rs @@ -28,7 +28,7 @@ fn reconcile_single_element( element: Option>, ) -> Rc> { let mut fiber = FiberNode::create_fiber_from_element(element.unwrap()); - fiber._return = Some(Rc::downgrade(&return_fiber)); + fiber._return = Some(return_fiber.clone()); Rc::new(RefCell::new(fiber)) } @@ -45,7 +45,7 @@ fn reconcile_single_text_node( ) .expect("props panic"); let mut created = FiberNode::new(WorkTag::HostText, Some(Rc::new(Object::into(props))), None); - created._return = Some(Rc::downgrade(&return_fiber.clone())); + created._return = Some(return_fiber.clone()); Rc::new(RefCell::new(created)) } diff --git a/packages/react-reconciler/src/fiber.rs b/packages/react-reconciler/src/fiber.rs index 29ea5d1..14a056b 100644 --- a/packages/react-reconciler/src/fiber.rs +++ b/packages/react-reconciler/src/fiber.rs @@ -3,9 +3,10 @@ use std::cell::RefCell; use std::collections::VecDeque; use std::fmt::{Debug, Formatter}; use std::ops::Deref; -use std::rc::{Rc, Weak}; +use std::rc::Rc; use wasm_bindgen::JsValue; +use web_sys::js_sys::Reflect; use shared::derive_from_js_value; @@ -26,7 +27,7 @@ pub struct FiberNode { key: Option, pub state_node: Option>, pub update_queue: Option>>, - pub _return: Option>>, + pub _return: Option>>, pub sibling: Option>>, pub child: Option>>, pub alternate: Option>>, @@ -37,7 +38,6 @@ pub struct FiberNode { pub memoized_state: Option>, } - impl FiberNode { pub fn new(tag: WorkTag, pending_props: Option>, key: Option) -> Self { Self { @@ -62,7 +62,7 @@ impl FiberNode { let _type = derive_from_js_value(ele.clone(), "type"); let key = match derive_from_js_value(ele.clone(), "key") { None => None, - Some(k) => k.as_string() + Some(k) => k.as_string(), }; let props = derive_from_js_value(ele.clone(), "props"); @@ -80,7 +80,7 @@ impl FiberNode { None => { return; } - Some(a) => a.clone() + Some(a) => a.clone(), }; let mut u = update_queue.borrow_mut(); @@ -157,7 +157,6 @@ pub struct FiberRootNode { pub finished_work: Option>>, } - impl FiberRootNode { pub fn new(container: Rc, host_root_fiber: Rc>) -> Self { Self { @@ -177,7 +176,31 @@ impl Debug for FiberRootNode { while let Some(current) = queue.pop_front() { let current_ref = current.borrow(); - writeln!(f, "{:?} -> ", current_ref.tag); + + match current_ref.tag { + WorkTag::FunctionComponent => { + write!(f, "{:?}", current.borrow()._type.as_ref().unwrap()); + } + WorkTag::HostRoot => { + write!(f, "{:?}", WorkTag::HostRoot); + } + WorkTag::HostComponent => { + write!(f, "{:?}", current.borrow()._type.as_ref().unwrap().as_string().unwrap()); + } + WorkTag::HostText => { + write!( + f, + "{:?}", + Reflect::get( + current.borrow().pending_props.as_ref().unwrap(), + &JsValue::from_str("content"), + ) + .unwrap() + .as_string() + .unwrap(), + ); + } + }; if let Some(ref child) = current_ref.child { queue.push_back(Rc::clone(child)); let mut sibling = child.clone().borrow().sibling.clone(); @@ -186,7 +209,27 @@ impl Debug for FiberRootNode { sibling = sibling.as_ref().unwrap().clone().borrow().sibling.clone(); } } + + if let Some(next) = queue.front() { + let next_ref = next.borrow(); + if let (Some(current_parent), Some(next_parent)) = + (current_ref._return.as_ref(), next_ref._return.as_ref()) + { + if !Rc::ptr_eq(current_parent, next_parent) { + writeln!(f, ""); + writeln!(f, "------------------------------------"); + continue; + } + } + + if current_ref._return.is_some() { + write!(f, ","); + } else { + writeln!(f, ""); + writeln!(f, "------------------------------------"); + } + } } }) } -} \ No newline at end of file +} diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index 74e063d..edc1ab4 100644 --- a/packages/react-reconciler/src/work_loop.rs +++ b/packages/react-reconciler/src/work_loop.rs @@ -47,13 +47,13 @@ impl WorkLoop { let mut parent = Rc::clone(&fiber).borrow()._return.clone(); while parent.is_some() { - node = parent.clone().unwrap().upgrade().unwrap(); - let rc = Rc::clone(&parent.unwrap().upgrade().unwrap()); + node = parent.clone().unwrap(); + let rc = Rc::clone(&parent.unwrap()); let rc_ref = rc.borrow(); let next = match rc_ref._return.as_ref() { None => None, Some(node) => { - let a = Rc::downgrade(&node.upgrade().unwrap()); + let a = node.clone(); Some(a) } }; @@ -92,7 +92,7 @@ impl WorkLoop { break; } - log!("{:?}", *root) + log!("{:?}", *root.clone().borrow()); // commit } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..2b9f188 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,5 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false From 97fcf7337ddb300aa14860ff04aab209a58ed25b Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Fri, 12 Apr 2024 11:51:55 +0800 Subject: [PATCH 5/6] blog-4 --- examples/hello-world/src/main.tsx | 5 +-- packages/react-reconciler/src/fiber.rs | 2 +- packages/react-reconciler/src/lib.rs | 5 --- packages/react-reconciler/src/work_loop.rs | 37 ++++++++++------------ 4 files changed, 19 insertions(+), 30 deletions(-) diff --git a/examples/hello-world/src/main.tsx b/examples/hello-world/src/main.tsx index 661991b..fa04a49 100644 --- a/examples/hello-world/src/main.tsx +++ b/examples/hello-world/src/main.tsx @@ -1,9 +1,6 @@ import {createRoot} from 'react-dom' - -const comp =
-

good

-
+const comp =

Hello World

const root = createRoot(document.getElementById("root")) root.render(comp) diff --git a/packages/react-reconciler/src/fiber.rs b/packages/react-reconciler/src/fiber.rs index 14a056b..dc5b6d2 100644 --- a/packages/react-reconciler/src/fiber.rs +++ b/packages/react-reconciler/src/fiber.rs @@ -34,7 +34,7 @@ pub struct FiberNode { pub _type: Option>, pub flags: Flags, pub subtree_flags: Flags, - pub memoized_props: JsValue, + pub memoized_props: Option>, pub memoized_state: Option>, } diff --git a/packages/react-reconciler/src/lib.rs b/packages/react-reconciler/src/lib.rs index c54e31d..990e484 100644 --- a/packages/react-reconciler/src/lib.rs +++ b/packages/react-reconciler/src/lib.rs @@ -4,8 +4,6 @@ use std::rc::Rc; use wasm_bindgen::JsValue; -use shared::log; - use crate::fiber::{FiberNode, FiberRootNode, StateNode}; use crate::update_queue::{create_update, enqueue_update}; use crate::work_loop::WorkLoop; @@ -40,7 +38,6 @@ impl Reconciler { let root = Rc::new(RefCell::new(FiberRootNode::new(Rc::new(container.clone()), host_root_fiber.clone()))); let r1 = root.clone(); host_root_fiber.borrow_mut().state_node = Some(Rc::new(StateNode::FiberRootNode(r1))); - log!("create_container, state_node {:?}", FiberNode::derive_state_node(host_root_fiber)); root.clone() } @@ -48,8 +45,6 @@ impl Reconciler { let host_root_fiber = Rc::clone(&root).borrow().current.clone(); let update = create_update(element); enqueue_update(host_root_fiber.borrow(), update); - log!("update_container, state_node {:?}", FiberNode::derive_state_node(host_root_fiber.clone())); - let mut work_loop = WorkLoop::new(self.host_config.clone()); work_loop.schedule_update_on_fiber(host_root_fiber); } diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index edc1ab4..655c011 100644 --- a/packages/react-reconciler/src/work_loop.rs +++ b/packages/react-reconciler/src/work_loop.rs @@ -10,8 +10,6 @@ use crate::fiber::{FiberNode, FiberRootNode, StateNode}; use crate::HostConfig; use crate::work_tags::WorkTag; -static mut WORK_IN_PROGRESS: Option>> = None; - pub struct WorkLoop { work_in_progress: Option>>, } @@ -22,6 +20,7 @@ impl WorkLoop { work_in_progress: None, } } + pub fn schedule_update_on_fiber(&mut self, fiber: Rc>) { let root = self.mark_update_lane_from_fiber_to_root(fiber); if root.is_none() { @@ -29,11 +28,7 @@ impl WorkLoop { } log!( "schedule_update_on_fiber - root container: {:?}", - root.clone() - .unwrap() - .clone() - .borrow() - .container + root.clone().unwrap().clone().borrow().container ); self.ensure_root_is_scheduled(root.unwrap()) @@ -63,18 +58,20 @@ impl WorkLoop { let fiber_node_rc = Rc::clone(&node); let fiber_node = fiber_node_rc.borrow(); if fiber_node.tag == WorkTag::HostRoot { - match fiber_node.state_node.clone() { - None => {} - Some(state_node) => { - let state_node = state_node; - return match &*state_node { - StateNode::FiberRootNode(fiber_root_node) => { - Some(Rc::clone(&fiber_root_node)) - } - StateNode::Element(_) => todo!(), - }; - } - } + // match fiber_node.state_node.clone() { + // None => {} + // Some(state_node) => { + // return match &*state_node { + // StateNode::FiberRootNode(fiber_root_node) => { + // Some(Rc::clone(&fiber_root_node)) + // } + // StateNode::Element(_) => todo!(), + // }; + // } + // } + // + let Some(StateNode::FiberRootNode(fiber_root_node)) = fiber_node.state_node.clone(); + return Some(Rc::clone(&fiber_root_node)); } None @@ -128,4 +125,4 @@ impl WorkLoop { self.work_in_progress = Some(next.unwrap()); } } -} \ No newline at end of file +} From 032e3c7f40cfb2eb15cc7dfa7bce2a46187a694a Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Sun, 14 Apr 2024 09:21:41 +0800 Subject: [PATCH 6/6] blog-4 fix bug --- packages/react-reconciler/src/fiber.rs | 2 +- packages/react-reconciler/src/work_loop.rs | 28 +++++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/react-reconciler/src/fiber.rs b/packages/react-reconciler/src/fiber.rs index dc5b6d2..26b013c 100644 --- a/packages/react-reconciler/src/fiber.rs +++ b/packages/react-reconciler/src/fiber.rs @@ -51,7 +51,7 @@ impl FiberNode { child: None, alternate: None, _type: None, - memoized_props: JsValue::null(), + memoized_props: None, memoized_state: None, flags: Flags::NoFlags, subtree_flags: Flags::NoFlags, diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index 655c011..a166880 100644 --- a/packages/react-reconciler/src/work_loop.rs +++ b/packages/react-reconciler/src/work_loop.rs @@ -58,20 +58,20 @@ impl WorkLoop { let fiber_node_rc = Rc::clone(&node); let fiber_node = fiber_node_rc.borrow(); if fiber_node.tag == WorkTag::HostRoot { - // match fiber_node.state_node.clone() { - // None => {} - // Some(state_node) => { - // return match &*state_node { - // StateNode::FiberRootNode(fiber_root_node) => { - // Some(Rc::clone(&fiber_root_node)) - // } - // StateNode::Element(_) => todo!(), - // }; - // } - // } - // - let Some(StateNode::FiberRootNode(fiber_root_node)) = fiber_node.state_node.clone(); - return Some(Rc::clone(&fiber_root_node)); + match fiber_node.state_node.clone() { + None => {} + Some(state_node) => { + return match &*state_node { + StateNode::FiberRootNode(fiber_root_node) => { + Some(Rc::clone(&fiber_root_node)) + } + StateNode::Element(_) => todo!(), + }; + } + } + + // let Some(StateNode::FiberRootNode(fiber_root_node)) = fiber_node.state_node.clone(); + // return Some(Rc::clone(&fiber_root_node)); } None