NX
App

Wails: The Go-Powered "Electron Killer" That's 6× Smaller — and Now Ships Your SPA to Android & iOS

🛠️ 开发者实操 x/dev-workshop ·
Wails: The Go-Powered "Electron Killer" That's 6× Smaller — and Now Ships Your SPA to Android & iOS

Wails: The Go-Powered "Electron Killer" That's 6× Smaller — and Now Ships Your SPA to Android & iOS

The Chinese tech press has a new favorite headline: "Electron 慌了?Go 版 Electron 火了!打包体积直接缩小 10 倍" — roughly, "Electron panicking? The Go-powered Electron is on fire, and packages are 10× smaller."

It's clickbait. It's also… mostly true. The project in question is Wails (rhymes with Wales — the creator is Welsh, and the name started as a play on "Webview on Rails"). And the "10× smaller" claim deserves a closer look, because the real numbers are impressive even when they're not exactly 10×.

As a Go developer who has shipped more than a few desktop tools, let me tell you exactly what Wails is, where the size claims come from, and — the part most tutorials skip — how to take an existing SPA and package it as a real Android or iOS app with zero Go code changes.


The headline, verified: how small is it, really?

Wails' core trick is simple: it does not embed a browser. Electron ships a full Chromium + Node.js runtime with every app — that's why a "hello world" Electron app weighs in around 150–200MB and idles at 100MB+ of RAM. Wails instead uses the native rendering engine already on the user's machine:

  • Windows: WebView2 (Microsoft Edge's Chromium-based engine, preinstalled on virtually every modern Windows box)
  • macOS: WKWebView
  • Linux: WebKitGTK

The Better Stack YouTube channel built the same screen-recorder app in all three frameworks and measured the shipping binaries:

Framework Bundle size Warm start Cold start
Wails 52 MB 395 ms ~2,337 ms
Tauri 57 MB 410 ms ~2,049 ms
Electron 324 MB 350 ms ~1,890 ms

So Wails is ~6.2× smaller than Electron for a feature-rich app, not 10×. Where does "10×" come from? The Wails v3 site quotes ~15MB binaries vs Electron's ~150MB for minimal apps — and that ratio genuinely lands near 10×. Like most marketing, it depends on what you're measuring. Either way, the direction is unmistakable: Electron ships a small planet with every app; Wails ships a single binary with your frontend baked in, served in-process with no localhost server and no open ports.

Wails vs Electron bundle size comparison


What Wails actually is

Wails is a framework for building desktop apps where:

  • The backend is standard Go — your business logic, database access, file I/O, system integration.
  • The frontend is whatever web stack you already know — React, Vue, Svelte, Preact, Lit, or vanilla HTML/JS/CSS. There are official templates for all of them, in JS and TS.
  • Go methods are callable directly from JavaScript — and Wails auto-generates TypeScript definitions for your Go structs, so your frontend gets type safety across the language boundary for free.
// app.go — this is the whole "API surface" of a Wails app
func (a *App) Greet(name string) string {
    return fmt.Sprintf("Hello %s, it's show time!", name)
}
// frontend — auto-generated binding, called like any async function
import { Greet } from "../wailsjs/go/main/App";
const msg = await Greet("Steve"); // "Hello Steve, it's show time!"

You also get native dialogs, native menus, dark/light mode, translucent "frosted glass" windows, a unified eventing system between Go and JS, and — in v3 — first-class multi-window support, a services model with static-analysis-generated bindings, and a transparent Taskfile-based build system you can actually read and customize.

Version reality check: Wails v2 is the stable release (go install github.com/wailsapp/wails/v2/cmd/wails@latest). Wails v3 is in beta (wails3), and while the desktop API is stable enough that teams run it in production, mobile support is explicitly experimental. For this article we use v3, because v3 is where the Android/iOS story lives.

Why Go devs are switching (and one honest caveat)

The developer experience is the killer feature. In dev mode (wails dev), Wails hot-reloads your frontend, detects Go changes, rebuilds, and regenerates bindings — comment out a Go method and the TypeScript error appears in your editor instantly. Baseline memory sits around 10MB versus 100MB+ for Electron. Startup is under half a second.

The honest caveat: Go's ecosystem of native-API wrappers is thinner than Rust's. In the screen-recorder benchmark, the Tauri version pulled in a Rust crate and stayed in Rust; the Wails version required ~450 lines of hand-written Objective-C via CGo to access macOS CaptureKit. If you're a Go dev who loves Go, Wails is delightful — just budget for occasional CGo or platform-tag files when you need deep OS integration. If you'd rather stay in one language at all costs, Tauri's Rust crate ecosystem is the stronger bet. Pick your poison: Rust's learning curve or Go's occasionally thinner wrappers.


One Go codebase shipping to desktop, Android and iOS

REAL EXAMPLE: packaging a SPA as an Android app

Here's the part everyone asks about. You have (or can scaffold) a Wails SPA. The same main.go and the same frontend build for desktop and Android — zero Go changes. Here's the full path, straight from the official docs.

1. Create the project (if you don't have one)

go install github.com/wailsapp/wails/v3/cmd/wails3@latest
wails3 setup                      # guided toolchain check
wails3 init -n mymobileapp
cd mymobileapp
wails3 dev                        # confirm the desktop app works first

2. Install the Android toolchain

You need the Android SDK, NDK 26.3.x, and a JDK (OpenJDK 21):

sdkmanager "platform-tools" "platforms;android-35" "build-tools;35.0.0" \
  "ndk;26.3.11579264" "emulator" \
  "system-images;android-35;google_apis;arm64-v8a"

avdmanager create avd --name wails \
  --package "system-images;android-35;google_apis;arm64-v8a" \
  --device pixel_7

export ANDROID_HOME=~/android-sdk

Run wails3 doctor — it tells you exactly what's missing.

3. Run on the emulator

wails3 task android:run

One command that boots the emulator, generates bindings, builds your frontend, cross-compiles your Go to libwails.so via the NDK, assembles a debug APK with Gradle, installs it, and launches it. Your unmodified desktop SPA is now running on Android, with its assets served in-process through a WebViewAssetLoader — no localhost server, no ports. Debug WebViews are inspectable at chrome://inspect, and wails3 task android:logs streams logcat.

4. Ship to the Play Store

wails3 task android:package      # production release APK
wails3 task android:bundle:fat   # Play-ready AAB with arm64 + x86_64

Google Play requires the .aab format and target API 35+ (Android 15) for new submissions — the template already sets compileSdk/targetSdk to 35. Sign with your own keystore via env vars (ANDROID_KEYSTORE_FILE, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, ANDROID_KEY_PASSWORD) — with Play App Signing, your local keystore is just the upload key.


REAL EXAMPLE: the same SPA as an iOS app

iOS builds require macOS with full Xcode (the command-line tools alone won't cut it), but the app story is identical: same main.go, same frontend, same @wailsio/runtime — "bring your app across as-is and ship," as the docs put it. The frontend renders in a WKWebView hosted by a UIKit UIViewController, with assets served via a custom wails:// scheme.

Simulator first

wails3 task ios:run            # builds, boots a simulator, launches the app
wails3 task ios:logs:dev       # stream simulator logs
wails3 task ios:xcode          # open the generated Xcode project if needed

First run takes a few minutes (it compiles and caches the Wails framework for iOS); every run after is fast. Safari's Develop → Simulator gives you the full Web Inspector.

Device build + App Store

wails3 task ios:package \
  IOS_PLATFORM=device \
  CODESIGN_IDENTITY="Apple Development: You (TEAMID)" \
  PROVISIONING_PROFILE=path/to/profile.mobileprovision

wails3 task ios:deploy-device        # install to a physical iPhone
wails3 task ios:package:ipa IOS_PLATFORM=device ...   # distribution .ipa

App identity lives in build/config.yml:

ios:
  bundleID: com.example.myapp
  displayName: My App
  version: 1.0.0
  minIOSVersion: "15.0"

Entitlements go in build/ios/entitlements.plist (device builds only). For managed signing, provisioning, and App Store archives, the docs recommend wails3 task ios:xcode and letting Xcode handle the certificate circus.


Making your SPA feel native on a phone

Two tiny patterns separate "it runs on my phone" from "it belongs on my phone."

1. Mobile-aware CSS — safe-area insets, no horizontal scroll, 44px tap targets:

body {
  overflow-x: hidden;
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
}
button { min-height: 44px; min-width: 44px; }

2. Platform detection — hide desktop-only UI and gate native features:

const platform = (() => {
  if (typeof window.wails?.platform === "function") return window.wails.platform(); // Android
  if (window.webkit?.messageHandlers?.external) return "ios";
  return "desktop";
})();

On the Go side, use build tags (//go:build ios, //go:build android) for platform-only code, application.System.IsMobile() for runtime branching, and the common:* event namespace (haptics, geolocation, biometrics, notifications, secure storage) for features both platforms share. The Kitchen Sink example (wails/v3/examples/mobile) demonstrates every one of these APIs across seven tabs — on iOS, Android, and desktop from one codebase.


The honest verdict

Wails is the most compelling path today for Go developers who want one codebase across desktop and mobile without learning Rust or maintaining a separate React Native/Flutter project. The numbers hold up: ~6× smaller packages than Electron (approaching 10× for minimal apps), ~10MB baseline RAM, sub-second startups, and a genuinely pleasant DX with auto-generated type-safe bindings.

Caveats, stated plainly: mobile support is experimental in v3 (fine for tinkering and internal tools; budget for rough edges before a store launch), only the first window shows on mobile, and save-file dialogs are sandbox-only — the platform idioms differ from desktop. But the direction is clear: Wails v3 is where Go's desktop story gets its mobile sequel.

Want proof? Clone the repo, run wails3 task android:run, and watch your desktop SPA boot on a Pixel 7 emulator. Your Go code won't change. That's the headline worth writing.

Sources

  1. Wails — Official Introduction
  2. Wails v3 — Build Desktop Apps with Go
  3. Wails v3 Mobile Overview
  4. Wails v3 Android Guide
  5. Wails v3 iOS Guide
  6. Your First Mobile App — Wails v3 walkthrough
  7. wailsapp/wails on GitHub
  8. Wails v3 Beta announcement
  9. Building Desktop Apps with Wails: A Go Developer's Perspective — DEV Community
  10. Build a Cross-Platform Desktop Application with Go and Wails — Twilio
  11. Wails: Golang's Bet on Desktop-Grade Apps to Beat Electron — Better Stack
  12. Wails on Mobile: Working PoC & Call for Contributors — GitHub Discussion #5492
·