diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml
index 6c6ecd2bdd..84fd92adc3 100644
--- a/benchmarks/Cargo.toml
+++ b/benchmarks/Cargo.toml
@@ -9,6 +9,9 @@ l0410 = { package = "leptos", version = "0.4.10", features = [
"ssr",
] }
leptos = { path = "../leptos", features = ["ssr", "nightly"] }
+leptos_reactive = { path = "../leptos_reactive", features = ["ssr", "nightly"] }
+tachydom = { git = "https://github.com/gbj/tachys", features = ["nightly"] }
+tachy_maccy = { git = "https://github.com/gbj/tachys", features = ["nightly"] }
sycamore = { version = "0.8", features = ["ssr"] }
yew = { version = "0.20", features = ["ssr"] }
tokio-test = "0.4"
diff --git a/benchmarks/src/ssr.rs b/benchmarks/src/ssr.rs
index 7fe755569f..034a75cae9 100644
--- a/benchmarks/src/ssr.rs
+++ b/benchmarks/src/ssr.rs
@@ -36,6 +36,43 @@ fn leptos_ssr_bench(b: &mut Bencher) {
r.dispose();
}
+#[bench]
+fn tachys_ssr_bench(b: &mut Bencher) {
+ use leptos::{create_runtime, create_signal, SignalGet, SignalUpdate};
+ use tachy_maccy::view;
+ use tachydom::view::Render;
+ let rt = create_runtime();
+ b.iter(|| {
+ fn counter(initial: i32) -> impl Render {
+ let (value, set_value) = create_signal(initial);
+ view! {
+
+
+ "Value: " {move || value().to_string()} "!"
+
+
+ }
+ }
+
+ let mut buf = String::with_capacity(1024);
+ let rendered = view! {
+
+ "Welcome to our benchmark page."
+ "Here's some introductory text."
+ {counter(1)}
+ {counter(2)}
+ {counter(3)}
+
+ };
+ rendered.to_html(&mut buf, &Default::default());
+ assert_eq!(
+ buf,
+ "Welcome to our benchmark page.
Here's some introductory text.
Value: 1!
Value: 2!
Value: 3!
"
+ );
+ });
+ rt.dispose();
+}
+
#[bench]
fn tera_ssr_bench(b: &mut Bencher) {
use serde::{Deserialize, Serialize};
diff --git a/benchmarks/src/todomvc/mod.rs b/benchmarks/src/todomvc/mod.rs
index 428138159c..51df21d219 100644
--- a/benchmarks/src/todomvc/mod.rs
+++ b/benchmarks/src/todomvc/mod.rs
@@ -2,6 +2,7 @@ use test::Bencher;
mod leptos;
mod sycamore;
+mod tachys;
mod tera;
mod yew;
@@ -17,13 +18,32 @@ fn leptos_todomvc_ssr(b: &mut Bencher) {
});
assert!(html.len() > 1);
});
+ runtime.dispose();
+}
+
+#[bench]
+fn tachys_todomvc_ssr(b: &mut Bencher) {
+ use ::leptos::*;
+ let runtime = create_runtime();
+ b.iter(|| {
+ use crate::todomvc::tachys::*;
+ use tachydom::view::Render;
+
+ let mut buf = String::new();
+ let rendered = TodoMVC(Todos::new());
+ rendered.to_html(&mut buf, &Default::default());
+ assert_eq!(
+ buf,
+ ""
+ );
+ });
+ runtime.dispose();
}
#[bench]
fn sycamore_todomvc_ssr(b: &mut Bencher) {
use self::sycamore::*;
- use ::sycamore::prelude::*;
- use ::sycamore::*;
+ use ::sycamore::{prelude::*, *};
b.iter(|| {
_ = create_scope(|cx| {
@@ -42,8 +62,7 @@ fn sycamore_todomvc_ssr(b: &mut Bencher) {
#[bench]
fn yew_todomvc_ssr(b: &mut Bencher) {
use self::yew::*;
- use ::yew::prelude::*;
- use ::yew::ServerRenderer;
+ use ::yew::{prelude::*, ServerRenderer};
b.iter(|| {
tokio_test::block_on(async {
@@ -69,11 +88,26 @@ fn leptos_todomvc_ssr_with_1000(b: &mut Bencher) {
});
}
+#[bench]
+fn tachys_todomvc_ssr_with_1000(b: &mut Bencher) {
+ use ::leptos::*;
+ let runtime = create_runtime();
+ b.iter(|| {
+ use crate::todomvc::tachys::*;
+ use tachydom::view::Render;
+
+ let mut buf = String::new();
+ let rendered = TodoMVC(Todos::new_with_1000());
+ rendered.to_html(&mut buf, &Default::default());
+ assert!(buf.len() > 20_000)
+ });
+ runtime.dispose();
+}
+
#[bench]
fn sycamore_todomvc_ssr_with_1000(b: &mut Bencher) {
use self::sycamore::*;
- use ::sycamore::prelude::*;
- use ::sycamore::*;
+ use ::sycamore::{prelude::*, *};
b.iter(|| {
_ = create_scope(|cx| {
@@ -92,8 +126,7 @@ fn sycamore_todomvc_ssr_with_1000(b: &mut Bencher) {
#[bench]
fn yew_todomvc_ssr_with_1000(b: &mut Bencher) {
use self::yew::*;
- use ::yew::prelude::*;
- use ::yew::ServerRenderer;
+ use ::yew::{prelude::*, ServerRenderer};
b.iter(|| {
tokio_test::block_on(async {
@@ -102,4 +135,19 @@ fn yew_todomvc_ssr_with_1000(b: &mut Bencher) {
assert!(rendered.len() > 1);
});
});
-}
\ No newline at end of file
+}
+
+#[bench]
+fn tera_todomvc_ssr(b: &mut Bencher) {
+ use ::leptos::*;
+ let runtime = create_runtime();
+ b.iter(|| {
+ use crate::todomvc::leptos::*;
+
+ let html = ::leptos::ssr::render_to_string(|| {
+ view! { }
+ });
+ assert!(html.len() > 1);
+ });
+ runtime.dispose();
+}
diff --git a/benchmarks/src/todomvc/tachys.rs b/benchmarks/src/todomvc/tachys.rs
new file mode 100644
index 0000000000..dfd9891aae
--- /dev/null
+++ b/benchmarks/src/todomvc/tachys.rs
@@ -0,0 +1,324 @@
+pub use leptos_reactive::*;
+use miniserde::*;
+use tachy_maccy::view;
+use tachydom::view::Render;
+use wasm_bindgen::JsCast;
+use web_sys::HtmlInputElement;
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Todos(pub Vec);
+
+const STORAGE_KEY: &str = "todos-leptos";
+
+impl Todos {
+ pub fn new() -> Self {
+ Self(vec![])
+ }
+
+ pub fn new_with_1000() -> Self {
+ let todos = (0..1000)
+ .map(|id| Todo::new(id, format!("Todo #{id}")))
+ .collect();
+ Self(todos)
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.0.is_empty()
+ }
+
+ pub fn add(&mut self, todo: Todo) {
+ self.0.push(todo);
+ }
+
+ pub fn remove(&mut self, id: usize) {
+ self.0.retain(|todo| todo.id != id);
+ }
+
+ pub fn remaining(&self) -> usize {
+ self.0.iter().filter(|todo| !(todo.completed)()).count()
+ }
+
+ pub fn completed(&self) -> usize {
+ self.0.iter().filter(|todo| (todo.completed)()).count()
+ }
+
+ pub fn toggle_all(&self) {
+ // if all are complete, mark them all active instead
+ if self.remaining() == 0 {
+ for todo in &self.0 {
+ if todo.completed.get() {
+ (todo.set_completed)(false);
+ }
+ }
+ }
+ // otherwise, mark them all complete
+ else {
+ for todo in &self.0 {
+ (todo.set_completed)(true);
+ }
+ }
+ }
+
+ fn clear_completed(&mut self) {
+ self.0.retain(|todo| !todo.completed.get());
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct Todo {
+ pub id: usize,
+ pub title: ReadSignal,
+ pub set_title: WriteSignal,
+ pub completed: ReadSignal,
+ pub set_completed: WriteSignal,
+}
+
+impl Todo {
+ pub fn new(id: usize, title: String) -> Self {
+ Self::new_with_completed(id, title, false)
+ }
+
+ pub fn new_with_completed(
+ id: usize,
+ title: String,
+ completed: bool,
+ ) -> Self {
+ let (title, set_title) = create_signal(title);
+ let (completed, set_completed) = create_signal(completed);
+ Self {
+ id,
+ title,
+ set_title,
+ completed,
+ set_completed,
+ }
+ }
+
+ pub fn toggle(&self) {
+ self.set_completed
+ .update(|completed| *completed = !*completed);
+ }
+}
+
+const ESCAPE_KEY: u32 = 27;
+const ENTER_KEY: u32 = 13;
+
+pub fn TodoMVC(todos: Todos) -> impl Render {
+ let mut next_id = todos
+ .0
+ .iter()
+ .map(|todo| todo.id)
+ .max()
+ .map(|last| last + 1)
+ .unwrap_or(0);
+
+ let (todos, set_todos) = create_signal(todos);
+ provide_context(set_todos);
+
+ let (mode, set_mode) = create_signal(Mode::All);
+
+ let add_todo = move |ev: web_sys::KeyboardEvent| {
+ todo!()
+ /* let target = event_target::(&ev);
+ ev.stop_propagation();
+ let key_code = ev.unchecked_ref::().key_code();
+ if key_code == ENTER_KEY {
+ let title = event_target_value(&ev);
+ let title = title.trim();
+ if !title.is_empty() {
+ let new = Todo::new(next_id, title.to_string());
+ set_todos.update(|t| t.add(new));
+ next_id += 1;
+ target.set_value("");
+ }
+ } */
+ };
+
+ let filtered_todos = create_memo::>(move |_| {
+ todos.with(|todos| match mode.get() {
+ Mode::All => todos.0.to_vec(),
+ Mode::Active => todos
+ .0
+ .iter()
+ .filter(|todo| !todo.completed.get())
+ .cloned()
+ .collect(),
+ Mode::Completed => todos
+ .0
+ .iter()
+ .filter(|todo| todo.completed.get())
+ .cloned()
+ .collect(),
+ })
+ });
+
+ // effect to serialize to JSON
+ // this does reactive reads, so it will automatically serialize on any relevant change
+ create_effect(move |_| {
+ ()
+ /* if let Ok(Some(storage)) = window().local_storage() {
+ let objs = todos
+ .get()
+ .0
+ .iter()
+ .map(TodoSerialized::from)
+ .collect::>();
+ let json = json::to_string(&objs);
+ if storage.set_item(STORAGE_KEY, &json).is_err() {
+ log::error!("error while trying to set item in localStorage");
+ }
+ } */
+ });
+
+ view! {
+
+
+
+
+ }
+}
+
+pub fn Todo(todo: Todo) -> impl Render {
+ let (editing, set_editing) = create_signal(false);
+ let set_todos = use_context::>().unwrap();
+ //let input = NodeRef::new();
+
+ let save = move |value: &str| {
+ let value = value.trim();
+ if value.is_empty() {
+ set_todos.update(|t| t.remove(todo.id));
+ } else {
+ (todo.set_title)(value.to_string());
+ }
+ set_editing(false);
+ };
+
+ view! {
+
+ /*
+
+
+
+
+ {move || {
+ editing()
+ .then(|| {
+ view! {
+
+ }
+ })
+ }} */
+
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Mode {
+ Active,
+ Completed,
+ All,
+}
+
+impl Default for Mode {
+ fn default() -> Self {
+ Mode::All
+ }
+}
+
+pub fn route(hash: &str) -> Mode {
+ match hash {
+ "/active" => Mode::Active,
+ "/completed" => Mode::Completed,
+ _ => Mode::All,
+ }
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct TodoSerialized {
+ pub id: usize,
+ pub title: String,
+ pub completed: bool,
+}
+
+impl TodoSerialized {
+ pub fn into_todo(self) -> Todo {
+ Todo::new_with_completed(self.id, self.title, self.completed)
+ }
+}
+
+impl From<&Todo> for TodoSerialized {
+ fn from(todo: &Todo) -> Self {
+ Self {
+ id: todo.id,
+ title: todo.title.get(),
+ completed: (todo.completed)(),
+ }
+ }
+}
diff --git a/benchmarks/src/todomvc/tera.rs b/benchmarks/src/todomvc/tera.rs
index dd6946e096..73ecebe813 100644
--- a/benchmarks/src/todomvc/tera.rs
+++ b/benchmarks/src/todomvc/tera.rs
@@ -87,7 +87,7 @@ static TEMPLATE: &str = r#"
"#;
#[bench]
-fn tera_todomvc(b: &mut Bencher) {
+fn tera_todomvc_ssr(b: &mut Bencher) {
use serde::{Deserialize, Serialize};
use tera::*;
@@ -127,7 +127,7 @@ fn tera_todomvc(b: &mut Bencher) {
}
#[bench]
-fn tera_todomvc_1000(b: &mut Bencher) {
+fn tera_todomvc_ssr_1000(b: &mut Bencher) {
use serde::{Deserialize, Serialize};
use tera::*;
@@ -174,4 +174,4 @@ fn tera_todomvc_1000(b: &mut Bencher) {
let _ = TERA.render("template.html", &ctx).unwrap();
});
-}
\ No newline at end of file
+}