Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Makes the wasm32-wasip1/2 target a first-class citizen for Leptos's Server-Side #3063

Merged
merged 24 commits into from
Nov 2, 2024

Conversation

raskyld
Copy link
Contributor

@raskyld raskyld commented Oct 5, 2024

DISCLAIMER: This is a draft, I am not a seasoned Rust developer so this PR need some attention to ensure the code is idiomatic and clean.

This is a side-project, not backed by my company so I need time to make it production ready, thanks for your understanding!

Abstract

This PR is a first step towards making Leptos capable of targeting wasm32-wasip1 (and wasm32-wasip2 when it is released) for the server-side.

ATM, I managed to wasmtime serve the component produced when running cargo-leptos build on this codebase.

I started working on this PR with the end-goal of making it easy to host the server-side on a wasmCloud lattice.

While wasmCloud is a CNCF Sandbox project, I would understand if you don't want to have anything related to this project in-tree. For this reason, this PR is focused on running on purely standard WASI. The produced component have 0 dependencies on any vendor. Which is why I think this PR could be merged in-tree.

For my following work, specifically focused on wasmCloud, I would do that out-of-tree. (Expect if maintainers have interest supporting wasmCloud as a first-class citizen aswell! 🚀)

Features

  • Add a generic feature to server_fn to make it portable across a wide range of platform.
  • Add a single-threaded version of any_spawner's custom executor feature (implemented by @stefnotch! 🎉) allowing the futures to run in the context of a WebAssembly runtime.
  • Add an ad-hoc async runtime executor for the wasm32 targets.
  • Add an integration crate leptos_wasi allowing Leptos users targeting the wasm32-wasip1 platform to make their app deployable on any WASI runtime (for now, I only tested wasmtime)
  • Support for both synchronous response and response streaming so most SsrMode are compatible
  • Users can choose how to serve static files, in my example implementation, I used a simple wasi:filesystem link provided
    by the --dir target/site/pkg flag of wasmtime serve.
  • Shortcut Mechanism: The component don't have any "initialisation" or "warm-up" phase, all is done on the fly and there is a shortcut mechanism that will skip all UI Rendering and/or Reactivity setup if the user is simply requesting a static file or calling a Server Function.

Limitations

  • For now, SsrMode::Static is not supported, I don't think it should be too hard to do, but for now it's not on my roadmap.
  • Optimized for cold-start : This crate makes the assumption that components are initiated for each incoming request, that is, the lifetime of the component is bound to the one of the request. For this reason, we do not do any memoization (like caching routes).

Road-map

  • Ask reviewer how we could improve the changes made to any_spawner, I really don't like my current changes.
  • Add a video to show-case the end result.. Most people won't read the PR if they don't see first what it does 🙈 (I will participate to the wasmCloud Community Call and will post the replay there since I will present the MR.
  • Test Server Functions
  • Implement an async runtime.
  • Use the wasi crate for the bindings.
  • Wait for the repo leptos-wasi in the organisation
  • Migrate to integration to this repo
  • Improve the library's error handling.

@benwis
Copy link
Contributor

benwis commented Oct 6, 2024

@raskyld This looks interesting. Adding generics to server fns to make them more versatile could be handy. I need to spend some time looking at what you did there.

I think you want to take a look at leptos-spin and leptos-spin-macro. We're keeping most new integrations out of the main crates to reduce the time needed to release a new feature, but I'm happy to create a leptos-wasi and leptos-wasi-macro in the leptos org if you'd like.

As far as the Leptos specific changes, it seems like you've broken some of the examples, so you'll want to review those

@raskyld
Copy link
Contributor Author

raskyld commented Oct 6, 2024

Hello @benwis thanks a lot for your answer.

Let me check that!
Also I kind of figured out your would off-tree some parts so that's fine for me!

I should move the whole integration/wasi in a standalone crate so right?
I don't have any macros yet, that's the advantage of using the new generic server_fn.

I will likely need an example / starter crate as well, ported from https://github.com/raskyld/leptos-wasmcloud

@raskyld raskyld force-pushed the feat/wasi-support branch from bed615d to 88f5e0d Compare October 6, 2024 12:39
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gbj since you are the author of this crate, could you guide me on how I implement a single-threaded implementation for running the server side in Wasm?

The current implementation works but I don't really like it.

I think I have no choice to expose a handle to LocalPool::run() since, unlike tokio and glib, there is no "ambiant" executor and the caller is responsible for starting the pool.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to look at the #3091, it looks like they're trying to add futures-executor. Is the wasip1 build that we do for Spin already do the single threaded executor/ Or is this unique to wasmcloud

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dicej pointed me out the executor used by spin is part of the spin SDK crate, so I should definitely go and have a look!

I will also check the linked PR since that would allow me to implement that out of tree aswell! Thanks!

@raskyld
Copy link
Contributor Author

raskyld commented Oct 9, 2024

Potentially, waiting for the release of the next Rust release that should come with a promotion of the wasm32-wasip2 target's Tier. It would bring a lot of nice things regarding the interop of Rust std but also I may not need to write a custom async runtime.. c.f. tokio-rs/tokio#4827

@raskyld raskyld changed the title Draft: Makes the standard wasm32-wasip1 target a first-class citizen for Leptos's Server-Side Draft: Makes the wasm32-wasip1/2 target a first-class citizen for Leptos's Server-Side Oct 9, 2024
@benwis
Copy link
Contributor

benwis commented Oct 10, 2024

Hello @benwis thanks a lot for your answer.

Let me check that! Also I kind of figured out your would off-tree some parts so that's fine for me!

I should move the whole integration/wasi in a standalone crate so right? I don't have any macros yet, that's the advantage of using the new generic server_fn.

I will likely need an example / starter crate as well, ported from https://github.com/raskyld/leptos-wasmcloud

Yeah, I'd make this a whole seperate crate. You could choose to wrap it up in a single integration crate like leptos-wasmcloud, or if you want to support other wasm function implementations you can do leptos-wasi

@raskyld
Copy link
Contributor Author

raskyld commented Oct 10, 2024

We definitely should go for leptos-wasi, the goal is to make the whole ecosystem benefit from that crate, individual runtimes and/or orchestrators can then implement their own logic separately.

@raskyld
Copy link
Contributor Author

raskyld commented Oct 10, 2024

CI errors may be my bad:

https://github.com/leptos-rs/leptos/pull/3063/files#diff-b0051fc93f2245d2fdd95cfff6790392036d7e84b3639181e26675ce8987a050R56-R59

I wanted to make my clippy more annoying since I count on that to grow my Rust knowledge a bit but It looks like it also applied to tests 🤔

@brooksmtownsend
Copy link

brooksmtownsend commented Oct 11, 2024

Chiming in from the wasmCloud side, very in support of a leptos-wasi crate that uses wasm32-wasip2 😄 If there's any work to make it a "first class citizen" in wasmCloud from the standard crate we can take that on, I'd rather have Leptos work out of the box with any component implementation.

Edit: I left a suggestion and a future improvement, hope that's alright!

@@ -21,6 +21,8 @@ nightly = []
ssr = []
actix = []
axum = []
wasi = ["generic"]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once wasm32-wasip2 is a stable target in Rust (coming in 1.82 afaik) the use of feature flags could be simplified, using the target directive instead https://doc.rust-lang.org/nightly/rustc/platform-support/wasm32-wasip2.html#conditionally-compiling-code

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True! I initially went for the feature to kind of follow the other integrations but since we have a target I may just use that!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

edit: This is live now in Rust 1.82!

@raskyld
Copy link
Contributor Author

raskyld commented Oct 18, 2024

#3091 has been merged, I will rework the PR on that point, my new approach will be to bundle a custom WASI async runtime exposed to Leptos as a CustomExecutor, I wanted to use a crate for that, but nothing seems "standard" atm so I will just default to an ad-hoc implementation.

This may be reconsidered once we add first-party support to tokio for all the pollables, see tokio-rs/mio#1836 for an exemple of early work (i.e., adding socket support to mio).

@raskyld
Copy link
Contributor Author

raskyld commented Oct 20, 2024

Implemented an async runtime which supports waiting for Pollable and waking specific Futures. 🎉

Tested a dumb "sleep 3s" c.f. raskyld/leptos-wasmcloud@a683304#diff-3f1994a48e642525e7a77d762690a04f386ec355bef4a97826ff0b4f54ef2239R61

This makes Suspense work correctly: the server return the HTML shell with the fallback and then streams the counter value 3 seconds later ⏱️

@raskyld
Copy link
Contributor Author

raskyld commented Oct 20, 2024

What is missing at this point is:

  • See how we can enable logging through wasi:logging, I am not sure wasmtime support exposing this world though. This would help with debugging the integration crate (maybe @brooksmtownsend have some hints on the state of the art for logging in a component? 🙏)
  • Improve the Error Handling, atm it's using a super generic Error type and I don't really like that. but that's more optimisation.
  • See with @benwis for the repository so I can migrate the leptos_wasi from this PR.

@brooksmtownsend
Copy link

have some hints on the state of the art for logging in a component? 🙏

Yeah, wasmtime doesn't support wasi:logging/[email protected] since that interface hasn't been through the proposal process far enough to be integrated into wasmtime. I would recommend, if running directly in wasmtime, just using stdout/stderr for logging since that's supported via wasi:cli

@raskyld raskyld changed the title Draft: Makes the wasm32-wasip1/2 target a first-class citizen for Leptos's Server-Side Makes the wasm32-wasip1/2 target a first-class citizen for Leptos's Server-Side Oct 23, 2024
@raskyld
Copy link
Contributor Author

raskyld commented Oct 23, 2024

Alright, I think the PR is ready-ish for a first review, but considering there is a lot of changes, we should likely start the migration to a dedicated repository.

As I test and play with this crate, I will make it incrementally more production ready (add test suites etc..).

@benwis
Copy link
Contributor

benwis commented Oct 23, 2024

@raskyld I've created the leptos_wasi repo under the leptos org and added you as a maintainer. Lmk if that doesn't work and we can get started!

@raskyld
Copy link
Contributor Author

raskyld commented Oct 24, 2024

Yay! Thanks 🙏

I will take the integration crate to a PR on this new repo after work :D 🎉

@raskyld
Copy link
Contributor Author

raskyld commented Oct 24, 2024

FYI, migration is done! I will present the PRs at the next wasmCloud Community Meeting (next Wednesday).
If you have an introduction speech somewhere so I can present Leptos more broadly, don't hesitate to tell me 🤝 !

@raskyld
Copy link
Contributor Author

raskyld commented Oct 24, 2024

I will rebase and run make format everywhere, but it's weird previous commit only had 1 failure 🤔 I need to check on that

Signed-off-by: Enzo "raskyld" Nocera <[email protected]>
This commit adds `From` implementations for the
`Req` and `Res` types using abstraction that are deemed
"platform-agnostic".

Indeed, both the `http` and `bytes` crates contains types
that allows us to represent HTTP Request and Response,
while being capable to target unconventional platforms
(they even have `no-std` support). This allows the
server_fn functions to target new platforms,
for example, the `wasm32-wasip*` targets.

Signed-off-by: Enzo "raskyld" Nocera <[email protected]>
Signed-off-by: Enzo "raskyld" Nocera <[email protected]>
Signed-off-by: Enzo "raskyld" Nocera <[email protected]>
I ran clippy in really annoying mode since I am still
learning Rust and I want to write clean idiomatic code.
I took suggestions that I thought made sense, if any
maintainers think those are *too much*, I can relax
those changes:

* Use `core` instead of `std` to ease migration to `no_std`
  (https://rust-lang.github.io/rust-clippy/master/index.html#/std_instead_of_core)
* Add documentation on exported types and statics
* Bring some types in, with `use`
* Add `#[non_exhaustive]` on types we are not sure we
  won't extend (https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_enums)
* Add `#[inline]` to help the compiler when doing
  cross-crate compilation and Link-Time optimization
  is not enabled. (https://rust-lang.github.io/rust-clippy/master/index.html#/missing_inline_in_public_items)
* Use generic types instead of anonymous `impl` params
  so callers can use the `::<>` turbofish syntax (https://rust-lang.github.io/rust-clippy/master/index.html#/impl_trait_in_params)

Signed-off-by: Enzo "raskyld" Nocera <[email protected]>
Signed-off-by: Enzo "raskyld" Nocera <[email protected]>
Remove clippy crate rules since it
seems to make tests fails in tests.

Signed-off-by: Enzo "raskyld" Nocera <[email protected]>
Signed-off-by: Enzo "raskyld" Nocera <[email protected]>
Signed-off-by: Enzo "raskyld" Nocera <[email protected]>
Signed-off-by: Enzo "raskyld" Nocera <[email protected]>
This commit adds a single-thread "local"
custom executor, which is useful for environments
like `wasm32` targets.

Signed-off-by: Enzo "raskyld" Nocera <[email protected]>
This commit adds a single-threaded
async runtime for `wasm32-wasip*`
targets.

Signed-off-by: Enzo "raskyld" Nocera <[email protected]>
This commit adds error types for the users
to implement better error handling.

Signed-off-by: Enzo "raskyld" Nocera <[email protected]>
Signed-off-by: Enzo "raskyld" Nocera <[email protected]>
Signed-off-by: Enzo "raskyld" Nocera <[email protected]>
@raskyld
Copy link
Contributor Author

raskyld commented Oct 30, 2024

Hopefully that should be it, I ran fmt target on crates I touched 🤞

@raskyld
Copy link
Contributor Author

raskyld commented Oct 31, 2024

Meh, I must be using cargo make in a wrong way or my rebase messed it, I will double check later today 😓

@raskyld
Copy link
Contributor Author

raskyld commented Oct 31, 2024

Here is the little demo I made at wasmCloud Community Call, excuse my terrible french accent that went wild 🤣

https://www.youtube.com/watch?v=SAAyukYJGeY&t=25s

@raskyld
Copy link
Contributor Author

raskyld commented Oct 31, 2024

Oh god I've just got it!
cargo make wasn't using my nightly toolchain for some reason 🙃
Now all should be gud!

Signed-off-by: Enzo "raskyld" Nocera <[email protected]>
@raskyld
Copy link
Contributor Author

raskyld commented Oct 31, 2024

Fixed the clippy error for server_fn, I am not sure why the other one fail though, all I see is CPU/Memory error from the GH Runner 🤔

@raskyld
Copy link
Contributor Author

raskyld commented Nov 1, 2024

Ah! Now I have the backtrace!
I forgot to make the features mutually exclusive, my bad, fixing it.. 🔧

@benwis
Copy link
Contributor

benwis commented Nov 2, 2024

@raskyld Bet that feels good!

Copy link
Contributor

@benwis benwis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thanks for all the hard work here!

@benwis benwis merged commit 2ef1723 into leptos-rs:main Nov 2, 2024
73 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants