202 lines
6.9 KiB
Text
202 lines
6.9 KiB
Text
---
|
||
title: Asset Server
|
||
description: How Wails v3 serves and embeds your web assets in development and production
|
||
sidebar:
|
||
order: 4
|
||
---
|
||
|
||
## Overview
|
||
|
||
Every Wails application ships a **single native executable** that combines:
|
||
|
||
1. Your *Go* backend
|
||
2. A *Web* frontend (HTML + JS + CSS)
|
||
|
||
The **Asset Server** is the glue that makes this possible.
|
||
It has **two operating modes** that are selected at compile-time via Go build
|
||
tags:
|
||
|
||
| Mode | Tag | Purpose |
|
||
|------|-----|---------|
|
||
| **Development** | `//go:build dev` | Fast iteration with hot-reload |
|
||
| **Production** | `//go:build !dev` | Zero-dependency, embedded assets |
|
||
|
||
The implementation lives in
|
||
`v3/internal/assetserver/` with clear file splits:
|
||
|
||
```
|
||
assetserver_dev.go # ⬅️ runtime dev server
|
||
assetserver_production.go # ⬅️ embedded server
|
||
assetserver_darwin.go # OS-specific helpers (same for linux/windows)
|
||
asset_fileserver.go # Shared static file logic
|
||
content_type_sniffer.go # MIME type detection
|
||
ringqueue.go # Tiny LRU for mime cache
|
||
```
|
||
|
||
---
|
||
|
||
## Development Mode
|
||
|
||
### Lifecycle
|
||
|
||
1. `wails3 dev` boots and **spawns your frontend dev server**
|
||
(Vite, SvelteKit, React-SWC …) by running the task defined in
|
||
`build/Taskfile.yml` (usually `npm run dev`).
|
||
2. Wails starts the **Dev Asset Server** listening on `localhost:<random>` and
|
||
tells the Go runtime to load `http://<host>:<VITE_PORT>` as the window URL.
|
||
3. Incoming requests are handled by `assetserver_dev.go`:
|
||
|
||
```
|
||
┌─────────┐ /runtime/... ┌─────────────┐
|
||
│ Browser │ ── native bridge ───▶ │ Runtime │
|
||
├─────────┤ └─────────────┘
|
||
│ JS │ / (index.html) proxy / -> Vite
|
||
└─────────┘ ◀─────────────┐
|
||
AssetServer │
|
||
▼
|
||
┌────────────┐
|
||
│ Vite Dev │
|
||
│ Server │
|
||
└────────────┘
|
||
```
|
||
|
||
4. Static files (`/assets/logo.svg`) are **served directly from disk** via
|
||
`asset_fileserver.go` (for speed) while anything unknown is **proxied** to
|
||
the framework dev server, giving you *instant* hot-module replacement.
|
||
|
||
### Features
|
||
|
||
* **Live Reload** – Vite/Snowpack/… injects HMR WebSocket; Wails only has to
|
||
proxy it.
|
||
* **Source Map Support** – because assets are not bundled, your browser devtools
|
||
map errors back to original source.
|
||
* **No Go Re-compile** – Only the frontend rebuilds; Go code stays running until
|
||
you change `.go` files.
|
||
|
||
### Switching Frameworks
|
||
|
||
The dev proxy is **framework-agnostic**:
|
||
|
||
* The root `Taskfile.yml` task file injects two env vars:
|
||
`APP_NAME` & `WAILS_VITE_PORT`.
|
||
* Taskfiles for each template emit those vars before running their dev servers.
|
||
* `assetserver_dev.go` simply proxies to that target.
|
||
|
||
Add a new template → define its dev task → Asset Server just works.
|
||
|
||
---
|
||
|
||
## Production Mode
|
||
|
||
When you run `wails3 build` the pipeline:
|
||
|
||
1. Runs the frontend **production build** (`npm run build`) producing
|
||
`/frontend/dist/**`.
|
||
2. **Embeds** that folder into `go:embed` FS at compile time (see
|
||
`bundled_assetserver.go` generated file).
|
||
3. Compiles the Go binary with `-tags production` (implicit).
|
||
|
||
### Request Handling
|
||
|
||
```go
|
||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||
// 1. Try embedded static assets (exact path)
|
||
// 2. Fallback to index.html for SPA routing
|
||
// 3. Sniff content-type if extension unknown
|
||
// 4. Set strong cache headers
|
||
}
|
||
```
|
||
|
||
* **MIME Detection** – If the build tool produced extension-less files (e.g.
|
||
`/assets/manifest`) `content_type_sniffer.go` inspects the first 512 bytes and
|
||
caches the result in a tiny lock-free LRU.
|
||
* **Ring Queue Caching** – Frequently accessed assets (logo, CSS) are kept
|
||
in-memory for the lifetime of the app, removing the need for disk or embed FS
|
||
lookups.
|
||
* **Security Headers** – Disallows `file://` navigation, enables `nosniff`.
|
||
|
||
Because everything is embedded, the shipped binary has **no external
|
||
dependencies** (even on Windows).
|
||
|
||
---
|
||
|
||
## Bridging Dev ↔ Prod
|
||
|
||
Both modes expose the **same public interface**:
|
||
|
||
```go
|
||
type AssetServer interface {
|
||
URL() string // dev: http://localhost:34115, prod: wails://app
|
||
Open() error // start listening
|
||
Close() // graceful shutdown
|
||
}
|
||
```
|
||
|
||
`pkg/application` happily uses whichever implementation was compiled in, meaning
|
||
**your application code does not change** between `dev` and `build`.
|
||
|
||
---
|
||
|
||
## How Frontend Frameworks Integrate
|
||
|
||
### Templates
|
||
|
||
Each official template (React, Vue, Svelte, Solid…) contains:
|
||
|
||
* `build/Taskfile.yml`
|
||
* `frontend/vite.config.ts` (or equivalent)
|
||
|
||
They export several tasks including:
|
||
|
||
| Task | Purpose |
|
||
|------|---------|
|
||
| `dev:frontend` | Starts the framework dev server on a **random free port** and prints it to stdout (`PORT=5173`). |
|
||
| `build:frontend` | Produces static assets into `dist/` + manifest for cache-busting. |
|
||
|
||
`internal/commands/dev.go` sets the `FRONTEND_DEVSERVER_URL` env var using the hard-coded `localhost` as the `host` and the emitted `VITE_PORT` env var as the `port` and launches the **Dev Asset Server**.
|
||
|
||
Frameworks remain fully decoupled from Go:
|
||
|
||
* No need to import Wails JS SDK at build time – the runtime injects it at
|
||
window creation.
|
||
* Any framework with an HTTP dev server can plug in.
|
||
|
||
---
|
||
|
||
## Extending / Customising
|
||
|
||
Need custom headers, auth, or gzip?
|
||
|
||
1. Implement `type Middleware func(http.Handler) http.Handler`
|
||
2. Register via `internal/assetserver/options.go`
|
||
3. For prod, remember to add the same middleware in `assetserver_production.go`.
|
||
|
||
---
|
||
|
||
## Key Source Files
|
||
|
||
| File | Role |
|
||
|------|------|
|
||
| `assetserver_dev.go` | Reverse proxy + disk file server |
|
||
| `assetserver_production.go` | Embedded FS handler |
|
||
| `options.go` | Config struct parsed from `pkg/options/assetserver` |
|
||
| `build_dev.go` / `build_production.go` | Build-tag wrappers selecting correct implementation |
|
||
| `bundled_assetserver.go` | Generated embed data (only present in production builds) |
|
||
|
||
---
|
||
|
||
## Gotchas & Debugging
|
||
|
||
* **White Screen in Prod** – usually SPA routing: ensure `History API Fallback`
|
||
is enabled in dev and `index.html` fallback works in prod.
|
||
* **404 in Dev** – mis-matched `FRONTEND_DEV_PORT`; run with
|
||
`WAILSDEV_VERBOSE=1` to print every proxied request.
|
||
* **Large Assets** – they are embedded; consider
|
||
[`assetserver.WithExternalDir("/path")`](https://pkg.go.dev) to load from disk.
|
||
|
||
---
|
||
|
||
You now know how the Wails **Asset Server** feeds your web code to the native
|
||
window in both **development** and **production**.
|
||
Master this layer and you can debug loading issues, add middlewares, or even
|
||
swap in a completely different frontend tool-chain with confidence.
|