fun-ui 1.0 open-sources a Chromium-backed UI layer for the Fun language
fun-ui open-sourced: bringing the browser interface back
On October 24, fun-ui 1.0 was open-sourced under the MIT license. The repository is at https://github.com/funlang/fun-ui, with the release tagged v1.0.0.
This thing doesn't draw buttons or do layout. It connects the Fun process to Chromium: windows and business logic stay on the Fun side, and the UI is written in plain HTML, CSS, and JavaScript, running inside a browser engine. The two sides call each other, each doing their own job.
Fun has always had its own windows, and the native controls from lib-ui.fun still work. The problem is that today's interfaces demand more and more from layout, charts, and animation, which native controls can't keep up with. The browser's existing capabilities are right there, so why not use them.
1. UI belongs to the browser, logic belongs to Fun
Let's draw the boundary first. The Fun process holds the window, handles arithmetic, file reads, database connections, and calls to native libraries. The page only displays and collects input. No business logic goes in the page, Fun only sends data down, and the two sides talk through four messages.
JavaScript calling Fun is called jCAL, and Fun replies with a jRET. Conversely, Fun calling JavaScript is called fCAL, and JavaScript replies with a fRET. All four messages are FD documents, separated by a type field, with simple fields: id, name, args, timeout, and for replies id, val, err.
On the Fun side, host classes inherit from Ui2Form, override onReady, and decide there which page to load. Handler functions are found by name, with the default prefix fd_:
class DemoForm = Ui2Form()
fun onReady()
Navigate('<h1>Hello, fun-ui</h1>');
end fun
fun fd_load_profile(args)
result = new [];
result.id = args[0];
result.name = '一了';
end fun
end class;
On the page side, just load two scripts, fd.js and fun-bridge.js, and then call directly:
<script src='fd.js'></script>
<script src='fun-bridge.js'></script>
<script>
Fun('load_profile', { id: 42 }).then(function (profile) {
document.querySelector('#name').textContent = profile.name;
});
</script>
window.Fun supports two syntaxes: Fun(name, ...args) returns a Promise, or pass an object with name, args, callback, timeout. Hyphens and spaces in names are replaced with underscores, and if not found it retries by removing separators, so load_profile and load-profile both hit the same method.
2. Four messages, two directions
The protocol itself isn't complicated; the trouble is binary data. Images, archives, file streams might all pass through messages, and can't be treated as strings.
fun-ui's approach is to prepend a 4-byte little-endian length header to each payload, with bit 31 set to 1 meaning the frame is compressed. Compression uses raw DEFLATE: the Fun side calls deflate, the browser side uses CompressionStream('deflate-raw'). Not compressed by default; only considered above 512 bytes. Either side can toggle compression independently, and thresholds are individually adjustable.
One small detail: the bridge script tags FD text with gbk encoding, not UTF-8. The reason is practical: old Windows code pages and existing Fun strings need to round-trip unchanged. Chromium supports gbk decoding, so the browser side must be a Chromium engine; this is written in the compatibility notes, not hidden.
fCAL return values take a different shortcut. The Fun side first allocates a shared buffer and hands the handle to JavaScript; JavaScript writes the fRET into it and only sends back the handle. Large data doesn't get copied back and forth twice.
3. Two transports, one protocol
First is WebView2, which embeds Chromium directly into the Fun window. Sitting in between is a tiny Delphi DLL, libwv2.dll, 32-bit, built with Delphi 2006, using WebView4Delphi underneath. It exports only a few functions: Init, Show, Navigate, PostData, ExecScript, Close, Free. The DLL doesn't bundle Chromium; it uses the system's WebView2 Runtime, so the download size stays in the same range as Fun itself.
(Complaint: it's 2026, and still a 32-bit DLL built with Delphi 2006. No other reason, WebView4Delphi was the shortest path, and if it runs, run it.)
Second is WebSocket. Ui2Proxy opens a local HTTP page on the default port 8278, and WebSocket uses the same frame format. The page loads fun-bridge-ws.js, and window.Fun works the same way, so dispatcher and page code don't need changes.
The current WebSocket version is still a workaround: the page still displays inside a WebView2 window, only the messages go through a socket instead of shared memory. Splitting it from Ui2Form so external browsers can connect directly is step two on the roadmap, and the shortest path to taking the UI outside Windows.
4. Scope and boundaries
The code is small. On the Fun side there are five modules, lib-ui2.fun, lib-ui2-proxy.fun, and three dispatchers, totaling 549 lines; the browser side has four scripts, fd.js, fd2.js, and two bridges, totaling 1631 lines; the Delphi host is 375 lines. Together that's 2555 lines, and with the Chinese README and CHANGELOG it reaches 2853 lines.
The boundaries are also spelled out:
- The first version only supports Windows.
libwv2.dllandUi2Formneed Windows plus the WebView2 Runtime. - The WebSocket path hasn't left the WebView2 window yet; external browsers wait for step two.
- Requires the Fun 9.x runtime and the standard library modules listed earlier.
- The browser must be a Chromium engine, because of
CompressionStreamand thegbktag.
These aren't omissions; they're deliberately narrowed gates for version one. Keep the protocol and JavaScript API stable first, and platform-specific code is locked in the host layer, so adding Linux and macOS later won't touch the pages.
5. Roadmap
The README lays out four steps, quoted verbatim:
- Windows baseline. WebView2 transport, WebSocket transport, FD serialization, dispatcher, and bridges, which is what's in the repo now.
- Portable WebSocket transport. Split
Ui2Proxyout of the WebView2 form so it serves external browsers; then uselib-winsock-lnxon Linux andlib-winsock-unxon macOS. The HTTP/WebSocket core is already shared inlib-winsock-base.fun. - Native webview backends. Add Linux and macOS hosts behind the
Ui2FormAPI, using WebKitGTK and WKWebView, with the message protocol and JavaScript bridges unchanged. - Release engineering. Build scripts for each platform, CI, packaging examples, compatibility matrix.
The wire protocol and API are planned to stay stable, with platform differences confined to the host layer as much as possible. That's easy to say; the proof will be in the feel of the next two steps.
Conclusion
Fun does the computing, the browser does the drawing. This division isn't new; Electron has done it for ages, except it stuffs an entire Chromium into every app. fun-ui chooses to use the system's existing WebView2, the runtime is still that few-hundred-KB fun.exe, and the UI is just a few HTML and JS files.
The first version only opens on Windows, a small window to catch some air. The code is MIT, and issues, patches, and examples are all accepted. The address again: https://github.com/funlang/fun-ui.