{"id":11696,"date":"2025-08-23T13:19:24","date_gmt":"2025-08-23T13:19:24","guid":{"rendered":"https:\/\/putridparrot.com\/blog\/?p=11696"},"modified":"2025-08-23T17:18:36","modified_gmt":"2025-08-23T17:18:36","slug":"wasm-with-rust-and-yew","status":"publish","type":"post","link":"https:\/\/putridparrot.com\/blog\/wasm-with-rust-and-yew\/","title":{"rendered":"WASM with Rust (and Yew)"},"content":{"rendered":"<p>In my previous post <a href=\"https:\/\/putridparrot.com\/blog\/wasm-with-rust-and-leptos\/\" target=\"_blank\">WASM with Rust (and Leptos)<\/a> we covered creating a Rust project which generate a binary for use within WASM, using Leptos and using Trunk to build and run it.<\/p>\n<p>There&#8217;s more than one framework for creating WASM\/WebAssembly projects in Rust, let&#8217;s look at another one, this time <strong>Yew<\/strong>.<\/p>\n<p>We&#8217;ll be using trunk (just as the previous post) to serve but I&#8217;ll repeat the step to install here<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\ncargo install trunk\r\n<\/pre>\n<p>I&#8217;m going to assume you&#8217;ve also added the target, but I&#8217;ll include here for completeness<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nrustup target add wasm32-unknown-unknown\r\n<\/pre>\n<p><strong>Getting started<\/strong><\/p>\n<p>We&#8217;re going to use a template to scaffold a basic Yew application, so create yourself a folder for your project then run<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\ncargo generate --git https:\/\/github.com\/yewstack\/yew-trunk-minimal-template\r\n<\/pre>\n<p>For mine I stuck with the defaults after naming it <em>wasm_app<\/em>. So the stable Yew version and no logging.<\/p>\n<p>Before we get into the code, let&#8217;s add a Trunk.toml (in the folder with the Cargo.toml) with this configuration<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n&#x5B;serve]\r\naddress = &quot;127.0.0.1&quot;\r\nport = 8081\r\n<\/pre>\n<p>Let&#8217;s see what Yew generated. From the app folder (mine was named wasm_app) run <\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\ntrunk serve --open\r\n<\/pre>\n<p>Straight up, Yew gives us a colourful starting point.<\/p>\n<p><strong>In the code<\/strong><\/p>\n<p>Let&#8217;s go through the code, so we know what we need if we&#8217;re creating a project without the template, but also to see what&#8217;s been added.<\/p>\n<p>If you check out the Cargo.toml it&#8217;s filled in a lot of package info. for us, so you might wish to go tweak there, but we have a single dependency<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n&#x5B;dependencies]\r\nyew = { version=&quot;0.21&quot;, features=&#x5B;&quot;csr&quot;] }\r\n<\/pre>\n<p>The Yew template includes index.scss for our styles and Trunk automatically compiles\/transpiles to the .css file of the same name within the dist. <\/p>\n<p>The index.html is lovely and simple, really the only addition from a bare bones index.html is the including the SASS link which tells the compiler to compile using SASS<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n&lt;!DOCTYPE html&gt;\r\n&lt;html&gt;\r\n  &lt;head&gt;\r\n    &lt;meta charset=&quot;utf-8&quot; \/&gt;\r\n    &lt;title&gt;Trunk Template&lt;\/title&gt;\r\n    &lt;link data-trunk rel=&quot;sass&quot; href=&quot;index.scss&quot; \/&gt;\r\n  &lt;\/head&gt;\r\n  &lt;body&gt;&lt;\/body&gt;\r\n&lt;\/html&gt;\r\n<\/pre>\n<p>In the src folder we have two files, main.rs and app.rs, within main.rs we have<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nmod app;\r\n\r\nuse app::App;\r\n\r\nfn main() {\r\n    yew::Renderer::&lt;App&gt;::new().render();\r\n}\r\n<\/pre>\n<p>Here we are basically telling Yew to render our App. Within the app.rs we have<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nuse yew::prelude::*;\r\n\r\n#&#x5B;function_component(App)]\r\npub fn app() -&gt; Html {\r\n    html! {\r\n        &lt;main&gt;\r\n            &lt;img class=&quot;logo&quot; src=&quot;https:\/\/yew.rs\/img\/logo.svg&quot; alt=&quot;Yew logo&quot; \/&gt;\r\n            &lt;h1&gt;{ &quot;Hello World!&quot; }&lt;\/h1&gt;\r\n            &lt;span class=&quot;subtitle&quot;&gt;{ &quot;from Yew with &quot; }&lt;i class=&quot;heart&quot; \/&gt;&lt;\/span&gt;\r\n        &lt;\/main&gt;\r\n    }\r\n}\r\n<\/pre>\n<p>Similar to Leptos, we have a macro for our HTML tags etc. but it&#8217;s <em>html!<\/em> here (not <em>view!<\/em>). Also the component is marked with the <em>function_component<\/em> annotation, but otherwise it&#8217;s very recognisable what&#8217;s happening here.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nuse yew::prelude::*;\r\n\r\n#&#x5B;function_component(App)]\r\npub fn app() -&gt; Html {\r\n    html! {\r\n        &lt;main&gt;\r\n            &lt;img class=&quot;logo&quot; src=&quot;https:\/\/yew.rs\/img\/logo.svg&quot; alt=&quot;Yew logo&quot; \/&gt;\r\n            &lt;h1&gt;{ &quot;Hello World!&quot; }&lt;\/h1&gt;\r\n            &lt;span class=&quot;subtitle&quot;&gt;{ &quot;from Yew with &quot; }&lt;i class=&quot;heart&quot; \/&gt;&lt;\/span&gt;\r\n        &lt;\/main&gt;\r\n    }\r\n}\r\n<\/pre>\n<p><strong>Let&#8217;s add some routing<\/strong><\/p>\n<p>Create yourself a new file named counter.rs, let&#8217;s implement the fairly standard counter.rs component &#8211; I should say the Yew web site has an example of the counter page on their Getting Started, so we&#8217;ll just take that and make a few tweaks<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nuse yew::prelude::*;\r\n\r\n#&#x5B;function_component(Counter)]\r\npub fn counter() -&gt; Html {\r\n    let counter = use_state(|| 0);\r\n    let on_add_click = {\r\n        let c = counter.clone();\r\n        move |_| { c.set(*c + 1); }\r\n    };\r\n\r\n    let on_subtract_click = {\r\n        let c = counter.clone();\r\n        move |_| { c.set(*c - 1); }\r\n    };\r\n\r\n    html! {\r\n        &lt;div&gt;\r\n            &lt;button onclick={on_add_click}&gt;{ &quot;+1&quot; }&lt;\/button&gt;\r\n            &lt;p&gt;{ *counter }&lt;\/p&gt;\r\n            &lt;button onclick={on_subtract_click}&gt;{ &quot;-1&quot; }&lt;\/button&gt;\r\n        &lt;\/div&gt;\r\n    }\r\n}\r\n<\/pre>\n<p>If you&#8217;ve used React, you&#8217;ll see this is very similar to the way we might write our React component. <\/p>\n<p>Ofcourse the syntax differs, but we have a <em>use_state<\/em> and event handler functions etc. The main difference is the way we&#8217;re cloning the value &#8211; by convention all those <em>c<\/em> variables would be named <em>counter<\/em> as well, but I wanted to make it clear as to what the scope of the <em>counter<\/em> variable was. <\/p>\n<p><em>On further reading &#8211; it appears the use_XXX syntax are hooks, see <a href=\"https:\/\/yew.rs\/docs\/concepts\/function-components\/hooks#pre-defined-hooks\" target=\"_blank\">Pre-defined Hooks<\/a><\/em><\/p>\n<p>When we clone the counter, we&#8217;re not cloning the value, we&#8217;re cloning the handle (or type UseStateHandler which implements Clone). All clones point to the same reactive cell, so you are essentially changing the value in that handle.<\/p>\n<p>Before trying this code out we need our router, so the Yew site says add the following dependency to the Cargo.toml file<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nyew-router = { git = &quot;https:\/\/github.com\/yewstack\/yew.git&quot; }\r\n<\/pre>\n<p>but I had version issues so instead used<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nyew-router = { version = &quot;0.18.0&quot; }\r\n<\/pre>\n<p>Now let&#8217;s change the app.rs file to the following<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nuse yew::prelude::*;\r\nuse yew_router::prelude::*;\r\nuse crate::counter::Counter;\r\n\r\n#&#x5B;derive(Clone, Routable, PartialEq)]\r\nenum Route {\r\n    #&#x5B;at(&quot;\/&quot;)]\r\n    Home,\r\n    #&#x5B;at(&quot;\/counter&quot;)]\r\n    Counter,\r\n    #&#x5B;not_found]\r\n    #&#x5B;at(&quot;\/404&quot;)]\r\n    NotFound,\r\n}\r\n\r\nfn switch(routes: Route) -&gt; Html {\r\n    match routes {\r\n        Route::Home =&gt; html! { &lt;h1&gt;{ &quot;Home&quot; }&lt;\/h1&gt; },\r\n        Route::Counter =&gt; { html! { &lt;Counter \/&gt; }},\r\n        Route::NotFound =&gt; html! { &lt;h1&gt;{ &quot;404&quot; }&lt;\/h1&gt; },\r\n    }\r\n}\r\n\r\n#&#x5B;function_component(App)]\r\npub fn app() -&gt; Html {\r\n    html! {\r\n        &lt;BrowserRouter&gt;\r\n            &lt;Switch&lt;Route&gt; render={switch} \/&gt;\r\n        &lt;\/BrowserRouter&gt;\r\n    }\r\n}\r\n<\/pre>\n<p>There&#8217;s a fair bit to digest, but hopefully it&#8217;s fairly obvious what&#8217;s happening thankfully. <\/p>\n<p>We create an enum of the routes with the <em>at<\/em> annotation mapping to the URL path. Then we use a function (named <em>switch<\/em> in this case) which maps the enum to the HTML. We&#8217;ve embedded HTML into the Home and NotFound routes but the Counter will render our Counter component as if it&#8217;s HTML.<\/p>\n<p>The final change is the app functions where we use the BrowserRouter and Switch along with our <em>switch<\/em> function to render the pages.<\/p>\n<p><strong>Code<\/strong><\/p>\n<p>Checkout the code on <a href=\"https:\/\/github.com\/putridparrot\/blog-projects\/tree\/master\/wasm_yew\/wasm_app\" target=\"_blank\">GitHub<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In my previous post WASM with Rust (and Leptos) we covered creating a Rust project which generate a binary for use within WASM, using Leptos and using Trunk to build and run it. There&#8217;s more than one framework for creating WASM\/WebAssembly projects in Rust, let&#8217;s look at another one, this time Yew. We&#8217;ll be using [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[191,748],"tags":[],"class_list":["post-11696","post","type-post","status-publish","format-standard","hentry","category-rust","category-wasm"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/11696","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/comments?post=11696"}],"version-history":[{"count":5,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/11696\/revisions"}],"predecessor-version":[{"id":11791,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/11696\/revisions\/11791"}],"wp:attachment":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/media?parent=11696"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/categories?post=11696"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/tags?post=11696"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}