Build frontend browser apps with Rust + WebAssembly. Supports server side rendering.



Actions Status Actions Status

Build frontend browser apps with Rust + WebAssembly. Supports server side rendering.

The Percy Book

This README gives a light introduction to Percy. Check out The Percy Book for a full walk through.

Stable Rust

Percy compiles on stable Rust with one caveat:

On nightly Rust you can create text nodes without quotes.

// Nightly Rust does not require quotes around text nodes.
html! { <div>My text nodes here </div> };

On stable Rust, quotation marks are required.

// Stable Rust requires quotes around text nodes.
html! { <div>{ "My text nodes here " }</div> };

This difference will go away once span locations are stabilized in the Rust compiler - Rust tracking issue.

Getting Started

The best way to get up to speed is by checking out The Percy Book, but here is a very basic example to get your feet wet with.

Quickstart - Getting your feet wet

Percy allows you to create applications that only have server side rendering, only client side rendering, or both server and client side rendering.

Here's a quick-and-easy working example of client side rendering that you can try right now:

First, Create a new project using

cargo new client-side-web-app --lib
cd client-side-web-app

Add the following files to your project.

touch index.html
touch app.css

Here's the directory structure:

├── Cargo.toml
├── index.html
├── app.css
└── src

Now edit each file with the following contents:

# contents of


cd "$(dirname "$0")"

mkdir -p public

cargo build --target wasm32-unknown-unknown
wasm-bindgen target/wasm32-unknown-unknown/debug/client_side_web_app.wasm --no-typescript --target web --out-dir ./public --debug
cp index.html public/
cp app.css public/

// contents of src/

use wasm_bindgen::prelude::*;
use web_sys;

use percy_dom::prelude::*;

struct App {
  pdom: PercyDom

impl App {
    pub fn new () -> App {
        let start_view = html! { <div> Hello </div> };

        let window = web_sys::window().unwrap();
        let document = window.document().unwrap();
        let body = document.body().unwrap();

        let mut pdom = PercyDom::new_append_to_mount(start_view, &body);

        let greetings = "Hello, World!";

        let end_view = html! {
           // Use regular Rust comments within your html
           <div class=["big", "blue"]>
              /* Interpolate values using braces */
              <strong>{ greetings }</strong>

                onclick=|_event| {
                   web_sys::console::log_1(&"Button Clicked!".into());
                // No need to wrap text in quotation marks (:
                Click me and check your console


        App { pdom }

# contents of Cargo.toml

name = "client-side-web-app"
version = "0.1.0"
authors = ["Friends of Percy"]
edition = "2018"

crate-type = ["cdylib"] # Don't forget this!

wasm-bindgen = "0.2"
js-sys = "0.3"
percy-dom = "0.7"

version = "0.3"
features = [

<!-- contents of index.html -->
<!DOCTYPE html>
<html lang="en">
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" type="text/css" href="app.css"/>
        <title>Client Side Demo</title>
    <body style='margin: 0; padding: 0; width: 100%; height: 100%;'>
        <script type="module">
            import init, {App} from '/client_side_web_app.js'
            async function run ()  {
                await init('/client_side_web_app_bg.wasm')
                new App()

/* contents of app.css */
.big {
  font-size: 30px;
.blue {
  color: blue;
.giant-button {
  font-size: 24px;
  font-weight: bold;

Now run

# Used to compile your Rust code to WebAssembly
cargo install wasm-bindgen-cli

# Or any other static file server that supports the application/wasm mime type
cargo install https

chmod +x ./

# Visit localhost:8080 in your browser
http ./public --port 8080

And you should see the following:

Client side example

Nice work!

More Examples

API Documentation


Always feel very free to open issues and PRs with any questions / thoughts that you have!

Even if it feels basic or simple - if there's a question on your mind that you can't quickly answer yourself then that's a failure in the documentation.

Much more information on how to contribute to the codebase can be found in the contributing section of The Percy Book!

To Test

To run all of the unit, integration and browser tests, grab the dependencies then :




  • Migrate to web-sys

    Migrate to web-sys


    This PR migrates virtual-dom-rs to use web-sys and deletes the percy-webapis crate.


    This was always the plan, we were just waiting for web-sys to mature.

    This PR makes it easier for people to use web-sys alongside Percy. And just generally means that we've staying in line with the wasm-bindgen project.


    • We were previously being a bit hacky and turning things into HtmlElements that really aren't elements. web-sys enforces things only being able to be type casted in ways that are actually specified in web-idl, so we had to refactor some of our virtual dom code a bit. No major overhauls, just being a bit more correct in a few places

    • Added some new browser tests to our jsdom test suite. A TODO is to deprecate this suite in favor of using wasm-bindgen-test in chrome and firefox

    • Added some documentation on how we handle text nodes

    • Overall there isn't any new functionality here.. Just some re-jigging to use the real types that we're supposed to use instead of making things that aren't HtmlElement into HtmlElement like we were before.

    Closes #24

    opened by chinedufn 20
  • Proc macro parsing example

    Proc macro parsing example

    As we talked about at Rust Belt Rust -- here is some demo code of a procedural macro that can parse html tags without a comma required after attribute values:

    // [package]
    // name = "html_macro"
    // version = "0.0.0"
    // edition = "2018"
    // [lib]
    // proc-macro = true
    // [dependencies]
    // syn = { version = "0.15", features = ["full", "extra-traits"] }
    // proc-macro2 = "0.4"
    extern crate proc_macro;
    use proc_macro2::{TokenStream, TokenTree};
    use syn::parse::{Parse, ParseStream, Result};
    use syn::{parse_macro_input, Expr, Ident, Token};
    struct Html {
        // TODO: also support content between the tags
        tags: Vec<Tag>,
    enum Tag {
        /// `<div id="app" class=*CSS>`
        Open {
            name: Ident,
            attrs: Vec<Attr>,
        /// `</div>`
        Close {
            name: Ident,
    struct Attr {
        key: Ident,
        value: Expr,
    impl Parse for Html {
        fn parse(input: ParseStream) -> Result<Self> {
            let mut tags = Vec::new();
            while !input.is_empty() {
                let tag: Tag = input.parse()?;
            Ok(Html { tags })
    impl Parse for Tag {
        fn parse(input: ParseStream) -> Result<Self> {
            let optional_close: Option<Token![/]> = input.parse()?;
            let is_close_tag = optional_close.is_some();
            let name: Ident = input.parse()?;
            let mut attrs = Vec::new();
            while input.peek(Ident) && !is_close_tag {
                let key: Ident = input.parse()?;
                let mut value_tokens = TokenStream::new();
                loop {
                    let tt: TokenTree = input.parse()?;
                    let peek_end_of_tag = input.peek(Token![>]);
                    let peek_start_of_next_attr = input.peek(Ident) && input.peek2(Token![=]);
                    if peek_end_of_tag || peek_start_of_next_attr {
                let value: Expr = syn::parse2(value_tokens)?;
                attrs.push(Attr { key, value });
            if is_close_tag {
                Ok(Tag::Close { name })
            } else {
                Ok(Tag::Open { name, attrs })
    pub fn html(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
        let parsed = parse_macro_input!(input as Html);
        // TODO: produce output data structure using `quote!`
        println!("{:#?}", parsed);

    Here is the input I was testing with:

    use html_macro::html;
    fn main() {
        html! { <div id="app" class=*CSS></div> }
    opened by dtolnay 17
  • Browserside Debugging with console

    Browserside Debugging with console

    I wasn't able to get this to work, but ideally, we'd have a mechanism for console.log-style browser debugging.

    Also, as a Rust bonus, the extra code for logging should be able to be removed with a compiler directive, so while there is debugging code present for development, it is stripped from the build in production.

    I checked out what was currently available, but I think we can do better, and simpler. They all seem to follow a similar formula:

    I would prefer it if I could just add info!("Message:", msg) macros here and there, and they just automatically get stripped out for release builds. I'm not experienced with Rust enough to put this plan into action, though, so I suppose I'm seeking help and comments.

    opened by cryptoquick 16
  • Refactor VirtualNode, convert into enum

    Refactor VirtualNode, convert into enum

    In the DOM, we mostly deal with elements, but there are also nodes. The relation is that an element is a node, but not the other way around.

    A node can have the following node types:

    • ..and a few others.

    Previously the VirtualNode would handle both text nodes and elements. A text node was a VirtualNode with a non-empty text field, and an element node was a VirtualNode with an empty text field.

    This is both a bit ugly and causes confusion. It also makes it possible to represent illegal states (e.g. a text node with children). Therefore this commit replaces the struct with an enum that has variants matching the node type.

    Furthermore, the DomUpdater now uses Node instead of Element, in order to support both element nodes and text nodes.

    Fixes #68.

    This is currently work in progress. The code should compile, but tests will probably fail. I'll try to take a look at the tests tomorrow. Early feedback should already be possible though!

    opened by dbrgn 12
  • Differences with `yew`

    Differences with `yew`

    What are the architectural differences to DenisKolodin/yew that are intentional, and what are incidental? What is the project's stance on koute/stdweb? This question was prompted by thinking of what an isomorphic network fetch would look like, and looking at

    opened by qm3ster 12
  • CSS-in-Rust and Inline stylesheets (inspired by sheetify)

    CSS-in-Rust and Inline stylesheets (inspired by sheetify)

    In a recent project I played around with sheetify, a JavaScript browserify transform that lets you write CSS write next to your view components... and I absolutely loved it.

    A big win for me was not needing to think about class names. If I had a some-component-view.js I just made my CSS block for that file someComponentCSS and called it a day. With a code generator this meant zero thinking about where to put or how to name styles.

    Another big win was not needing to think about where / how to organize styles. When the style is just next to the component there is no "what CSS file to I put this in?" question to answer.

    // rough example in JS land... some-component-view.js
    var someComponentCSS = css`
    :host {
      background-color: red;
    return html`<div class='${css}></div>`
    // ...

    Of course there are downsides to inline stylesheets. The first that comes to mind is that it is much less popular than sass/css so less people know it so it's non-standard.

    But, support for inline stylesheets in Percy would be completely optional. If you don't want them you can still just do

    html! { <div class='my-class',></div> }

    With that background out of the way... here's how I think we can do inline stylesheets in Rust:

    opened by chinedufn 12
  • Custom Components

    Custom Components

    I've implemented custom components in Percy, similar to what you see in React, so users can use a <MyComponent/> pattern rather than {my_component()} which feels a bit more up to date with competitor frameworks.

    I've had to implement a list of valid html tags to do this (I took them from here).

    If this isn't the best way of going around this please let me know, or if this is an unwanted change then that's fine too.

    I tried to implement children in the same way React handles them, ie a children prop gets added into the child component but I've been round in circles with this - I kept hitting borrowing problems. I also had a look how Yew does it (which is similar to React) and still couldn't figure out a nice way of doing it. Any help on this would be appreciated. Children actually do work but they're just appended to the end of the parent, like in the normal HTML tags. I'm hoping it wouldn't require a huge refactor for it.

    opened by rickihastings 11
  • Download example

    Download example

    This pull request is to add the feature to download JSON from the Github API for the isomorphic example. The JSON data holds information about the contributors for the master branch of Percy.

    Right now only the client performs the fetch request, but I'd imagine there's a way to have the server perform the same operation before serving the route. I'm just not completely sure how to go about doing that.

    The contributors are stored in an Option<Vec<Contributor>> field for State. While Contributor holds the login name and html url for each contributor. Right now the definition for Contributor lives in app/src/

    opened by aubaugh 11
  • Allow closures with any amount of arguments

    Allow closures with any amount of arguments

    Right now if you don't provide an argument to an event callback you see this error

      error[E0593]: closure is expected to take 1 argument, but it takes 0 arguments
        --> examples/browser/src/
      21 |       let end_view = html! {
         |  ____________________^
      22 | |        <div class="big blue">
      23 | |           <strong>Hello, World!</strong>
      24 | |
      ...  |
      27 | |             onclick=|| {
         | |                     -- takes 0 arguments
      ...  |
      33 | |        </div>
      34 | |     };
         | |_____^ expected closure that takes 1 argument
         = note: required for the cast to the object type `dyn std::ops::FnMut(_)`
      help: consider changing the closure to take and ignore the expected argument
      27 |             onclick=|_| {
         |                     ^^^
      error: aborting due to previous error

    To fix this we need to use this line

    in order to dynamically generate the number of underscores on this line

    Something like this should work:

    let underscores: Vec<TokenStream> = (0..arg_count).map(|| { quote! { _ } }).collect();
    Box::new(#value) as Box<FnMut( #(#underscores),*)>

    Along with a unit test right under here that's basically the same thing but with no parameter defined for the closure

    good first issue 
    opened by chinedufn 10
  • Define isomorphic

    Define isomorphic

    It has a very specific mathematical and chemical meaning, but clearly neither applies here. Most readers will have no clue what you're talking about, so I think it needs definition.

    opened by alexreg 10
  • Ditch the terminology

    Ditch the terminology "isomorphic"

    @cryptoquick brought up some good points in that this is a source of confusion.

    Let's ditch this terminology in favor of something that more people will intuitively understand.

    @cryptoquick mind dropping your thoughts in here and we can hash out some better phrasing?

    opened by chinedufn 8
  • Fix parenthesized blocks in html macro

    Fix parenthesized blocks in html macro

    let val = 5;
    html! {<div>({val})</div>}.to_string();
    // Currently returns: "<div>({val})</div>"
    // Should return: "<div>(5)</div>"
    opened by chinedufn 0
  • Allow Support for multiple parameters in `JSX` syntax

    Allow Support for multiple parameters in `JSX` syntax

    The Problem

    • An error is thrown whenever I try to use pass in more than 1 argument to a "component" using the <Component /> syntax.
    Screenshot 2022-06-18 at 11 02 54
    • This is the component
    Screenshot 2022-06-18 at 11 02 37
    • This is the error thrown
    Screenshot 2022-06-18 at 11 11 11
    • The only alternative is to render the component like this:
    Screenshot 2022-06-18 at 11 12 28


    It would be nice if we could allow support for multiple arguments in the JSX syntax as well


    Or let me know if there is a particular reason for this. Thanks

    opened by AkinAguda 4
  • Building nojs-first applications (and some thoughts about an isomorphic data layer)

    Building nojs-first applications (and some thoughts about an isomorphic data layer)

    Hi. While I started out... a while ago... with html/js, I haven't really kept up with trends with recent trends. However, I've been really intrigued by Remix ( and the fact that it seems so possible to build applications that work for users that have javascript entirely disabled. However, it's not Rust and doesn't really handle data isomorphically the way I think might be possible.

    I see that Percy supports SSR, but is it amendable to a SSR-first model? How does Percy's model compare to Remix's? Do you think it could take any inspiration from it?

    Similarly, I'm curious if you have thoughts about isomorphic data access -- I'm wondering if something like BonsaiDB could be used to allow highly isomorphic Rust code that works for an application to be used in a SSR-only mode, or a thicker-client mode where the Rust codes executes templates on the client side with the same data-access layer hitting a partially replicated BonsaiDB stored offline in-browser.

    (I've also been thinking about something like Percy, with some prescribed data layer, that could also then use that data layer to also subscribe to changes from the server, either via SSE or via BonsaiDB-native features). I think these features would make a pretty neat/unique full package, but maybe some of that should just live outside percy, if it's not too ambitious in general?

    opened by colemickens 1
  • Broken tests in html-macro 0.7.0 and 0.7.1

    Broken tests in html-macro 0.7.0 and 0.7.1

    I upgraded to html-macro 0.7.1, but my project testsuite started failing due to the way spaces are handled.

    So as a sanity-check, I ran the tests in percy itself:

    $ cd crates/html-macro-test
    $ cargo test
    test result: FAILED. 33 passed; 18 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.27s

    It seems like a lot of tests are failing. This happens on commit 2aed65476bc4d4660c0eb2fe6c733ce96bf6aee7 (0.7.0) as well.

    opened by dbrgn 4
  • Docs: Broken CSS

    Docs: Broken CSS

    On for percy-dom 0.7.1 ( you link to for documentation. The CSS on that page seems a bit broken:


    Any reason why you don't just link to instead?

    opened by dbrgn 1
  • Crate renames

    Crate renames

    Hi, it seems that virtual-dom-rs was first renamed to percy-vdom and then to percy-dom. However, the old crates still exist on and there's nothing that hints at a rename.

    Maybe you could push an update with a README that informs people about the rename, and/or yank the old crates?

    opened by dbrgn 1
Chinedu Francis Nwafili
Chinedu Francis Nwafili
Mod_wasm - an extension module for the Apache HTTP Server (httpd) that enables the usage of WebAssembly (Wasm).

mod_wasm is an extension module for the Apache HTTP Server (httpd) that enables the usage of WebAssembly (Wasm). This module will allow to execute certain tasks in the backend in a very efficient and secure way.

VMware  Labs 67 Dec 21, 2022
Realtime audio processing / synthesis using Rust/WASM in the browser.

Rust Audio About This repo is my investigation into using Rust for creative audio coding on various platforms (e.g. desktop, web, etc.), but especiall

Austin Theriot 30 Jan 5, 2023
Lunatic based webserver embedding WASM. Supports scaling down to zero and up to infinity.

Frenezulo A WASM-embedding webserver build on top of submillisecond and lunatic. Build to serve as an entry point for microservices compiled to WASM.

Kai Jellinghaus 13 Oct 23, 2022
WebAssembly implementation from scratch in Safe Rust with zero dependencies

wain wain is a WebAssembly INterpreter written in Rust from scratch with zero dependencies. An implementation of WebAssembly. Features: No unsafe code

Linda_pp 328 Jan 2, 2023
A notebook app integrated with todo lists utility. Developed with Rust, WebAssembly, Yew and Trunk. A notebook app integrated with todo-list utility. Project is a Rust WASM app running in browser. Taking advantage of Yew and Trunk, it

null 45 Dec 31, 2022
witgen is a library to generate .wit files for WebAssembly in Rust

witgen witgen is a library to help you generate wit definitions in a wit file for WebAssembly. Using this lib in addition to wit-bindgen will help you

Coenen Benjamin 28 Nov 9, 2022
Rust bindings for Supabase JavaScript library via WebAssembly.

supabase-js-rs Rust bindings for Supabase JavaScript library via WebAssembly. Usage Add supabase-js-rs to Cargo.toml supabase-js-rs = { version = "0.1

Valery Stepanov 8 Jan 13, 2023
plugy empowers you to construct agnostic dynamic plugin systems using Rust and WebAssembly.

plugy plugy is a plugin system designed to enable the seamless integration of Rust-based plugins into your application. It provides a runtime environm

Geoffrey Mureithi 22 Aug 12, 2023
Let's pretend that life-before-main exists for Rust targeting WebAssembly

Let's pretend that life-before-main exists for Rust targeting WebAssembly. Installation Add a dependency on wasm-init. This crate intentionally provid

Ruan Pearce-Authers 7 Aug 29, 2023
Code for my workshop "Production-ready WebAssembly with Rust" presented at RustLab 2023 in Florence

Workshop: Production-ready WebAssembly with Rust A workshop on Rust for WebAssembly by Alberto Schiabel (@jkomyno). ?? This workshop was first present

Alberto Schiabel 14 Nov 23, 2023
A 3D bin packing library in Rust/WebAssembly.

packme-wasm Demo This repository hosts an implementation of Dube, E., & Kanavathy L. (2006). Optimizing Three-Dimensional Bi

Ade Yahya Prasetyo 17 Feb 25, 2024
NPM package distributing biscuit in WebAssembly for web components

Biscuit playground This is an example application for Biscuit tokens, where you can manipulate tokens and their verification in your browser. build wi

null 0 Dec 30, 2021
`wasm-snip` replaces a WebAssembly function's body with an `unreachable`

wasm-snip wasm-snip replaces a Wasm function's body with an unreachable instruction. API Docs | Contributing | Chat Built with ?? ?? by The Rust and W

Rust and WebAssembly 177 Dec 28, 2022
WebAssembly (Wasm) interpreter.

Continuous Integration Test Coverage Documentation wasmi- WebAssembly (Wasm) Interpreter wasmi was conceived as a component of parity-ethere

Parity Technologies 1k Jan 4, 2023
Dependency solver for Elm, made in WebAssembly

Dependency solver for Elm, made in WebAssembly This repo holds a dependency solver for the elm ecosystem compiled to a WebAssembly module. The wasm mo

Matthieu Pizenberg 3 Jun 16, 2022
A simple code for checking crate 'prost' on WebAssembly (🦀 + 🕸️ = 💖)

rust-wasm-prost This repository is a simple code for checking crate 'prost' on WebAssembly ( ?? + ??️ = ?? ). What is prost? prost is a Protocol Buffe

Chris Ohk 6 Apr 5, 2022
Version of Clue made to be compilable in WebAssembly (WIP)

Clue is a programming language that compiles into Lua code with a syntax similar to languages like C or Rust. Clue tries to be almost as simple as Lua

Clue 2 Jun 16, 2022
Vite + Webassembly starter project

Vite + Typescript+ Webassembly A starter project for you to create a blazingly fast web application Before getting started You need to get these prere

Saugi 9 Aug 18, 2022
Low level tooling for WebAssembly in JavaScript using wasm-tools

js-wasm-tools js-wasm-tools compiles some of the API of wasm-tools to JavaScript and WebAssembly via wasm-bindgen. This offers low level tooling for W

Dominic Elm 59 Dec 19, 2022