This weekend I looked into integrating WebAssembly in web components. This post is a write-up of what I learned.

Web Component Integration

This experiment is framework agnostic, but for the purposes of my demo I have used C++ compiled to WASM consumed by web components created using Angular Elements.

Using Angular Elements is a convenient way to create web components since you can leverage the full power of the Angular framework for things like template binding.

C++

For the C++ part I decided to use the emscripten compiler, which seems to be the standard for C++ WASM projects these days.

For the purposes of this demo I have created a snippet of C++ code that returns a list of friends. I am a bit rusty when it comes to C++ these days, so I would be grateful for any feedback you may have. The full source is included below:

friend.h

class Friend {
  public:
    std::string to_json();
    std::string first_name;
    std::string last_name;
  private:
    std::string format(std::string name, std::string value);
};

friend.cpp

#include<string>
#include "friend.h"

std::string Friend::to_json() {
  return "{" + format("firstName", first_name) + "," + format("lastName", last_name) + "}";
}

std::string Friend::format(std::string name, std::string val) {
  return "\"" + name + "\":" + "\"" + val + "\"";
}

friends.h

#include<string>
#include <vector>
#include "friend.h"

class Friends {
  public:
    char* to_json();
    void add_friend(std::string first_name, std::string last_name);
    
  private:
    std::vector<Friend> _friends;
};

friends.cpp

#include<string>
#include "friends.h"

void Friends::add_friend(std::string first_name, std::string last_name)
{
  Friend f;
  f.first_name = first_name;
  f.last_name = last_name;

  _friends.push_back(f);
}

char* Friends::to_json() {
  
  std::string json = "[";
  
  for(int i = 0; i < _friends.size(); i++)
  {
    json += _friends[i].to_json();

    if(i < _friends.size() -1)
    {
      json += ",";
    }
  }
  
  json += "]";

  char* char_array = new char[json.length()]();
  strcpy(char_array, json.c_str());

  return char_array;
}

show-friends.cpp

#include <emscripten.h>
#include "friends.h"

extern "C" void show_friends() {
  Friends friends;
  friends.add_friend("Joe", "Smith");
  friends.add_friend("Jane", "Doe");

  char* json = friends.to_json();

  EM_ASM({
      const e = document.createElement("friends-list");
      e.data = UTF8ToString($0);
      document.body.appendChild(e);    
  }, json);

  delete json;
}

Between friend.cpp and friends.cpp I have implemented a hierarchical object model with simple support for serializing the model to a json string. In show-friends.cpp I am wiring up a C++ function that will be exposed as the public api that can be accessed by Javascript in the browser.

EM_ASM is a “glue” function provided by emscripted for the purpose of calling JavaScript code from C++. In this example I am running a JavaScript snippet to instantiate a web component and pass it a serialized json string as an attribute. Generally it’s a bit cumbersome to pass complex objects between C++ and Javascript since you have to write data directly into the shared WASM memory. Passing data as a json string is easy enough though.

JavaScript

The code for the Angular element can be found below. The key point here is to expose an attribute with a setter that deserializes the data json to a JavaScript object.

import { Component, Input, ChangeDetectorRef } from '@angular/core';

@Component({
  template: `
  <h5>Friends</h5>
  <div *ngFor="let friend of friends">
    {{ friend.firstName }} {{ friend.lastName }}
  </div>
  `
})
export class FriendListComponent {
  friends: any;

  constructor(private ref: ChangeDetectorRef) { }

  @Input()
  set data(value: string) {
    this.friends = JSON.parse(value);
    this.ref.detectChanges();
  }
}

The output of the C++ compiler consists of a Javascript file accompanied by a .wasm file. The first step in any WebAssembly based application is to load these two files. You only have to load the JavaScript file since it will trigger the loading of the .wasm file. Once the .wasm module is loaded the final step is to wrap the C++ function as a regular JavaScript function. See example below:

Module.onRuntimeInitialized = () => {
    const showFriends = Module.cwrap("show_friends");  //wrap original c++ function
    showFriends();
}; 

Including the above code snippet will call into the C++ code and cause the web element to be added to the page and render a list of friends from the data created in the C++ code.

Demo

I have added the source code on Github if you want to give it a try.

#webassembly #web-development

Using WebAssembly with Web Components
3.10 GEEK