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
2 changes: 1 addition & 1 deletion examples/hello-world/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export {default} from './suspense'
export {default} from './lazy'
3 changes: 3 additions & 0 deletions examples/hello-world/src/lazy/Cpn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Cpn() {
return <div>Cpn</div>
}
19 changes: 19 additions & 0 deletions examples/hello-world/src/lazy/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {Suspense, lazy} from 'react'

function delay(promise) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(promise)
}, 2000)
})
}

const Cpn = lazy(() => import('./Cpn').then((res) => delay(res)))

export default function App() {
return (
<Suspense fallback={<div>loading</div>}>
<Cpn />
</Suspense>
)
}
17 changes: 12 additions & 5 deletions examples/hello-world/src/suspense/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,21 @@ function fetchData(id, timeout) {

export default function App() {
return (
<Suspense fallback={<div>loading</div>}>
<Child />
<Suspense fallback={<div>Out loading</div>}>
<Child id={1} timeout={1000} />
<Suspense fallback={<div>Inner loading</div>}>
<Child id={2} timeout={2000} />
</Suspense>
</Suspense>
)
}

function Child() {
const {data} = use(fetchData(1, 1000))
function Child({id, timeout}) {
const {data} = use(fetchData(id, timeout))

return <span>{data}</span>
return (
<div>
{id}:{data}
</div>
)
}
88 changes: 53 additions & 35 deletions packages/react-reconciler/src/begin_work.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,42 +57,44 @@ pub fn begin_work(
};

// TODO work with suspense
// let current = { work_in_progress.borrow().alternate.clone() };
let current = { work_in_progress.borrow().alternate.clone() };

// if current.is_some() {
// let current = current.clone().unwrap();
// let old_props = current.borrow().memoized_props.clone();
// let old_type = current.borrow()._type.clone();
// let new_props = work_in_progress.borrow().pending_props.clone();
// let new_type = work_in_progress.borrow()._type.clone();
// if !Object::is(&old_props, &new_props) || !Object::is(&old_type, &new_type) {
// unsafe { DID_RECEIVE_UPDATE = true }
// } else {
// let has_scheduled_update_or_context =
// check_scheduled_update_or_context(current.clone(), render_lane.clone());
// // The current fiber lane is not included in render_lane
// // TODO context
// if !has_scheduled_update_or_context {
// unsafe { DID_RECEIVE_UPDATE = false }
// match work_in_progress.borrow().tag {
// WorkTag::ContextProvider => {
// let new_value = derive_from_js_value(
// &work_in_progress.borrow().memoized_props,
// "value",
// );
// let context =
// derive_from_js_value(&work_in_progress.borrow()._type, "_context");
// push_provider(&context, new_value);
// }
// _ => {}
// }
// return Ok(bailout_on_already_finished_work(
// work_in_progress,
// render_lane,
// ));
// }
// }
// }
if current.is_some() {
let current = current.clone().unwrap();
let old_props = current.borrow().memoized_props.clone();
let old_type = current.borrow()._type.clone();
let new_props = work_in_progress.borrow().pending_props.clone();
let new_type = work_in_progress.borrow()._type.clone();
if !Object::is(&old_props, &new_props) || !Object::is(&old_type, &new_type) {
unsafe { DID_RECEIVE_UPDATE = true }
} else {
let has_scheduled_update_or_context =
check_scheduled_update_or_context(current.clone(), render_lane.clone());
// The current fiber lane is not included in render_lane
// TODO context
if !has_scheduled_update_or_context
&& current.borrow().tag != WorkTag::SuspenseComponent
{
unsafe { DID_RECEIVE_UPDATE = false }
match work_in_progress.borrow().tag {
WorkTag::ContextProvider => {
let new_value = derive_from_js_value(
&work_in_progress.borrow().memoized_props,
"value",
);
let context =
derive_from_js_value(&work_in_progress.borrow()._type, "_context");
push_provider(&context, new_value);
}
_ => {}
}
return Ok(bailout_on_already_finished_work(
work_in_progress,
render_lane,
));
}
}
}

work_in_progress.borrow_mut().lanes = Lane::NoLane;
// if current.is_some() {
Expand All @@ -117,9 +119,25 @@ pub fn begin_work(
WorkTag::Fragment => Ok(update_fragment(work_in_progress.clone())),
WorkTag::SuspenseComponent => Ok(update_suspense_component(work_in_progress.clone())),
WorkTag::OffscreenComponent => Ok(update_offscreen_component(work_in_progress.clone())),
WorkTag::LazyComponent => update_lazy_component(work_in_progress.clone(), render_lane),
};
}

fn update_lazy_component(
work_in_progress: Rc<RefCell<FiberNode>>,
render_lane: Lane,
) -> Result<Option<Rc<RefCell<FiberNode>>>, JsValue> {
let lazy_type = { work_in_progress.borrow()._type.clone() };
let payload = derive_from_js_value(&lazy_type, "_payload");
let init_jsvalue = derive_from_js_value(&lazy_type, "_init");
let init = init_jsvalue.dyn_ref::<Function>().unwrap();
let Component = init.call1(&JsValue::null(), &payload)?;
work_in_progress.borrow_mut()._type = Component.clone();
work_in_progress.borrow_mut().tag = WorkTag::FunctionComponent;
let child = update_function_component(work_in_progress, Component.clone(), render_lane);
child
}

fn mount_suspense_fallback_children(
work_in_progress: Rc<RefCell<FiberNode>>,
primary_children: JsValue,
Expand Down
5 changes: 4 additions & 1 deletion packages/react-reconciler/src/fiber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use wasm_bindgen::JsValue;
use web_sys::js_sys::Reflect;

use shared::{
derive_from_js_value, log, type_of, REACT_MEMO_TYPE, REACT_PROVIDER_TYPE, REACT_SUSPENSE_TYPE,
derive_from_js_value, log, type_of, REACT_LAZY_TYPE, REACT_MEMO_TYPE, REACT_PROVIDER_TYPE,
REACT_SUSPENSE_TYPE,
};

use crate::fiber_context::ContextItem;
Expand Down Expand Up @@ -179,6 +180,8 @@ impl FiberNode {
fiber_tag = WorkTag::ContextProvider;
} else if _typeof == REACT_MEMO_TYPE {
fiber_tag = WorkTag::MemoComponent;
} else if _typeof == REACT_LAZY_TYPE {
fiber_tag = WorkTag::LazyComponent;
} else {
log!("Unsupported type {:?}", _type);
}
Expand Down
25 changes: 20 additions & 5 deletions packages/react-reconciler/src/fiber_throw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,20 @@ use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
use web_sys::js_sys::Function;

use crate::{
fiber::FiberRootNode, fiber_flags::Flags, fiber_lanes::Lane,
suspense_context::get_suspense_handler, work_loop::ensure_root_is_scheduled, JsValueKey,
fiber::{FiberNode, FiberRootNode},
fiber_flags::Flags,
fiber_lanes::Lane,
suspense_context::get_suspense_handler,
work_loop::{ensure_root_is_scheduled, mark_update_lane_from_fiber_to_root},
JsValueKey,
};

fn attach_ping_listener(root: Rc<RefCell<FiberRootNode>>, wakeable: JsValue, lane: Lane) {
fn attach_ping_listener(
root: Rc<RefCell<FiberRootNode>>,
source_fiber: Rc<RefCell<FiberNode>>,
wakeable: JsValue,
lane: Lane,
) {
let mut ping_cache_option: Option<HashMap<JsValueKey, Rc<RefCell<HashSet<Lane>>>>> =
root.borrow().ping_cache.clone();
let mut ping_cache: HashMap<JsValueKey, Rc<RefCell<HashSet<Lane>>>>;
Expand Down Expand Up @@ -54,6 +63,7 @@ fn attach_ping_listener(root: Rc<RefCell<FiberRootNode>>, wakeable: JsValue, lan
}
root.clone().borrow_mut().mark_root_updated(lane.clone());
root.clone().borrow_mut().mark_root_pinged(lane.clone());
mark_update_lane_from_fiber_to_root(source_fiber.clone(), lane.clone());
ensure_root_is_scheduled(root.clone());
}) as Box<dyn Fn()>);
let ping = closure.as_ref().unchecked_ref::<Function>().clone();
Expand All @@ -63,7 +73,12 @@ fn attach_ping_listener(root: Rc<RefCell<FiberRootNode>>, wakeable: JsValue, lan
}
}

pub fn throw_exception(root: Rc<RefCell<FiberRootNode>>, value: JsValue, lane: Lane) {
pub fn throw_exception(
root: Rc<RefCell<FiberRootNode>>,
source_fiber: Rc<RefCell<FiberNode>>,
value: JsValue,
lane: Lane,
) {
if !value.is_null()
&& type_of(&value, "object")
&& derive_from_js_value(&value, "then").is_function()
Expand All @@ -74,6 +89,6 @@ pub fn throw_exception(root: Rc<RefCell<FiberRootNode>>, value: JsValue, lane: L
suspense_boundary.borrow_mut().flags |= Flags::ShouldCapture;
}

attach_ping_listener(root, value, lane)
attach_ping_listener(root, source_fiber, value, lane)
}
}
24 changes: 20 additions & 4 deletions packages/react-reconciler/src/work_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use scheduler::{
unstable_cancel_callback, unstable_schedule_callback_no_delay, unstable_should_yield_to_host,
Priority,
};
use shared::{is_dev, log};
use shared::{derive_from_js_value, is_dev, log, type_of};

use crate::begin_work::begin_work;
use crate::commit_work::{
Expand Down Expand Up @@ -42,7 +42,9 @@ static ROOT_COMPLETED: u8 = 2;
static ROOT_DID_NOT_COMPLETE: u8 = 3;

static NOT_SUSPENDED: u8 = 0;
static SUSPENDED_ON_DATA: u8 = 6;
static SUSPENDED_ON_ERROR: u8 = 1;
static SUSPENDED_ON_DATA: u8 = 2;
static SUSPENDED_ON_DEPRECATED_THROW_PROMISE: u8 = 4;

pub fn schedule_update_on_fiber(fiber: Rc<RefCell<FiberNode>>, lane: Lane) {
if is_dev() {
Expand Down Expand Up @@ -505,7 +507,16 @@ fn handle_throw(root: Rc<RefCell<FiberRootNode>>, mut thrown_value: JsValue) {
unsafe { WORK_IN_PROGRESS_SUSPENDED_REASON = SUSPENDED_ON_DATA };
thrown_value = get_suspense_thenable();
} else {
// TODO
let is_wakeable = !thrown_value.is_null()
&& type_of(&thrown_value, "object")
&& derive_from_js_value(&thrown_value, "then").is_function();
unsafe {
WORK_IN_PROGRESS_SUSPENDED_REASON = if is_wakeable {
SUSPENDED_ON_DEPRECATED_THROW_PROMISE
} else {
SUSPENDED_ON_ERROR
};
};
}

unsafe {
Expand All @@ -520,7 +531,12 @@ fn throw_and_unwind_work_loop(
lane: Lane,
) {
reset_hooks_on_unwind(unit_of_work.clone());
throw_exception(root.clone(), thrown_value, lane.clone());
throw_exception(
root.clone(),
unit_of_work.clone(),
thrown_value,
lane.clone(),
);
unwind_unit_of_work(unit_of_work);
}

Expand Down
1 change: 1 addition & 0 deletions packages/react-reconciler/src/work_tags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ pub enum WorkTag {
SuspenseComponent = 13,
OffscreenComponent = 14,
MemoComponent = 15,
LazyComponent = 16,
}
52 changes: 52 additions & 0 deletions packages/react/src/lazy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use js_sys::{Function, Reflect};
use shared::derive_from_js_value;
use wasm_bindgen::{prelude::Closure, JsCast, JsValue};

pub static UNINITIALIZED: i8 = -1;
static PENDING: i8 = 0;
static RESOLVED: i8 = 1;
static REJECTED: i8 = 2;

pub fn lazy_initializer(payload: JsValue) -> Result<JsValue, JsValue> {
let status = derive_from_js_value(&payload, "_status");
if status == UNINITIALIZED {
let ctor = derive_from_js_value(&payload, "_result");
let ctor_fn = ctor.dyn_ref::<Function>().unwrap();
let thenable = ctor_fn.call0(ctor_fn).unwrap();
let then_jsvalue = derive_from_js_value(&thenable, "then");
let then = then_jsvalue.dyn_ref::<Function>().unwrap();

let payload1 = payload.clone();
let on_resolve_closure = Closure::wrap(Box::new(move |module: JsValue| {
Reflect::set(&payload1, &"_status".into(), &JsValue::from(RESOLVED));
Reflect::set(&payload1, &"_result".into(), &module);
}) as Box<dyn Fn(JsValue) -> ()>);
let on_resolve = on_resolve_closure
.as_ref()
.unchecked_ref::<Function>()
.clone();
on_resolve_closure.forget();

let payload2 = payload.clone();
let on_reject_closure = Closure::wrap(Box::new(move |err: JsValue| {
Reflect::set(&payload2, &"_status".into(), &JsValue::from(REJECTED));
Reflect::set(&payload2, &"_result".into(), &err);
}) as Box<dyn Fn(JsValue) -> ()>);
let on_reject = on_reject_closure
.as_ref()
.unchecked_ref::<Function>()
.clone();

then.call2(&thenable, &on_resolve, &on_reject);

Reflect::set(&payload, &"_status".into(), &JsValue::from(PENDING));
Reflect::set(&payload, &"_result".into(), &thenable);
}

if status == RESOLVED {
let module = derive_from_js_value(&payload, "_result");
return Ok(derive_from_js_value(&module, "default"));
} else {
return Err(derive_from_js_value(&payload, "_result"));
}
}
29 changes: 27 additions & 2 deletions packages/react/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
use js_sys::{Array, Object, Reflect, JSON};
use js_sys::{Array, Function, Object, Reflect, JSON};
use lazy::{lazy_initializer, UNINITIALIZED};
use wasm_bindgen::prelude::*;

use shared::{
derive_from_js_value, REACT_CONTEXT_TYPE, REACT_ELEMENT_TYPE, REACT_MEMO_TYPE,
derive_from_js_value, REACT_CONTEXT_TYPE, REACT_ELEMENT_TYPE, REACT_LAZY_TYPE, REACT_MEMO_TYPE,
REACT_PROVIDER_TYPE,
};

use crate::current_dispatcher::CURRENT_DISPATCHER;

pub mod current_dispatcher;
mod lazy;

fn resolve_key(val: &JsValue) -> JsValue {
if val.is_undefined() {
Expand Down Expand Up @@ -199,3 +201,26 @@ pub fn memo(_type: &JsValue, compare: &JsValue) -> JsValue {
);
fiber_type.into()
}

#[wasm_bindgen]
pub fn lazy(ctor: &JsValue) -> JsValue {
let payload = Object::new();
Reflect::set(&payload, &"_status".into(), &JsValue::from(UNINITIALIZED));
Reflect::set(&payload, &"_result".into(), ctor);

let lazy_type = Object::new();

Reflect::set(
&lazy_type,
&"$$typeof".into(),
&JsValue::from_str(REACT_LAZY_TYPE),
);
Reflect::set(&lazy_type, &"_payload".into(), &payload);
let closure = Closure::wrap(
Box::new(lazy_initializer) as Box<dyn Fn(JsValue) -> Result<JsValue, JsValue>>
);
let f = closure.as_ref().unchecked_ref::<Function>().clone();
closure.forget();
Reflect::set(&lazy_type, &"_init".into(), &f);
lazy_type.into()
}
1 change: 1 addition & 0 deletions packages/shared/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use web_sys::wasm_bindgen::{JsCast, JsValue};
pub static REACT_ELEMENT_TYPE: &str = "react.element";
pub static REACT_CONTEXT_TYPE: &str = "react.context";
pub static REACT_PROVIDER_TYPE: &str = "react.provider";
pub static REACT_LAZY_TYPE: &str = "react.lazy";
pub static REACT_MEMO_TYPE: &str = "react.memo";
pub static REACT_SUSPENSE_TYPE: &str = "react.suspense";
pub static REACT_FRAGMENT_TYPE: &str = "react.fragment";
Expand Down