Owebview : OCaml binding to the webview library

Hi everyone! :waving_hand:

I’d like to share a small project I’ve been working on: owebview, a set of OCaml bindings for webview.

What is webview?

webview is a tiny, cross-platform library for building desktop GUIs using the operating system’s built-in web engine — WebKit on macOS, WebKitGTK on Linux, and WebView2 on Windows. Instead of shipping a whole browser like Electron, you reuse the system one, so your apps stay small. You create a window, point it at some HTML (or a URL), and you can call back and forth between the page’s JavaScript and your host language.

The gap owebview fills

What makes webview really appealing is its ecosystem: it already has bindings in a lot of languages — Go (the reference one), Rust, Python, C#, Nim, Zig, and many more. As far as I could tell, though, there wasn’t one for OCaml.

owebview is an attempt to fill that gap: a thin binding that lets you drive webview directly from OCaml, with a native bridge between the page’s JavaScript and your OCaml functions.

What it looks like

A complete app is about ten lines:


let () =
  let w = Webview.create () in
  Webview.set_title w "My first owebview app";
  Webview.set_size w ~width:480 ~height:320 Webview.Hint_none;
  Webview.set_html w
  {|<!doctype html>
      <html><body style="font-family: system-ui; text-align: center">
        <h1>Hello from OCaml 👋</h1>
      </body></html>|};
  Webview.run w;
  Webview.destroy w

And you can expose OCaml functions to the page — here window.add(a, b)
returns a JavaScript Promise resolved from OCaml:

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)

Try it

# Run the bundled example
git clone https://github.com/korkorran/owebview.git
cd owebview
dune exec examples/hellowv.exe

# Or pin it into your own project
opam pin add owebview https://github.com/korkorran/owebview.git

Then just add (libraries owebview.webview) to your dune file. The webview.h header is vendored, and the platform-specific C++ flags are detected at build time (via pkg-config on Linux), so there’s nothing to wire up by hand.

Honest status & a request

This is a compact binding / starting point, not a complete library yet:
some pieces (unbind, dispatch, full binding memory management) are intentionally left out for now.

It’s also developed and tested mainly on macOS. I’d love feedback from people running it on Linux distributions — does it compile, do the opam depexts resolve, does the webkit2gtk-4.1 backend behave on your distro?

Issues and PRs are very welcome.

Repository: GitHub - korkorran/owebview: Ocaml binding to the webview library · GitHub

Thanks for reading — happy to hear thoughts, suggestions, and especially Linux build reports!

Very cool. I’ll take it for a spin. I think my package pure-html will complement this nicely for building HTML in a type-safe way: dream-html - Render HTML, SVG, MathML, htmx markup from your OCaml Dream backend server

Awesome!!! I hope you will not abandon the project and cover all the possibilities.

I’ve been able to run the example in debian using a minimum of changes:

diff --git a/lib/dune b/lib/dune
index e7103b5..caf1797 100644
--- a/lib/dune
+++ b/lib/dune
@@ -17,9 +17,11 @@
  ; Link flags (WebKit/Cocoa frameworks on macOS, GTK/WebKitGTK libs on Linux)
  ; are detected at build time too — see the rule below.
  (c_library_flags
+  -lstdc++
   (:include c_library_flags.sexp)))
 
 ; Generate the platform-specific flag files with dune-configurator.
+
 (rule
  (targets c_flags.sexp c_library_flags.sexp)
  (deps
diff --git a/vendor/webview.h b/vendor/webview.h
index fa8ea07..93ba379 100644
--- a/vendor/webview.h
+++ b/vendor/webview.h
@@ -614,8 +614,8 @@ public:
   }
 
   void eval(const std::string &js) {
-    webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(m_webview), js.c_str(),
-                                   nullptr, nullptr, nullptr);
+    webkit_web_view_evaluate_javascript(WEBKIT_WEB_VIEW(m_webview), js.c_str(), -1,
+                                   nullptr, nullptr, nullptr, nullptr, nullptr);
   }
 
 private:

I’ve also been able to compile and run the examples from the legacy webview project in windows in the cygwin environment generated by opam. There is hope!

Using the same logic, I’ve been able to compile and run the example in windows:

diff --git a/lib/config/discover.ml b/lib/config/discover.ml
index 54c6956..c55fd5c 100644
--- a/lib/config/discover.ml
+++ b/lib/config/discover.ml
@@ -7,6 +7,11 @@ let std_flags = [ "-std=c++11" ]
 let macos_link_flags =
   [ "-lc++"; "-lobjc"; "-framework"; "WebKit"; "-framework"; "Cocoa" ]

+let mingw_flags = [ "-std=c++14"; "-isystem"; "PATH/TO/microsoft_web_webview2-src/build/native/include" ]
+
+let mingw_link_flags =
+  [ "-ladvapi32"; "-lole32"; "-lshell32"; "-lshlwapi"; "-luser32"; "-lversion" ]
+
 (* Linux: the webview backend is GTK 3 + WebKitGTK. *)
 let linux_packages = [ "gtk+-3.0"; "webkit2gtk-4.1" ]

@@ -34,6 +39,7 @@ let () =
       let cflags, link_flags =
         match system with
         | "macosx" -> (std_flags, macos_link_flags)
+        | "mingw64" -> (mingw_flags, mingw_link_flags)
         | _ -> (
             (* Assume a Linux system with pkg-config + the -dev packages. *)
             match linux_flags c with
diff --git a/lib/dune b/lib/dune
index e7103b5..c74a325 100644
--- a/lib/dune
+++ b/lib/dune
@@ -17,6 +17,7 @@
  ; Link flags (WebKit/Cocoa frameworks on macOS, GTK/WebKitGTK libs on Linux)
  ; are detected at build time too — see the rule below.
  (c_library_flags
+  -lstdc++
   (:include c_library_flags.sexp)))

 ; Generate the platform-specific flag files with dune-configurator.


It’s dirty: the path to microsoft_web_webview2 is hardcoded (but I’ve applied the same rules than webview), but the binding is working fine!

You did a great job with your library!

After some hands-on testing, I wanted to share a quick thought about the current direction:

It feels like you’re trying to achieve two goals at once:

  1. providing a binding for OCaml
  2. packaging a library working out of the box

But vendoring the underlying library isn’t sustainable for every use case. You’ll inevitably end up rewriting code that’s already available in the upstream repo (e.g., the fix for the function run_javascript being deprecated), and that energy would be better spent on building the binding, not re-implementing what’s already there.

Also, Webview already supports building both shared and static libraries — why not treat those as pre-requisites instead of trying to manage compilation logic yourself? I understand that this can tempting to do everything, but the same benefit can be done with a good documentation explaining how to compile webview (I’ve spent the afternoon yesterday to make it work in cygwin, building owebview was much easer after that). As a developer, I’m not afraid of extra steps when building my environment, as long as the steps are clear and detailed.

This message is a personal opinion/hint, I do not want to discourage you, feel free to disagree (-: The library is very promising. I think there is a need for a UI library working on the differents OS, and I really want to see this owebview going further. But this also something very complex, and I’m afraid of seeing the library going in too many directions.

Looks interesting! Here’s a question, though it’s really more about webview than owebview per se.

I see a clear appeal to this approach for authors of native apps who don’t want to deal with a proprietary UI toolkit (or multiple of them, when implementing cross-platform support). But suppose that I were trying to decide between building a webview-based app or an ordinary webapp that optionally runs locally, via a regular browser. I’m curious what advantages webview provides for this use case, particularly in terms of OS integration. (I gather that the app receives its own icon in the OS task manager and can be shown/hidden independently of the standard web browser, but I wondered if the integration goes beyond that.)

Hi @conroj !
Thank you for your question.

Webview is C/C++ library calls the native Webkit(macOS) - webkitGTK (Linux) - Webkit2(Windows) library, enabling to pop up a HTML window with JS and CSS embed. The strength of this library is that you can call your process from the embed JavaScript. There is an API in Webview dedicated, you just need a bind step.

The Owebview Ocaml library let you use the webview library as you would do in C/C++, but in OCaml (still the library is not entirely covered). The bindings you call from your JS code can be native OCaml functions. They have the same capabilities as a regular OCaml program, so you have access to the unix library, etc. and the integration with the OS is complete.

Using Owebview is comparable to use another GUI library, except that the rendering is done with HTML/CSS/JS