This repository contains the source code for the VpnHood! WebUI, built with Vue 3 and Vite.
To get the project up and running, follow these steps.
You'll need Node.js (v16+) installed on your system. You can download it from the official Node.js website.
- Clone the repository:
git clone https://github.com/vpnhood/VpnHood.Client.WebUI.git
- Install dependencies:
npm install
The WebUI needs to know where to find the VpnHood API. The .env.development file already exists in the project.
Open it and configure the VITE_API_BASE_URL to match your environment.
| Environment | VpnHood! Client | VpnHood! Connect |
|---|---|---|
| Windows | VITE_API_BASE_URL=http://my-vpnhood:9571 |
VITE_API_BASE_URL=http://my-vpnhood-connect:9571 |
| Android | VITE_API_BASE_URL=http://<YOUR_DEVICE-IP>:9581 |
VITE_API_BASE_URL=http://<YOUR_DEVICE-IP>:9571 |
💡 Important:
- Replace
<YOUR_DEVICE-IP>with the IP address of the device hosting the VpnHood app.- If you are connecting from a separate device, the VpnHood app must be running in debug configuration.
To start a development server with hot-reloading:
npm run devvite serveTo build a production-ready version:
npm run buildvite buildThis command generates the compiled files in the dist directory.
After building the project, you'll need to package the output for use with the VpnHood app.
- Navigate to the
distdirectory. - Compress the contents of the
distdirectory (not the folder itself) into a zip file namedSPA.zip. - Copy
SPA.zipto the target project directory.
dist/assets is not Vite's output (that goes to dist/bundle): it is the folder of files the app
loads at runtime, by name — and the native (Avalonia) UI, which the app can show instead of
this SPA, reads the very same files from the same folder once the app has extracted the bundle.
Nothing in it is hashed or inlined, so a name is a contract between the two UIs:
| Path | Source | Loaded by |
|---|---|---|
assets/images/* |
src/assets/images |
Util.getAssetPath(name), the few url()s in styles |
assets/flags/<code>.png |
src/assets/flags |
VpnHoodApp.getCountryFlag(code) |
assets/fonts/*.ttf |
src/assets/fonts |
the @font-face rules in src/assets/styles |
assets/fonts/MaterialDesignIcons.ttf |
generated by build/icon-font-plugin.ts |
virtual:vh-icon-font.css |
assets/locales/<code>.json |
src/locales (the translator's) |
loadLocale(code) |
assets/content/<lang>/*.md |
src/content (the translator's) |
the native UI only; this SPA compiles them |
build/assets-folder-plugin.ts copies those folders into the bundle verbatim, serves them from
src/ while vite dev runs, and — because a name here is not an import the bundler would
resolve — checks at build time that every name either UI asks for is a file that exists: the
/assets/… URLs and bare file names in src, and the native UI's own names in
build/native-ui-images.txt. A typo or a rename fails the build, naming the file that wrote it.
The icon font is a subset of @mdi/font: the icons named in src and by Vuetify, plus the ones
the native UI names in build/native-ui-icons.txt. A name that is not an icon fails the build.
Both native-UI lists are written from the C# by _sync-native-assets.ps1 in that project.
Fonts stay ttf — the one format a WebView and Avalonia both read; woff2 would only add a
second copy.