v0.1.0 · prebuilt C++ web-view host

Tiny native desktop apps
from your web UI

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.

$ npm i -D @mwguerra/hull
zsh — my-app
native window
My App
{ ok: true, echo: "hello" }
theme · persisted in C++
notes · sqlite
  • Ship without the DevOps
  • Bridge calls run in C++
  • Encrypted at rest, optional
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.

Batteries included, in C++

Every call goes UI → C++ and returns a Promise. The real work happens in the native host — the same bridge across all three frameworks.

TLS HTTP

httpGet / httpPost run on a C++ worker thread via cpp‑httplib + OpenSSL, injecting a Bearer token from the keychain.

OS keychain

saveCredential writes to Credential Manager / Keychain / libsecret. Write‑only — secrets never return to JS.

Embedded SQLite

Parameterized db.query / exec / migrate against per‑user SQLite — hardened with trusted_schema=OFF.

Two-way native state

useNativeState / nativeSetting persist in C++ and push changes back to every subscriber.

Files & uploads

files.write / read accept string, Uint8Array, ArrayBuffer or Blob — into the per‑user dir through the secure layer.

Printing

listPrinters + raw ESC/POS over Winspool / CUPS, or printNetwork to a port‑9100 device.

Encryption at rest

The secure build adds AES for files & settings and SQLCipher for the DB. Flip secure: true in .hullrc.

Browser dev mode

hull dev --browser runs the UI in your browser with the real bridge over HTTP/SSE, plus a dev‑only inspector.

Builds & native installers

hull build inlines the whole UI into one HTML and packages it with the host; hull installer wraps it into a .exe / .dmg / .deb.

Works with your framework

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.

package.json
{
  "scripts": {
    "dev": "hull dev",
    "build": "hull build",
    "start": "hull start",
    "installer": "hull installer"
  },
  "devDependencies": {
    "@mwguerra/hull": "^0.1.0",
    "vite": "^6.0.0"
  }
}
src/main.js
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();
vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({ plugins: [react()] });
src/App.jsx
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>}
    </>
  );
}
vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

export default defineConfig({ plugins: [vue()] });
src/App.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>

One bridge, every framework

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.

talk to C++
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");
  • ping(text)Sync echo for diagnostics.
  • httpGet / httpPostcpp-httplib + OpenSSL on a worker thread.
  • db.query / execParameterized, per-user SQLite.
  • files.write / readUpload storage through the secure layer.
  • saveCredentialOS keychain — write-only.
  • nativeSetting(key)Two-way store: get / set / subscribe.
  • listPrintersWinspool / CUPS, raw ESC/POS.
  • hasBridge / isNativeDetect native host vs browser dev.
Full bridge reference →

From npm install to a shipped app

One dev dependency and a handful of commands. The prebuilt host does the native work; your Vite toolchain does the rest.

1

Install

The prebuilt host arrives as a platform‑gated optional dependency — npm installs only the one for your machine, e.g. hull‑win32‑x64.

2

Develop

hull dev renders your Vite app in a native window with HMR and a dev inspector. --browser keeps the real bridge over HTTP/SSE.

3

Ship

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.

Native on every desktop

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 viewWebView2 (Edge)WebKitWebKitGTK 6
CredentialsCredential ManagerKeychainlibsecret
PrintingWinspoolCUPSCUPS
Window iconruntime (GDI+)app bundle.desktop entry
Build host onWindowsmacOSany 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.

Ship a desktop app this afternoon

Add Hull to any Vite project and run npm run dev. Zero config — the window title and storage namespace come from your package.json.

$ npm i -D @mwguerra/hull