YEW - a framework for client-side web-apps

YEW - a framework for making Elm/React/Angular-like client web-apps with Rust

This crate for someone who want to use Rust everywhere, even in browser to write UIs.

I've spent some time to achieve the following goals:

  • JSX-like templates (with standard single-line or multiline comments)
  • Pure Rust expressions in html templates
  • Aimed at the WebAssembly revolution
  • MVC approach is identical to Elm platform
  • But simpler to start, because it allow to put a dirty items (with side-effects!) into templates
  • Manage application state like Redux do
  • Virtual DOM approach for patching tree by small changes
  • Keep compatible with third-party crates like: serde, chrono and more
  • .. and most importantly - Rust

Example application:

extern crate chrono;
#[macro_use] extern crate yew;

use chrono::prelude::*;
use yew::html;

struct Model { value: i64 }

enum Msg {
    Increment,
    Decrement,
}

fn update(model: &mut Model, msg: Msg) {
    match msg {
        Msg::Increment => { model.value = model.value + 1; }
        Msg::Decrement => { model.value = model.value - 1; }
    }
}

fn view(model: &Model) -> html::Html<Msg> {
    html! {
        <div>
            <nav class="menu",>
                <button onclick=|_| Msg::Increment,>{ "Increment" }</button>
                // No more html comments! use Rust/JS style
                <button onclick=|_| Msg::Decrement,>{ "Decrement" }</button>
            </nav>
            <p>{ model.value }</p>
            <p>{ Local::now() }</p> /* with side-effects! */
        </div>
    }
}

fn main() {
    let model = Model { value: 0 };
    html::program(model, update, view);
}

If you want to see more powerful example than look at TodoMVC implementation here.

60 Likes

Hi. Nice work but I don't like putting HTML code in strings in another language (especially in controller code). I prefer to have both separate.

2 Likes

Thank you for the feedback! Do you mean Angular style of templates? Yeah, it's useful, but I mostly relied on Elm and JSX where html translates to native language and don't use strings at all. I did that to take advantage of:

  • Strictly-typed HTML and CSS in the future
  • Use prospect of zero-cost advantages and performance (we can eliminate html-representation at all)
  • Checking templates during compilation (the reason why I liked to use Elm)

If you want to keep templates separately I think you can put them to another module or even crate.

13 Likes

This is astounding. Good work, I'll be following it closely :slight_smile:

1 Like

This is the dream! What do error messages look like?

2 Likes

I sympathize with both sides, and this looks really great! I personally really like Elm's function-y style of HTML templates because I only ever have to deal with one language; there's no gaps between the programming and markup generation. Is that possible with this?

1 Like

Awesome!!!

1 Like

As a programmer writing a backend in rust and front-end in elm. I would be looking forward to port the client side so as to have minimal effort for users to install without using more than 1 compilers. This is a nice christmas gift.

8 Likes

Just wanted to say; this is amazing! I intend to have a good look at it during my break. Thank you for working on this.

3 Likes

I like that very much. However coil you explain why ‘Msg::Decrement‘ uses ‘value‘ and not ‘model.value‘ ?

2 Likes

You are right, it was a mistake! I accidentally removed it when I tried to make example more compact )

I was just playing around with your project and it seems super cool! I noticed that there is no way to perform side effects (or commands as elm terms them). Would you be open to a PR that includes that? I was digging through the code and it seems that you aren't completely married to the immutability of elm (using &mut Model in update for example). Does that mean you're also planning on side effects being slightly different to accommodate ergonomics and performance or do you want a system similar to commands?

3 Likes

Thank you everyone for the inspiring feedback! :hugs:
stdweb helped me very much! Thanks to koute!

Near future I will improve the framework: check benchmarks, try to cover it with tests and adapt to another targets. So far I've tested it with asmjs target and have to check more.

Some words about error messages. In the current implementation I can't check everything during comilation, but I will improve it as possible. Some examples below.

First case: If I tried to use float as href attribute (error during comilation):

  Compiling counter v0.1.0 (file:///home/denis/yew/examples/counter)
error[E0277]: the trait bound `yew::html::Href: std::convert::From<{float}>` is not satisfied
  --> src/main.rs:29:5
   |
29 | /     html! {
30 | |         <div>
31 | |             <nav class="menu", href=0.1,>
32 | |                 <button onclick=|_| Msg::Increment,>{ "Increment" }</button>
...  |
37 | |         </div>
38 | |     }
   | |_____^ the trait `std::convert::From<{float}>` is not implemented for `yew::html::Href`
   |
   = help: the following implementations were found:
             <yew::html::Href as std::convert::From<std::string::String>>
             <yew::html::Href as std::convert::From<&'a str>>
   = note: required because of the requirements on the impl of `std::convert::Into<yew::html::Href>` for `{float}`
   = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

Second case: if I forgot the closing tag (error during runtime):

<nav class="menu",>
    <button onclick=|_| Msg::Increment,>{ "Increment" }</button>
    <button onclick=|_| Msg::Decrement,>{ "Decrement" }
</nav>

It fires error on start in the browser's console:

Encountered a panic!
Panic error message: wrong closing tag: <button> -> </nav>
Panic location: /home/denis/yew/src/macros.rs:189
6 Likes

Yes, you are right! I could not at full capacity use the opportunities of Rust's checking of borrow and mutability. That's because browser's environment uses global references and garbage collector and I can take a Document context and any Element everywhere. Don't know the optimal solution now, but I will improve it. Thank you!

One drawback i see is most traditional "MVC" systems is that it heavily leans towards SSR templating and becomes a mess to use with JS frameworks (Angular/React/Vue ...) in a non-API way.

I agree that the frameworks for single-page apps not suit well for generated (SSR) templates. It seems you need something like meta-app for a dynamic template. I think raw JS is a good tool for meta-apps, but it also possible to do similar thing with "all-in-one" frameworks if you will add declaration type which your server will return. Something like:

#[derive(Deserialize)]
struct UiDeclaration {
    blocks: Vec<Block>,
}

#[derive(Deserialize)]
struct Block {
    fields: Vec<Field>,
}

#[derive(Deserialize)]
struct Field {
  // Something related to field
}

The main advantage is that you have strictly-typed solution and you'll never break it with wrong tag.

1 Like

Congrats with the first release! =)

I have one question/idea: did you consider using something else than pure HTML template to generate HTML? E.g. something like HAML?

It' has its own pros and cons of course. It may be less obvious for people who don't know it yet. However it's more expressive, means less to write, means smaller chances to write invalid HTML.

3 Likes

Thank you! :slight_smile:
I was considering about different implementations of markup, and some is simpler and clearer to implement with Rust macros (Elm markup, for example). I tried to make it look more like React in templates to make the markup more understandable for the average developer. But everyone can reimplement own markup for YEW, because the macro only builds a tree of VNodes.

7 Likes

Is server side rendering supported?

Partially. This framework removes body when it's loaded. You can put to the body of your static html file everything and it will be viewed till the framework completely loaded. You can even run some embedded JS and save any status to a local storage which YEW will read later and will "improve functionality". But this kind of DOM patching is fragile, like unsafe area in Rust.

1 Like