Yew - Rust & WebAsse-frontend framework

    Yew is an analogue of React and Elm, written entirely on Rust and compiled into an honest WebAssembly. In the article, Denis Kolodin, the developer of Yew, talks about how you can create a framework without a garbage collector, effectively provide immutable, without the need to copy the state due to the rules of ownership of Rust data, and what are the features when translating Rust to WebAssembly.



    The post was prepared based on the report of Denis at the conference HolyJS 2018 Piter . Under the cut - video and text transcript of the report.


    Denis Kolodin works in the Bitfury Group company, which is developing various blockchain solutions. For more than two years, he has been kodit in Rust, a programming language from Mozilla Research. During this time, Denis managed to thoroughly study this language and use it to develop various system applications, the backend. Now, in connection with the advent of the WebAssembly standard, I began to look towards the frontend.

    Agenda


    Today we will learn what Yew is (the name of the framework is read the same as the English word “you” - you; “yew” is a yew tree translated from English).

    We will talk a little about the architectural aspects, about what ideas the framework is built on, about the possibilities that are built into it, as well as about the features that Rust gives us in addition to other languages.

    At the end, I'll show you how to start using Yew and WebAssembly today.

    What is Yew?


    First of all, it is WebAssembly, i.e. executable bytecode that works in browsers. It is needed in order to run complex algorithms on the user’s side, for example, cryptography, encoding / decoding. It's easier to implement it in the system languages, than to fasten crutches.

    WebAssembly is a standard that is clearly described, understood and supported by all modern browsers. It allows the use of various programming languages. And this is primarily interesting because you can reapply the code created by the community in other languages.

    If you wish, you can completely write an application to WebAssembly, and Yew allows you to do this, but it is important not to forget that even in this case, JavaScript remains in the browser. It is needed to prepare WebAssembly — take a module (WASM), add an environment to it and start it. Those. without javascript is indispensable. Therefore, WebAssembly is worth considering as an extension rather than a revolutionary alternative to JS.

    How does the development




    You have the source, there is a compiler. You translate all this into a binary format and launch it in a browser. If the browser is old, without WebAssembly support, then emscripten is required. This, roughly speaking, is a WebAssembly emulator for the browser.

    Yew - wasm framework ready for use


    Let's go to Yew. I developed this framework at the end of last year. Then I wrote some kind of cryptocurrency application on Elm and faced with the fact that due to the limitations of the language I cannot create a recursive structure. And at that moment I thought: in Rust, my problem would be solved very easily. And since I write 99% of the time on Rust and just adore this language precisely because of its capabilities, I decided to experiment - compile the application with the same update-function in Rust.

    The first sketch took me several hours, I had to figure out how to compile a WebAssembly. I launched it and realized that in just a few hours I had laid a core, which is very easy to develop. It took me just a few more days to bring it all to the minimum framework framework.

    I put it in open source, but did not expect it to be any popular. However, today he has collected more than 4 thousand stars on GitHub. See the project can be on the link . There are also many examples.

    The framework is written entirely in Rust. Yew supports compiling directly to WebAssembly (wasm32-unknown-unknown target) without emscripten. If necessary, you can work through emscripten.

    Architecture


    Now a few words about how the framework differs from the traditional approaches that exist in the world of JavaScript.

    To begin with, I’ll show you the limitations of the language I encountered in Elm. Take the case when there is a model and there is a message that allows you to transform this model.

    typealias Model = 
        { value : Int 
        } 
      
    type Msg 
        = Increment 
        | Decrement 
    

    case msg of 
        Increment -> 
          { value = model.value + 1 } 
        Decrement -> 
          { value = model.value - 1 }
    

    In Elm, we simply create a new model and display it on the screen. The previous version of the model remains unchanged. Why am I emphasizing this? Because the Yew model is mutable, and this is one of the most frequent questions. Next, I will explain why this is done.

    Initially, I walked the classic path when the model was created anew. But as the framework evolved, I saw that there was no point in keeping the previous version of the model. Rust allows you to track the lifetime of all data, changeable or not. And so I can change the model safely, knowing that Rust controls the absence of conflict.

    structModel { 
        value: i64, 
    } 
      
    enumMsg { 
        Increment, 
        Decrement, 
    } 
    

    match msg { 
        Msg::Increment => { 
            self.value += 1; 
        } 
        Msg::Decrement => { 
            self.value -= 1; 
        } 
    }
    

    This is the first moment. The second point: why do we need the old version of the model? In the same Elm there is hardly any problem of some kind of competitive access. The old model is needed only to understand when to render. Awareness of this moment allowed me to completely get rid of immutable and not keep the old version.



    Look at the option when we have a function updateand two fields - valueand name. There is a value that is saved when we enter inputdata in the field . The model is changing.



    It is important that the value is valuenot involved in the rendering . And therefore we can change it as much as necessary. But we do not need to influence the DOM tree and do not need to initiate these changes.

    This gave me the idea that only a developer can know the right moment when it is really necessary to initiate rendering. For the initiation, I began to use a flag - just a boolean value - ShouldRenderthat signals that the model has changed and you need to start rendering. At the same time there are no overhead costs for constant comparisons, there is no memory consumption - applications written in Yew are as efficient as possible.

    In the example above, no memory allocation occurred at all, except for the message that was generated and sent. The model has retained its state, and this is reflected in the rendering only with the help of the flag.

    Opportunities


    Writing a framework that works in WebAssembly is not an easy task. We have JavaScript, but it has to create some kind of environment with which to interact, and this is a huge amount of work. The initial version of these bundles looked like this:



    I took a demonstration from another project. There are many projects that go this way, but he quickly leads to a dead end. After all, the framework is a fairly large development and you have to write a lot of docking code. I began to use libraries in Rust, which are called crates, in particular, the crate Stdweb.

    JS integrated


    With the help of Rust macros, you can extend the language - we can insert JavaScript into the Rust code, this is a very useful feature of the language.

    let handle = js! { 
        var callback = @{callback}; 
        var action = function() { 
            callback(); 
        }; 
        var delay = @{ms}; 
        return { 
            interval_id: setInterval(action, delay), 
            callback: callback, 
        }; 
    }; 
    

    Using macros and Stdweb allowed me to quickly and efficiently write all the necessary bundles.

    Jsx templates


    At first, I followed the path of Elm and started using templates implemented with code.

    fnview(&self) -> Html<Context, Self> { 
        nav("nav", ("menu"), vec![ 
            button("button", (), ("onclick", || Msg::Clicked)), 
            tag("section", ("ontop"), vec![ 
                p("My text...") 
            ]) 
        ]) 
    }
    

    I have never been a React supporter. But when I started writing my own framework, I realized that JSX in React is a very cool thing. There is a very convenient presentation of code templates.

    In the end, I took a macro on Rust and injected Rust directly inside the opportunity to write HTML markup, which immediately generates elements of the virtual tree.

    impl Renderable<Context, Model> for Model { 
        fnview(&self) -> Html<Context, Self> { 
            html! { 
                <div> 
                    <nav class="menu",> 
                        <button onclick=|_| Msg::Increment,>{ "Increment" }</button> 
                        <button onclick=|_| Msg::Decrement,>{ "Decrement" }</button> 
                    </nav> 
                    <p>{ self.value }</p> 
                    <p>{ Local::now() }</p> 
                </div> 
            } 
        } 
    } 
    

    It can be said that JSX-like patterns are pure code patterns, but on steroids. They are presented in a convenient format. Also note that here I directly insert a Rust expression into the button (a Rust expression can be inserted inside these templates). This allows for very tight integration.

    Honest components


    Then I began to develop templates and implemented the possibility of using components. This is the first issue that was made in the repository. I implemented components that can be used in the template code. You simply declare an honest structure on Rust and write some properties for it. And these properties can be set directly from the template.



    I note once again the important thing that these templates are honestly generated Rust code. Therefore, any error will be noticed here by the compiler. Those. you can't go wrong, as is often the case in JavaScript development.

    Typed areas


    Another interesting feature is that when a component is placed inside another component, it can see the type of the parent message.



    The compiler rigidly binds these types and will not give you the opportunity to make a mistake. When processing events, messages that a component is waiting for or can send will have to fully comply with the parent.

    Other features


    From Rust I transferred the implementation directly to the framework, which allows you to conveniently use various serialization / deserialization formats (providing it with additional wrappers). Below is an example: we are accessing local storage and, restoring the data, we indicate some kind of wrapper - what we expect json here.

    Msg::Store => { 
        context.local_storage.store(KEY, Json(&model.clients)); 
    } 
    Msg::Restore => { 
         iflet Json(Ok(clients)) = context.local_storage.restore(KEY) { 
             model.clients = clients; 
        } 
    }
    

    There can be any format, including binary. Accordingly, serialization and deserialization become transparent and convenient.

    The idea of ​​another opportunity that I realized came from the users of the framework. They asked for fragments. And here I am faced with an interesting thing. Having seen the ability to insert fragments into the DOM tree in JavaScript, I first decided that it would be very easy to implement such a function in my framework. But I tried this option and it turned out that it does not work. I had to figure it out, walk on this tree, see what changed there, etc.

    The Yew framework uses a virtual DOM tree, everything initially exists in it. In fact, when some changes appear in the template, they turn into patches that already change the rendered DOM tree.

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

    Additional benefits


    Rust provides many more powerful features, I’ll only talk about the most important ones.

    Services: interaction with the outside world


    The first opportunity I want to talk about is services. You can describe the required functionality as a service, publish it as a crate and reuse it.

    In Rust very high quality implemented the ability to create libraries, their integration, docking and gluing. In fact, you can create different APIs for interacting with your service, including JavaScript. In this case, the framework can interact with the outside world, despite the fact that it works within the WebAssembly runtime.

    Examples of services:

    • TimeOutService;
    • IntervalService;
    • FetchService;
    • WebSocketService;
    • Custom Services ...

    Rust services and crates: crates.io .

    Context: State Requirements


    Another thing that I implemented in the framework is not quite traditional, this is the context. In React there is a Context API, I used Context in a different sense. The Yew framework consists of the components that you make, and Context is a global state. Components may not take into account this global state, and may impose some requirements - that the global entity meets some criteria.

    Suppose our abstract component requires the ability to upload something to S3.



    Below you can see that he uses this unloading, i.e. sends data to S3. Such a component can be laid out in the form of a crate. The user who downloads this component and adds the template inside his application will encounter an error - the compiler will ask him where is the S3 support? The user will need to implement this support. After this, the component automatically begins to live a full life.

    Where is it needed? Imagine: you are creating a component with clever cryptography. He has requirements that the surrounding context should allow him to log in somewhere. All you need to do is add an authorization form in the template and in your context implement the link with your service. Those. This will be literally three lines of code. After that, the component starts working.

    Imagine that we have dozens of different components. And they all have the same requirement. This allows you to implement some functionality once to revive all the components and pull up the data that is needed. Right out of context. And the compiler will not allow you to make a mistake: if you do not implement an interface that requires a component, it will not work.

    Therefore, you can easily create very fastidious buttons that will ask for some API or other features. Thanks to Rust and the system of these interfaces (they are called trait in Rust), it is possible to declare the requirements of the component.

    The compiler will not let you make a mistake


    Imagine that we are creating a component with some properties, one of which is the ability to set call back. And, for example, we set the property and missed one letter in its name.



    We are trying to compile, Rust responds to this quickly. He says that we were mistaken and there is no such property:



    As you can see, Rust directly uses this template and can render all errors inside the macro. He suggests what the property should actually be called. If you have passed the compiler, then you will not have stupid runtime errors like typos.

    Now let's imagine we have a button that asks our global context to be able to connect to S3. And create a context that does not implement S3 support. Let's see what will happen.



    The compiler reports that we inserted a button, but this interface is not implemented for context.



    It remains only to enter the editor, add a connection to Amazon in the context, and everything will start. You can create ready-made services with some kind of API, then simply add to the context, substitute a link to it - and the component immediately comes to life. This allows you to do very cool things: you add components, create a context, stuff it with services. And it all works fully automatically, it takes minimal effort to tie it all together.

    How to start using Yew?


    What to start with if you want to try compiling a WebAssembly application? And how can this be done using the Yew framework?

    Rust-to-wasm compilation


    First, you need to install a compiler. For this there is a rustup tool: Plus, you may need emscripten. What can it be useful for? Most of the libraries that are written for system programming languages, especially for Rust (initially system), are developed for Linux, Windows and other full-fledged OSes. Obviously, there are no many possibilities in the browser. For example, the generation of random numbers in a browser is done differently from Linux. emscripten comes in handy if you want to use libraries that require a system API.

    curl https://sh.rustup.rs -sSf | sh





    Libraries and the entire infrastructure are quietly moving to an honest WebAssembly, and emscripten is no longer required (you use JavaScript-based capabilities to generate random numbers and other things), but if you need to build something that is not supported at all in the browser, you can't do without emscripten .

    I also recommend using cargo-web:

    cargo install cargo-web

    It is possible to compile WebAssembly without additional utilities. But cargo-web is a cool tool that provides several things that are useful to JavaScript developers. In particular, it will monitor the files: if you make any changes, it will start compiling immediately (the compiler does not provide such functions). In this case, Cargo-web will allow you to speed up development. There are different build systems for Rust, but cargo is 99.9% of all projects.

    A new project is created as follows: Then simply start the project: I gave an example of an honest WebAssembly. If you need to compile under emscripten (the rust compiler itself can connect emscripten), in the very last element you can insert the word

    cargo new --bin my-project

    [package]
    name = "my-project"
    version = "0.1.0"
     
    [dependencies]
    yew = "0.3.0"



    cargo web start --target wasm32-unknown-unknown

    unknownemscriptenThat will allow you to use more crates. Do not forget that emscripten is a rather large additional body kit to your file. Therefore, it is better to write honest WebAsse installation code.

    Existing restrictions


    Anyone who has experience in coding in system programming languages ​​can be upset by limitations in the framework. Not all libraries can be used in WebAssembly. For example, there are no threads in the JavaScript environment. WebAssembly basically does not declare this, and you, of course, can use it in a multi-threaded environment (this is an open question), but JavaScript is still a single-threaded environment. Yes, there are workers, but this is isolation, so no streams will be there.

    It would seem that without streams you can live. But if you want to use thread-based libraries, for example, you want to add some runtime, it may not take off.

    There is also no system API here besides the one that you transfer from JavaScript to WebAssembly. So many libraries will not transfer. You cannot write and read files directly, you cannot open sockets, you cannot write to the network. If you want to make a web-socket for example, you need to drag it out of javascript.

    Another disadvantage is that the WASM debugger exists, but no one has seen it. He is still in such a raw state that it is unlikely to be useful to you. Therefore, debugging WebAssembly is a complex issue.

    When using Rust almost all runtime problems will be associated with errors in business logic, they will be easy to fix. But very rarely low-level bugs appear - for example, some of the libraries do wrong docking - and this is a difficult question. For example, at the moment there is such a problem: if I compile the framework with emscripten and there is a variable memory cell, the ownership of which is then taken, then it is given, emscripten falls apart somewhere in the middle (and I’m not even sure that it is emscripten). Know, if you stumble upon a problem somewhere in the middleware at a low level, then it will be difficult to fix it at the moment.

    Future of the framework


    How will Yew continue to develop? I see its main purpose in creating monolithic components. You will have a compiled WebAsse-file, and you will simply insert it into the application. For example, it may provide cryptographic capabilities, rendering, or editing.

    JS integration


    JavaScript integration will be enhanced. A large number of cool libraries are written in JavaScript that are convenient to use. And there are examples in the repository where I show how you can use the existing JavaScript library directly from the Yew framework.

    Typed CSS


    Since Rust is used, it is obvious that you can add typed CSS that can be generated in the same macro as in the example of a JSX-like template engine. In this case, the compiler will check, for example, whether you have assigned some other attribute instead of color. It saves tons of your time.

    Finished components


    I also look in the direction of creating a ready-to-use component. On the framework, you can make racks that will provide, for example, a set of some buttons or elements that will be connected as a library, added to templates and used.

    Improved performance in particular cases


    Performance is a very delicate and complex issue. Is WebAssembly faster than JavaScript? I have no proof of a positive or negative answer. Feels like and for some very simple tests that I conducted, WebAssembly runs very fast. And I have full confidence that its performance will be higher than that of JavaScript, just because it is a low-level byte code, where memory allocation and a lot of other moments requiring resources are not required.

    More contributors


    I would like to attract more contributors. Doors to participate in the framework are always open. Anyone who wants to upgrade, understand the kernel and transform the tools that a large number of developers work with can easily connect and suggest their own edits.

    Many contributors have already participated in the project. But there are no Core contributors at the moment, because for this you need to understand the vector of development of the framework, but it has not yet been clearly formulated. But there is a backbone, guys, who know a lot about Yew - about 30 people. If you also want to add something to the framework, always please send a pull request.

    Documentation


    A mandatory item in my plans is the creation of a large amount of documentation on how to write applications on Yew. Obviously, the development approach in this case is different from what we saw in React and Elm.

    Sometimes guys show me interesting cases, how to use the framework. Still, creating a framework is not the same thing as writing professionally on it. The practice of using the framework is still being formed.

    Try it, install Rust, expand your capabilities as a developer. Mastering WebAssembly will be useful to each of us, because the creation of very complex applications is the moment we have been waiting for. In other words, WebAssembly is not only about the web browser, but it is generally runtime that is precisely developed and will be developed even more actively.

    If you like the report, pay attention: a new HolyJS will take place on November 24-25 in Moscow , and there will also be many interesting things there. Already known information about the program is on the website, and tickets can be purchased in the same place ( prices will rise from October 1 ).

    Also popular now: