How to use Rust Yew

In this post, we will prepare development environment for Rust Yew. Then, we will write minimal code with it and learn how to deploy it in your website also.
If you want to save your time and have experience in Rust, just clone the Steadylearner Web repository with
$git clone https://github.com/steadylearner/Webassembly.git
then inside Yew folder
$yarn $rustup default nightly $cargo install cargo-web
and $yarn watch:rs for development and $yarn prod for production files in release.
Then, make a route to serve index.html and modify paths to link them if you find a problem with it.
Prerequisite
Install Rust first if you haven't yet. Visit Rust Website for more information.
With this post, you will have everything ready to develop Rust Yew Web application. Before you invest more time for it, read the documentations from Yew, especially src and examples folders. If you see the source code of it, you can see that many of them are from Cargo Web. So it will be helpful for you to read its documentation also.
In case of rollupjs, it will be sufficient for you to know that
'To use it with a configuration file, pass the --config or -c flags.'(https://rollupjs.org/guide/en#configuration-files)
If you want to use your own favicon after you read this post, please clear cache of your web Browser first and use your file with name favicon.ico instead in static folder.
Table of Contents
- Install Cargo Web to use Yew
- How to prepare minimal files for it
- Rust Yew example from its official website
- Improve it
- How to upload it in your website
- Conclusion
1. Install Cargo Web to use Yew
Before this post, I doubted that my machine with Linux Ubuntu 18.04 would work with Yew or not. When you read the documentation from the Cargo Web, you can see that there are many options and it was difficult to decide what to use.
$rustup default nightly // 1. $cargo install cargo-web // 2.
-
You will need nightly features of Rust to use Yew. You can use override to make nightly directory specific instead of glboal.
-
It will take long. You may use this time to read Build a rust frontend with Yew post and its relevant information.
When you see that Cargo Web installation is completed. You have everything ready to write Rust yew. Its main role is to help you use JavaScript, HTML etc in Rust Yew.
Execute command below to make Cargo Web work well.
$echo 'default-target = "wasm32-unknown-unknown"' > Web.toml
Then, build some files and install NPM packages to make the entire Rust Yew project work.
Then, open the console in it and type $yarn watch:rs or $yarn prod and you will see the latest Yew example in your browser.
2. How to prepare minimal files for it
Create a minimal folder structure to start.
├── Cargo.toml ├── package.json ├── rollup.config.js ├── src │ ├── components │ ├── lib.rs │ └── main.rs ├── static │ ├── favicon.ico │ ├── index.css │ ├── index.html ├── Web.toml
We don't need to edit Web.toml and you can modify folder or file names for Cargo.toml, rollup.config.js.
Then, we only need to edit package.json, and src and static folders. For favicon.ico you can use your image instead of it.
You don't have to care for components and lib.rs file also. You can delete all files in components and empty lib.rs file but don't delete it because it is included in Cargo.toml.
In those processes, we removed some options and we only need these.
├── package.json ├── src │ └── main.rs ├── static │ ├── index.css │ ├── index.html
We will start from package.json.
{ "scripts": { "build:js": "rollup -c", "build:rs": "cargo web deploy --release", "build:copy": "cp target/deploy/index.css release/ && cp target/deploy/index.wasm release/ && cp target/deploy/index.html release/ && cp target/deploy/favicon.ico release/", "build": "run-s clean:deploy build:rs build:js build:copy", "clean:deploy": "rm -rf /release", "prod": "run-s build serve", "serve": "serve -p 8080 release", "watch:rs": "cargo web start --release", "test": "echo \"Error: no tests!\" && exit 1" }, "devDependencies": { "@babel/core": "^7.1.6", "@babel/preset-env": "^7.1.6", "autoprefixer": "^9.3.1", "nodemon": "^1.18.6", "npm-run-all": "^4.1.3", "rollup": "^0.67.3", "rollup-plugin-babel": "^4.0.3", "rollup-plugin-uglify": "^6.0.0", "rollup-plugin-wasm": "^3.0.0", "serve": "^11.0.2" } }
and you can
-
$yarn watch:rs when you develope
-
$yarn prod before you prepare production files.
We have index.html, index.css and main.rs left to make this work.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Web by Steadylearner</title> <meta name="description" content="Yew example made by https://www.steadylearner.com" /> <meta name="thumbnail" content="https://avatars0.githubusercontent.com/u/32325099?s=460&v=4" /> <link rel="stylesheet" type="text/css" href="steadylearner.css" /> <link rel="stylesheet" type="text/css" href="index.css" /> </head> <body> <script src="custom.js"></script> <script src="index.js"></script> </body> </html>
3. Rust Yew example from its official website
Yew has many examples. But we will use a minimal setup and start the development with it.
It will be similar to the code snippet below and copy it your main.rs file.
#[macro_use] extern crate yew; use yew::prelude::*; // M in MVC struct Model { value: i64, } // C in MVC enum Msg { DoIt, } impl Component for Model { type Message = Msg; type Properties = (); fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self { Self { value: 0, } } fn update(&mut self, msg: Self::Message) -> ShouldRender { match msg { Msg::DoIt => self.value = self.value + 1 } true } } // V in MVC impl Renderable<Model> for Model { fn view(&self) -> Html<Self> { html! { <div> <button onclick=|_| Msg::DoIt,>{ "+1" }</button> <p>{ self.value }</p> </div> } } } fn main() { yew::initialize(); App::<Model>::new().mount_to_body(); yew::run_loop(); }
This is the payload to control the app.
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self { Self { value: 0, } } fn update(&mut self, msg: Self::Message) -> ShouldRender { match msg { Msg::DoIt => self.value = self.value + 1 } true }
This is to render the view of it.
html! { <div> <button onclick=|_| Msg::DoIt,>{ "+1" }</button> <p>{ self.value }</p> </div> }
The entire main.rs file is the minimal example to show how to control state with Yew.
We do not need to edit fn create part cause we just need them only when we start it.
If you use $yarn watch:rs or yarn prod in your folder it will show you counter app in your localhost.
You have minimal development environment ready to start to use Yew.
4. Improve it
You are already ready to use Yew with the previous parts. You only need to do these.
-
Modify your html to be used inside Rust html! macro
-
Write JavaScript equivalent methods to update state in Rust Yew way.
But you haven't seen how to use CSS and what if you want to use various methods instead of just update it?
Then, you can refer to the example below.
#[macro_use] extern crate yew; use yew::prelude::*; struct Model { value: i64, } // 1. enum Msg { Plus, Minus, Zero, } impl Component for Model { type Message = Msg; type Properties = (); fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self { Self { value: 0, } } // 2. fn update(&mut self, msg: Self::Message) -> ShouldRender { match msg { Msg::Plus => self.value = self.value + 1, Msg::Minus => self.value = self.value - 1, Msg::Zero => self.value = 0, } true } } impl Renderable<Model> for Model { fn view(&self) -> Html<Self> { // 3. html! { <section class=("flex", "center", "height-vh"), > <section> <button class=("hover-blue", "cursor-pointer"), onclick=|_| Msg::Plus, title="Click this to plus one", > { "+1" } </button> <button class=("hover-red", "cursor-pointer"), onclick=|_| Msg::Minus, title="Click this to minus one", > { "-1" } </button> <p class=("flex", "center", "cursor-pointer"), onclick=|_| Msg::Zero, title="Click this back to zero", > { self.value } </p> </section> </section> } } } fn main() { yew::initialize(); App::<Model>::new().mount_to_body(); yew::run_loop(); }
-
We write more options for enum Msg
-
Modify update parts to make work with it
-
Include some class names, title and event handlers for html! macro to work
For html! is macro, we don't need to invest time to find what they do, just follow the rules.
It is important to notice that you should write , when you write prop - attribute for your html tags.
You can live edit your app with $yarn watch:rs. Whenever you modify the html example, you will see message similar to this
==== Triggering `cargo build` ==== Compiling index v0.1.0
and it take a little bit long because Rust will statically verify your web application and wouldn't compile if there is a problem in your code.
If you build the file with $yarn prod at this point, you will see console message similar to this.
$cp target/deploy/index.css release/ && cp target/deploy/
Your production files ready at release folder.
├── favicon.ico ├── index.css ├── index.html ├── index.js └── index.wasm
5. How to upload it in your website
The files we made before are just static files. To upload it to your website, do these.
-
Make a route for the index.html and verify it work
-
Edit path for other static files such as index.css, index.js, index.wasm and favicon.ico etc.
In this part, we will use Rust Rocket framework and its relevant codes. But, you can use whatever web framework and languages you want.
We will fstart with get.rs to write a route to serve index.html file we made before.
use std::io; use std::path::{PathBuf}; use rocket::response::{NamedFile, Redirect}; // [Web] #[get("/yew_counter")] pub fn yew_counter() -> io::Result<NamedFile> { NamedFile::open("static/yew_counter/index.html") }
You can infer that you should include all the static files you made before in static/yew_counter folder.
and main.rs to serve routes and start your application.
#![feature(proc_macro_hygiene, decl_macro, custom_attribute, rustc_private, type_ascription, async_await)] #[macro_use] extern crate rocket; #[macro_use] extern crate rocket_contrib; mod route; use crate::route::{static_files, get}; fn rocket() -> rocket::Rocket { rocket::ignite() .mount( "/", routes![ static_files::file, get::index, get::yew_counter, ], ) .register(catchers![error::not_found]) } fn main() { rocket().launch(); }
They are simplified and you can use your own codes instead. You are ready with your server side code.
Make yew_counter folder and we should edit index.html and index.js file.
Because the environment to serve file is different between development and production, we have to modify paths to make everything work in server side.
You can refer to Steadylearner Web repository and static_files_in_server_example in Yew/referenece folder if you want the entire example.
Inside index.html it have parts to link CSS files and JavaScript files to it.
<link rel="stylesheet" type="text/css" href="steadylearner.css" /> <link rel="stylesheet" type="text/css" href="index.css" /> <body> <script src="index.js"></script> </body>
You can modify it to
<link rel="stylesheet" type="text/css" href="/static/css/steadylearner.css" /> <link rel="stylesheet" type="text/css" href="/static/yew_counter/index.css" /> <body> <script src="/static/yew_counter/index.js"></script> </body>
In your index.js, you can see that code to serve web assembly files similar to
if (typeof process === "object") { var path = require("path"); var wasm_path = path.join(__dirname, "index.wasm"); var buffer = fs.readFileSync(wasm_path); var mod = new WebAssembly.Module(buffer); var wasm_instance = new WebAssembly.Instance(mod, instance.imports); return instance.initialize(wasm_instance); } else { var file = fetch("index.wasm", { credentials: "same-origin" });
It might not work in your server side so you can modify it to
if (typeof process === "object") { var wasm_path = "/static/yew_counter/index.wasm"; var buffer = fs.readFileSync(wasm_path); var mod = new WebAssembly.Module(buffer); var wasm_instance = new WebAssembly.Instance(mod, instance.imports); return instance.initialize(wasm_instance); } else { var file = fetch("/static/yew_counter/index.wasm", { credentials: "same-origin" });
It is JavaScript but what we do is the same. You just modify paths to work well inside with other files in your machine.
Everything is ready. You can use $cargo run --bin main or other command to start your app in production.
production here because what you need to do after this process is just to copy and paste them if you use virtual machine for Linux to deploy your website.
6. Conclusion
We could set up development environment for Rust Yew. Then, we improved the official example. We could learn how to deploy it.
What we made are just static files and there is no difference from other CSS, HTML and JavaScript files etc.
Thanks and please share this post with others.