Skip to content

Commit

Permalink
feat: blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
mrchantey committed Jan 13, 2025
1 parent d8731db commit efaabef
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 17 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ tokio = { version = "1.42", features = [
"rt-multi-thread",
"time",
] }
flume = "0.11.1"
# tokio-stream = "0.1.14"
rayon = "1.7.0"

Expand Down
2 changes: 2 additions & 0 deletions crates/sweet_rsx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ forky = { workspace = true, features = ["fs"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
forky = { workspace = true, features = ["web"] }
console_log.workspace = true
flume.workspace = true
getrandom.workspace = true
js-sys.workspace = true
console_error_panic_hook.workspace = true
wasm-bindgen.workspace = true
wasm-bindgen-futures.workspace = true
web-sys.workspace = true
Expand Down
1 change: 1 addition & 0 deletions crates/sweet_rsx/examples/hello_world.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<div data-sid="0" data-sblock="4,6,18">the 11th value is 11</div>
<button onclick="_sweet.event(0,event)">Click me</button>

<script>
Expand Down
24 changes: 20 additions & 4 deletions crates/sweet_rsx/examples/hello_world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,40 @@ fn set_target_html(e: &Event, s: &str) {
}

impl HydrateClient for HelloWorld {
fn hydrate() -> ParseResult<Hydrated> {
fn hydrate(
self,
send: flume::Sender<(usize, String)>,
) -> ParseResult<Hydrated> {
let mut count = 0;


let handle_click = move |e: Event| {
count += 1;
set_target_html(&e, &format!("you did it {} times!", count));
let str = count.to_string();
set_target_html(&e, &format!("you did it {str} times!"));
send.send((0, str.clone())).unwrap();
send.send((1, str)).unwrap();
};

Ok(Hydrated {
events: vec![Box::new(handle_click)],
blocks: vec![
HydratedBlock {
node_id: 0,
part_index: 1,
},
HydratedBlock {
node_id: 0,
part_index: 3,
},
],
})
}
}



fn main() -> ParseResult<()> {
let hydrated = HelloWorld::hydrate()?;
SweetLoader::default().load(hydrated);
SweetLoader::default().load(HelloWorld)?;
Ok(())
}
6 changes: 6 additions & 0 deletions crates/sweet_rsx/src/sweet_loader/catch_uncanny.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
globalThis._sweet = {
uncanny: [],
event: (id, event) => {
globalThis._sweet.uncanny.push([id, event])
}
}
13 changes: 12 additions & 1 deletion crates/sweet_rsx/src/sweet_loader/hydrate_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,20 @@ pub type HydratedEvent = Box<dyn FnMut(Event)>;
/// active or otherwise
pub struct Hydrated {
pub events: Vec<HydratedEvent>,
pub blocks: Vec<HydratedBlock>,
}

pub struct HydratedBlock {
pub node_id: usize,
/// The index of the part in the node
/// ie for `hello {name}` the part index would be 1
pub part_index: usize,
}


pub trait HydrateClient {
fn hydrate() -> ParseResult<Hydrated>;
fn hydrate(
self,
send: flume::Sender<(usize, String)>,
) -> ParseResult<Hydrated>;
}
61 changes: 61 additions & 0 deletions crates/sweet_rsx/src/sweet_loader/live_block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use super::HydratedBlock;
use crate::rsx::ParseError;
use crate::rsx::ParseResult;
use wasm_bindgen::JsCast;
use web_sys::HtmlElement;



pub struct LiveBlock {
pub el: HtmlElement,
pub parts: Vec<String>,
}
impl LiveBlock {
pub fn new(block: &HydratedBlock) -> ParseResult<Self> {
let document = web_sys::window().unwrap().document().unwrap();
let el = document
.query_selector(&format!("[data-sid=\"{}\"]", block.node_id))
.ok()
.flatten()
.ok_or_else(|| {
ParseError::Hydration(format!(
"failed to find block element with id {}",
block.node_id
))
})?;
let el = el.dyn_into::<HtmlElement>().unwrap();
let parts = Self::parse_parts(&el)?;

Ok(Self { el, parts })
}

/// for a given element with the attribute `s:block`, split the inner text into parts.
/// We dont currently handle child nodes
/// If there is no attribute there is only one part.
/// example: <div s:id="0" s:block="4,6,18">the 11th value is 11</div>
fn parse_parts(el: &HtmlElement) -> ParseResult<Vec<String>> {
let block = el
.get_attribute("data-sblock")
.unwrap_or_else(|| "".to_string());
if el.child_element_count() != 0 {
return Err(ParseError::Hydration(
"block elements with children is not yet supported".to_string(),
));
}

let indices = block
.split(",")
.map(|s| s.parse::<usize>().unwrap())
.collect::<Vec<usize>>();

let inner_text = el.inner_text();
let mut parts = Vec::new();
let mut start = 0;
for i in indices {
parts.push(inner_text[start..i].to_string());
start = i;
}
parts.push(inner_text[start..].to_string());
Ok(parts)
}
}
3 changes: 3 additions & 0 deletions crates/sweet_rsx/src/sweet_loader/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
pub mod hydrate_client;
#[allow(unused_imports)]
pub use self::hydrate_client::*;
pub mod live_block;
#[allow(unused_imports)]
pub use self::live_block::*;
pub mod sweet_loader;
#[allow(unused_imports)]
pub use self::sweet_loader::*;
6 changes: 0 additions & 6 deletions crates/sweet_rsx/src/sweet_loader/sweet_loader.js

This file was deleted.

81 changes: 78 additions & 3 deletions crates/sweet_rsx/src/sweet_loader/sweet_loader.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,88 @@
use super::Hydrated;
use crate::prelude::*;
use wasm_bindgen::prelude::Closure;
use wasm_bindgen::JsCast;
use web_sys::Event;
use web_sys::HtmlElement;



/**
The sweet loader is the core of sweet_rsx, responsible for gluing together the hydrated events and blocks with
the dom.
The core of the event system is a global object called `_sweet`.
All events will look something like `onclick="_sweet.event(0,event)"`, where the first argument is the index in which the parser found the event block. Ids may skip numbers if a conditional block, rsx fragments, etc are found.
A tiny shim (111 bytes) is used to collect all events that are emmitted while the wasm is loading.
On initialization all of these events will be played back in order, and `_sweet` will directly call the handlers with corresponding id.
**/
#[derive(Default)]
pub struct SweetLoader;

impl SweetLoader {
pub fn load(self, mut hydrated: Hydrated) {
pub fn load(self, page: impl HydrateClient) -> ParseResult<()> {
console_error_panic_hook::set_once();

let (send, recv) = flume::unbounded();
let Hydrated { events, blocks } = page.hydrate(send)?;
self.handle_events(events)?;
self.handle_blocks(blocks, recv)?;
Ok(())
}


pub fn handle_blocks(
&self,
blocks: Vec<HydratedBlock>,
recv: flume::Receiver<(usize, String)>,
) -> ParseResult<()> {
let mut live_blocks = Vec::<Option<LiveBlock>>::new();


wasm_bindgen_futures::spawn_local(async move {
while let Ok(block_event) = recv.recv_async().await {
let block = blocks.get(block_event.0).expect(&format!(
"failed to get hydrated block with id: {}",
block_event.0
));
let block_str = block_event.1;

let live_block = {
if let Some(Some(block)) =
live_blocks.get_mut(block.node_id)
{
block
} else {
let live_block = LiveBlock::new(block).unwrap();

if live_blocks.len() <= block.node_id {
live_blocks.resize_with(block.node_id + 1, || None);
}

live_blocks[block.node_id] = Some(live_block);
live_blocks
.get_mut(block.node_id)
.unwrap()
.as_mut()
.unwrap()
}
};

live_block.parts[block.part_index] = block_str;
let inner_html = live_block.parts.join("");
live_block.el.set_inner_html(&inner_html);
}
});
Ok(())
}
pub fn handle_events(
&self,
mut events: Vec<HydratedEvent>,
) -> ParseResult<()> {
let mut func = move |id: usize, evt: Event| {
let handler = hydrated.events.get_mut(id).expect(&format!(
let handler = events.get_mut(id).expect(&format!(
"failed to get hydrated event with id: {}",
id
));
Expand Down Expand Up @@ -56,10 +129,12 @@ impl SweetLoader {

closure.forget();
});
Ok(())
}
}



pub mod sweet_loader_extern {
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
Expand Down
2 changes: 1 addition & 1 deletion crates/sweet_test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ glob.workspace = true
clap.workspace = true

#💡 async
flume = "0.11.1"
flume.workspace = true
rayon.workspace = true
futures.workspace = true
thread_local = "1.1.8"
Expand Down
4 changes: 2 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ expand-rsx:
just watch cargo expand -p sweet_rsx --example rsx_macro

hello-world *args:
forky serve target/hello_world & \
just watch 'just _hello-world {{args}}'

_hello-world *args:
Expand All @@ -92,5 +93,4 @@ _hello-world *args:
--out-dir ./target/hello_world \
--out-name bindgen \
--target web \
~/.cargo_target/wasm32-unknown-unknown/debug/examples/hello_world.wasm
forky serve target/hello_world
~/.cargo_target/wasm32-unknown-unknown/debug/examples/hello_world.wasm

0 comments on commit efaabef

Please sign in to comment.