/tinywasm

tinywasm is a lightweight toolbox designed to streamline the compilation of Go packages to WebAssembly (WASM) using the TinyGo compiler.

Primary LanguageGoMIT LicenseMIT

tinywasm

Project Badges

Go package for intelligent WebAssembly compilation with automatic file detection and 3-mode compiler system.

Features

  • 3-Mode Compiler System: Large ("L"), Medium ("M"), Small ("S")
  • DevTUI Integration: FieldHandler interface for interactive mode switching
  • Smart file detection via prefixes (frontend/backend separation)
  • Triple compiler support: Go standard (fast dev), TinyGo debug (-opt=1), TinyGo production (-opt=z)
  • VS Code auto-configuration for WASM development
  • Dual Output Architecture: WASM binaries in src/web/public/, watchable JS in src/web/ui/js/

Quick Start

// Basic usage
config := tinywasm.NewConfig() // Pre-configured with defaults
config.SourceDir = "src/cmd/webclient"
config.OutputDir = "src/web/public"
config.WasmExecJsOutputDir = "src/web/ui/js"

tw := tinywasm.New(config)
tw.NewFileEvent("src/cmd/webclient/main.go", ".go", "src/cmd/webclient/main.go", "write")

// DevTUI Integration - 3 Mode System
fmt.Println("Current mode:", tw.Value()) // "L" (coding / large build)

// New API: Change now reports progress via a channel instead of returning (msg, err).
// Create and consume a progress channel before calling Change to avoid blocking.
progress := make(chan string)
go func() {
	for p := range progress {
		fmt.Println("Status:", p)
	}
}()
tw.Change("M", progress) // Switch to medium (debug) mode; messages arrive on the progress channel

// Advanced configuration
config := &tinywasm.Config{
    AppRootDir:          "/path/to/project",
    SourceDir:           "src/cmd/webclient",
    OutputDir:           "src/web/public",
    WasmExecJsOutputDir: "src/web/ui/js",
    MainInputFile:       "main.go",
    OutputName:          "main",
	BuildLargeSizeShortcut:   "L",  // Customizable shortcuts (default: L = Large/fast with go)
	BuildMediumSizeShortcut:    "M",
	BuildSmallSizeShortcut: "S",
    Logger:              logger,
}

DevTUI FieldHandler Interface

TinyWasm implements the DevTUI FieldHandler interface for interactive development:

// DevTUI Integration
label := tw.Label()           // "Compiler Mode"
current := tw.Value()         // Current mode shortcut ("L", "M", "S")
canEdit := tw.Editable()      // true
timeout := tw.Timeout()       // 0 (no timeout)

// Interactive mode change with the new Change API
// Change now has signature: Change(newValue string, progress chan<- string)
// All validation messages, warnings (e.g. auto-compilation failed) and success
// messages are sent through the provided channel. The channel is closed when
// the operation completes.
progress := make(chan string)
go func() {
	for p := range progress {
		// Handle progress messages (show in TUI, log, etc.)
		fmt.Println("Progress:", p)
	}
}()
tw.Change("M", progress)
// No return values; read errors and status from the progress channel above.

VS Code Integration

Auto-creates .vscode/settings.json with WASM environment:

{"gopls": {"env": {"GOOS": "js", "GOARCH": "wasm"}}}

API

Core:

  • New(config *Config) *TinyWasm
  • NewConfig() *Config - Pre-configured with sensible defaults
  • NewFileEvent(fileName, ext, path, event string) error
  • ShouldCompileToWasm(fileName, path string) bool

DevTUI FieldHandler Interface:

  • Label() string - Returns "Compiler Mode"
  • Value() string - Current mode shortcut ("L", "M", "S")
  • Editable() bool - Returns true (field is editable)
  • Change(newValue string, progress chan<- string) - Switch compiler mode and report progress via the provided channel. Validation errors, auto-compilation warnings and success messages are sent to the channel; the implemention closes the channel when finished.
  • Timeout() time.Duration - Returns 0 (no timeout)

Legacy Compiler Methods (deprecated):

  • TinyGoCompiler() bool - Use Value() instead
  • SetTinyGoCompiler(bool) error - Use Change() instead
  • VerifyTinyGoInstallation() error

Utils:

  • MainInputFileRelativePath() string
  • UnobservedFiles() []string
  • JavascriptForInitializing() (string, error)

Config

type Config struct {
	AppRootDir          string        // application root directory (absolute), defaults to "."
	SourceDir           string        // directory containing Go source (relative) eg: "src/cmd/webclient"
	OutputDir           string        // directory for WASM binary output (relative) eg: "src/web/public"
	WasmExecJsOutputDir string        // directory for watchable JS runtime (relative) eg: "src/web/ui/js"
	MainInputFile       string        // main input file for WASM compilation (default: "main.go")
	OutputName          string        // output name for WASM file (default: "main")
	Logger              func(message ...any) // For logging output to external systems (e.g., TUI, console)

	// NEW: Shortcut configuration (default: "f", "b", "m")
	BuildLargeSizeShortcut    string // "L" (large/fast) compile fast with go
	BuildMediumSizeShortcut     string // "M" (medium/debug) compile with tinygo debug
	BuildSmallSizeShortcut string // "S" (small/minimal) compile with tinygo minimal binary size

	// gobuild integration fields
	Callback           func(error)     // Optional callback for async compilation
	CompilingArguments func() []string // Build arguments for compilation (e.g., ldflags)

	// DisableWasmExecJsOutput prevents automatic creation of wasm_exec.js file
	// Useful when embedding wasm_exec.js content inline (e.g., Cloudflare Pages Advanced Mode)
	DisableWasmExecJsOutput bool
}

// Pre-configured constructor (recommended)
func NewConfig() *Config

Dual Output Architecture

TinyWasm produces two types of outputs that serve different purposes in the build pipeline:

1. WASM Binary Output (OutputDir)

  • Location: src/web/public/main.wasm
  • Purpose: Final compiled WebAssembly binary loaded by the browser
  • Consumed by: Browser at runtime
  • Modes: All three compilation modes produce output here

2. Watchable JavaScript Output (WasmExecJsOutputDir)

  • Location: src/web/ui/js/wasm_exec.js
  • Purpose: Mode-specific JavaScript runtime that:
    • Informs external tools about the current compilation mode (Go vs TinyGo)
    • Triggers file watchers to reload the browser when mode changes
    • Gets compiled together with other JavaScript by external asset bundlers
  • Consumed by: File watchers (e.g., devwatch) and asset bundlers (e.g., assetmin)
  • Important: TinyWasm's only responsibility is writing the correct wasm_exec.js according to the active mode. External tools handle final bundling.

Why Two Separate Directories?

  1. Separation of Concerns: Runtime assets vs. build-time integration
  2. Build Pipeline Integration: File watchers track wasm_exec.js changes
  3. No Dev/Prod States: All modes use the same directories
  4. Mode Transparency: External tools detect mode changes via wasm_exec.js

Mode Switching

// Example usage with the new channel-based Change API:
progress := make(chan string)
go func() {
	for p := range progress {
		fmt.Println(p)
	}
}()
tw.Change("S", progress) // production mode with TinyGo -opt=z

// Repeat for other modes as needed (always provide and consume a progress channel)
// tw.Change("M", progress)
// tw.Change("L", progress)

Requirements

  • Go 1.20+
  • TinyGo (optional, required for debug/production modes)
  • DevTUI (optional, for interactive development)

Benefits:

  • 🎯 3 optimized modes instead of binary choice
  • 🔧 DevTUI integration for interactive development
  • 📦 Smaller debug builds with TinyGo -opt=1
  • Auto-recompilation on mode switch
  • 🛠️ Better error handling with validation
  • 🏗️ Dual output architecture for better build pipeline integration