Getting Started with Yew

As much as I dislike the traditional “Hello, World” program as a means to illustrate real-world lessons, I do think they have value in giving you an initial exposure to something brand new.

The Yew crate is unlike anything you’ve seen thus far in the book, and so we’re going to start off with a simple example of how to build a Yew application before getting into the real work of building the live multi-user chat.

The samples in this chapter are written with Yew version 0.4.0. If you’re reading this book and the crates.io[24] version of Yew is up to 0.5.0 or later, you’ll need to decide which version you want to use. Thanks to Cargo’s excellent version pinning, the book sample written against 0.4.0 will always work as it appears in the dead tree version of this book. However, Yew 0.5.0 looks like it might contain some syntax improvements as well as multi-threaded worker support. If you end up deciding to use Yew for a real application, you might want to invest some time in learning the new syntax (which is likely to include breaking changes from 0.4.0).

What Is Yew?

Inspired by the Elm language and the React JavaScript framework, Yew is a modern web application framework designed to compile to WebAssembly (wasm32-unknown-unknown), asmjs, or emscripten. We’ll only be using the pure Rust WebAssembly target in this book.

Yew is a framework based on components, contexts, and message passing, creating a powerful environment for building responsive front-end applications that still operate nicely within the confines of Rust’s requirements and safety constraints. It supports a virtual DOM, reusable fragments and components, and has many of the features we look for in modern JavaScript web UI frameworks.

Yew has two main Rust traits, which are very similar to interfaces in other languages. These traits are Component and Renderable. The component is responsible for providing the business logic and managing state for a discrete portion of the user interface. Anything that implements the Renderable trait is, as the name implies, responsible for producing the HTML necessary to render that entity in place within a virtual DOM. If you’ve had any exposure to React, then you’ll recognize the power and performance benefits you get from a virtual DOM.

Yew’s power comes from the use of a custom plugin build tool (which you’ll install shortly), some library code, and a large number of macros in the Yew crate and others upon which it depends. It generates an enormous amount of wiring code that allows you to think in terms of pure Rust, components, and renderables, and not worry too much about how that Rust is going to work inside a browser as a WebAssembly module.

The Yew Component trait has create, update, change, and destroy functions. When you build a component you implement these functions to manage state and logic. The Yew Renderable trait simply contains the view function, which returns HTML that should be rendered to the client.

Why Yew?

The purpose of this chapter is to show you how to build a web application using (nearly) nothing but Rust code compiled to WebAssembly. When we traditionally go to build web applications, we have to write things in JavaScript and HTML and CSS and we typically need to know a ton of frameworks on top of that (e.g., Vue, React, Angular), we need to know front-end build tools and back-end build tools, and we need to figure out where to split our code—what goes in the back end and what goes in the front end?

Frameworks like Yew let us build everything in Rust, give us the benefit of strong typing and safe code that we get from Rust, allow us to use a single, unified toolchain for our web application, and dramatically simplify the development process. Not every React application should be immediately re-written as a “Rust-pure WebAssembly Yew” app, but hopefully by the end of the chapter, you’ll have an idea of where these kinds of frameworks can come in handy. Yew is not the only web framework available for Rust and WebAssembly, but it’s a mature and accessible one, so it makes for a decent book example.

As an added bonus, working with the Yew framework will expose you to some more foundational aspects of Rust, including traits, generics, referencing modules, mutable reference passing, and more.

Building Your First Yew Application

Building an application with Yew means composing a hierarchy of UI elements composed of components and renderables. The sample you’ll build is a simple counter. You’ll render a number and, whenever a user clicks a nearby button, the number will increase.

That might seem overly simplistic, but remember this is being done from inside Rust, built into a WebAssembly module, and then executed as JavaScript. It’s worth it to take the effort to build this small sample to see how components and renderables work in Yew before moving onto a bigger problem domain. State will be maintained entirely within your Rust code, and you won’t have to worry about which part of it is a back-end component and which is front-end.

The first thing you’ll need to do is install Yew’s required build plugin, cargo-web. To do that, issue the following command at a terminal prompt:

 $ ​​cargo​​ ​​install​​ ​​cargo-web

Depending on how much Rust development you’ve been doing on your machine, this can take quite a while for all of the components to be downloaded and compiled.

Don’t confuse the cargo command with the rustup command; rustup is responsible for managing your installed toolchains and targets while cargo is responsible for building and creating your projects. For the rest of this chapter, you’ll be building and running your applications with the cargo web command instead of the usual cargo.

To get started, create a new project (deleting the edition = "2018" line) with cargo new (I called mine yewcounter). Unlike previous Rust projects, you don’t have to declare that the application is a dynamic library, as cargo web will take care of those details. Create a Cargo.toml that looks like this one:

 [package]
 name = ​"yewcounter"
 version = ​"0.1.0"
 authors = [​"Kevin Hoffman <[email protected]"​]
 
 [dependencies]
 stdweb = ​"0.4.2"
 yew = ​"0.4.0"

The next thing we’ll do is create a src/main.rs, which is typically reserved for standalone application binaries and not library modules.[25] cargo web takes care of the project metadata and compilation configuration for us, but I like being able to think of this as an application and not just an isolated module so the use of main.rs feels right.

In this new file, you’ll see a Context structure. It implements a trait called AsMut. Anything that implements this trait indicates that it can expose a mutable reference to the type given in the generic type parameter. In this case, the context can provide a mutable reference to a service called ConsoleService, a service that provides access to JavaScript’s console variable:

 extern​ crate yew;
 extern​ crate yewcounter; ​// refers to lib.rs
 
 use​ ​yew​::​prelude​::*;
 use​ ​yew​::​services​::​console​::ConsoleService;
 use​ ​yewcounter​::Model;
 
 pub​ ​struct​ Context {
  console: ConsoleService,
 }
 
 impl​ AsMut<ConsoleService> ​for​ Context {
 fn​ ​as_mut​(&​mut​ ​self​) ​->​ &​mut​ ConsoleService {
  &​mut​ ​self​.console
  }
 }
 
 fn​ ​main​() {
 yew​::​initialize​();
 let​ context = Context {
  console: ​ConsoleService​::​new​(),
  };
 let​ app: App<_, Model> = ​App​::​new​(context);
  app​.mount_to_body​();
 yew​::​run_loop​();
 }

From a polymorphism perspective, the goal of what’s happening with this struct and trait is the ability to pass a concrete struct type known only to the main module and have the components and renderables be able to use the services contained within that context. In other words, the UI code relies on the ability to obtain a mutable reference to a console service, but they aren’t tightly coupled to how that service is made available. In other languages, you’d accomplish something like this with dependency injection or structural typing.

In addition to components and renderables, Yew also has the concept of services. Services, within the realm of Yew applications, are designed to expose “headless” (no UI) functionality to UI components. In this sample, the service we’re using exposes the ability to log to JavaScript’s console as a service. In the next sample in the chapter, you’ll create a service that exposes a multi-user chat engine.

The code in the main function is pretty standard for all Yew applications—initialize the Yew runtime, create the context that’s appropriate for your application, then create an application for your model that takes your context as a parameter, and finally kick off the execution loop. Next, you’ll define your model, which is the data read and manipulated by your component.

We’ll put the model, component, and renderable in lib.rs in the same src directory as main.rs. For a more complex application, we might choose a more robust module hierarchy (you’ll see some of those later in the book). In the case of a simple counter, you can express the model as just a single field on a struct:

 pub​ ​struct​ Model {
  value: i64,
 }

In keeping with the React[26]-like architecture, we need to figure out which messages (Redux developers might call them actions) we want to pass through the component to produce changes within the model, which in turn changes how the component renders. The Yew crate’s stock counter sample comes with the following messages, defined by a Rust enum:

 pub​ ​enum​ Msg {
  Increment,
  Decrement,
 Bulk​(Vec<Msg>),
 }

You might have noticed that one of the variants (Bulk) can actually contain a vector (array) of itself. As you’ll see, this lets us bundle up multiple messages together and send them through the component as a single batch. Before getting into the full code listing for the implementation, there’s some new Rust syntax to cover that you’ll see:

 impl​<C> Component<C> ​for​ Model
  where
  C: AsMut<ConsoleService> {
  ...
 }

This syntax combines Rust’s generics system with its trait system. This is really one of the first areas beyond borrows, moves, and references where Rust’s syntax may start to confuse people who have backgrounds in other languages. This syntax indicates that, for any instance of the Model struct, this scope contains a bound Component trait implementation when the type parameter to that component (C) implements the AsMut<ConsoleService> trait.

Putting much of the syntax details aside, this code boils down to this statement—Model can be treated as a Component so long as we can extract a mutable reference to a ConsoleService from the component. If you look back at the src/main.rs code, you’ll see that we don’t actually instantiate our Model directly. Instead, we pass it as a type parameter to Yew’s App struct.

There’s a similar, slightly more complex syntax that’s used to define the Renderable responsible for emitting the HTML for the component:

 impl​<C> Renderable<C, Model> ​for​ Model
  where
  C: AsMut<ConsoleService> + 'static {
  ...
 }

Essentially this code says that the Model struct can render anything that is of type Model with a context type parameter that allows us to extract a mutable reference to a ConsoleService.

I personally find this type of Rust syntax “bumpy,” and it doesn’t read naturally for me. The only way I learned it was simply to get used to it. Others, possibly those with more exposure to traditional C++, find this syntax more natural and low-friction. The other potentially confusing bit of syntax is the ’static bit—a lifetime specifier.

The Rust compiler prevents us from accessing values that may no longer exist. To do this, it needs to know how long (relatively) those values should exist. Most of the time, Rust can infer a memory lifetime and save us the bother of explicitly defining one, and it does an even better job of eliding these details in the 2018 edition syntax. But why do we need a lifetime specifier here? I’m glad you asked!

In this one line of fairly dense syntax, you can assume that the code inside the implementation block will, at some point, obtain a mutable reference to something of type ConsoleService. The Rust compiler must now police this lifetime to ensure that it lasts long enough for us to invoke the log method on a console instance without the console instance being null due to going out of scope or being deallocated. Here, the static lifetime specifier is used. Without boring you with 20 pages of detail, this doesn’t necessarily mean the value lasts forever. It just means that it has an unlimited potential lifetime as far as the Rust compiler is concerned. Since we know this is JavaScript’s console variable, we can assume that its lifetime will never end so long as the WebAssembly module is loaded.

If you were to remove the static lifetime specifier and then try to compile the code in the upcoming listing, you’d get one of Rust’s famously expressive and helpful error messages:

 error: Could not compile `yewcounter`.
 
 To learn more, run the command again with --verbose.
 error[E0310]: the parameter type `C` may not live long enough
  -->​ src/lib.rs:54:9
  |
 49 | impl<C>​ Renderable<C, Model> ​for ​Model
  | - help: consider adding an explicit lifetime bound `C: 'static`...
 ...
 54 | / html! {
 55 | | <div>
 56 | | <nav class="menu",>
 57 | | <button onclick=|_| Msg::Increment,>​{ ​"Increment"​ }</button>
 ... |
 63 | | </div>
 64 | | }
  | |_________^
  |
 note: ...so that the type `C` will meet its required lifetime bounds
  -->​ src/lib.rs:54:9
  |
 54 | / html! {
 55 | | <div>
 56 | | <nav class="menu",>
 57 | | <button onclick=|_| Msg::Increment,>​{ ​"Increment"​ }</button>
 ... |
 63 | | </div>
 64 | | }
  | |_________^

The error message even suggests that we should consider adding a static lifetime bound so that the C type will meet its requirements. I really do love the Rust compiler error messages—an enormous amount of community effort has gone into making them readable and provide useful hints.

Here is the completed src/lib.rs code that implements a component and a renderable for the simple counter model:

 extern​ crate stdweb;
 #[macro_use]
 extern​ crate yew;
 
 use​ ​stdweb​::​web​::Date;
 use​ ​yew​::​prelude​::*;
 use​ ​yew​::​services​::​console​::ConsoleService;
 
 pub​ ​struct​ Model {
  value: i64,
 }
 
 pub​ ​enum​ Msg {
  Increment,
  Decrement,
 Bulk​(Vec<Msg>),
 }
 
 impl​<C> Component<C> ​for​ Model
 where
  C: AsMut<ConsoleService>,
 {
 type​ Message = Msg;
 type​ Properties = ();
 
 fn​ ​create​(_: ​Self​::Properties, _: &​mut​ Env<C, Self>) ​->​ Self {
  Model { value: 0 }
  }
 fn​ ​update​(&​mut​ ​self​, msg: ​Self​::Message,
  env: &​mut​ Env<C, Self>) ​->​ ShouldRender {
 match​ msg {
 Msg​::Increment ​=>​ {
 self​.value = ​self​.value + 1;
  env​.as_mut​()​.log​(​"plus one"​);
  }
 Msg​::Decrement ​=>​ {
 self​.value = ​self​.value ​-​ 1;
  env​.as_mut​()​.log​(​"minus one"​);
  }
 Msg​::​Bulk​(list) ​=>​ ​for​ msg in list {
 self​​.update​(msg, env);
  env​.as_mut​()​.log​(​"Bulk action"​);
  },
  }
 true
  }
 }
 
 impl​<C> Renderable<C, Model> ​for​ Model
 where
  C: AsMut<ConsoleService> + 'static,
 {
 fn​ ​view​(&​self​) ​->​ Html<C, Self> {
  html! {
  <div>
  <nav class=​"menu"​,>
  <button onclick=|_| ​Msg​::Increment,>{ ​"Increment"​ }</button>
  <button onclick=|_| ​Msg​::Decrement,>{ ​"Decrement"​ }</button>
  <button onclick=|_| ​Msg​::​Bulk​(vec![​Msg​::Increment,
 Msg​::Increment]),>
  { ​"Increment Twice"​ }
  </button>
  </nav>
  <p>{ ​self​.value }</p>
  <p>{ ​Date​::​new​()​.to_string​() }</p>
  </div>
  }
  }
 }

The html! procedural macro (you can spot procedural macros by their exclamatory nature!) takes the elements contained within it and produces real HTML, which the Yew virtual DOM will render as soon as it deems appropriate. The html! macro is defined by the stdweb[27] crate, along with the js! macro that will emit in-situ JavaScript wherever encountered in your Rust code.

Pay close attention to what’s inside the html! macro, though. It might look like regular HTML, but it’s really just tokens waiting to be parsed by Rust. As such, you’ll notice that the macro requires a comma after every attribute=value segment, even if it precedes the closing markup character. It takes a little getting used to, and readers with experience using React’s JSX have seen this kind of mild frustration before. If you’re using a text editor with real Rust support, though, it should be able to detect syntax errors in your macro and try to warn you (though the error message may often seem obscure).

With these two files written, it’s time to compile and run this application. If you’re wondering where the index.js JavaScript file is or the index.html file is—we don’t need them. At least, not for this sample because Yew builds some reasonable defaults for us via cargo web:

 $ ​​cargo​​ ​​web​​ ​​build​​ ​​--target=wasm32-unknown-unknown
  Compiling yewcounter v0.1.0
 (file:///home/kevin/Code/Rust/wasmbook/khrust/Book/code/yewcounter)
  Finished release [optimized] target(s) in 0.19s
  Compiling yewcounter v0.1.0
 (file:///home/kevin/Code/Rust/wasmbook/khrust/Book/code/yewcounter)
  Finished release [optimized] target(s) in 3.57s
  Processing "yewcounter.wasm"...
  Finished processing of "yewcounter.wasm"!
 $ ​​cargo​​ ​​web​​ ​​start​​ ​​--target=wasm32-unknown-unknown
  Finished release [optimized] target(s) in 0.04s
  Processing "yewcounter.wasm"...
  Finished processing of "yewcounter.wasm"!
 
 If you need to serve any extra files, put them in the 'static' directory
 in the root of your crate. They'll be served alongside your application.
 You can also put a 'static' directory in your 'src' directory.
 
 Your application is being served at '/yewcounter.js'. It will be automatically
 rebuilt if you make any changes in your code.
 
 You can access the web server at `http://[::1]:8000`.

Let’s open the indicated website and see what we get:

images/yew/yewcounter.png

Playing with this application feels just like it would if you had written it entirely in JavaScript—clicking the buttons provides immediate feedback. You can also see the invocations of the console service by checking the JavaScript console. And you can see that the invocations are happening inside yewcounter.js, a file generated completely on your behalf by the Yew build tool:

 Finished loading Rust wasm module 'yewcounter'
 plus one
 minus one
 plus one
 Bulk action

Now that you’ve seen the basics behind building a service, a component, and a renderable in Yew, let’s build on that knowledge by creating a multi-user chat application sitting on top of a third-party JavaScript chat engine.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.119.229