Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion __tests__/react-dom/ReactFunctionComponent-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ function FunctionComponent(props) {
return <div>{props.name}</div>;
}

function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms)
})
}

describe('ReactFunctionComponent', () => {
beforeEach(() => {
jest.resetModules();
Expand All @@ -25,10 +31,11 @@ describe('ReactFunctionComponent', () => {
ReactTestUtils = require('../utils/test-utils')
});

it('should render stateless component', () => {
it('should render stateless component', async () => {
const el = document.createElement('div');
// console.log(FunctionComponent.toString())
ReactDOM.createRoot(el).render(<FunctionComponent name="A"/>);
await sleep(10)
expect(el.textContent).toBe('A');
});

Expand Down
2 changes: 1 addition & 1 deletion examples/hello-world/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {createRoot} from 'react-dom'
import App from "./App.tsx";
import App from './App.tsx'

const root = createRoot(document.getElementById("root"))
root.render(<App/>)
Expand Down
48 changes: 48 additions & 0 deletions packages/react-dom/src/host_config.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use std::any::Any;
use std::cell::RefCell;
use std::rc::Rc;

use js_sys::{Function, global, Promise};
use js_sys::JSON::stringify;
use wasm_bindgen::JsValue;
use wasm_bindgen::prelude::*;
use web_sys::{Node, window};

use react_reconciler::HostConfig;
Expand Down Expand Up @@ -31,6 +34,20 @@ pub fn to_string(js_value: &JsValue) -> String {
})
}

#[wasm_bindgen]
extern "C" {
type Global;

#[wasm_bindgen]
fn queueMicrotask(closure: &JsValue);

#[wasm_bindgen]
fn setTimeout(closure: &JsValue, timeout: i32);

#[wasm_bindgen(method, getter, js_name = queueMicrotask)]
fn hasQueueMicrotask(this: &Global) -> JsValue;
}

impl HostConfig for ReactDomHostConfig {
fn create_text_instance(&self, content: &JsValue) -> Rc<dyn Any> {
let window = window().expect("no global `window` exists");
Expand Down Expand Up @@ -135,4 +152,35 @@ impl HostConfig for ReactDomHostConfig {
}
}
}

fn schedule_microtask(&self, callback: Box<dyn FnMut()>) {
let closure = Rc::new(RefCell::new(Some(Closure::wrap(callback))));

if global()
.unchecked_into::<Global>()
.hasQueueMicrotask()
.is_function()
{
let closure_clone = closure.clone();
queueMicrotask(&closure_clone.borrow_mut().as_ref().unwrap().as_ref().unchecked_ref::<JsValue>());
closure_clone.borrow_mut().take().unwrap_throw().forget();
} else if js_sys::Reflect::get(&*global(), &JsValue::from_str("Promise"))
.map(|value| value.is_function())
.unwrap_or(false)
{
let promise = Promise::resolve(&JsValue::NULL);
let closure_clone = closure.clone();
let c = Closure::wrap(Box::new(move |_v| {
let b = closure_clone.borrow_mut();
let function = b.as_ref().unwrap().as_ref().unchecked_ref::<Function>();
let _ = function.call0(&JsValue::NULL);
}) as Box<dyn FnMut(JsValue)>);
let _ = promise.then(&c);
c.forget();
} else {
let closure_clone = closure.clone();
setTimeout(&closure_clone.borrow_mut().as_ref().unwrap().as_ref().unchecked_ref::<JsValue>(), 0);
closure_clone.borrow_mut().take().unwrap_throw().forget();
}
}
}
13 changes: 8 additions & 5 deletions packages/react-reconciler/src/begin_work.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,33 @@ use shared::derive_from_js_value;
use crate::child_fiber::{mount_child_fibers, reconcile_child_fibers};
use crate::fiber::{FiberNode, MemoizedState};
use crate::fiber_hooks::render_with_hooks;
use crate::fiber_lanes::Lane;
use crate::update_queue::process_update_queue;
use crate::work_tags::WorkTag;

pub fn begin_work(
work_in_progress: Rc<RefCell<FiberNode>>,
render_lane: Lane,
) -> Result<Option<Rc<RefCell<FiberNode>>>, JsValue> {
let tag = work_in_progress.clone().borrow().tag.clone();
return match tag {
WorkTag::FunctionComponent => update_function_component(work_in_progress.clone()),
WorkTag::HostRoot => Ok(update_host_root(work_in_progress.clone())),
WorkTag::FunctionComponent => update_function_component(work_in_progress.clone(), render_lane),
WorkTag::HostRoot => Ok(update_host_root(work_in_progress.clone(), render_lane)),
WorkTag::HostComponent => Ok(update_host_component(work_in_progress.clone())),
WorkTag::HostText => Ok(None),
};
}

fn update_function_component(
work_in_progress: Rc<RefCell<FiberNode>>,
render_lane: Lane,
) -> Result<Option<Rc<RefCell<FiberNode>>>, JsValue> {
let next_children = render_with_hooks(work_in_progress.clone())?;
let next_children = render_with_hooks(work_in_progress.clone(), render_lane)?;
reconcile_children(work_in_progress.clone(), Some(next_children));
Ok(work_in_progress.clone().borrow().child.clone())
}

fn update_host_root(work_in_progress: Rc<RefCell<FiberNode>>) -> Option<Rc<RefCell<FiberNode>>> {
fn update_host_root(work_in_progress: Rc<RefCell<FiberNode>>, render_lane: Lane) -> Option<Rc<RefCell<FiberNode>>> {
let work_in_progress_cloned = work_in_progress.clone();

let base_state;
Expand All @@ -44,7 +47,7 @@ fn update_host_root(work_in_progress: Rc<RefCell<FiberNode>>) -> Option<Rc<RefCe

{
work_in_progress.clone().borrow_mut().memoized_state =
process_update_queue(base_state, update_queue, work_in_progress.clone());
process_update_queue(base_state, update_queue, work_in_progress.clone(), render_lane);
}

let next_children = work_in_progress_cloned.borrow().memoized_state.clone();
Expand Down
19 changes: 17 additions & 2 deletions packages/react-reconciler/src/fiber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use shared::{derive_from_js_value, log, type_of};

use crate::fiber_flags::Flags;
use crate::fiber_hooks::Hook;
use crate::fiber_lanes::{Lane, merge_lanes};
use crate::update_queue::{Update, UpdateQueue};
use crate::work_tags::WorkTag;

Expand All @@ -37,6 +38,7 @@ impl MemoizedState {
}

pub struct FiberNode {
pub lanes: Lane,
pub index: u32,
pub tag: WorkTag,
pub pending_props: JsValue,
Expand Down Expand Up @@ -125,6 +127,7 @@ impl FiberNode {
flags: Flags::NoFlags,
subtree_flags: Flags::NoFlags,
deletions: vec![],
lanes: Lane::NoLane,
}
}

Expand Down Expand Up @@ -154,7 +157,7 @@ impl FiberNode {
};

let mut u = update_queue.borrow_mut();
u.shared.pending = Some(update);
u.shared.pending = Some(Rc::new(RefCell::new(update)));
}

pub fn create_work_in_progress(
Expand Down Expand Up @@ -227,6 +230,8 @@ pub struct FiberRootNode {
pub container: Rc<dyn Any>,
pub current: Rc<RefCell<FiberNode>>,
pub finished_work: Option<Rc<RefCell<FiberNode>>>,
pub pending_lanes: Lane,
pub finished_lane: Lane,
}

impl FiberRootNode {
Expand All @@ -235,8 +240,18 @@ impl FiberRootNode {
container,
current: host_root_fiber,
finished_work: None,
pending_lanes: Lane::NoLane,
finished_lane: Lane::NoLane,
}
}

pub fn mark_root_finished(&mut self, lane: Lane) {
self.pending_lanes &= !lane;
}

pub fn mark_root_updated(&mut self, lane: Lane) {
self.pending_lanes = merge_lanes(self.pending_lanes.clone(), lane)
}
}

struct QueueItem {
Expand All @@ -263,7 +278,7 @@ impl Debug for FiberRootNode {
while let Some(QueueItem { node: current, depth }) = queue.pop_front() {
let current_ref = current.borrow();

write!(f, "{:?}", current_ref);
write!(f, "{:?}", current_ref).expect("TODO: panic print FiberNode");

if let Some(ref child) = current_ref.child {
queue.push_back(QueueItem::new(Rc::clone(child), depth + 1));
Expand Down
20 changes: 10 additions & 10 deletions packages/react-reconciler/src/fiber_hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ use web_sys::js_sys::{Function, Object, Reflect};
use shared::log;

use crate::fiber::{FiberNode, MemoizedState};
use crate::fiber_lanes::{Lane, request_update_lane};
use crate::update_queue::{
create_update, create_update_queue, enqueue_update, process_update_queue, UpdateQueue,
};
use crate::work_loop::WorkLoop;
use crate::work_loop::schedule_update_on_fiber;

#[wasm_bindgen]
extern "C" {
Expand All @@ -21,7 +22,7 @@ extern "C" {
static mut CURRENTLY_RENDERING_FIBER: Option<Rc<RefCell<FiberNode>>> = None;
static mut WORK_IN_PROGRESS_HOOK: Option<Rc<RefCell<Hook>>> = None;
static mut CURRENT_HOOK: Option<Rc<RefCell<Hook>>> = None;
pub static mut WORK_LOOP: Option<Rc<RefCell<WorkLoop>>> = None;
static mut RENDER_LANE: Lane = Lane::NoLane;

#[derive(Debug, Clone)]
pub struct Hook {
Expand Down Expand Up @@ -56,9 +57,10 @@ fn update_hooks_to_dispatcher(is_update: bool) {
updateDispatcher(&object.into());
}

pub fn render_with_hooks(work_in_progress: Rc<RefCell<FiberNode>>) -> Result<JsValue, JsValue> {
pub fn render_with_hooks(work_in_progress: Rc<RefCell<FiberNode>>, lane: Lane) -> Result<JsValue, JsValue> {
unsafe {
CURRENTLY_RENDERING_FIBER = Some(work_in_progress.clone());
RENDER_LANE = lane;
}

let work_in_progress_cloned = work_in_progress.clone();
Expand Down Expand Up @@ -89,6 +91,7 @@ pub fn render_with_hooks(work_in_progress: Rc<RefCell<FiberNode>>) -> Result<JsV
CURRENTLY_RENDERING_FIBER = None;
WORK_IN_PROGRESS_HOOK = None;
CURRENT_HOOK = None;
RENDER_LANE = Lane::NoLane;
}

children
Expand Down Expand Up @@ -257,6 +260,7 @@ fn update_state(_: &JsValue) -> Result<Vec<JsValue>, JsValue> {
base_state,
queue.clone(),
CURRENTLY_RENDERING_FIBER.clone().unwrap(),
RENDER_LANE.clone(),
);
}
log!("memoized_state {:?}", hook_cloned.borrow().memoized_state);
Expand All @@ -281,14 +285,10 @@ fn dispatch_set_state(
update_queue: Rc<RefCell<UpdateQueue>>,
action: &JsValue,
) {
let update = create_update(action.clone());
let lane = request_update_lane();
let update = create_update(action.clone(), lane.clone());
enqueue_update(update_queue.clone(), update);
unsafe {
WORK_LOOP
.as_ref()
.unwrap()
.clone()
.borrow()
.schedule_update_on_fiber(fiber.clone());
schedule_update_on_fiber(fiber.clone(), lane);
}
}
25 changes: 25 additions & 0 deletions packages/react-reconciler/src/fiber_lanes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use bitflags::bitflags;

bitflags! {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Lane: u8 {
const NoLane = 0b0000000000000000000000000000000;
const SyncLane = 0b0000000000000000000000000000001;
// const AsyncLane = 0b0000000000000000000000000000010;
}
}

pub fn get_highest_priority(lanes: Lane) -> Lane {
let lanes = lanes.bits();
let highest_priority = lanes & (lanes.wrapping_neg());
Lane::from_bits_truncate(highest_priority)
}

pub fn merge_lanes(lane_a: Lane, lane_b: Lane) -> Lane {
lane_a | lane_b
}

pub fn request_update_lane() -> Lane {
Lane::SyncLane
}

27 changes: 16 additions & 11 deletions packages/react-reconciler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ use std::rc::Rc;

use wasm_bindgen::JsValue;

use crate::complete_work::CompleteWork;
use crate::fiber::{FiberNode, FiberRootNode, StateNode};
use crate::fiber_hooks::WORK_LOOP;
// use crate::fiber_hooks::{WORK_LOOP as Fiber_HOOKS};
use crate::fiber_lanes::Lane;
use crate::update_queue::{create_update, create_update_queue, enqueue_update};
use crate::work_loop::WorkLoop;
use crate::work_loop::schedule_update_on_fiber;
use crate::work_tags::WorkTag;

mod begin_work;
Expand All @@ -20,6 +22,11 @@ mod fiber_hooks;
mod update_queue;
mod work_loop;
mod work_tags;
mod fiber_lanes;
mod sync_task_queue;

pub static mut HOST_CONFIG: Option<Rc<dyn HostConfig>> = None;
static mut COMPLETE_WORK: Option<CompleteWork> = None;

pub trait HostConfig {
fn create_text_instance(&self, content: &JsValue) -> Rc<dyn Any>;
Expand All @@ -28,17 +35,17 @@ pub trait HostConfig {
fn append_child_to_container(&self, child: Rc<dyn Any>, parent: Rc<dyn Any>);
fn remove_child(&self, child: Rc<dyn Any>, container: Rc<dyn Any>);
fn commit_text_update(&self, text_instance: Rc<dyn Any>, content: String);

fn insert_child_to_container(
&self,
child: Rc<dyn Any>,
container: Rc<dyn Any>,
before: Rc<dyn Any>,
);
fn schedule_microtask(&self, callback: Box<dyn FnMut()>);
}

pub struct Reconciler {
host_config: Rc<dyn HostConfig>,
pub host_config: Rc<dyn HostConfig>,
}

impl Reconciler {
Expand All @@ -63,19 +70,17 @@ impl Reconciler {

pub fn update_container(&self, element: JsValue, root: Rc<RefCell<FiberRootNode>>) -> JsValue {
let host_root_fiber = Rc::clone(&root).borrow().current.clone();
let update = create_update(element.clone());
let root_render_priority = Lane::SyncLane;
let update = create_update(element.clone(), root_render_priority.clone());
enqueue_update(
host_root_fiber.borrow().update_queue.clone().unwrap(),
update,
);
let work_loop = Rc::new(RefCell::new(WorkLoop::new(self.host_config.clone())));
unsafe {
WORK_LOOP = Some(work_loop.clone());
HOST_CONFIG = Some(self.host_config.clone());
COMPLETE_WORK = Some(CompleteWork::new(self.host_config.clone()));
schedule_update_on_fiber(host_root_fiber, root_render_priority);
}
work_loop
.clone()
.borrow()
.schedule_update_on_fiber(host_root_fiber);
element.clone()
}
}
9 changes: 9 additions & 0 deletions packages/react-reconciler/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use crate::fiber_lanes::Lane;

mod fiber_lanes;

fn main() {
let mut a = Lane::NoLane | Lane::SyncLane;
println!("{:?}", a);
println!("{:?}", a == !Lane::NoLane)
}
Loading