更新项目 DenisKolodin/yew

Build Status

Yew

Yew is a modern Rust framework inspired by Elm and ReactJS.

Become a sponsor on Patreon

Cutting Edge technologies

Rust to WASM compilation

This framework is designed to be compiled into modern browsers' runtimes: wasm, asm.js, emscripten.

To prepare the developments environment use installation instruction here: wasm-and-rust

Clean MVC approach inspired by Elm and Redux

Yew implements strict application state management based on message passing and updates:

src/main.rs

#[macro_use]
extern crate yew;
use yew::prelude::*;

type Context = ();

struct Model { }

enum Msg {
    DoIt,
}

impl Component<Context> for Model {
    // Some details omitted. Explore the examples to get more.

    type Message = Msg;
    type Properties = ();

    fn create(_: Self::Properties, _: &mut Env<Context, Self>) -> Self {
        Model { }
    }

    fn update(&mut self, msg: Self::Message, _: &mut Env<Context, Self>) -> ShouldRender {
        match msg {
            Msg::DoIt => {
                // Update your model on events
                true
            }
        }
    }
}

impl Renderable<Context, Model> for Model {
    fn view(&self) -> Html<Context, Self> {
        html! {
            // Render your model here
            <button onclick=|_| Msg::DoIt,>{ "Click me!" }</button>
        }
    }
}

fn main() {
    yew::initialize();
    let app: App<_, Model> = App::new(());
    app.mount_to_body();
    yew::run_loop();
}

Predictable mutability and lifetimes (thanks Rust) make it possible to reuse a single instance of the model without needing to create a fresh one every update. It helps reduce memory allocations.

JSX-like templates with html! macro

Feel free to put pure Rust code into HTML tags with all the compiler's and borrow checker benefits.

html! {
    <section class="todoapp",>
        <header class="header",>
            <h1>{ "todos" }</h1>
            { view_input(&model) }
        </header>
        <section class="main",>
            <input class="toggle-all",
                   type="checkbox",
                   checked=model.is_all_completed(),
                   onclick=|_| Msg::ToggleAll, />
            { view_entries(&model) }
        </section>
    </section>
}

Components

Yew supports components! You can create a new one by implementing a Component trait and including it directly into the html! template:

html! {
    <nav class="menu",>
        <MyButton: title="First Button",/>
        <MyButton: title="Second Button",/>
    </nav>
}

Scopes

Components lives in Angular-like scopes with parent-to-child (properties) and child-to-parent (events) interaction.

Properties also are pure Rust types with strict checking during compilation.

html! {
    <nav class="menu",>
        <MyButton: color=Color::Red,/>
        <MyButton: onclick=|_| ParentMsg::DoIt,/>
    </nav>
}

Fragments

Yew supports fragments: elements without a parent which could be attached somewhere later.

html! {
    <>
        <tr><td>{ "Row" }</td></tr>
        <tr><td>{ "Row" }</td></tr>
        <tr><td>{ "Row" }</td></tr>
    </>
}

Virtual DOM, independent loops, fine updates

Yew framework uses its own virtual-dom representation. It updates the browser's DOM with tiny patches when properties of elements had changed. Every component lives in its own independent loop, interacts with the environment (Scope) by messages passing and supports fine control of rendering.

The ShouldRender return value informs the loop when the component should be re-rendered:

fn update(&mut self, msg: Self::Message, _: &mut Env<Context, Self>) -> ShouldRender {
    match msg {
        Msg::UpdateValue(value) => {
            self.value = value;
            true
        }
        Msg::Ignore => {
            false
        }
    }
}

It's more effective than comparing the model after every update, because not every model change leads to a view update. It lets us skip model comparison checks entirely. You can control updates very accurately.

Rust/JS/C-style comments in templates

Use single-line or multi-line Rust comments inside html-templates.

html! {
    <section>
   /* Write some ideas
    * in multiline comments
    */
    <p>{ "and tags could be placed between comments!" }</p>
    // <li>{ "or single-line comments" }</li>
    </section>
}

Third-party crates and pure Rust expressions inside

You can use external crates and put values from them into the template:

extern crate chrono;
use chrono::prelude::*;

impl Renderable<Context, Model> for Model {
    fn view(&self) -> Html<Context, Self> {
        html! {
            <p>{ Local::now() }</p>
        }
    }
}

Some crates don't support the true wasm target (wasm32-unknown-unknown) yet.

Services

Yew has implemented pluggable services that allow you to call external APIs, such as: JavaScript alerts, timeout, storage, fetches and websockets. It's a handy alternative to subscriptions.

Implemented:

  • IntervalService
  • TimeoutService
  • StorageService
  • DialogService
  • FetchService
  • WebSocketService
use yew::services::console::ConsoleService;
use yew::services::timeout::TimeoutService;

struct Context {
    console: ConsoleService,
    timeout: TimeoutService<Msg>,
}

impl Component<Context> for Model {
    fn update(&mut self, msg: Self::Message, context: &mut Env<Context, Self>) -> ShouldRender {
        match msg {
            Msg::Fire => {
                let send_msg = context.send_back(|_| Msg::Timeout);
                context.timeout.spawn(Duration::from_secs(5), send_msg);
            }
            Msg::Timeout => {
                context.console.log("Timeout!");
            }
        }
    }
}

Can't find an essential service? Want to use library from npm? You can reuse JavaScript libraries with stdweb capabilities and create your own service implementation. Here's an example below of how to wrap the ccxt library:

pub struct CcxtService(Option<Value>);

impl CcxtService {
    pub fn new() -> Self {
        let lib = js! {
            return ccxt;
        };
        CcxtService(Some(lib))
    }

    pub fn exchanges(&mut self) -> Vec<String> {
        let lib = self.0.as_ref().expect("ccxt library object lost");
        let v: Value = js! {
            var ccxt = @{lib};
            console.log(ccxt.exchanges);
            return ccxt.exchanges;
        };
        let v: Vec<String> = v.try_into().expect("can't extract exchanges");
        v
    }

    // Wrap more methods here!
}

Easy-to-use data conversion and destructuring

Yew allows for serialization (store/send and restore/recieve) formats.

Implemented: JSON, TOML, YAML, MSGPACK, CBOR

In development: BSON, XML

use yew::format::Json;

#[derive(Serialize, Deserialize)]
struct Client {
    first_name: String,
    last_name: String,
}

struct Model {
    clients: Vec<Client>,
}

impl Component<Context> for Model {
    fn update(&mut self, msg: Self::Message, context: &mut Env<Context, Self>) -> ShouldRender {
        Msg::Store => {
            // Stores it, but in JSON format/layout
            context.local_storage.store(KEY, Json(&model.clients));
        }
        Msg::Restore => {
            // Tries to read and destructure it as JSON formatted data
            if let Json(Ok(clients)) = context.local_storage.restore(KEY) {
                model.clients = clients;
            }
        }
    }
}

By default only Json format available, but you can activate more with features in Cargo.toml of your project:

[dependencies]
yew = { git = "https://github.com/DenisKolodin/yew", features = ["toml", "yaml", "msgpack", "cbor"] }

Development setup

Clone or download this repository.

Add necessary targets to your compiler:

$ rustup target add wasm32-unknown-emscripten

We used wasm32-unknown-emscripten target here, because not every crate could be compiled to the pure wasm32-unknown-unknown target. But the crates still improving and you can do it soon.

To build this project you need to have cargo-web installed:

$ cargo install cargo-web

Add --force option to ensure the latest version.

Build

$ cargo web build

Running Tests

$ ./ci/run_tests.sh

Running the examples

There are many examples that show how the framework works: counter, crm, custom_components, dashboard, fragments, game_of_life, mount_point, npm_and_rest, timer, todomvc, two_apps.

To start an example enter its directory and start it with cargo-web:

$ cargo web start

To run an optimised build instead of a debug build use:

$ cargo web start --release

Note: By default cargo-web will use Emscripten to generate asm.js. You can also compile to WebAssembly if you add either --target=wasm32-unknown-emscripten or --target=wasm32-unknown-unknown, where the first one will use Emscripten and the second one will use Rust's native WebAssembly backend (Rust nightly only!).