WebFront is a header-only C++23 library for building rich, modern user interfaces. Keep application logic in C++ and create reactive interfaces with TypeScript, React and the existing JavaScript package ecosystem. WebFront brings the UI tools into your application: there is no separate JavaScript development environment to install.
Register your simulation, bootstrap the tools, then build the interface in the application's terminal. life-project is the working directory for the UI sources:
The C++ application of the animation, as text
#include <webfront/WebFront.hpp>
#include <bitset>
using namespace std;
constexpr int N = 32;
using Grid = bitset<N * N>;
Grid nextLife(const Grid& cells) {
Grid next;
auto index = [](int x, int y) {
return size_t((y + N) % N * N + (x + N) % N);
};
for (int y = 0; y < N; ++y)
for (int x = 0; x < N; ++x) {
int n = 0;
for (int dy = -1; dy <= 1; ++dy)
for (int dx = -1; dx <= 1; ++dx)
if (dx || dy)
n += cells[index(x + dx, y + dy)];
auto i = index(x, y);
next[i] = n == 3 || (cells[i] && n == 2);
}
return next;
}
int main() {
webfront::NativeApp app{"life-project"};
app.cppFunction("life.step", &nextLife);
app.bootstrap("developer");
app.show();
}The animation shows the C++ application and the Game of Life example, with terminal output and timing condensed and the preview expanded for readability. Follow the walkthrough below ↓
The example starts with a C++ simulation, adds a React interface, then embeds that interface in the executable. You need Git, CMake 3.31+ and a C++23 compiler. The initial CMake configuration, the first bootstrap and the first wf install need internet access; afterwards the downloaded environment and packages are cached and can be verified offline.
git clone https://github.com/ambroise-leclerc/WebFront.git
cd WebFront
mkdir build
cd build
cmake ..
cmake --build .No option is needed: the build enables this platform's native window engine when its dependencies are found (see step 2). The commands below start from this build directory. With a multi-configuration generator such as Visual Studio, executables are in examples\Debug\ (--config Release selects examples\Release\).
Life.hpp implements Conway's B3/S23 rules on a wrapping 32 × 32 grid. Each step creates a new std::bitset<1024>. LifeConsole.cpp runs it without a GUI:
examples/WebFrontLifeConsoleOn Windows with Visual Studio run examples\Debug\WebFrontLifeConsole.exe.
The essential change in LifeDeveloper.cpp is:
#include "LifeFrontend.hpp"
webfront::demo::LifeApp app{"life-project"};
app.cppFunction("life.step", &webfront::demo::nextLife);
app.bootstrap("developer");
app.show();The project directory gives the application access to its development files. bootstrap("developer") opens the integrated terminal and loads the version-pinned developer environment, checking its SHA-512 integrity. Downloads are cached, so later launches work offline. The terminal runs wf commands; no system shell is exposed and nothing is installed on your machine.
The build you made above is enough. Start the example from the build directory:
examples/WebFrontLifeDeveloperOn Windows with Visual Studio run examples\Debug\WebFrontLifeDeveloper.exe. Without argument, the example uses life-project in the current directory (build/life-project), creates it when it is missing and reuses an existing one without touching its files; every life-project/... path below is relative to it. The pinned environment is used.
Native window. At configuration, CMake enables the native engine of your platform when it finds its dependencies, with no option to pass:
| Platform | Engine | Detected when |
|---|---|---|
| Windows | Microsoft Edge WebView2 Evergreen | an MSVC-compatible compiler is used (the SDK is downloaded; the Evergreen runtime is checked at start) |
| macOS 13.3+ | System Cocoa / WebKit | Clang and a deployment target of at least 13.3 |
| Linux | WebKitGTK 6.0 | pkg-config webkitgtk-6.0 succeeds; installation |
On macOS, this opens a WKWebView window using the system Cocoa and WebKit frameworks, with no engine download.
The example constructs the application and calls show() on the main thread; the HTTP/WebSocket server runs on its own thread.
Closing the window stops the server and exits the example. An explicit deployment target below 13.3 disables automatic detection;
explicitly enabling WKWebView with such a target fails configuration. See the macOS guide.
When the engine is missing, configuration still succeeds. CMake prints a Native window: no native engine will be built notice that says what to install, and WebFrontLifeDeveloper is compiled for the default browser: at start it prints that notice, serves the same application and opens it in your system browser. Install the dependency and run cmake .. again to get the native window. CEF (-DWEBFRONT_EMBED_CEF=ON) makes CEFFrontend available to your own applications; the examples do not select it.
Advanced options.
- Choose the engine yourself with
-DWEBFRONT_EMBED_WEBVIEW2=ON|OFF,-DWEBFRONT_EMBED_WKWEBVIEW=ON|OFFor-DWEBFRONT_EMBED_WEBKITGTK=ON|OFF; an explicit value is never replaced by the detection.-DWEBFRONT_AUTODETECT_NATIVE=OFFdisables it. Test builds (-DWEBFRONT_ENABLE_TESTING=ON) keep the explicit selection. - Automatic engine choices are recalculated on each configuration and do not create an engine option in the cache. To return from an explicit macOS choice to automatic detection, run
cmake .. -UWEBFRONT_EMBED_WKWEBVIEW(or remove the corresponding engine option on another platform). - Another project directory, and another environment archive, can be passed:
examples/WebFrontLifeDeveloper <project> [<environment-url> <sha512-integrity>], for instance an environment built from the sources.
The example reports startup failures with a nonzero exit status.
In the application's terminal, run:
wf create life
wf init
wf install react@19.3.1 react-dom@19.3.1
wf dev ui/main.tsx
wf create lifewrites the editable TSX and CSS starter intoui/; it never overwrites an existing directory.wf initwriteswebfront.project.json, the manifest every other command reads: dependencies, entries, providers, scripts and permissions.wf installresolves the packages and their dependencies from the registry, verifies their integrity and records exact versions inwebfront-packages.lock.json. It does not run the installation scripts of packages.wf restorerebuilds the cache from the lock on another machine, andwf restore --offlineverifies it without any network.wf devcompiles the TSX with esbuild-wasm, opens the interface inside the application and rebuilds it whenever you save a source. A build error appears in the terminal and over the preview, which keeps its last good version. Ctrl+C stops it;wf buildcompiles once without opening anything.
Edit life-project/ui/main.tsx and style.css with your usual editor. React renders the field and controls; each generation awaits the C++ callback:
await webFront.ready;
const step = webFront.cppFunction('life.step');
const next = await step(cells);cells and next are 128-byte Uint8Array grids mapped to C++ std::bitset<1024>, least-significant bit first in each byte. JavaScript calls always return a Promise: await them or handle rejection. No Life rules run in JavaScript.
wf test runs your Jasmine specs in the application's window, under the jasmine-core your lock pins. Install it, create life-project/tests/life.spec.js and list it in webfront.project.json (wf init does not copy the starter's recipe, so the manifest only holds what you add):
describe('the Life grid', () => {
it('packs 32 by 32 cells into 128 bytes, least significant bit first', () => {
const cells = new Uint8Array(32 * 32 / 8);
for (const i of [330, 331, 332]) cells[i >> 3] |= 1 << (i & 7);
expect(cells.length).toBe(128);
expect(cells[41]).toBe(0b00011100);
});
});wf install jasmine-core@7.0.2
"test": {"specs": ["tests/life.spec.js"]}wf test
The command ends with a nonzero code when a spec fails.
The builds remove TypeScript types without checking them; wf check runs the TypeScript compiler that your lock pins, in the browser. It needs a tsconfig.json and the type packages of your imports. A complete one for the starter, the same as the journey tests use, is:
{
"compilerOptions": {
"strict": true,
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"noEmit": true
},
"include": ["ui"]
}The type packages come next: wf install typescript@5.9.3 @types/react@19.3.0 @types/react-dom@19.3.0 then wf check type-check the starter. Both commands are described in the developer environment reference.
An optional native automation launcher exposes the developer window through local stdio MCP or a JSON CLI, with explicit permissions for check/build/test/preview and bounded UI inspection/actions. It requires an environment containing automation API v1 (0.6.3 or later), built from the sources or obtained from a verified published distribution.
Play or pause, advance one generation, paint cells, switch patterns, change speed or select a plan view. Generation and population counters reflect the C++ simulation. Only one generation request is outstanding at a time; failures stop playback and appear in the interface.
Declare the entry and the provider in webfront.project.json ("entries": {"main": "ui/main.tsx"} and "providers": {"LifeUI": {"entry": "main", "output": "generated/LifeUI.hpp"}}), or pass them explicitly. In the application's terminal:
wf embed ui/main.tsx --output generated/LifeUI.hpp
This builds the interface and packages its resources and dependency notices into a compressed C++ header. Close the development window, then reconfigure the same build directory (the engine is kept):
cmake .. -DWEBFRONT_LIFE_ASSETS="$PWD/life-project/generated/LifeUI.hpp"
cmake --build . --target WebFrontLifeEmbedded
examples/WebFrontLifeEmbeddedFor Visual Studio launch examples\Debug\WebFrontLifeEmbedded.exe.
LifeEmbedded.cpp replaces the development project and bootstrap with the generated provider:
#include "LifeFrontend.hpp"
webfront::demo::LifeAppWithAssets<webfront::fs::LifeUI> app;
app.cppFunction("life.step", &webfront::demo::nextLife);
app.show();Recompilation is explicit. The resulting application runs offline without the project directory, developer callbacks or downloaded tool cache. The selected native engine's deployment requirements still apply; this is not an application installer.
The examples' LifeFrontend.hpp selects NativeOrBrowserFrontend for both stages: a native window when the engine is built and a session exists, the default browser otherwise.
Your own application can select webfront::NativeApp or webfront::NativeAppWithAssets explicitly when it requires a native window.
WebFront's implementation lives under include/. There is no WebFront library binary to build; CMake supplies its C++ dependencies and platform settings:
add_subdirectory(path/to/WebFront)
add_executable(MyApp main.cpp)
target_link_libraries(MyApp PRIVATE WebFront::WebFront)As a subproject, examples and tests are off by default and compiler settings do not leak into your targets. WEBFRONT_BUILD_EXAMPLES=OFF disables examples in a standalone build too.
GCC 16 crashes inside stdexec (internal compiler error: Segmentation fault in __query.hpp) when CMake scans C++ modules and adds -fmodules-ts, which happens by default for C++20 and later targets (CMP0155, Ninja). WebFront is header-only and needs no module scan. Turn it off for the targets that link WebFront and use no C++ modules, after creating them:
add_executable(MyApp main.cpp)
target_link_libraries(MyApp PRIVATE WebFront::WebFront)
webfront_disable_module_scan(MyApp)Alternatively, set(CMAKE_CXX_SCAN_FOR_MODULES OFF) before creating your targets does it for all of them; use it only if no target of your project uses C++ modules, since CMake then cannot order their compilation. Targets with source-level scan properties or CXX_MODULES file sets are still scanned.
Configuring WebFront as a subproject with GCC 16 and scanning enabled prints a warning naming each affected target, with these workarounds.
webfront::WebFront always uses the system browser. For a native window with the same browser fallback as the examples, add this after linking your target (also works after CPMAddPackage):
webfront_use_native_window(MyApp)Select the application type in main.cpp using the definition supplied by the helper:
#include <webfront/WebFront.hpp>
#if defined(WEBFRONT_NATIVE_WINDOW)
using App = webfront::WebFrontWithFrontend<webfront::frontend::NativeWebViewFrontend>;
#else
using App = webfront::WebFront;
#endif
int main() {
App app;
app.show();
}To get a native window when one can be opened and the system browser otherwise (for example over SSH, in a container or in CI, where no display exists), use WebFrontWithFrontend<webfront::frontend::NativeOrBrowserFrontend>: no #if is needed, and the reason for a fallback is printed once. See docs/FRONTENDS.md.
Native engines are detected by default, except in WebFront's own test and fuzzing builds (WEBFRONT_ENABLE_TESTING=ON or WEBFRONT_ENABLE_FUZZING=ON); a parent project's own ENABLE_TESTING or ENABLE_FUZZING affects neither the detection nor WebFront's tests. The helper applies the native engine's target setup and defines WEBFRONT_NATIVE_WINDOW only when that engine is enabled; otherwise CMake prints installation advice and the application uses the default browser. On Windows, detection requires an MSVC-compatible compiler and downloads the pinned WebView2 SDK; the Evergreen runtime must be installed separately. Linux needs WebKitGTK 6.0 development files, and macOS needs Clang and macOS 13.3 or later. Explicit WEBFRONT_EMBED_<ENGINE>=ON|OFF values take precedence; WEBFRONT_AUTODETECT_NATIVE=OFF disables detection. No option needs to be set before add_subdirectory/CPM for the default path.
For explicit engine selection, webfront_native_frontend_option(out) and webfront_setup_frontends_for_target(MyApp) are also callable from the parent or other directories. The latter applies setup for all enabled engines; the native helper selects only the platform's native engine and never CEF. See WKWebView, WebKitGTK and the frontend architecture.
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
-DWEBFRONT_EMBED_CEF=OFF -DWEBFRONT_ENABLE_TESTING=ON
cmake --build build --parallel 2
ctest --test-dir build --output-on-failure --parallel 4Configuring with tests enabled downloads, and verifies against their SHA-512, the pinned bootstrap environment and the packages of the test registry; the embedded assets are generated and checked by CMake alone (the webfront-embedded-assets target regenerates them). Normal C++ builds use versioned headers. Browser tests require an enabled engine; Linux CEF tests use Xvfb. See AGENTS.md for contributor commands and developer environment testing.
CTest runs up to four tasks at a time; independent driver sessions use OS-assigned ports, while
shared projects and nested builds retain resource locks.
See test timings and parallelism for measurements and constraints.
ctest -L fast runs the unit tests in about a second; test tiers describes what CI runs on each pull request, weekly and before a merge into master.
CI runs one combined C++ / native-browser job per desktop platform on each code change. The Linux job also collects and prints GCC line coverage (no external upload). Linux Clang, sanitizers, fuzzing and CEF compatibility run weekly, on demand and on pull requests into master. Manual dispatch with repeat_desktop_tests=true also runs the complete desktop CTest suite three times, recreating its fixtures on each pass. Workflow concurrency cancels superseded runs; dependency and compiler caches avoid repeated work. Platform badges above identify the configured matrix; the CI badge reports its aggregate status. Execution times depend on cache state and runner availability.
- WebFront owns an HTTP/WebSocket server on a background thread. Callbacks run there, one at a time; avoid blocking it. Browser calls (
jsFunction,app.broadcast(name)(...)) can be made from any thread and never wait for it; see threads. Native windows must be created and shown on the main thread. Application coroutines useapp.spawn(task)orui.spawn(task); see execution and cancellation. - JavaScript calls to C++ always return Promises. C++
ui.jsFunction(name)(...)is fire-and-forget;ui.jsFunction<R>(name)(...)returnsstd::future<R>. Missing functions, exceptions, malformed replies and disconnects fail the typed call. - The binary bridge supports scalars, strings, tuples, numeric typed arrays and packed bitsets. Arbitrary objects do not cross it. Strings have a 16-bit encoded length limit.
- Development project access is explicit and intended for trusted local code. Downloaded tools run with the application's bridge authority, and script permissions frame only the
contextAPI: neither is a sandbox. Only packages that run in a browser are supported; installation scripts and native add-ons are not executed. Builds transform TypeScript without type checking;wf checktype-checks with the compiler the project locks. - The optional
@webfront/reactadapter supplies coalesced C++ stores and empty React islands.WebFrontLifeReactdemonstrates a C++ panel within the Game of Life React UI; its package archive is prepared locally for registry publication. - Packaging, accessibility and native-engine deployment still need application-specific work. The optional
ui.document()API provides DOM islands, automatically batched writes, event callbacks and grouped sender reads. See project status for known limitations.
WebFront is dual-licensed under Apache-2.0 OR CECILL-B, at your option. You may use, modify and redistribute WebFront under either license; you do not need to comply with both simultaneously. See LICENSE for the full Apache 2.0 and CeCILL-B version 1 texts, including the French and English versions of CeCILL-B.
Third-party components retain their own licenses and notices. The WebFront license choice does not replace the licenses of its dependencies or bundled third-party code.
Developer environment reference · architecture decisions · contributing

