Render your existing Vanilla‑JS, React, or Vue app in the OS web view and drive a C++ backend over a JSON bridge. No compiler, no Electron, no bundled browser engine.
A small prebuilt native binary renders your Vite app in the OS web view. Your app stays plain JS / React / Vue.
Every call goes UI → C++ and returns a Promise. The real work happens in the native host — the same bridge across all three frameworks.
httpGet / httpPost run on a C++ worker thread via cpp‑httplib + OpenSSL, injecting a Bearer token from the keychain.
saveCredential writes to Credential Manager / Keychain / libsecret. Write‑only — secrets never return to JS.
Parameterized db.query / exec / migrate against per‑user SQLite — hardened with trusted_schema=OFF.
useNativeState / nativeSetting persist in C++ and push changes back to every subscriber.
files.write / read accept string, Uint8Array, ArrayBuffer or Blob — into the per‑user dir through the secure layer.
listPrinters + raw ESC/POS over Winspool / CUPS, or printNetwork to a port‑9100 device.
The secure build adds AES for files & settings and SQLCipher for the DB. Flip secure: true in .hullrc.
hull dev --browser runs the UI in your browser with the real bridge over HTTP/SSE, plus a dev‑only inspector.
hull build inlines the whole UI into one HTML and packages it with the host; hull installer wraps it into a .exe / .dmg / .deb.
The C++ backend and the JSON bridge are identical across frameworks — only the UI layer and the optional state hook differ. Copy a recipe and trim.
{
"scripts": {
"dev": "hull dev",
"build": "hull build",
"start": "hull start",
"installer": "hull installer"
},
"devDependencies": {
"@mwguerra/hull": "^0.1.0",
"vite": "^6.0.0"
}
}import { ping, nativeSetting } from "@mwguerra/hull/bridge";
// call C++ and show the result
document.querySelector("#ping").onclick = async () => {
const res = await ping("hello"); // { ok: true, echo: "hello" }
out.textContent = JSON.stringify(res);
};
// a two-way persisted setting
const theme = nativeSetting("theme");
theme.subscribe((v) =>
document.documentElement.classList.toggle("dark", v === "dark"));
theme.load();import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({ plugins: [react()] });import { useState } from "react";
import { ping } from "@mwguerra/hull/bridge";
import { useNativeState } from "@mwguerra/hull/react";
export default function App() {
const [out, setOut] = useState(null);
const [theme, setTheme] = useNativeState("theme"); // persisted in C++
return (
<>
<button onClick={async () => setOut(await ping("hi"))}>Send</button>
{out && <pre>{JSON.stringify(out)}</pre>}
</>
);
}import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({ plugins: [vue()] });<script setup>
import { ref } from "vue";
import { ping } from "@mwguerra/hull/bridge";
import { useNativeState } from "@mwguerra/hull/vue";
const out = ref(null);
const theme = useNativeState("theme"); // a ref, persisted in C++
async function send() { out.value = await ping("hi"); }
</script>
<template>
<button @click="send">Send</button>
<pre v-if="out">{{ out }}</pre>
</template>Import from @mwguerra/hull/bridge and call the native host directly. The same functions back Vanilla JS, React, and Vue — only the optional state hook changes.
import { httpPost, saveCredential, db } from "@mwguerra/hull/bridge";
const res = await httpPost("https://api.example.com/x", { a: 1 }); // TLS, in C++
await saveCredential("api.example.com", "default", token); // OS keychain
await db.migrate(["CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT)"]);
await db.exec("INSERT INTO notes (body) VALUES (?)", ["hello"]);
const notes = await db.query("SELECT * FROM notes ORDER BY id DESC");One dev dependency and a handful of commands. The prebuilt host does the native work; your Vite toolchain does the rest.
The prebuilt host arrives as a platform‑gated optional dependency — npm installs only the one for your machine, e.g. hull‑win32‑x64.
hull dev renders your Vite app in a native window with HMR and a dev inspector. --browser keeps the real bridge over HTTP/SSE.
hull build single‑files the UI and packages it into a versioned, ready‑to‑ship archive — then hull installer turns it into a native .exe / .dmg / .deb.
End users only need the OS web‑view runtime — preinstalled on Windows 11 and macOS, libwebkitgtk‑6.0 on Linux.
| Windows x64 | macOS (Apple Silicon) | Linux x64 | |
|---|---|---|---|
| Web view | WebView2 (Edge) | WebKit | WebKitGTK 6 |
| Credentials | Credential Manager | Keychain | libsecret |
| Printing | Winspool | CUPS | CUPS |
| Window icon | runtime (GDI+) | app bundle | .desktop entry |
| Build host on | Windows | macOS | any OS via Docker |
Prebuilt hosts ship for win32‑x64, darwin‑arm64 (Apple Silicon — Intel Macs aren't supported), and linux‑x64. A host must be built on its own OS — true cross‑compile isn't realistic for WebView2 / WebKit — except Linux, which builds from any OS via Docker.
Add Hull to any Vite project and run npm run dev. Zero config — the window title and storage namespace come from your package.json.