[ANN] Owebview 0.1 — native desktop windows with a web UI, from OCaml

Hi everyone,

I’m happy to announce the first release of Owebview, OCaml bindings to webview. It means an embedded web rendering engine in your OCaml apps. It is now available on opam:

opam install owebview

Owebview opens a native window backed by the operating system’s own web engine — WebKit on macOS, WebKitGTK on Linux, WebView2 on Windows — and lets you drive it from OCaml. No Electron, no bundler, no packaged browser: a single executable and some HTML.

You can find a blog post about why Owebview is a solution for your OCaml GUIs.


An example window with the three.js library

Native dependencies

The system web engine, and nothing else:

  • macOS — WebKit / Cocoa, already part of the system. Nothing to install.
  • Linuxgtk+-3.0 and webkit2gtk-4.1, pulled in by the conf-gtk3-webkit opam package (published alongside this release), so opam install resolves the depexts for your distribution.
  • Windows — the WebView2 runtime ships with Windows 10 and 11; the SDK headers are located through the NuGet cache at build time (nuget install Microsoft.Web.WebView2).

The webview header itself is vendored, so nothing is fetched at build time, and the platform compile/link flags are detected by a dune-configurator script.

Examples

The repository ships several runnable examples:

dune exec examples/hellowv/hellowv.exe          # bindings, assets, Dock icon
dune exec examples/timer/timer_posix.exe        # threads + dispatch
dune exec examples/timer/timer_lwt.exe          # the same, driven by Lwt
dune exec examples/d3/d3.exe                    # a D3.js chart
dune exec examples/three/three.exe              # WebGL via three.js
dune exec examples/js_of_ocaml/hellowv.exe      # frontend written in OCaml too

The last one is worth a look if you like the idea of one language end to end: the page’s logic is written with Brr and compiled by js_of_ocaml, so both sides of the bridge are OCaml.

Tutorial

There is a six-step tutorial that builds up from a first window to a full application: on-disk assets, the JS ↔ OCaml bridge, an asynchronous backend with Lwt, a 100% OCaml frontend, and the platform details of application icons.

Design notes

This is a thin binding, deliberately. It stays close to the C API and leaves higher-level conveniences to the caller — in particular, binding arguments and results are exchanged as JSON text, and choosing a JSON library is up to you.

Feedback wanted, especially on Linux

Owebview has been tested on macOS, Windows, Fedora, Ubuntu.
Reports about building and running it on others Linux distributions would be particularly valuable: does it compile, do the depexts resolve, does the webkit2gtk-4.1 backend behave as expected on your distro, and how does the Dock icon story go on your desktop environment? Windows reports are welcome too.

If you are interested in this library, please email me at my address frederic.ln.lang@gmail.com.

Special thanks to @Chimrod for his contribution in this release.

Happy hacking!

Very cleanly and nicely presented library ! Thanks !

From your blog post Owebview — a solution for your OCaml GUIs · korkorran

...

(* Expose window.add(a, b) to the page. *)
Webview.bind w "add" (fun id req ->
    let result =
      match Scanf.sscanf_opt req "[%d,%d]" (fun a b -> a + b) with
      | Some n -> string_of_int n
      | None -> "null"
    in
    Webview.return w id ~error:false ~result)

...

Here I notice the boundary crossings between Javascript and OCaml (see sscanf above ) seem to involve string → int conversions ). Should Javascript and OCaml communicate via some sort of FFI and not string-ify and destring-ify ?

Are you building some applications that require Owebview yourself ? Just curious about what needs you are addressing for yourself or if this is just a library you want to put out there.

Thanks! On the boundary: binding arguments are stringified on the JavaScript side and de-stringified on the OCaml side — the arguments arrive as a JSON array in a string, and Webview.return sends JSON text back. From there you can build something close to a typed FFI on top, using a JSON serialization library such as Yojson (with ppx_deriving_yojson, for instance) to encode and decode your own types at the boundary.

→ I plan to use the library on personal projects, but nothing is decided yet. I will probably try with libraries managing dynamically the DOM of HTML pages and post on this thread when I have something solid.

→ So Owebview is a side project but I hope to be able to continue developing it and that it will grow over time, making it a solid choice for building desktop apps with OCaml.

I created a small demo app using my preferred stack:

I am really quite satisfied with main.ml:

open Demo_app
open Router
open Server

let router : [ `Home | `Htmlact | `Increment | `Not_found ] router =
  one_of
    [
      get /? nil >> `Home;
      get / s "htmlact.js" /? nil >> `Htmlact;
      post / s "increment" /? nil >> `Increment;
    ]

let port = 8080

let serve () =
  Eio_main.run @@ fun env ->
  serve ~port ~env ~router @@ fun { route; _ } ->
  match route with
  | `Home -> html `OK Content.(index ())
  | `Htmlact -> raw ~content_type:"text/javascript" `OK Content.htmlact
  | `Increment -> html `OK Content.(increment ())
  | `Not_found -> empty `Not_found

let () =
  let _ = Thread.create serve () in
  let open Webview in
  let webview = create ~debug:true () in
  set_title webview "demo-app";
  set_size webview ~width:800 ~height:600 Webview.Hint_none;
  navigate webview (Printf.sprintf "http://127.0.0.1:%d/" port);
  run webview;
  destroy webview

The structure was inspired by Evan Czaplikci’s recent work

Nice use of navigate webview here.

But my understanding is that you usually want to use Owebview when you want to basically avoid creating server/client apps. Use it when you want to create a traditional native GUI style app in which the presentation logic is written in javascript while the backend logic is written in OCaml and they communicate with function calls (the Javascript<->OCaml FFI) rather than HTTP calls.

I am not sure I understand your point. The notion of client does not really make sense here as the only code running in the browser is the Htmlact blob. Building up a GUI by passing values through the FFI to Javascript seems much more effortful compared to just writing some OCaml functions that produce HTML. Is there a reason you think that the FFI is preferable?

In the example you posted you used a eio based webserver. The eio webserver is your server and the webpage insider your webview at 127.0.0.1 is your client.

Owebview will be a good solution when you don’t want this client-server paradigm. In a normal GUI application that you would build with the Qt C++ toolkit etc., Android native application, ios native application there is simply one application and no client / server architecture. This allows state to be shared in the whole application without the webpage - backend “jump” which requires all kinds of funny tricks like cookies etc. to share state between webpage and backend. FFI will actually be less tedious in some ways.

@frederic-lang is the creator of this library so it would be great if corrected me if I was wrong in my understanding.

Edit: Added an excerpt from blogpost

I think the creators hint at this:

So the interface is a web page, but the application is not a web application. There is no server to run, no HTTP port to pick, no serialization protocol to design between two processes. Your handlers are ordinary OCaml functions, with access to the filesystem, to native libraries, to the rest of your program.

That page also says:

Nothing forces the front-end to be JavaScript…The straightforward path is to write real .html, .css and .js files and load them — that is what the hellowv example does, and it means the UI can be built by anyone comfortable with the web, with the browser devtools working as usual…What matters is that the choice is yours, and that it is not all-or-nothing: start with a plain HTML page, move the parts that benefit from types over to OCaml, keep a JavaScript library where a JavaScript library is the right answer.

I don’t think the creators intend to dictate the architecture of the apps built with this library. Also, for webapps it’s natural to gravitate towards a client-server architecture. I don’t see anything inherently wrong with it, depending on what the app does.

When developing an application using hypertext as the engine of application state, the client/server distinction largely breaks down.

You say

This allows state to be shared in the whole application without the webpage

and I think this is precisely the appeal of using Htmlact/HTMX over a classic client/server model. In the example I posted, the counter is computed and rendered in the backend.

@kentookura Your example shows a way of using the library I did not think about. Actually I have run your example and it works. I am not very familiar with this stack so I have no hintful advices about it.

Driving a UI from the back-end is a bit puzzling at first sight, but yes, depending on what the app does and the implementation of your existing code it can be a good choice as @yawaramin said. Still the traditional way of building an app should to provide bindings through the owebview bridge and implement the UI via plain html, css, js file (more surely generated with js_of_ocaml).

I just thought of a use case that makes this approach necessary–if you want to make a hardened, no-JS app that uses only HTML form submissions to update anything. In this case you want to talk to a server that will return plain HTML/CSS, no JS. You might want to do this to reduce the surface area exposed to attack. Eg, if you’re deploying the app in a publicly-accessible area.

Yeah, eventually, Ocaml have it.

Games & Apps around have been using webview extensively for GUI… some of my favorite was Plasticity xD. Although, we may just use some native GUI like Raylib, the convenient is undeniable..