leptos-rs / leptos Goto Github PK
View Code? Open in Web Editor NEWBuild fast web applications with Rust.
Home Page: https://leptos.dev
License: MIT License
Build fast web applications with Rust.
Home Page: https://leptos.dev
License: MIT License
Discussed briefly in #34, but I'm opening a new issue since it's mostly unrelated.
I took a look at the usage of Clone in the signals API and found it could be removed fairly easily. Resources and Memos were slightly less simple:
Resource::with()
was added, with similar behavior to Signal::with()
to allow access without cloningimpl FnMut(Option<&T>) -> T
instead of impl FnMut(Option<T>) -> T
I have a branch for these changes, but it's built on top of my branch for #35, so I'll open a PR for it once that gets merged.
JSON-encoding each field separately using your chosen library (serde
, serde-lite
, or miniserde
) sounds like a great idea, but actually works terrible for form inputs: the strings they send, because they're not wrapped in quotes, don't parse as a JSON string. Oops.
Implement a helper method on the <Form/>
component to derive it automatically from an action, using its (server function) URL as the form
's action, updating the action's version
when the form
submits, and sending errors to the action's error field.
Some of the examples (that use SSR + hydration) are divided into three directories: one for the server, one for the client, and one for the app. This used to be necessary to allow you to compile the hydration code to Wasm and the SSR code for the server without causing feature conflicts.
This problem no longer exists because the feature flags are no longer contradictory. So the hackernews
and counter_isomorphic
examples can be rewritten to use a single-create/single-directory structure instead.
See the model here.
Per @jquesada2016 in #53 (comment) it would be a great idea to put together a short guide to make it easier for people to contribute.
I'm curious to hear from others what sort of stuff would be useful in there.
Outline of the various packages and what goes where? General approach of each? Normal flow of execution in the program?
What would be useful?
Given the following code:
use leptos::*;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn start() {
mount_to_body(view_fn);
}
fn view_fn(cx: Scope) -> impl Mountable {
let (focus, set_focus) = create_signal(cx, false);
let focused = move || {
if focus() {
view! { cx, <p></p> }
} else {
view! { cx, <span /> }
}
};
view! { cx,
<div>
<input on:focus=move |_| set_focus(true) on:blur=move |_| set_focus(false) />
{focused}
</div>
}
}
on:focus
and on:blur
never fire. on:click
does, however.
We've been talking on Discord a little about this.
I think it should be possible, now that the csr
/ssr
/hydrate
features aren't mutually exclusive, to write a complete server-rendered-and-browser-hydrated app in a single directory.
Browser/specific code can compile on the server, but not run. Server code usually can't compile for Wasm.
You'd need to use #[wasm_bindgen]
to annotate the function you are using to hydrate the app (i.e., basically the few lines of code that are in hackernews-client
), and call that function from the HTML you're generating on the server.
Then put the actual server code behind a #[cfg(feature = "ssr")]
flag so that it doesn't try to compile actix-web
or whatever to Wasm.
Bonus points if it's also Trunk-compatible for an easy dev-mode client-side-rendered version.
If someone wants to try it out in the hackernews
directory and submit a PR I'd give them major kudos. @akesson it seems like this may be up your alley as you're working on cargo-leptos
too... Let me know if you're interested in giving it a try.
Currently the syn-rsx
crate doesn't allow mixing colons and dashes in attribute names, which breaks the on:
syntax in custom event names, which are encouraged to have a dash like my-event
.
I had a PR merged to syn-rsx
that would allow this, but there are also some other breaking changes in their 0.9 release, so I'll need to make sure we're up to date.
I'm really ignorant about CI setups so I'd love some help with this.
Various parts of the repo use ordinary cargo test
. I also have wasm-bindgen tests set up for some of the examples.
Using version 0.0.15
, the following results in out of order rending:
use leptos::*;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn start() {
mount_to_body(view_fn);
}
fn view_fn(cx: Scope) -> impl Mountable {
view! { cx,
<form>
<TextInput />
<button>"Clcik me?"</button>
</form>
}
}
#[component]
fn TextInput(cx: Scope) -> Element {
view! {cx,
<input />
}
}
If I inline the component, i.e, <form><input /> ...
, then it works, if I remove the surrounding <form>
then it also renders correctly.
The following example renders nothing, I assume this is related to #53.
use leptos::*;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn start() {
mount_to_body(view_fn);
}
fn view_fn(cx: Scope) -> impl Mountable {
let (sig, _) = create_signal(cx, view! { cx, <span /> });
let data = || {
view! { cx,
<>
<h1>"1"</h1>
<h1>"2"</h1>
<h1>"3"</h1>
</>
}
};
view! { cx,
<span>
{data} // <-- wrapping this in a <span /> fixes rendering
<h1>"4"</h1>
</span>
}
}
We can enable use on either stable or nightly Rust by detecting version with the rustversion
crate instead of using the stable
flag. This should be fairly simple... only leptos_reactive
and leptos_dom
need to change at all, and then have the feature removed in the main leptos
package.
I'm pretty sure this is actually both unsound and unnecessary; we want to force people to move signals into the closure anyway.
Through experimentation, I've noticed that using CSS minification tooling that analyzes the source code for classes, such as TailwindCSS, purge CSS, and Atomic fail to recognize classes which start with class-
, but do work with classes that start with class:
due to notations from JS frameworks, such as Svelte. It might be a good idea to consolidate class names to always use class:
, instead of having two seperate notations. Ideally, this should also be extended to on:
, but is outside the scope of this issue.
These tools can be made to recognize classes prefixed by class-
, but it might not be obvious to users.
There's an on_cleanup
function that can be used to run arbitrary code before a scope is disposed, e.g., before a row is dropped in a <For/>
component or before a nested route is disposed in the router (because you're navigating away from it).
It would be awesome to have a Resource::cancel()
available so you can do things like abort in-flight fetch
requests, etc.
I've seen plenty of Future
s that have some kind of cancel/abort feature. For example, the oneshot
channel in the futures
crate returns Result<Option<T>, Canceled>
.
Does anyone know there's a common way to indicate a cancellable future? I'm imagining, for example, a Cancellable
trait so that we have something like
pub fn create_cancellable_resource<S, T, Fu>(
cx: Scope,
source: impl Fn() -> S + 'static,
fetcher: impl Fn(S) -> Fu + 'static,
) -> Resource<S, T>
where
S: PartialEq + Debug + Clone + 'static,
T: Debug + Clone + Serialize + DeserializeOwned + 'static,
Fu: ***Cancellable*** + Future<Output = T> + 'static,
{
}
I'm realizing as I write this that, because the Resource
struct isn't generic over the type of its Future
at the moment (only create_resource
is, then it stores it as a Pin<Box<dyn Future ...>>
or somesuch) this is a little cumbersome.
I guess I could just define my own Cancellable
trait for people to implement, but I'd love to have something from the wider Rust ecosystem if it exists.
The following example results in a panic:
use leptos::*;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn start() {
console_error_panic_hook::set_once();
mount_to_body(view_fn);
}
fn view_fn(cx: Scope) -> impl Mountable {
view! { cx,
<TemplateComponent></TemplateComponent>
}
}
#[component]
fn TemplateComponent(cx: Scope) -> Element {
view! { cx,
<template>
<div>"Just doing the Lord's work."</div>
</template>
}
}
This only seems to happen if directly returning a <template />
from the top level of a component.
The full stack trace is:
Uncaught (in promise) Error: `unwrap_throw` failed
at imports.wbg.__wbindgen_throw (wasm.js?t=1667929395579:292:15)
at wasm_bindgen::throw_str::hafba1a1a4c068143 (wasm_bg.wasm:0x4508d)
at <core::option::Option<T> as wasm_bindgen::UnwrapThrowExt<T>>::expect_throw::h01d589852a07341a (wasm_bg.wasm:0x3ac67)
at wasm_bindgen::UnwrapThrowExt::unwrap_throw::h568eba629f01842e (wasm_bg.wasm:0x42212)
at wasm::TemplateComponent::hb3dcae73f423c169 (wasm_bg.wasm:0x3149c)
at wasm::view_fn::{{closure}}::h573caef1eccbc7ff (wasm_bg.wasm:0x3f78f)
at leptos_reactive::scope::Scope::untrack::hf1b86e9da7ad8dd0 (wasm_bg.wasm:0x320d5)
at leptos_dom::create_component::ha566be4d30a0487a (wasm_bg.wasm:0x3fae6)
at wasm::view_fn::he21f89cb96e65078 (wasm_bg.wasm:0x392b3)
at core::ops::function::Fn::call::ha4ca8e44e588d793
Also, reading the stack trace, just a heads up, using unwrap_throw
is unsound as it leads the stack in a potentially non-reentrant state, so if a user catches the JS exception, they will most likely experience UB.
consider adopting ideas from qwik framework.
leverage seralization on the client to enable:
i believe qwik is bringing some inovative ideas into the web app world and since this project is still in early development you can still possibly incorporate them.
Currently these are just a stub... I'd like to figure them out so the router is usable for both circumstances, but I want to take some time to make sure the API is right.
The following code is allowed:
use leptos::*;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn start() {
mount_to_body(view_fn);
}
fn view_fn(cx: Scope) -> impl Mountable {
let (focus, set_focus) = create_signal(cx, false);
let focused = move || {
if focus() {
view! { cx, <p></p> }
} else {
view! { cx, <span /> }
}
};
view! { cx,
<div>
<input on:focus=move |_| set_focus(true) on:blur=move |_| set_focus(false) />
{focused}
</div>
}
}
but the following is not:
view! { cx,
<input on:focus=move |_| set_focus(true) on:blur=move |_| set_focus(false) />
{focused}
}
I do understand why, because the macro expects these items to be Element
rather than impl IntoElement
, but thought it was worth mentioning in case this was not intentional.
Examples are excluded from the workspace and this makes rust-analyzer skip them on my machine. Do you use a special setup to circumvent that, or is this an oversight? While I don't have much experience with setting up rust projects, I feel like examples should simply be members of the workspace.
The following valid class name does not compile because of the 25
:
view! { cx,
<span class-opacity-25=true />
}
See router example. Contacts list should never reload, but reloads when any navigation happens.
I am working on a repro, but just wanted to raise the issue.
Here is a backtrace:
panicked at 'already borrowed: BorrowMutError', C:\Users\jose2\.cargo\git\checkouts\leptos-852582868f14a87e\21d7346\leptos_reactive\src\signal.rs:726:32
Stack:
Error
at http://localhost:5173/src/wasm/wasm.js:428:21
at logError (http://localhost:5173/src/wasm/wasm.js:237:18)
at imports.wbg.__wbg_new_abda76e883ba8a5f (http://localhost:5173/src/wasm/wasm.js:427:66)
at console_error_panic_hook::Error::new::hca9359d70b7a65b0 (http://localhost:5173/src/wasm/wasm_bg.wasm:wasm-function[7886]:0x2525df)
at console_error_panic_hook::hook_impl::hd6342f4a1d3ee1fa (http://localhost:5173/src/wasm/wasm_bg.wasm:wasm-function[1491]:0x151490)
at console_error_panic_hook::hook::h5583c33e69aaa70c (http://localhost:5173/src/wasm/wasm_bg.wasm:wasm-function[8484]:0x25aaca)
at core::ops::function::Fn::call::h4709acb035a3f1ae (http://localhost:5173/src/wasm/wasm_bg.wasm:wasm-function[7565]:0x24d60d)
at std::panicking::rust_panic_with_hook::h3534cfedda92faa6 (http://localhost:5173/src/wasm/wasm_bg.wasm:wasm-function[3320]:0x1d0181)
at std::panicking::begin_panic_handler::{{closure}}::h0b26f08fa80310e7 (http://localhost:5173/src/wasm/wasm_bg.wasm:wasm-function[4244]:0x1fa6ba)
at std::sys_common::backtrace::__rust_end_short_backtrace::hbf7b83761deb4f3e (http://localhost:5173/src/wasm/wasm_bg.wasm:wasm-function[9085]:0x2624d1)
Uncaught (in promise) RuntimeError: unreachable
at __rust_start_panic (wasm_bg.wasm:0x26966c)
at rust_panic (wasm_bg.wasm:0x264e18)
at std::panicking::rust_panic_with_hook::h3534cfedda92faa6 (wasm_bg.wasm:0x1d01ac)
at std::panicking::begin_panic_handler::{{closure}}::h0b26f08fa80310e7 (wasm_bg.wasm:0x1fa6ba)
at std::sys_common::backtrace::__rust_end_short_backtrace::hbf7b83761deb4f3e (wasm_bg.wasm:0x2624d1)
at rust_begin_unwind (wasm_bg.wasm:0x23a916)
at core::panicking::panic_fmt::h855c11889f2fb721 (wasm_bg.wasm:0x25989d)
at core::result::unwrap_failed::ha83ac9268ec678d9 (wasm_bg.wasm:0x212ae5)
at core::cell::RefCell<T>::borrow_mut::h38eb0755cd71904b (wasm_bg.wasm:0x198361)
at leptos_reactive::signal::SignalId::update::h68bb1fd62dd948e3 (wasm_bg.wasm:0x3a8e7)
It's great to see that there is a new Rust web frameworks like the on of yours :)
I'm very curious about what the fundamental conceptual difference is from sycamore?
Writting the following:
view! { cx,
<div class:hidden=true />
}
results in
<div class="class:hidden" ></div>
Same for the class-
variant.
I'd love to see some simple benchmarks of server-side rendering performance across different Rust frameworks (Leptos, Sycamore, Yew, Dioxus?) using cargo bench
This could be something fairly simple: as long as it involves two or three components, maybe a <For/>
component or other relevant keyed list. If it's too simple (like just rendering a simple large static template) it will be very unfair, since Leptos would probably compile it to a single string already.
Implement progressively-enhanced POST
for forms in leptos_router
It seems like resources are currently forced to be serializable to support server side rendering (and potentially resumability in the future). This makes sense, but it excludes some use cases that would otherwise make sense as resources. For example, I was experimenting with accessing a webcam in leptos (using nokhwa). This is an async operation and, reading the docs, it seemed like a resource was a perfect fit. However, a webcam handle isn't serializable so I ended up with this slightly more awkward implementation:
use leptos::*;
use nokhwa::{js_camera::JSCameraConstraintsBuilder, JSCamera};
use std::{cell::RefCell, rc::Rc};
async fn get_camera(set_camera: WriteSignal<Option<Rc<RefCell<JSCamera>>>>) {
let constraints = JSCameraConstraintsBuilder::new().build();
let camera = JSCamera::new(constraints).await;
match camera {
Ok(camera) => {
log::info!("Got camera");
set_camera(Some(Rc::new(RefCell::new(camera))));
}
Err(e) => {
log::error!("Failed to get camera: {}", e);
}
}
}
#[component]
pub fn UserWebcam(cx: scope) -> Element {
let (camera, set_camera) = create_signal::<Option<Rc<RefCell<JSCamera>>>>(cx, None);
spawn_local(get_camera(set_camera));
create_effect(cx, move |_| {
if let Some(camera) = camera.get() {
if let Err(e) = (*camera).borrow_mut().attach("camera_out", false) {
log::error!("Failed to attach camera: {}", e);
} else {
log::info!("Attached camera");
}
}
});
view! {cx,
<video id="camera_out" class="user_webcam"/>
}
}
It works, but it's not ideal and it can't integrate with functionality like suspense contexts (unless I'm missing something).
It feels like the most natural solution would be to split resources into serializable and non-serializable types. Serializable resources would keep the current behavior but non-serializable ones would be handled fully on the client side regardless of rendering configuration. Happy to take a stab at implementing this if you're open to a PR, but figured I'd open an issue first to at least make sure this functionality doesn't already exist somewhere.
Often when receiving optional props, it would be really convenient to allow the macro to accept option types for event handlers and such. This would also allow conditionally attaching event listeners which can become performance hits on components which are heavily reused.
Or just actually build the server fn type and dispatch the action.
At the moment these are all defined by urls on the pattern /function_name
This works but is obviously dumb.
/api
" while registering themmy_server_fn
in two different modules and they'd both try to be served at /my_server_fn
, with stupid consequences.The macro should prepend the current module path to the URL, and the registration function should take an optional prefix, which should feed back into the server fn itself so it updates its URL to include that prefix.
Loaders should probably be something more like the following. This allows them to do fine-grained reactive reloads only in response to particular params or queries on which they actually depend. Currently, they do a Remix-style “reload whenever query or params change,” which is not great. The main issue here is establishing a stable URL for the create_server_resource
(or whatever) so that the loader runs on either client or server, but the server resource runs only on server and can be accessed using a JSON endpoint.
fn contact_data(cx: Scope) -> Resource<String, Contact>{
let params = use_params_map(cx);
let id = create_memo(cx, |_| params.get("id"));
create_server_resource(cx, move || id(), |id: String| fetch_contact_data_for_user(&id))
}
Would it be possible to create a Discord for an informal way of communicating?
I'm possibly going to use Leptos for a project that needs to launch in some months and I'd like to know my degree of insanity for even considering it ;)
Much of the reactive system is built on RefCell
, which for some reason gives really unhelpful error messages in the browser, even in debug mode, because it doesn't give line numbers as it does in the console. (At least for me. Chrome tends to be better than Firefox with Wasm errors, but still not great.)
I usually don't see panic messages in Wasm error output either, so I have a debug_warn!
macro in leptos_reactive
that uses println!
syntax to log a warning to the console but is stripped out in release builds for bundle size.
It would be good to replace all the borrow_mut()
a in leptos_reactive
with try_borrow_mut()
s that catch and log an appropriate error with debug_warn!
Some of these don't even need to panic: for example if you're trying to run an effect or update a signal they can log the warning and then do nothing. Reading a signal that has been disposed does need to panic (unless someone has a better solution), because otherwise there's no value to return.
This helps with debugging issues like #25
Just like the href property on an A component, Form action should take an IntoHref type, so it can update dynamically. This is important for making sure relative routes work if params change, because the form is not rerendered.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.