Blog Post: Building interactive web pages with Guile Hoot


A question we frequently hear in discussions about WebAssembly (Wasm) is:

“Can Wasm call the DOM (Document Object Model) API?”

The answer is: Yes, thanks to Wasm Garbage Collection!

In this post, we will use Guile Hoot (our Scheme to Wasm compiler) to demonstrate how a language that compiles to Wasm GC can be used to implement the kind of interactivity we’re used to implementing with JavaScript. We’ll start very simple and build up to something that resembles a React-like application.

In our previous post about running Scheme in the browser, we had to use quite a lot of JavaScript code to call Scheme procedures (functions), render the user interface, and handle user input. However, today we’re pleased to announce that those days are behind us; Hoot 0.2.0, released today, now includes a foreign function interface (FFI) for calling JavaScript from Scheme. In other words, the vast majority of code in a Hoot application can now be written directly in Scheme!

Hello, world!

Let’s start with a “Hello, world” application that simply adds a text node to the document body.

(define-foreign document-body
  "document" "body"
  ;; Parameters: none
  ;; Result: an external reference which may be null
  -> (ref null extern))

(define-foreign make-text-node
  "document" "createTextNode"
  ;; Parameters: a string
  ;; Result: an external reference which may be null
  (ref string) -> (ref null extern))

(define-foreign append-child!
  "element" "appendChild"
  ;; Parameters: two external references which may be null
  ;; Result: an external reference which may be null
  (ref null extern) (ref null extern) -> (ref null extern))

(append-child! (document-body) (make-text-node "Hello, world!"))

The define-foreign syntax declares a Wasm import with a given signature that is bound to a Scheme variable. In this example, we’d like access to document.body, document.createTextNode, and element.appendChild.

Each import has a two-part name, which correspond to the strings in the define-foreign expressions above. Imported functions have a signature specifying the parameter and result types.

[This post is a long and detailed tutorial which contains a live page-embedded example task-manager app using the new FFI… ]