I’m going to be going through some of the steps from Leptos Getting Started. Hopefully we’ll be able to add something here.
Prerequisites
We’re going to use Trunk to run our application, so first off we need to make sure we’ve installed it
cargo install trunk
Trunk allows us to build our code, run a server up and run our WASM application, moreover it’s watching for changes and so will rebuild and redeploy things as you go. Web style development with a compiled language. We’ll cover more on trunk later.
Creating our project
Create yourself a folder for your application and run the terminal in that folder.
We need to create our project – although I use RustRover from JetBrains, let’s go “old school” as use cargo to create our project etc.
Run the following (obviously change wasm_app to something more meaningful for your app name
cargo new wasm_app --bin
cd into the application (as above, mine’s named wasm_app).
cargo add leptos --features=csr
We’ll need to add the WASN target
rustup target add wasm32-unknown-unknown
in the root folder (where your Cargo.toml file is) create an index.html with the following
<!DOCTYPE html> <html> <head></head> <body></body> </html>
Next create a cd into the src folder and edit the main.rs – it should look like the following
use leptos::prelude::*;
fn main() {
leptos::mount::mount_to_body(|| view! { <p>"Hello, world!"</p> })
}
The mount_to_body, as the name suggests, essentially injects your WASM code into the <body></body> element.
Now, from the root folder (i.e. where index.html is) run
trunk serve --open
If the default port (8080) is already in use we can specify the port using
trunk serve --open --port 8081
If all went well then you see your default browser showing the web page, if not then open a browser window and navigate to http://localhost:8081/
To save having to set the port via the CLI, you can also create a trunk.toml file in the root folder with something like this in it
[serve] address = "127.0.0.1" port = 8081
Taking it a bit further
We’ve got ourselves a really simple WASM page.
Let’s move this a little further by creating a component for our application.
Create a file in src named app.rs and we’ll add the following
use leptos::prelude::*;
#[component]
pub fn App() -> impl IntoView {
view! {
<p>"Hello, world!"</p>
}
}
and change the main.rs to this
mod app;
use leptos::mount::mount_to_body;
use crate::app::App;
fn main() {
mount_to_body(App);
}
If you kept trunk running it will automatically rebuild the code and refresh the browser.
Components
The component (below) returns HTML via the view macro and as you can see, this returns a trait IntoView. As you can see we mark the function as a #[component]
#[component]
pub fn App() -> impl IntoView {
view! {
<p>"Hello, world!"</p>
}
}
Note: you might want to change your text to prove to yourself that the did indeed get updated in the web page when you saved it – assuming trunk was running.
This is a pretty simple starting point, so let’s add some more bits to this…
Let’s change the App function to this
use leptos::prelude::*;
#[component]
pub fn App() -> impl IntoView {
let (count, set_count) = signal(0);
view! {
<button on:click=move |_| set_count.set(count.get() + 1)>Up</button>
<div>{count}</div>
<button on:click=move |_| set_count.set(count.get() - 1)>Down</button>
}
}
The signal (which is a reactive variable) may remind you of something like useState in React, we deconstruct the signal (which has the default of 0) into a count (getter) and a set_count (setter). To be honest the set and get functions seem odd if you’re using the properties such as C#, but that’s the way it is is Rust.
The count value is of type ReadSignal and set_count is of type WriteSignal.
We can also set the value of count using the following within the closure. Ultimately this should be a more performant way of doing things. The example above might be preferred for readability (although that’s debatable) – it does however look more inline with the way we get values etc. I’ll leave others to debate the pros and cons, for me the line below is effecient.
*set_count.write() += 1
Routing
Let’s rename the app.rs file to counter.rs (also rename the function to Counter) and create a new app.rs file which will acts as the router to our components. We’ll need to add this to the Cargo.toml dependencies
leptos_router = "0.8.5"
and in the app.rs paste the following code
use leptos::prelude::*;
use leptos_router::{
components::{Route, Router, Routes},
StaticSegment,
};
use crate::counter::Counter;
use crate::home::Home;
#[component]
pub fn App() -> impl IntoView {
view! {
<Router>
<Routes fallback=|| "Page not found.">
<Route path=StaticSegment("") view=Home />
<Route path=StaticSegment("counter") view=Counter />
</Routes>
</Router>
}
}
You’ll need to add the mod to the main.rs file to include the home reference.
I’ve also added a home.rs file with the following
use leptos::prelude::*;
#[component]
pub fn Home() -> impl IntoView {
view! {
<p>Welcome to your new app!</p>
}
}
As you can see, the router routes / to our Home component and the /counter to our Counter component.
Meta data from code
Whilst we have an index.html which you can edit, we might want to supply some of the meta data etc. via the app’s code.
Add the following to Cargo.toml dependencies
leptos_meta = "0.8.5"
Now in main.rs add
use leptos::view; use leptos_meta::*;
and now change the code to
fn main() {
mount_to_body(|| {
provide_meta_context();
view! {
<Title text="Welcome to My App" />
<Meta name="description" content="This is my app." />
<App />
}
});
}
The provide_meta_context() function allows us to inject metadata such as <title>, <meta> and <script>
Code
Code for this post is available on GitHub.