A Windows desktop app bootstrapper that pairs a native C++ / Win32 + WebView2 host with a modern Vite + React + TypeScript frontend. Use it as a starting template for building lightweight desktop apps whose UI is web tech but whose shell and OS integration are native C++ — without bundling a whole browser like Electron.
| Project | Tech | Role |
|---|---|---|
twilight/ |
C++ / Win32 | Native host window that embeds WebView2 and exposes a JSON message bridge |
twilight-hammer/ |
Vite + React + TypeScript | The web UI rendered inside the WebView |
- Debug: the C++ host starts the Vite dev server itself and navigates to
http://localhost:5174/. The dev server is owned by the host process (via a Win32 Job Object), so it shuts down automatically when you stop debugging. - Release: the frontend is built to a
web/folder next to the.exe, and the host loads it fromfile:///.../web/index.html. - Bridge: the UI calls
call<Req, Res>(uri, data)(seetwilight-hammer/src/containers/api.ts); the host dispatches byurito handlers registered intwilight.cpp.
Install before opening the solution:
- Visual Studio 2022/2026 with the Desktop development with C++ workload.
- Node.js (includes npm) — v18+ recommended (developed with Node 24 / npm 9+).
npmmust be on yourPATH. - WebView2 Runtime — preinstalled on current Windows 10/11. If missing, install the Evergreen runtime from Microsoft.
- Inno Setup 6 — only needed to build the installer (
publish.ps1). Expected atC:\Program Files (x86)\Inno Setup 6\ISCC.exe.
NuGet packages (WebView2, JsonCpp, WIL) are restored automatically on first build.
- Open
twilight.sln. - Set the startup project to
twilight(the C++ project): right-click it in Solution Explorer → Set as Startup Project. The C++ host now launches the dev server itself, so you do not need to starttwilight-hammerseparately. - Select a configuration:
Debug+ a platform (x64recommended). - Restore happens automatically; if the frontend ever looks stale, run
npm installonce insidetwilight-hammer/.
First build triggers
npm installand a Vite build via the project dependency, so it can take a bit longer than later builds.
- Press F5 (or Ctrl+F5). The host builds, starts Vite on port 5174, waits until it's ready, then shows the window.
- Debugging the C++ side: normal Visual Studio breakpoints.
- Debugging the TypeScript/React side: press F12 in the running app window to open the WebView DevTools (enabled in Debug builds).
- Stop debugging: this also terminates the Vite dev server automatically — no orphaned terminal.
If port 5174 is already in use, startup fails fast with a message box (by design). Change the port in twilight/dev_server.cpp (see placeholders below) if you need a different one.
- Debug build: just build/run in the IDE as above.
- Release build (no installer): switch to
Releaseand build. This producestwilight.exe, the required DLLs, and aweb/folder in the output directory — but does not create an installer.
Publishing is a separate, opt-in step so normal Release builds stay fast.
.\publish.ps1 # Release | x64 (default)
.\publish.ps1 -Platform ARM64
.\publish.ps1 -Platform Win32- The script does a clean Release rebuild for the chosen platform and runs Inno Setup.
- Output:
dist\twilight_setup_<platform>.exe. - Equivalent raw command:
msbuild twilight.sln -p:Configuration=Release -p:Platform=x64 -p:Publish=true. - Requires Inno Setup 6 (otherwise the build warns and skips the installer step).
The name "twilight" appears in two very different roles. You only need to change the user-facing branding. The structural/internal names can stay as-is; renaming them is optional and higher-effort.
These control what your users actually see (window title, exe name, installer, About box):
| What | File | Line / setting |
|---|---|---|
| Window title | twilight/twilight.rc |
IDS_APP_TITLE "twilight" |
App exe name (twilight.exe) |
twilight/twilight.vcxproj |
add <TargetName>YourApp</TargetName> (defaults to the project name today) |
| Error dialog captions | twilight/twilight.cpp |
the L"Twilight" caption in each MessageBoxW |
| HTML tab title | twilight-hammer/index.html |
<title>Twilight Hammer</title> |
| About screen title + copyright | twilight-hammer/src/App.tsx |
<Title1>Twilight Hammer</Title1>, ©…M1Knight Technology |
| Home demo heading | twilight-hammer/src/App.tsx |
Vite + React + WebView2 + C++ (optional) |
| Sample message text | twilight-hammer/src/components/SendMessage.tsx |
"Hello Twilight Hammer!" (optional) |
Installer branding — setup_script.iss:
| Setting | Purpose |
|---|---|
AppId={{...}} |
Generate a NEW GUID for your app (Tools → Create GUID). Do not reuse. |
AppName, AppVersion, AppPublisher, App*URL |
Shown in the installer/Programs list |
DefaultDirName={autopf}\Twilight |
Install folder |
DefaultGroupName=Twilight |
Start-menu group |
OutputBaseFilename=twilight_setup_{#VSPlatform} |
Installer file name |
UninstallDisplayIcon, [Icons], [Run] entries |
Reference the exe — update if you change the exe name |
If you rename the exe (
<TargetName>), also update thetwilight.exereferences insetup_script.iss([Files],[Icons],[Run],UninstallDisplayIcon) and the installer name inpublish.ps1.
These are not shown to users. Changing them is optional refactoring and risks breaking build wiring, so it's fine to keep them:
- Source folder names
twilight/andtwilight-hammer/— the folder nametwilight-hammeris hardcoded intwilight/dev_server.cppand in the.vcxprojPreBuildEvents (cd $(SolutionDir)twilight-hammer). Renaming requires updating all of those, so leave it unless you have a reason. - C++ file names
twilight.cpp,twilight.h,twilight.rc,resource.h. <RootNamespace>twilight</RootNamespace>in the.vcxproj.- Resource IDs
IDS_APP_TITLE,IDC_TWILIGHT,IDI_TWILIGHT, etc. inresource.h— these are identifiers, not text. nameinpackage.json(twilight-hammer) — internal npm package name.[twilight]debug log prefix indev_server.cpp— only visible in the debugger Output window.- Solution/project GUIDs in
twilight.sln.
Rule of thumb: change strings the user reads; keep identifiers the build relies on.
Swap twilight/twilight.ico and twilight/small.ico with your own (keep the same file names, or update the ICON references in twilight.rc).
twilight.sln Solution
publish.ps1 One-click installer build (opt-in)
setup_script.iss Inno Setup script (installer definition)
twilight/ C++ / Win32 + WebView2 host
twilight.cpp Entry point, window, WebView2 setup, bridge dispatch
dev_server.{h,cpp} Debug-only Vite dev server lifecycle (Job Object)
handlers.h, hello.cpp, file.cpp Bridge request handlers
twilight.rc, resource.h Win32 resources (title, icons)
twilight-hammer/ Vite + React + TypeScript frontend
src/containers/api.ts Frontend side of the JSON bridge (call<Req,Res>)
src/App.tsx UI (Home / Example / About)
- Add a handler in the C++ host (see
hello.cpp/file.cppfor the pattern) and register it inRegisterHandlers()intwilight.cppunder a newuri. - Call it from the frontend:
call<RequestType, ResponseType>("/your/uri", payload). - Synchronous handlers return immediately; asynchronous ones post their result back later (see the file-open example).