This repository is a demonstration project created during the fsbondtec Christmas Hackathon 2025.
C++ objects are seamlessly integrated into modern web applications as a modern Qt alternative for web-based UIs. While Qt uses QML/Qt Quick or Qt WebEngine for GUI development, WebBridge leverages standard web technologies.
Motivation: A key advantage over Qt is the significantly reduced boilerplate code. In Qt, a simple property requires extensive boilerplate with getter, setter, signal, and backing field:
// Qt approach - verbose boilerplate
Q_PROPERTY(bool aBool READ aBool WRITE setABool NOTIFY aBoolChanged)
bool aBool() const {
return _aBool;
}
void setABool(bool v) {
if (v != _aBool) {
_aBool = v;
emit aBoolChanged();
}
}
signals:
void aBoolChanged();
private:
bool _aBool;
// WebBridge approach - minimal and clean
property<bool> aBool;
The solution is based on webview (C++ wrapper for Microsoft WebView2/Chromium) and a Python code generator (tools/generate.py) that uses tree-sitter to analyze C++ classes and automatically generate C++ registration headers and TypeScript type definitions. The build process automatically invokes the code generator via CMake, making the workflow seamless. Code generation is required because C++26 reflection is not yet available.
pip install conan)1. Create Conda environment
The environment includes Python 3.12 and the required packages for the code generator (tree-sitter, jinja2):
conda env create -f environment.yml
# or, if the environment already exists:
conda env update --file environment.yml --name webbridge_hackathon --prune
2. Configure and build
The configure.bat script will:
configure.bat
Then build using the provided build script:
# Build Debug (default)
build.bat
# Build Release
build.bat --release
# Rebuild (clean first)
build.bat --rebuild --release
VS Code Integration:
Ctrl+Shift+B to access build tasks (Build, Rebuild, Clean, etc.)F5 to build and debug - launch configurations are available in .vscode/launch.jsonNote: The frontend (Vite + Svelte 5 + TypeScript) is automatically built as part of the CMake build process. The compiled assets are embedded into the C++ application via CMakeRC and served over HTTP by the ResourceServer.
3. Run the application
After a successful build:
# Debug build (with DevTools):
build\src\Debug\webbridge_hackathon.exe
# Release build (without DevTools):
build\src\Release\webbridge_hackathon.exe
Every class to be exposed to the web must inherit from webbridge::object. The API is inspired by Qt and provides the following mechanisms for JavaScript integration:
The automatically generated code is functionally inspired by Qt and Qt's MOC (Meta-Object Compiler), but the WebBridge classes require significantly less boilerplate code than their Qt equivalents.
All public methods of a webbridge::object class are automatically published to JavaScript.
A function marked with the [[async]] attribute is executed in a separate worker thread. This prevents blocking the main thread on the C++ side. On the JavaScript side, both synchronous and asynchronous methods always return a Promise and never block the main thread.
Properties are similar to primitive data types but require access via the parenthesis operator () in C++. In JavaScript, properties are exposed as Svelte-compatible, reactive stores. They are read-only in JavaScript; changes to the property value in C++ are automatically and immediately propagated to JavaScript.
Events are the WebBridge equivalent of the Qt signal/slot mechanism.
WebBridge supports exposing constants as both static (class-wide) and non-static (instance-specific). Both variants are automatically exported to JavaScript and are available there as read-only values.
WebBridge implements robust error handling, distinguishing between JavaScript client errors (4xxx) and C++ server errors (5xxx). Errors are serialized as JSON objects and, for asynchronous operations, are propagated as rejected Promises.
Error format:
{
"error": {
"code": 4001,
"message": "Invalid argument type",
"details": { "param": "value", "expected": "string" },
"stack": "at function (file.js:10:5)",
"origin": "javascript"
}
}
Error codes:
4000-4999: JavaScript errors (e.g., 4001 = JSON_PARSE_ERROR during parameter deserialization)5000-5999: C++ errors (e.g., 5000 = RUNTIME_ERROR during runtime errors)Inspired by JSON-RPC 2.0, GraphQL, and HTTP status codes. Promises are automatically rejected on error, enabling clean exception handling with async/await syntax.
The following example shows how to define a C++ class with methods, properties, and events for web integration with WebBridge.
#include "webbridge/object.h"
class MyObject : public webbridge::object
{
public:
property<bool> aBool = false;
property<std::string> strProp;
event<int, bool> aEvent;
inline static constexpr auto PI = 3.141592654;
const std::string version = "1.0";
public:
[[async]] void foo(std::string_view val) {
// long-running action
strProp = val;
aEvent.emit(42, false);
}
bool bar() const {
// Parenthesis operator accesses value
return !aBool();
}
};
const myObj = await MyObject.create();
// ...
myObj.aBool.subscribe(value => {
console.log('aBool updated:', value);
});
const myObj = await MyObject.create();
// Example: call a synchronous method
// Blocks the main thread in C++, but JavaScript waits asynchronously
const result = await myObj.bar();
console.log('Result of bar():', result);
// Example: call an asynchronous method ([[async]] = worker thread in C++)
// Does not block the main thread in either C++ or JavaScript
myObj.foo('new value').then(() => {
console.log('foo() completed');
});
const myObj = await MyObject.create();
// Register event listener (similar to Node.js EventEmitter)
myObj.aEvent.on((intValue, boolValue) => {
console.log('Event received:', intValue, boolValue);
});
// Alternatively, one-time event
myObj.aEvent.once((intValue, boolValue) => {
console.log('One-time event:', intValue, boolValue);
});
const myObj = await MyObject.create();
console.log(myObj.version); // Instance constant: "1.0"
console.log(MyObject.PI); // Static constant: 3.141592654
To make C++ classes available in JavaScript, they must be explicitly registered. The code generator creates the necessary binding files, which are then included in CMake. In your main.cpp, you must call the generated registration function:
#include "MyObject_registration.h"
int main() {
// Register the class for JavaScript
webbridge::register_type<MyObject>();
// ... initialize and run your webview ...
}
The current implementation has the following limitations:
This project is licensed under the MIT License - see the LICENSE file for details.
Third-party licenses can be found in THIRD-PARTY-NOTICES.txt.