mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
updating SSR benchmarks to include tachys
This commit is contained in:
parent
73112c9faa
commit
a1a9d41a7a
5 changed files with 424 additions and 12 deletions
|
@ -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"
|
||||
|
|
|
@ -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! {
|
||||
<div>
|
||||
<button on:click=move |_| set_value.update(|value| *value -= 1)>"-1"</button>
|
||||
<span>"Value: " {move || value().to_string()} "!"</span>
|
||||
<button on:click=move |_| set_value.update(|value| *value += 1)>"+1"</button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
let mut buf = String::with_capacity(1024);
|
||||
let rendered = view! {
|
||||
<main>
|
||||
<h1>"Welcome to our benchmark page."</h1>
|
||||
<p>"Here's some introductory text."</p>
|
||||
{counter(1)}
|
||||
{counter(2)}
|
||||
{counter(3)}
|
||||
</main>
|
||||
};
|
||||
rendered.to_html(&mut buf, &Default::default());
|
||||
assert_eq!(
|
||||
buf,
|
||||
"<main><h1>Welcome to our benchmark page.</h1><p>Here's some introductory text.</p><div><button>-1</button><span>Value: <!>1<!>!</span><button>+1</button></div><div><button>-1</button><span>Value: <!>2<!>!</span><button>+1</button></div><div><button>-1</button><span>Value: <!>3<!>!</span><button>+1</button></div></main>"
|
||||
);
|
||||
});
|
||||
rt.dispose();
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn tera_ssr_bench(b: &mut Bencher) {
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
|
@ -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,
|
||||
"<main><section class=\"todoapp\"><header class=\"header\"><h1>todos</h1><input placeholder=\"What needs to be done?\" autofocus=\"\" class=\"new-todo\"></header><section class=\"main hidden\"><input id=\"toggle-all\" type=\"checkbox\" class=\"toggle-all\"><label for=\"toggle-all\">Mark all as complete</label><ul class=\"todo-list\"></ul></section><footer class=\"footer hidden\"><span class=\"todo-count\"><strong>0</strong><!> items<!> left</span><ul class=\"filters\"><li><a href=\"#/\" class=\"selected selected\">All</a></li><li><a href=\"#/active\" class=\"\">Active</a></li><li><a href=\"#/completed\" class=\"\">Completed</a></li></ul><button class=\"clear-completed hidden hidden\">Clear completed</button></footer></section><footer class=\"info\"><p>Double-click to edit a todo</p><p>Created by <a href=\"http://todomvc.com\">Greg Johnston</a></p><p>Part of <a href=\"http://todomvc.com\">TodoMVC</a></p></footer></main>"
|
||||
);
|
||||
});
|
||||
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 {
|
||||
|
@ -103,3 +136,18 @@ fn yew_todomvc_ssr_with_1000(b: &mut Bencher) {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[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! { <TodoMVC todos=Todos::new()/> }
|
||||
});
|
||||
assert!(html.len() > 1);
|
||||
});
|
||||
runtime.dispose();
|
||||
}
|
||||
|
|
324
benchmarks/src/todomvc/tachys.rs
Normal file
324
benchmarks/src/todomvc/tachys.rs
Normal file
|
@ -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<Todo>);
|
||||
|
||||
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<String>,
|
||||
pub set_title: WriteSignal<String>,
|
||||
pub completed: ReadSignal<bool>,
|
||||
pub set_completed: WriteSignal<bool>,
|
||||
}
|
||||
|
||||
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::<HtmlInputElement>(&ev);
|
||||
ev.stop_propagation();
|
||||
let key_code = ev.unchecked_ref::<web_sys::KeyboardEvent>().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::<Vec<Todo>>(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::<Vec<_>>();
|
||||
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! {
|
||||
<main>
|
||||
<section class="todoapp">
|
||||
<header class="header">
|
||||
<h1>"todos"</h1>
|
||||
<input
|
||||
class="new-todo"
|
||||
placeholder="What needs to be done?"
|
||||
autofocus=""
|
||||
/>
|
||||
</header>
|
||||
<section class="main" class:hidden=move || todos.with(|t| t.is_empty())>
|
||||
<input
|
||||
id="toggle-all"
|
||||
class="toggle-all"
|
||||
r#type="checkbox"
|
||||
//prop:checked=move || todos.with(|t| t.remaining() > 0)
|
||||
on:input=move |_| set_todos.update(|t| t.toggle_all())
|
||||
/>
|
||||
<label r#for="toggle-all">"Mark all as complete"</label>
|
||||
<ul class="todo-list">
|
||||
{filtered_todos.get().into_iter().map(Todo).collect::<Vec<_>>()}
|
||||
</ul>
|
||||
</section>
|
||||
<footer class="footer" class:hidden=move || todos.with(|t| t.is_empty())>
|
||||
<span class="todo-count">
|
||||
<strong>{move || todos.with(|t| t.remaining().to_string())}</strong>
|
||||
{move || if todos.with(|t| t.remaining()) == 1 { " item" } else { " items" }}
|
||||
" left"
|
||||
</span>
|
||||
<ul class="filters">
|
||||
<li>
|
||||
<a
|
||||
href="#/"
|
||||
class="selected"
|
||||
class:selected=move || mode() == Mode::All
|
||||
>
|
||||
"All"
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#/active" class:selected=move || mode() == Mode::Active>
|
||||
"Active"
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#/completed" class:selected=move || mode() == Mode::Completed>
|
||||
"Completed"
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<button
|
||||
class="clear-completed hidden"
|
||||
class:hidden=move || todos.with(|t| t.completed() == 0)
|
||||
on:click=move |_| set_todos.update(|t| t.clear_completed())
|
||||
>
|
||||
"Clear completed"
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
<footer class="info">
|
||||
<p>"Double-click to edit a todo"</p>
|
||||
<p>"Created by " <a href="http://todomvc.com">"Greg Johnston"</a></p>
|
||||
<p>"Part of " <a href="http://todomvc.com">"TodoMVC"</a></p>
|
||||
</footer>
|
||||
</main>
|
||||
}
|
||||
}
|
||||
|
||||
pub fn Todo(todo: Todo) -> impl Render {
|
||||
let (editing, set_editing) = create_signal(false);
|
||||
let set_todos = use_context::<WriteSignal<Todos>>().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! {
|
||||
<li class="todo" class:editing=editing class:completed=move || (todo.completed)()>
|
||||
/* <div class="view">
|
||||
<input class="toggle" r#type="checkbox"/>
|
||||
<label on:dblclick=move |_| set_editing(true)>{move || todo.title.get()}</label>
|
||||
<button
|
||||
class="destroy"
|
||||
on:click=move |_| set_todos.update(|t| t.remove(todo.id))
|
||||
></button>
|
||||
</div>
|
||||
{move || {
|
||||
editing()
|
||||
.then(|| {
|
||||
view! {
|
||||
<input
|
||||
class="edit"
|
||||
class:hidden=move || !(editing)()
|
||||
/>
|
||||
}
|
||||
})
|
||||
}} */
|
||||
</li>
|
||||
}
|
||||
}
|
||||
|
||||
#[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)(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -87,7 +87,7 @@ static TEMPLATE: &str = r#"<main>
|
|||
</main>"#;
|
||||
|
||||
#[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::*;
|
||||
|
||||
|
|
Loading…
Reference in a new issue