diff --git a/Cargo.lock b/Cargo.lock index 9d49099..ec39ddf 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,9 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "shared" version = "0.1.0" +dependencies = [ + "web-sys", +] [[package]] name = "syn" diff --git a/examples/hello-world/src/main.tsx b/examples/hello-world/src/main.tsx index e70c233..fa04a49 100644 --- a/examples/hello-world/src/main.tsx +++ b/examples/hello-world/src/main.tsx @@ -1,7 +1,6 @@ import {createRoot} from 'react-dom' - -const comp =
hello world
+const comp =

Hello World

const root = createRoot(document.getElementById("root")) root.render(comp) 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..5a3765a --- /dev/null +++ b/packages/react-reconciler/src/begin_work.rs @@ -0,0 +1,68 @@ +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); + 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..3469df1 --- /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(return_fiber.clone()); + 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(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..26b013c 100644 --- a/packages/react-reconciler/src/fiber.rs +++ b/packages/react-reconciler/src/fiber.rs @@ -1,2 +1,235 @@ +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; + +use wasm_bindgen::JsValue; +use web_sys::js_sys::Reflect; + +use shared::derive_from_js_value; + +use crate::fiber_flags::Flags; +use crate::update_queue::{Update, UpdateQueue, UpdateType}; +use crate::work_tags::WorkTag; + #[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: Option>, + pub memoized_state: Option>, +} + +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: None, + 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(), + }) + } +} + +pub struct FiberRootNode { + pub container: Rc, + pub current: Rc>, + pub finished_work: Option>>, +} + +impl FiberRootNode { + pub fn new(container: Rc, host_root_fiber: Rc>) -> Self { + Self { + container, + current: host_root_fiber, + 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(); + + 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(); + while sibling.is_some() { + queue.push_back(Rc::clone(sibling.as_ref().unwrap())); + 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, "------------------------------------"); + } + } + } + }) + } +} 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..990e484 100644 --- a/packages/react-reconciler/src/lib.rs +++ b/packages/react-reconciler/src/lib.rs @@ -3,12 +3,19 @@ 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 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 +25,28 @@ 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))); + 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); + 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..a166880 --- /dev/null +++ b/packages/react-reconciler/src/work_loop.rs @@ -0,0 +1,128 @@ +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; + +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(); + 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 = node.clone(); + 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) => { + 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 + } + + 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; + } + + log!("{:?}", *root.clone().borrow()); + // 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()) + self.work_in_progress = None; + } else { + log!( + "perform_unit_of_work - next {:?}", + next.clone().unwrap().clone().borrow().tag + ); + self.work_in_progress = Some(next.unwrap()); + } + } +} 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..1d3b016 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" + 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 + } + } +} + 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 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/)