Go WebAssembly: Building High-Performance Web Applications

57 min read 11595 words

Table of Contents

In the evolving landscape of web development, performance has become a critical differentiator. Users expect desktop-like responsiveness from web applications, and developers are constantly seeking technologies that can deliver this experience. WebAssembly (WASM) represents a paradigm shift in web performance, offering near-native execution speeds within the browser’s sandbox. When combined with Go’s efficiency and developer-friendly features, WebAssembly creates a powerful platform for building high-performance web applications that were previously unattainable with JavaScript alone.

Go’s support for WebAssembly compilation has matured significantly, enabling developers to leverage Go’s strengths—strong typing, efficient concurrency, and excellent performance characteristics—in browser environments. This capability opens new possibilities for web applications that require intensive computation, complex data processing, or high-performance graphics, all while maintaining Go’s clean syntax and robust error handling.

This comprehensive guide explores the intersection of Go and WebAssembly, providing a deep dive into compilation processes, application architecture, JavaScript interoperability, and optimization techniques. Whether you’re building data visualization tools, browser-based games, or computation-heavy web applications, understanding how to effectively use Go with WebAssembly will expand your toolkit for delivering exceptional web experiences.


Understanding Go WebAssembly Compilation

WebAssembly represents a fundamental shift in how code executes in browsers. Unlike JavaScript, which is interpreted or JIT-compiled at runtime, WebAssembly is a binary instruction format designed as a compilation target for languages like Go. Understanding this compilation process is essential for effective WASM development.

The WebAssembly Compilation Pipeline

Go’s WebAssembly support involves a sophisticated compilation pipeline that transforms Go code into WASM binaries:

package main

import (
	"fmt"
)

// Simple function to demonstrate compilation
func add(a, b int) int {
	return a + b
}

func main() {
	result := add(5, 7)
	fmt.Println("5 + 7 =", result)
}

To compile this Go code to WebAssembly:

GOOS=js GOARCH=wasm go build -o main.wasm main.go

This command sets the target operating system to “js” and architecture to “wasm”, instructing the Go compiler to produce WebAssembly output instead of native machine code.

WebAssembly Module Structure

A compiled WebAssembly module has a specific binary structure that’s important to understand:

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

// WasmModuleHeader represents a simplified view of a WASM module header
type WasmModuleHeader struct {
	MagicNumber uint32 // Always 0x6d736100 (ASCII for "\0asm")
	Version     uint32 // Current version is 1
	// Followed by sections...
}

// WasmSection represents a section in a WASM module
type WasmSection struct {
	ID      byte
	Size    uint32
	Content []byte
}

// SectionNames maps section IDs to their names
var SectionNames = map[byte]string{
	0:  "Custom",
	1:  "Type",
	2:  "Import",
	3:  "Function",
	4:  "Table",
	5:  "Memory",
	6:  "Global",
	7:  "Export",
	8:  "Start",
	9:  "Element",
	10: "Code",
	11: "Data",
}

// Simplified demonstration of WASM structure analysis
func analyzeWasmStructure(wasmBytes []byte) {
	// Check magic number and version
	if len(wasmBytes) < 8 {
		fmt.Println("Invalid WASM binary: too short")
		return
	}
	
	header := (*WasmModuleHeader)(unsafe.Pointer(&wasmBytes[0]))
	fmt.Printf("Magic number: 0x%x\n", header.MagicNumber)
	fmt.Printf("Version: %d\n", header.Version)
	
	// In a real implementation, we would parse the sections here
	fmt.Println("WASM binary contains these sections:")
	
	// This is simplified - actual parsing would involve proper decoding
	offset := 8 // Skip header
	for offset < len(wasmBytes) {
		if offset+1 >= len(wasmBytes) {
			break
		}
		
		sectionID := wasmBytes[offset]
		offset++
		
		// Read section size (simplified - real implementation would use LEB128)
		size := uint32(wasmBytes[offset])
		offset++
		
		sectionName := "Unknown"
		if name, ok := SectionNames[sectionID]; ok {
			sectionName = name
		}
		
		fmt.Printf("- Section %d (%s): %d bytes\n", sectionID, sectionName, size)
		
		// Skip section content
		offset += int(size)
	}
}

// Note: This is a simplified demonstration and wouldn't work on actual WASM binaries
// without proper LEB128 decoding and section parsing

Go’s Runtime in WebAssembly

When compiling Go to WebAssembly, the Go runtime is included in the binary, which has important implications:

package main

import (
	"fmt"
	"runtime"
	"unsafe"
)

func main() {
	// Display Go runtime information in WASM environment
	fmt.Println("Go Version:", runtime.Version())
	fmt.Println("GOOS:", runtime.GOOS)
	fmt.Println("GOARCH:", runtime.GOARCH)
	fmt.Println("NumCPU:", runtime.NumCPU())
	fmt.Println("Compiler:", runtime.Compiler)
	
	// Memory model information
	var x int
	fmt.Printf("Size of int: %d bytes\n", unsafe.Sizeof(x))
	
	// Garbage collection still works in WASM
	memStats := runtime.MemStats{}
	runtime.ReadMemStats(&memStats)
	fmt.Printf("Allocated memory: %d bytes\n", memStats.Alloc)
	fmt.Printf("Total memory allocated: %d bytes\n", memStats.TotalAlloc)
	fmt.Printf("System memory: %d bytes\n", memStats.Sys)
	fmt.Printf("Number of GC cycles: %d\n", memStats.NumGC)
}

When executed in a browser, this code reveals that the Go runtime is fully functional within the WebAssembly environment, including garbage collection, which is a significant advantage over other languages that compile to WebAssembly.

Size Optimization Techniques

One challenge with Go WebAssembly is the size of the resulting binaries. Here are techniques to reduce binary size:

package main

import (
	"fmt"
)

// TinyGoExample demonstrates code that would be ideal for TinyGo compilation
// TinyGo produces much smaller WASM binaries than standard Go
func TinyGoExample() {
	fmt.Println("Hello from TinyGo!")
}

func main() {
	TinyGoExample()
	
	// Size optimization techniques:
	// 1. Use TinyGo for smaller binaries:
	//    tinygo build -o main.wasm -target wasm main.go
	
	// 2. Use build tags to exclude unnecessary packages
	
	// 3. Use the -ldflags="-s -w" option to strip debug information:
	//    GOOS=js GOARCH=wasm go build -ldflags="-s -w" -o main.wasm main.go
	
	// 4. Use wasm-opt from the Binaryen toolkit for post-compilation optimization:
	//    wasm-opt -Oz main.wasm -o optimized.wasm
	
	fmt.Println("Size optimization techniques for Go WASM:")
	fmt.Println("1. Standard Go WASM binary: ~2-5MB")
	fmt.Println("2. With stripped debug symbols: ~1-3MB")
	fmt.Println("3. TinyGo WASM binary: ~80-600KB")
	fmt.Println("4. TinyGo + wasm-opt: ~70-500KB")
}

TinyGo is particularly valuable for WebAssembly development as it produces significantly smaller binaries, though with some feature limitations compared to standard Go.


Building Your First Go WASM Application

Let’s walk through the process of creating a complete Go WebAssembly application, from compilation to browser integration.

Setting Up the Development Environment

First, we need to set up our development environment:

package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
)

// setupWasmDevelopment demonstrates the setup process for Go WASM development
func setupWasmDevelopment() {
	// Step 1: Verify Go installation
	cmd := exec.Command("go", "version")
	output, err := cmd.Output()
	if err != nil {
		fmt.Println("Error: Go is not installed or not in PATH")
		return
	}
	fmt.Printf("Using %s\n", output)
	
	// Step 2: Create project structure
	projectStructure := `
project/
├── main.go         # Go source code
├── index.html      # HTML entry point
├── wasm_exec.js    # Go's WebAssembly support JS
└── server.go       # Simple HTTP server for testing
`
	fmt.Println("Create project structure:")
	fmt.Println(projectStructure)
	
	// Step 3: Copy wasm_exec.js from Go installation
	goRoot := os.Getenv("GOROOT")
	if goRoot == "" {
		fmt.Println("GOROOT environment variable not set")
		return
	}
	
	wasmExecPath := filepath.Join(goRoot, "misc", "wasm", "wasm_exec.js")
	fmt.Printf("Copy %s to your project directory\n", wasmExecPath)
	
	// Step 4: Create a simple HTTP server for testing
	serverCode := `
package main

import (
	"flag"
	"log"
	"net/http"
)

func main() {
	port := flag.String("port", "8080", "Port to serve on")
	dir := flag.String("dir", ".", "Directory to serve")
	flag.Parse()

	fs := http.FileServer(http.Dir(*dir))
	http.Handle("/", fs)

	log.Printf("Serving %s on HTTP port: %s\n", *dir, *port)
	log.Fatal(http.ListenAndServe(":"+*port, nil))
}
`
	fmt.Println("Create server.go with this content:")
	fmt.Println(serverCode)
	
	// Step 5: Compilation command
	fmt.Println("\nCompile your Go code to WebAssembly:")
	fmt.Println("GOOS=js GOARCH=wasm go build -o main.wasm main.go")
	
	// Step 6: Run the server
	fmt.Println("\nRun the server:")
	fmt.Println("go run server.go")
	
	fmt.Println("\nAccess your application at http://localhost:8080")
}

func main() {
	setupWasmDevelopment()
}

Creating a Basic WASM Application

Now, let’s create a simple Go WebAssembly application:

// main.go
package main

import (
	"fmt"
	"syscall/js"
)

// Function to be exported to JavaScript
func add(this js.Value, args []js.Value) interface{} {
	if len(args) != 2 {
		return "Error: Expected 2 arguments"
	}
	
	// Convert JS values to Go types
	a := args[0].Int()
	b := args[1].Int()
	
	// Perform calculation
	result := a + b
	
	// Return result to JavaScript
	return result
}

// Function to modify the DOM
func updateDOM(this js.Value, args []js.Value) interface{} {
	// Get the document object
	document := js.Global().Get("document")
	
	// Create a new paragraph element
	p := document.Call("createElement", "p")
	
	// Set its text content
	p.Set("textContent", "This paragraph was created by Go WebAssembly!")
	
	// Append it to the body
	document.Get("body").Call("appendChild", p)
	
	return nil
}

func main() {
	fmt.Println("Go WebAssembly Initialized")
	
	// Create a channel to keep the program running
	c := make(chan struct{}, 0)
	
	// Register functions to be called from JavaScript
	js.Global().Set("goAdd", js.FuncOf(add))
	js.Global().Set("goUpdateDOM", js.FuncOf(updateDOM))
	
	// Keep the program running
	<-c
}

HTML and JavaScript Integration

Here’s how to integrate the WebAssembly module with HTML and JavaScript:

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Go WebAssembly Example</title>
    <script src="wasm_exec.js"></script>
    <script>
        // Initialize Go WASM runtime
        const go = new Go();
        
        // Load and instantiate the WebAssembly module
        WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject)
            .then((result) => {
                // Start the Go WASM instance
                go.run(result.instance);
                
                // Now we can call Go functions
                console.log("2 + 3 =", goAdd(2, 3));
                
                // Add a button to call Go function
                const button = document.createElement("button");
                button.textContent = "Call Go Function";
                button.addEventListener("click", () => {
                    goUpdateDOM();
                });
                document.body.appendChild(button);
            })
            .catch(err => {
                console.error("Failed to load WASM:", err);
            });
    </script>
</head>
<body>
    <h1>Go WebAssembly Example</h1>
    <p>Open the browser console to see output from Go</p>
</body>
</html>

Complete Application Example

Let’s build a more practical example—a real-time data processing application:

// data_processor.go
package main

import (
	"fmt"
	"math"
	"syscall/js"
)

// DataPoint represents a single data point
type DataPoint struct {
	X float64
	Y float64
}

// DataProcessor handles data processing operations
type DataProcessor struct {
	data []DataPoint
}

// NewDataProcessor creates a new data processor
func NewDataProcessor() *DataProcessor {
	return &DataProcessor{
		data: make([]DataPoint, 0),
	}
}

// AddPoint adds a data point
func (dp *DataProcessor) AddPoint(x, y float64) {
	dp.data = append(dp.data, DataPoint{X: x, Y: y})
}

// CalculateStatistics computes basic statistics on the data
func (dp *DataProcessor) CalculateStatistics() (min, max, avg, stdDev float64) {
	if len(dp.data) == 0 {
		return 0, 0, 0, 0
	}
	
	// Calculate min, max, and sum
	min = dp.data[0].Y
	max = dp.data[0].Y
	sum := 0.0
	
	for _, point := range dp.data {
		if point.Y < min {
			min = point.Y
		}
		if point.Y > max {
			max = point.Y
		}
		sum += point.Y
	}
	
	// Calculate average
	avg = sum / float64(len(dp.data))
	
	// Calculate standard deviation
	sumSquaredDiff := 0.0
	for _, point := range dp.data {
		diff := point.Y - avg
		sumSquaredDiff += diff * diff
	}
	
	stdDev = math.Sqrt(sumSquaredDiff / float64(len(dp.data)))
	
	return min, max, avg, stdDev
}

// ApplyTransformation applies a mathematical transformation to all data points
func (dp *DataProcessor) ApplyTransformation(transformFunc func(float64) float64) {
	for i := range dp.data {
		dp.data[i].Y = transformFunc(dp.data[i].Y)
	}
}

// GetDataAsJSArray returns the data as a JavaScript array
func (dp *DataProcessor) GetDataAsJSArray() js.Value {
	// Create a JavaScript array
	jsArray := js.Global().Get("Array").New(len(dp.data))
	
	// Populate the array with data points
	for i, point := range dp.data {
		jsPoint := js.Global().Get("Object").New()
		jsPoint.Set("x", point.X)
		jsPoint.Set("y", point.Y)
		jsArray.SetIndex(i, jsPoint)
	}
	
	return jsArray
}

// JavaScript wrapper functions
func jsAddPoint(this js.Value, args []js.Value) interface{} {
	if len(args) != 2 {
		return "Error: Expected 2 arguments (x, y)"
	}
	
	x := args[0].Float()
	y := args[1].Float()
	
	processor.AddPoint(x, y)
	return nil
}

func jsCalculateStatistics(this js.Value, args []js.Value) interface{} {
	min, max, avg, stdDev := processor.CalculateStatistics()
	
	result := js.Global().Get("Object").New()
	result.Set("min", min)
	result.Set("max", max)
	result.Set("average", avg)
	result.Set("standardDeviation", stdDev)
	
	return result
}

func jsApplyTransformation(this js.Value, args []js.Value) interface{} {
	if len(args) != 1 || !args[0].InstanceOf(js.Global().Get("Function")) {
		return "Error: Expected a function argument"
	}
	
	jsTransformFunc := args[0]
	
	// Create a Go function that calls the JavaScript function
	transformFunc := func(value float64) float64 {
		result := jsTransformFunc.Invoke(value)
		return result.Float()
	}
	
	processor.ApplyTransformation(transformFunc)
	return nil
}

func jsGetData(this js.Value, args []js.Value) interface{} {
	return processor.GetDataAsJSArray()
}

// Global processor instance
var processor *DataProcessor

func main() {
	fmt.Println("Data Processor WebAssembly Module Initialized")
	
	// Initialize the processor
	processor = NewDataProcessor()
	
	// Register JavaScript functions
	js.Global().Set("addDataPoint", js.FuncOf(jsAddPoint))
	js.Global().Set("calculateStatistics", js.FuncOf(jsCalculateStatistics))
	js.Global().Set("applyTransformation", js.FuncOf(jsApplyTransformation))
	js.Global().Set("getProcessedData", js.FuncOf(jsGetData))
	
	// Keep the program running
	<-make(chan struct{})
}

And the corresponding HTML/JavaScript:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Go WASM Data Processor</title>
    <script src="wasm_exec.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
        .chart-container { height: 400px; }
        .controls { margin: 20px 0; }
        .stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin: 20px 0; }
        .stat-box { border: 1px solid #ddd; padding: 10px; border-radius: 4px; }
        .stat-value { font-size: 1.5em; font-weight: bold; }
    </style>
    <script>
        let chart;
        
        // Initialize Go WASM
        const go = new Go();
        WebAssembly.instantiateStreaming(fetch("data_processor.wasm"), go.importObject)
            .then((result) => {
                go.run(result.instance);
                initializeApp();
            });
        
        function initializeApp() {
            // Create chart
            const ctx = document.getElementById('dataChart').getContext('2d');
            chart = new Chart(ctx, {
                type: 'line',
                data: {
                    datasets: [{
                        label: 'Data Points',
                        backgroundColor: 'rgba(75, 192, 192, 0.2)',
                        borderColor: 'rgba(75, 192, 192, 1)',
                        data: []
                    }]
                },
                options: {
                    scales: {
                        x: { type: 'linear', position: 'bottom' }
                    }
                }
            });
            
            // Add event listeners
            document.getElementById('addRandomData').addEventListener('click', addRandomData);
            document.getElementById('calculateStats').addEventListener('click', updateStatistics);
            document.getElementById('applySquareRoot').addEventListener('click', () => applyTransformation(Math.sqrt));
            document.getElementById('applySquare').addEventListener('click', () => applyTransformation(x => x * x));
        }
        
        function addRandomData() {
            const count = parseInt(document.getElementById('dataCount').value);
            
            for (let i = 0; i < count; i++) {
                const x = Math.random() * 100;
                const y = Math.random() * 100;
                addDataPoint(x, y);
            }
            
            updateChart();
            updateStatistics();
        }
        
        function addDataPoint(x, y) {
            // Call Go function to add data point
            addDataPoint(x, y);
        }
        
        function updateStatistics() {
            // Call Go function to calculate statistics
            const stats = calculateStatistics();
            
            document.getElementById('minValue').textContent = stats.min.toFixed(2);
            document.getElementById('maxValue').textContent = stats.max.toFixed(2);
            document.getElementById('avgValue').textContent = stats.average.toFixed(2);
            document.getElementById('stdDevValue').textContent = stats.standardDeviation.toFixed(2);
        }
        
        function applyTransformation(func) {
            // Call Go function to apply transformation
            applyTransformation(func);
            updateChart();
            updateStatistics();
        }
        
        function updateChart() {
            // Get data from Go
            const data = getProcessedData();
            
            // Update chart
            chart.data.datasets[0].data = data.map(point => ({
                x: point.x,
                y: point.y
            }));
            
            chart.update();
        }
    </script>
</head>
<body>
    <h1>Go WebAssembly Data Processor</h1>
    
    <div class="chart-container">
        <canvas id="dataChart"></canvas>
    </div>
    
    <div class="controls">
        <label for="dataCount">Number of random data points:</label>
        <input type="number" id="dataCount" value="50" min="1" max="1000">
        <button id="addRandomData">Add Random Data</button>
        <button id="calculateStats">Calculate Statistics</button>
        <button id="applySquareRoot">Apply Square Root</button>
        <button id="applySquare">Apply Square</button>
    </div>
    
    <div class="stats">
        <div class="stat-box">
            <div>Minimum</div>
            <div id="minValue" class="stat-value">0.00</div>
        </div>
        <div class="stat-box">
            <div>Maximum</div>
            <div id="maxValue" class="stat-value">0.00</div>
        </div>
        <div class="stat-box">
            <div>Average</div>
            <div id="avgValue" class="stat-value">0.00</div>
        </div>
        <div class="stat-box">
            <div>Std Deviation</div>
            <div id="stdDevValue" class="stat-value">0.00</div>
        </div>
    </div>
</body>
</html>

This example demonstrates a practical application of Go WebAssembly for real-time data processing in the browser, showcasing the integration between Go and JavaScript.


Advanced WebAssembly Patterns

As you become more comfortable with basic Go WebAssembly development, you can explore more sophisticated patterns and techniques.

State Management in WASM Applications

Managing state between JavaScript and Go requires careful consideration:

package main

import (
	"encoding/json"
	"fmt"
	"sync"
	"syscall/js"
)

// AppState represents the application state
type AppState struct {
	mu            sync.RWMutex
	counter       int
	items         []string
	isInitialized bool
	userData      map[string]interface{}
}

// Global state instance
var state = &AppState{
	counter:       0,
	items:         make([]string, 0),
	isInitialized: false,
	userData:      make(map[string]interface{}),
}

// Initialize the application state
func (s *AppState) Initialize(initialData js.Value) {
	s.mu.Lock()
	defer s.mu.Unlock()
	
	if initialData.Type() == js.TypeObject {
		// Extract counter if present
		if counterVal := initialData.Get("counter"); counterVal.Type() == js.TypeNumber {
			s.counter = counterVal.Int()
		}
		
		// Extract items if present
		if itemsVal := initialData.Get("items"); itemsVal.Type() == js.TypeObject && itemsVal.InstanceOf(js.Global().Get("Array")) {
			length := itemsVal.Length()
			s.items = make([]string, length)
			for i := 0; i < length; i++ {
				s.items[i] = itemsVal.Index(i).String()
			}
		}
		
		// Extract userData if present
		if userDataVal := initialData.Get("userData"); userDataVal.Type() == js.TypeObject {
			// Convert JavaScript object to Go map
			keys := js.Global().Get("Object").Call("keys", userDataVal)
			length := keys.Length()
			for i := 0; i < length; i++ {
				key := keys.Index(i).String()
				value := userDataVal.Get(key)
				
				// Handle different types
				switch value.Type() {
				case js.TypeBoolean:
					s.userData[key] = value.Bool()
				case js.TypeNumber:
					s.userData[key] = value.Float()
				case js.TypeString:
					s.userData[key] = value.String()
				default:
					// For complex objects, store as JSON string
					jsonStr := js.Global().Get("JSON").Call("stringify", value).String()
					s.userData[key] = jsonStr
				}
			}
		}
	}
	
	s.isInitialized = true
	fmt.Println("State initialized:", s)
}

// GetState returns the current state as a JavaScript object
func (s *AppState) GetState() js.Value {
	s.mu.RLock()
	defer s.mu.RUnlock()
	
	result := js.Global().Get("Object").New()
	result.Set("counter", s.counter)
	
	// Convert items to JS array
	itemsArray := js.Global().Get("Array").New(len(s.items))
	for i, item := range s.items {
		itemsArray.SetIndex(i, item)
	}
	result.Set("items", itemsArray)
	
	// Convert userData to JS object
	userData := js.Global().Get("Object").New()
	for k, v := range s.userData {
		switch val := v.(type) {
		case string:
			userData.Set(k, val)
		case int:
			userData.Set(k, val)
		case float64:
			userData.Set(k, val)
		case bool:
			userData.Set(k, val)
		default:
			// Try to convert to JSON string
			if jsonBytes, err := json.Marshal(val); err == nil {
				userData.Set(k, string(jsonBytes))
			} else {
				userData.Set(k, fmt.Sprintf("%v", val))
			}
		}
	}
	result.Set("userData", userData)
	
	return result
}

// IncrementCounter increases the counter and returns the new value
func (s *AppState) IncrementCounter() int {
	s.mu.Lock()
	defer s.mu.Unlock()
	
	s.counter++
	return s.counter
}

// AddItem adds an item to the items list
func (s *AppState) AddItem(item string) {
	s.mu.Lock()
	defer s.mu.Unlock()
	
	s.items = append(s.items, item)
}

// SetUserData sets a value in the userData map
func (s *AppState) SetUserData(key string, value interface{}) {
	s.mu.Lock()
	defer s.mu.Unlock()
	
	s.userData[key] = value
}

// JavaScript wrapper functions
func jsInitializeState(this js.Value, args []js.Value) interface{} {
	if len(args) > 0 {
		state.Initialize(args[0])
	} else {
		state.Initialize(js.Null())
	}
	return nil
}

func jsGetState(this js.Value, args []js.Value) interface{} {
	return state.GetState()
}

func jsIncrementCounter(this js.Value, args []js.Value) interface{} {
	return state.IncrementCounter()
}

func jsAddItem(this js.Value, args []js.Value) interface{} {
	if len(args) > 0 && args[0].Type() == js.TypeString {
		state.AddItem(args[0].String())
	}
	return nil
}

func jsSetUserData(this js.Value, args []js.Value) interface{} {
	if len(args) >= 2 && args[0].Type() == js.TypeString {
		key := args[0].String()
		value := args[1]
		
		// Convert JS value to Go value
		var goValue interface{}
		
		switch value.Type() {
		case js.TypeBoolean:
			goValue = value.Bool()
		case js.TypeNumber:
			goValue = value.Float()
		case js.TypeString:
			goValue = value.String()
		default:
			// For complex objects, store as JSON string
			jsonStr := js.Global().Get("JSON").Call("stringify", value).String()
			goValue = jsonStr
		}
		
		state.SetUserData(key, goValue)
	}
	return nil
}

func main() {
	fmt.Println("State Management WebAssembly Module Initialized")
	
	// Register JavaScript functions
	js.Global().Set("initializeState", js.FuncOf(jsInitializeState))
	js.Global().Set("getState", js.FuncOf(jsGetState))
	js.Global().Set("incrementCounter", js.FuncOf(jsIncrementCounter))
	js.Global().Set("addItem", js.FuncOf(jsAddItem))
	js.Global().Set("setUserData", js.FuncOf(jsSetUserData))
	
	// Keep the program running
	<-make(chan struct{})
}

This pattern demonstrates a thread-safe approach to managing state between JavaScript and Go, with proper synchronization and type conversion.

Component Architecture for WASM Applications

For larger applications, a component-based architecture can help manage complexity:

package main

import (
	"fmt"
	"syscall/js"
)

// Component represents a UI component with its own state and behavior
type Component interface {
	Render() js.Value
	HandleEvent(event string, args []js.Value) interface{}
	Mount(parent js.Value)
	Unmount()
}

// BaseComponent provides common functionality for components
type BaseComponent struct {
	ID       string
	Element  js.Value
	Children []Component
}

// Counter is a simple counter component
type Counter struct {
	BaseComponent
	Count int
}

// NewCounter creates a new counter component
func NewCounter(id string) *Counter {
	return &Counter{
		BaseComponent: BaseComponent{
			ID:       id,
			Children: make([]Component, 0),
		},
		Count: 0,
	}
}

// Render creates the DOM elements for the counter
func (c *Counter) Render() js.Value {
	document := js.Global().Get("document")
	
	// Create container div
	div := document.Call("createElement", "div")
	div.Set("id", c.ID)
	
	// Create count display
	countDisplay := document.Call("createElement", "span")
	countDisplay.Set("textContent", fmt.Sprintf("Count: %d", c.Count))
	countDisplay.Set("id", c.ID+"-display")
	
	// Create increment button
	incButton := document.Call("createElement", "button")
	incButton.Set("textContent", "Increment")
	incButton.Set("id", c.ID+"-increment")
	
	// Add event listener
	incButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		return c.HandleEvent("increment", args)
	}))
	
	// Assemble component
	div.Call("appendChild", countDisplay)
	div.Call("appendChild", document.Call("createElement", "br"))
	div.Call("appendChild", incButton)
	
	c.Element = div
	return div
}

// HandleEvent handles component events
func (c *Counter) HandleEvent(event string, args []js.Value) interface{} {
	switch event {
	case "increment":
		c.Count++
		// Update the display
		document := js.Global().Get("document")
		display := document.Call("getElementById", c.ID+"-display")
		if display.Truthy() {
			display.Set("textContent", fmt.Sprintf("Count: %d", c.Count))
		}
	}
	return nil
}

// Mount adds the component to the DOM
func (c *Counter) Mount(parent js.Value) {
	element := c.Render()
	parent.Call("appendChild", element)
}

// Unmount removes the component from the DOM
func (c *Counter) Unmount() {
	if c.Element.Truthy() {
		c.Element.Call("remove")
	}
}

// TodoList is a more complex component
type TodoList struct {
	BaseComponent
	Items []string
}

// NewTodoList creates a new todo list component
func NewTodoList(id string) *TodoList {
	return &TodoList{
		BaseComponent: BaseComponent{
			ID:       id,
			Children: make([]Component, 0),
		},
		Items: make([]string, 0),
	}
}

// Render creates the DOM elements for the todo list
func (t *TodoList) Render() js.Value {
	document := js.Global().Get("document")
	
	// Create container div
	div := document.Call("createElement", "div")
	div.Set("id", t.ID)
	
	// Create title
	title := document.Call("createElement", "h3")
	title.Set("textContent", "Todo List")
	
	// Create input field
	input := document.Call("createElement", "input")
	input.Set("type", "text")
	input.Set("id", t.ID+"-input")
	input.Set("placeholder", "Add new item...")
	
	// Create add button
	addButton := document.Call("createElement", "button")
	addButton.Set("textContent", "Add")
	addButton.Set("id", t.ID+"-add")
	
	// Add event listener
	addButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		return t.HandleEvent("add", args)
	}))
	
	// Create list
	list := document.Call("createElement", "ul")
	list.Set("id", t.ID+"-list")
	
	// Add existing items
	for _, item := range t.Items {
		li := document.Call("createElement", "li")
		li.Set("textContent", item)
		list.Call("appendChild", li)
	}
	
	// Assemble component
	div.Call("appendChild", title)
	div.Call("appendChild", input)
	div.Call("appendChild", addButton)
	div.Call("appendChild", list)
	
	t.Element = div
	return div
}

// HandleEvent handles component events
func (t *TodoList) HandleEvent(event string, args []js.Value) interface{} {
	switch event {
	case "add":
		document := js.Global().Get("document")
		input := document.Call("getElementById", t.ID+"-input")
		if input.Truthy() {
			text := input.Get("value").String()
			if text != "" {
				// Add to items
				t.Items = append(t.Items, text)
				
				// Add to DOM
				list := document.Call("getElementById", t.ID+"-list")
				if list.Truthy() {
					li := document.Call("createElement", "li")
					li.Set("textContent", text)
					list.Call("appendChild", li)
				}
				
				// Clear input
				input.Set("value", "")
			}
		}
	}
	return nil
}

// Mount adds the component to the DOM
func (t *TodoList) Mount(parent js.Value) {
	element := t.Render()
	parent.Call("appendChild", element)
}

// Unmount removes the component from the DOM
func (t *TodoList) Unmount() {
	if t.Element.Truthy() {
		t.Element.Call("remove")
	}
}

// Application is the main application component
type Application struct {
	BaseComponent
	Counter  *Counter
	TodoList *TodoList
}

// NewApplication creates a new application
func NewApplication(id string) *Application {
	app := &Application{
		BaseComponent: BaseComponent{
			ID:       id,
			Children: make([]Component, 0),
		},
	}
	
	// Create child components
	app.Counter = NewCounter(id + "-counter")
	app.TodoList = NewTodoList(id + "-todo")
	
	// Add to children
	app.Children = append(app.Children, app.Counter)
	app.Children = append(app.Children, app.TodoList)
	
	return app
}

// Render creates the DOM elements for the application
func (a *Application) Render() js.Value {
	document := js.Global().Get("document")
	
	// Create container div
	div := document.Call("createElement", "div")
	div.Set("id", a.ID)
	
	// Create title
	title := document.Call("createElement", "h2")
	title.Set("textContent", "Go WebAssembly Component Demo")
	
	// Assemble component
	div.Call("appendChild", title)
	
	a.Element = div
	return div
}

// HandleEvent handles component events
func (a *Application) HandleEvent(event string, args []js.Value) interface{} {
	// No events at application level
	return nil
}

// Mount adds the application and its children to the DOM
func (a *Application) Mount(parent js.Value) {
	element := a.Render()
	parent.Call("appendChild", element)
	
	// Mount children
	for _, child := range a.Children {
		child.Mount(element)
	}
}

// Unmount removes the application and its children from the DOM
func (a *Application) Unmount() {
	// Unmount children first
	for _, child := range a.Children {
		child.Unmount()
	}
	
	// Unmount self
	if a.Element.Truthy() {
		a.Element.Call("remove")
	}
}

// JavaScript wrapper functions
func jsCreateApplication(this js.Value, args []js.Value) interface{} {
	id := "wasm-app"
	if len(args) > 0 && args[0].Type() == js.TypeString {
		id = args[0].String()
	}
	
	app := NewApplication(id)
	
	// Get the mount point
	document := js.Global().Get("document")
	mountPoint := document.Get("body")
	if len(args) > 1 && args[1].Type() == js.TypeString {
		mountPointID := args[1].String()
		element := document.Call("getElementById", mountPointID)
		if element.Truthy() {
			mountPoint = element
		}
	}
	
	// Mount the application
	app.Mount(mountPoint)
	
	return nil
}

func main() {
	fmt.Println("Component Architecture WebAssembly Module Initialized")
	
	// Register JavaScript functions
	js.Global().Set("createApplication", js.FuncOf(jsCreateApplication))
	
	// Keep the program running
	<-make(chan struct{})
}

This component architecture demonstrates how to structure larger WebAssembly applications using a component-based approach similar to modern JavaScript frameworks.


JavaScript Interoperability

One of the most powerful aspects of WebAssembly is its ability to interoperate with JavaScript. This section explores advanced techniques for seamless integration between Go and JavaScript.

Bidirectional Function Calls

Effective communication between Go and JavaScript is essential for WebAssembly applications:

package main

import (
	"fmt"
	"syscall/js"
)

// CallbackRegistry manages JavaScript callbacks from Go
type CallbackRegistry struct {
	callbacks map[string]js.Func
}

// NewCallbackRegistry creates a new callback registry
func NewCallbackRegistry() *CallbackRegistry {
	return &CallbackRegistry{
		callbacks: make(map[string]js.Func),
	}
}

// Register adds a JavaScript callback function
func (r *CallbackRegistry) Register(name string, callback js.Func) {
	// Release any existing callback with the same name
	if existing, ok := r.callbacks[name]; ok {
		existing.Release()
	}
	
	r.callbacks[name] = callback
}

// Call invokes a registered callback
func (r *CallbackRegistry) Call(name string, args ...interface{}) js.Value {
	if callback, ok := r.callbacks[name]; ok {
		// Convert Go values to JS values
		jsArgs := make([]interface{}, len(args))
		for i, arg := range args {
			jsArgs[i] = arg
		}
		
		return callback.Invoke(jsArgs...)
	}
	
	fmt.Printf("Warning: Callback '%s' not registered\n", name)
	return js.Undefined()
}

// Release releases all callbacks
func (r *CallbackRegistry) Release() {
	for name, callback := range r.callbacks {
		callback.Release()
		delete(r.callbacks, name)
	}
}

// Global callback registry
var registry = NewCallbackRegistry()

// RegisterCallback registers a JavaScript function as a callback
func RegisterCallback(this js.Value, args []js.Value) interface{} {
	if len(args) != 2 || args[0].Type() != js.TypeString || !args[1].InstanceOf(js.Global().Get("Function")) {
		return "Error: Expected (string, function) arguments"
	}
	
	name := args[0].String()
	callback := js.FuncOf(func(this js.Value, callbackArgs []js.Value) interface{} {
		// Convert args to a slice of interface{} for easier handling
		goArgs := make([]interface{}, len(callbackArgs))
		for i, arg := range callbackArgs {
			// Convert JS values to Go values based on type
			switch arg.Type() {
			case js.TypeBoolean:
				goArgs[i] = arg.Bool()
			case js.TypeNumber:
				goArgs[i] = arg.Float()
			case js.TypeString:
				goArgs[i] = arg.String()
			default:
				goArgs[i] = arg
			}
		}
		
		// Call the JavaScript function
		return args[1].Invoke(goArgs...)
	})
	
	registry.Register(name, callback)
	return nil
}

// CallJavaScript calls a registered JavaScript function
func CallJavaScript(name string, args ...interface{}) js.Value {
	return registry.Call(name, args...)
}

// Example Go function that will call back to JavaScript
func ProcessDataWithCallback(this js.Value, args []js.Value) interface{} {
	if len(args) < 1 {
		return "Error: Expected at least one argument"
	}
	
	// Process the data in Go
	data := args[0]
	
	// Prepare result object
	result := js.Global().Get("Object").New()
	
	if data.Type() == js.TypeObject && data.InstanceOf(js.Global().Get("Array")) {
		length := data.Length()
		sum := 0.0
		
		// Calculate sum
		for i := 0; i < length; i++ {
			sum += data.Index(i).Float()
		}
		
		// Calculate average
		avg := sum / float64(length)
		
		// Set properties on result object
		result.Set("sum", sum)
		result.Set("average", avg)
		result.Set("count", length)
		
		// Call back to JavaScript with the result
		CallJavaScript("onProcessingComplete", result)
	}
	
	return result
}

// Example of passing functions from Go to JavaScript
func CreateCalculator(this js.Value, args []js.Value) interface{} {
	// Create a calculator object
	calculator := js.Global().Get("Object").New()
	
	// Add methods to the calculator
	calculator.Set("add", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		if len(args) < 2 {
			return "Error: Expected two arguments"
		}
		return args[0].Float() + args[1].Float()
	}))
	
	calculator.Set("subtract", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		if len(args) < 2 {
			return "Error: Expected two arguments"
		}
		return args[0].Float() - args[1].Float()
	}))
	
	calculator.Set("multiply", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		if len(args) < 2 {
			return "Error: Expected two arguments"
		}
		return args[0].Float() * args[1].Float()
	}))
	
	calculator.Set("divide", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		if len(args) < 2 {
			return "Error: Expected two arguments"
		}
		if args[1].Float() == 0 {
			return "Error: Division by zero"
		}
		return args[0].Float() / args[1].Float()
	}))
	
	return calculator
}

func main() {
	fmt.Println("JavaScript Interoperability WebAssembly Module Initialized")
	
	// Register functions to be called from JavaScript
	js.Global().Set("registerCallback", js.FuncOf(RegisterCallback))
	js.Global().Set("processDataWithCallback", js.FuncOf(ProcessDataWithCallback))
	js.Global().Set("createCalculator", js.FuncOf(CreateCalculator))
	
	// Keep the program running
	<-make(chan struct{})
}

Example JavaScript usage:

// Register a callback function
registerCallback("onProcessingComplete", function(result) {
    console.log("Processing complete!");
    console.log("Sum:", result.sum);
    console.log("Average:", result.average);
    console.log("Count:", result.count);
});

// Call Go function with data
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const result = processDataWithCallback(data);

// Create and use a calculator object from Go
const calculator = createCalculator();
console.log("5 + 3 =", calculator.add(5, 3));
console.log("10 - 4 =", calculator.subtract(10, 4));
console.log("6 * 7 =", calculator.multiply(6, 7));
console.log("20 / 5 =", calculator.divide(20, 5));

Working with DOM and Browser APIs

Go WebAssembly can interact with the DOM and browser APIs through JavaScript:

package main

import (
	"fmt"
	"syscall/js"
	"time"
)

// DOM manipulation functions
func CreateElement(tagName string) js.Value {
	document := js.Global().Get("document")
	return document.Call("createElement", tagName)
}

func GetElementById(id string) js.Value {
	document := js.Global().Get("document")
	return document.Call("getElementById", id)
}

func QuerySelector(selector string) js.Value {
	document := js.Global().Get("document")
	return document.Call("querySelector", selector)
}

func QuerySelectorAll(selector string) js.Value {
	document := js.Global().Get("document")
	return document.Call("querySelectorAll", selector)
}

// Example: Create a canvas-based animation
func CreateCanvasAnimation(this js.Value, args []js.Value) interface{} {
	// Get container element
	containerId := "animation-container"
	if len(args) > 0 && args[0].Type() == js.TypeString {
		containerId = args[0].String()
	}
	
	container := GetElementById(containerId)
	if !container.Truthy() {
		fmt.Println("Container element not found")
		return nil
	}
	
	// Create canvas element
	canvas := CreateElement("canvas")
	canvas.Set("width", 400)
	canvas.Set("height", 300)
	canvas.Set("style", "border: 1px solid black;")
	
	// Append canvas to container
	container.Call("appendChild", canvas)
	
	// Get canvas context
	ctx := canvas.Call("getContext", "2d")
	
	// Animation variables
	x := 50.0
	y := 50.0
	dx := 2.0
	dy := 1.5
	radius := 20.0
	
	// Create animation frame callback
	var animationCallback js.Func
	animationCallback = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		// Clear canvas
		ctx.Call("clearRect", 0, 0, canvas.Get("width").Int(), canvas.Get("height").Int())
		
		// Draw circle
		ctx.Call("beginPath")
		ctx.Call("arc", x, y, radius, 0, 2*3.14159)
		ctx.Set("fillStyle", "blue")
		ctx.Call("fill")
		ctx.Call("closePath")
		
		// Update position
		x += dx
		y += dy
		
		// Bounce off walls
		if x+radius > canvas.Get("width").Float() || x-radius < 0 {
			dx = -dx
		}
		if y+radius > canvas.Get("height").Float() || y-radius < 0 {
			dy = -dy
		}
		
		// Request next frame
		js.Global().Call("requestAnimationFrame", animationCallback)
		return nil
	})
	
	// Start animation
	js.Global().Call("requestAnimationFrame", animationCallback)
	
	// Return a function to stop the animation
	return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		animationCallback.Release()
		return nil
	})
}

// Example: Working with browser APIs
func FetchData(this js.Value, args []js.Value) interface{} {
	if len(args) < 2 || args[0].Type() != js.TypeString || !args[1].InstanceOf(js.Global().Get("Function")) {
		return "Error: Expected (url, callback) arguments"
	}
	
	url := args[0].String()
	callback := args[1]
	
	// Create a Promise
	fetch := js.Global().Call("fetch", url)
	
	// Handle response
	then := fetch.Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		response := args[0]
		return response.Call("json")
	}))
	
	// Handle JSON data
	then.Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		data := args[0]
		callback.Invoke(data)
		return nil
	}))
	
	// Handle errors
	then.Call("catch", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		err := args[0]
		fmt.Println("Fetch error:", err.Get("message").String())
		return nil
	}))
	
	return nil
}

// Example: Using browser storage
func StorageExample(this js.Value, args []js.Value) interface{} {
	// Get localStorage
	localStorage := js.Global().Get("localStorage")
	
	// Set item
	localStorage.Call("setItem", "goWasmTimestamp", time.Now().String())
	
	// Get item
	timestamp := localStorage.Call("getItem", "goWasmTimestamp").String()
	fmt.Println("Stored timestamp:", timestamp)
	
	// Create a result object
	result := js.Global().Get("Object").New()
	result.Set("timestamp", timestamp)
	result.Set("storageAvailable", true)
	
	return result
}

func main() {
	fmt.Println("DOM and Browser API WebAssembly Module Initialized")
	
	// Register functions to be called from JavaScript
	js.Global().Set("createCanvasAnimation", js.FuncOf(CreateCanvasAnimation))
	js.Global().Set("fetchData", js.FuncOf(FetchData))
	js.Global().Set("storageExample", js.FuncOf(StorageExample))
	
	// Keep the program running
	<-make(chan struct{})
}

Example HTML usage:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Go WASM DOM Example</title>
    <script src="wasm_exec.js"></script>
    <script>
        // Initialize Go WASM
        const go = new Go();
        WebAssembly.instantiateStreaming(fetch("dom_api.wasm"), go.importObject)
            .then((result) => {
                go.run(result.instance);
                
                // Create animation
                const stopAnimation = createCanvasAnimation("animation-container");
                
                // Fetch data example
                fetchData("https://jsonplaceholder.typicode.com/todos/1", function(data) {
                    console.log("Fetched data:", data);
                    document.getElementById("fetch-result").textContent =
                        JSON.stringify(data, null, 2);
                });
                
                // Storage example
                const storageResult = storageExample();
                document.getElementById("storage-result").textContent =
                    "Timestamp: " + storageResult.timestamp;
                
                // Add stop button functionality
                document.getElementById("stop-button").addEventListener("click", function() {
                    stopAnimation();
                    this.disabled = true;
                });
            });
    </script>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
        .section { margin: 20px 0; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
        pre { background-color: #f5f5f5; padding: 10px; border-radius: 4px; overflow: auto; }
    </style>
</head>
<body>
    <h1>Go WebAssembly DOM and Browser API Examples</h1>
    
    <div class="section">
        <h2>Canvas Animation</h2>
        <div id="animation-container"></div>
        <button id="stop-button">Stop Animation</button>
    </div>
    
    <div class="section">
        <h2>Fetch API Example</h2>
        <pre id="fetch-result">Loading...</pre>
    </div>
    
    <div class="section">
        <h2>Storage API Example</h2>
        <div id="storage-result"></div>
    </div>
</body>
</html>

// Render creates the DOM elements for the counter func (c *Counter) Render() js.Value { document := js.Global().Get(“document”)

// Create container div
div := document.Call("createElement", "div")
div.Set("id", c.ID)

// Create count display
countDisplay := document.Call("createElement", "span")
countDisplay.Set("textContent", fmt.Sprintf("Count: %d", c.Count))
countDisplay.Set("id", c.ID+"-display")

// Create increment button
incButton := document.Call("createElement", "button")
incButton.Set("textContent", "Increment")
incButton.Set("id", c.ID+"-increment")

// Add event listener
incButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
	return c.HandleEvent("increment", args)
}))

// Assemble component
div.Call("appendChild", countDisplay)
div.Call("appendChild", document.Call("createElement", "br"))
div.Call("appendChild", incButton)

c.Element = div
return div

}

// HandleEvent handles component events func (c *Counter) HandleEvent(event string, args []js.Value) interface{} { switch event { case “increment”: c.Count++ // Update the display document := js.Global().Get(“document”) display := document.Call(“getElementById”, c.ID+"-display") if display.Truthy() { display.Set(“textContent”, fmt.Sprintf(“Count: %d”, c.Count)) } } return nil }

// Mount adds the component to the DOM func (c *Counter) Mount(parent js.Value) { element := c.Render() parent.Call(“appendChild”, element) }

// Unmount removes the component from the DOM func (c *Counter) Unmount() { if c.Element.Truthy() { c.Element.Call(“remove”) } }

// TodoList is a more complex component type TodoList struct { BaseComponent Items []string }

// NewTodoList creates a new todo list component func NewTodoList(id string) *TodoList { return &TodoList{ BaseComponent: BaseComponent{ ID: id, Children: make([]Component, 0), }, Items: make([]string, 0), } }

// Render creates the DOM elements for the todo list func (t *TodoList) Render() js.Value { document := js.Global().Get(“document”)

// Create container div
div := document.Call("createElement", "div")
div.Set("id", t.ID)

// Create title
title := document.Call("createElement", "h3")
title.Set("textContent", "Todo List")

// Create input field
input := document.Call("createElement", "input")
input.Set("type", "text")
input.Set("id", t.ID+"-input")
input.Set("placeholder", "Add new item...")

// Create add button
addButton := document.Call("createElement", "button")
addButton.Set("textContent", "Add")
addButton.Set("id", t.ID+"-add")

// Add event listener
addButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
	return t.HandleEvent("add", args)
}))

// Create list
list := document.Call("createElement", "ul")
list.Set("id", t.ID+"-list")

// Add existing items
for _, item := range t.Items {
	li := document.Call("createElement", "li")
	li.Set("textContent", item)
	list.Call("appendChild", li)
}

// Assemble component
div.Call("appendChild", title)
div.Call("appendChild", input)
div.Call("appendChild", addButton)
div.Call("appendChild", list)

t.Element = div
return div

}

// HandleEvent handles component events func (t *TodoList) HandleEvent(event string, args []js.Value) interface{} { switch event { case “add”: document := js.Global().Get(“document”) input := document.Call(“getElementById”, t.ID+"-input") if input.Truthy() { text := input.Get(“value”).String() if text != "" { // Add to items t.Items = append(t.Items, text)

			// Add to DOM
			list := document.Call("getElementById", t.ID+"-list")
			if list.Truthy() {
				li := document.Call("createElement", "li")
				li.Set("textContent", text)
				list.Call("appendChild", li)
			}
			
			// Clear input
			input.Set("value", "")
		}
	}
}
return nil

}

// Mount adds the component to the DOM func (t *TodoList) Mount(parent js.Value) { element := t.Render() parent.Call(“appendChild”, element) }

// Unmount removes the component from the DOM func (t *TodoList) Unmount() { if t.Element.Truthy() { t.Element.Call(“remove”) } }

// Application is the main application component type Application struct { BaseComponent Counter *Counter TodoList *TodoList }

// NewApplication creates a new application func NewApplication(id string) *Application { app := &Application{ BaseComponent: BaseComponent{ ID: id, Children: make([]Component, 0), }, }

// Create child components
app.Counter = NewCounter(id + "-counter")
app.TodoList = NewTodoList(id + "-todo")

// Add to children
app.Children = append(app.Children, app.Counter)
app.Children = append(app.Children, app.TodoList)

return app

}

// Render creates the DOM elements for the application func (a *Application) Render() js.Value { document := js.Global().Get(“document”)

// Create container div
div := document.Call("createElement", "div")
div.Set("id", a.ID)

// Create title
title := document.Call("createElement", "h2")
title.Set("textContent", "Go WebAssembly Component Demo")

// Assemble component
div.Call("appendChild", title)

a.Element = div
return div

}

// HandleEvent handles component events func (a *Application) HandleEvent(event string, args []js.Value) interface{} { // No events at application level return nil }

// Mount adds the application and its children to the DOM func (a *Application) Mount(parent js.Value) { element := a.Render() parent.Call(“appendChild”, element)

// Mount children
for _, child := range a.Children {
	child.Mount(element)
}

}

// Unmount removes the application and its children from the DOM func (a *Application) Unmount() { // Unmount children first for _, child := range a.Children { child.Unmount() }

// Unmount self
if a.Element.Truthy() {
	a.Element.Call("remove")
}

}

// JavaScript wrapper functions func jsCreateApplication(this js.Value, args []js.Value) interface{} { id := “wasm-app” if len(args) > 0 && args[0].Type() == js.TypeString { id = args[0].String() }

app := NewApplication(id)

// Get the mount point
document := js.Global().Get("document")
mountPoint := document.Get("body")
if len(args) > 1 && args[1].Type() == js.TypeString {
	mountPointID := args[1].String()
	element := document.Call("getElementById", mountPointID)
	if element.Truthy() {
		mountPoint = element
	}
}

// Mount the application
app.Mount(mountPoint)

return nil

}

func main() { fmt.Println(“Component Architecture WebAssembly Module Initialized”)

// Register JavaScript functions
js.Global().Set("createApplication", js.FuncOf(jsCreateApplication))

// Keep the program running
<-make(chan struct{})

}


This component architecture demonstrates how to structure larger WebAssembly applications using a component-based approach similar to modern JavaScript frameworks.

---

### JavaScript Interoperability

One of the most powerful aspects of WebAssembly is its ability to interoperate with JavaScript. This section explores advanced techniques for seamless integration between Go and JavaScript.

#### Bidirectional Function Calls

Effective communication between Go and JavaScript is essential for WebAssembly applications:

```go
package main

import (
	"fmt"
	"syscall/js"
)

// CallbackRegistry manages JavaScript callbacks from Go
type CallbackRegistry struct {
	callbacks map[string]js.Func
}

// NewCallbackRegistry creates a new callback registry
func NewCallbackRegistry() *CallbackRegistry {
	return &CallbackRegistry{
		callbacks: make(map[string]js.Func),
	}
}

// Register adds a JavaScript callback function
func (r *CallbackRegistry) Register(name string, callback js.Func) {
	// Release any existing callback with the same name
	if existing, ok := r.callbacks[name]; ok {
		existing.Release()
	}
	
	r.callbacks[name] = callback
}

// Call invokes a registered callback
func (r *CallbackRegistry) Call(name string, args ...interface{}) js.Value {
	if callback, ok := r.callbacks[name]; ok {
		// Convert Go values to JS values
		jsArgs := make([]interface{}, len(args))
		for i, arg := range args {
			jsArgs[i] = arg
		}
		
		return callback.Invoke(jsArgs...)
	}
	
	fmt.Printf("Warning: Callback '%s' not registered\n", name)
	return js.Undefined()
}

// Release releases all callbacks
func (r *CallbackRegistry) Release() {
	for name, callback := range r.callbacks {
		callback.Release()
		delete(r.callbacks, name)
	}
}

// Global callback registry
var registry = NewCallbackRegistry()

// RegisterCallback registers a JavaScript function as a callback
func RegisterCallback(this js.Value, args []js.Value) interface{} {
	if len(args) != 2 || args[0].Type() != js.TypeString || !args[1].InstanceOf(js.Global().Get("Function")) {
		return "Error: Expected (string, function) arguments"
	}
	
	name := args[0].String()
	callback := js.FuncOf(func(this js.Value, callbackArgs []js.Value) interface{} {
		// Convert args to a slice of interface{} for easier handling
		goArgs := make([]interface{}, len(callbackArgs))
		for i, arg := range callbackArgs {
			// Convert JS values to Go values based on type
			switch arg.Type() {
			case js.TypeBoolean:
				goArgs[i] = arg.Bool()
			case js.TypeNumber:
				goArgs[i] = arg.Float()
			case js.TypeString:
				goArgs[i] = arg.String()
			default:
				goArgs[i] = arg
			}
		}
		
		// Call the JavaScript function
		return args[1].Invoke(goArgs...)
	})
	
	registry.Register(name, callback)
	return nil
}

// CallJavaScript calls a registered JavaScript function
func CallJavaScript(name string, args ...interface{}) js.Value {
	return registry.Call(name, args...)
}

// Example Go function that will call back to JavaScript
func ProcessDataWithCallback(this js.Value, args []js.Value) interface{} {
	if len(args) < 1 {
		return "Error: Expected at least one argument"
	}
	
	// Process the data in Go
	data := args[0]
	
	// Prepare result object
	result := js.Global().Get("Object").New()
	
	if data.Type() == js.TypeObject && data.InstanceOf(js.Global().Get("Array")) {
		length := data.Length()
		sum := 0.0
		
		// Calculate sum
		for i := 0; i < length; i++ {
			sum += data.Index(i).Float()
		}
		
		// Calculate average
		avg := sum / float64(length)
		
		// Set properties on result object
		result.Set("sum", sum)
		result.Set("average", avg)
		result.Set("count", length)
		
		// Call back to JavaScript with the result
		CallJavaScript("onProcessingComplete", result)
	}
	
	return result
}

// Example of passing functions from Go to JavaScript
func CreateCalculator(this js.Value, args []js.Value) interface{} {
	// Create a calculator object
	calculator := js.Global().Get("Object").New()
	
	// Add methods to the calculator
	calculator.Set("add", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		if len(args) < 2 {
			return "Error: Expected two arguments"
		}
		return args[0].Float() + args[1].Float()
	}))
	
	calculator.Set("subtract", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		if len(args) < 2 {
			return "Error: Expected two arguments"
		}
		return args[0].Float() - args[1].Float()
	}))
	
	calculator.Set("multiply", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		if len(args) < 2 {
			return "Error: Expected two arguments"
		}
		return args[0].Float() * args[1].Float()
	}))
	
	calculator.Set("divide", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		if len(args) < 2 {
			return "Error: Expected two arguments"
		}
		if args[1].Float() == 0 {
			return "Error: Division by zero"
		}
		return args[0].Float() / args[1].Float()
	}))
	
	return calculator
}

func main() {
	fmt.Println("JavaScript Interoperability WebAssembly Module Initialized")
	
	// Register functions to be called from JavaScript
	js.Global().Set("registerCallback", js.FuncOf(RegisterCallback))
	js.Global().Set("processDataWithCallback", js.FuncOf(ProcessDataWithCallback))
	js.Global().Set("createCalculator", js.FuncOf(CreateCalculator))
	
	// Keep the program running
	<-make(chan struct{})
}

Example JavaScript usage:

// Register a callback function
registerCallback("onProcessingComplete", function(result) {
    console.log("Processing complete!");
    console.log("Sum:", result.sum);
    console.log("Average:", result.average);
    console.log("Count:", result.count);
});

// Call Go function with data
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const result = processDataWithCallback(data);

// Create and use a calculator object from Go
const calculator = createCalculator();
console.log("5 + 3 =", calculator.add(5, 3));
console.log("10 - 4 =", calculator.subtract(10, 4));
console.log("6 * 7 =", calculator.multiply(6, 7));
console.log("20 / 5 =", calculator.divide(20, 5));

Working with DOM and Browser APIs

Go WebAssembly can interact with the DOM and browser APIs through JavaScript:

package main

import (
	"fmt"
	"syscall/js"
	"time"
)

// DOM manipulation functions
func CreateElement(tagName string) js.Value {
	document := js.Global().Get("document")
	return document.Call("createElement", tagName)
}

func GetElementById(id string) js.Value {
	document := js.Global().Get("document")
	return document.Call("getElementById", id)
}

func QuerySelector(selector string) js.Value {
	document := js.Global().Get("document")
	return document.Call("querySelector", selector)
}

func QuerySelectorAll(selector string) js.Value {
	document := js.Global().Get("document")
	return document.Call("querySelectorAll", selector)
}

// Example: Create a canvas-based animation
func CreateCanvasAnimation(this js.Value, args []js.Value) interface{} {
	// Get container element
	containerId := "animation-container"
	if len(args) > 0 && args[0].Type() == js.TypeString {
		containerId = args[0].String()
	}
	
	container := GetElementById(containerId)
	if !container.Truthy() {
		fmt.Println("Container element not found")
		return nil
	}
	
	// Create canvas element
	canvas := CreateElement("canvas")
	canvas.Set("width", 400)
	canvas.Set("height", 300)
	canvas.Set("style", "border: 1px solid black;")
	
	// Append canvas to container
	container.Call("appendChild", canvas)
	
	// Get canvas context
	ctx := canvas.Call("getContext", "2d")
	
	// Animation variables
	x := 50.0
	y := 50.0
	dx := 2.0
	dy := 1.5
	radius := 20.0
	
	// Create animation frame callback
	var animationCallback js.Func
	animationCallback = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		// Clear canvas
		ctx.Call("clearRect", 0, 0, canvas.Get("width").Int(), canvas.Get("height").Int())
		
		// Draw circle
		ctx.Call("beginPath")
		ctx.Call("arc", x, y, radius, 0, 2*3.14159)
		ctx.Set("fillStyle", "blue")
		ctx.Call("fill")
		ctx.Call("closePath")
		
		// Update position
		x += dx
		y += dy
		
		// Bounce off walls
		if x+radius > canvas.Get("width").Float() || x-radius < 0 {
			dx = -dx
		}
		if y+radius > canvas.Get("height").Float() || y-radius < 0 {
			dy = -dy
		}
		
		// Request next frame
		js.Global().Call("requestAnimationFrame", animationCallback)
		return nil
	})
	
	// Start animation
	js.Global().Call("requestAnimationFrame", animationCallback)
	
	// Return a function to stop the animation
	return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		animationCallback.Release()
		return nil
	})
}

// Example: Working with browser APIs
func FetchData(this js.Value, args []js.Value) interface{} {
	if len(args) < 2 || args[0].Type() != js.TypeString || !args[1].InstanceOf(js.Global().Get("Function")) {
		return "Error: Expected (url, callback) arguments"
	}
	
	url := args[0].String()
	callback := args[1]
	
	// Create a Promise
	fetch := js.Global().Call("fetch", url)
	
	// Handle response
	then := fetch.Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		response := args[0]
		return response.Call("json")
	}))
	
	// Handle JSON data
	then.Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		data := args[0]
		callback.Invoke(data)
		return nil
	}))
	
	// Handle errors
	then.Call("catch", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		err := args[0]
		fmt.Println("Fetch error:", err.Get("message").String())
		return nil
	}))
	
	return nil
}

// Example: Using browser storage
func StorageExample(this js.Value, args []js.Value) interface{} {
	// Get localStorage
	localStorage := js.Global().Get("localStorage")
	
	// Set item
	localStorage.Call("setItem", "goWasmTimestamp", time.Now().String())
	
	// Get item
	timestamp := localStorage.Call("getItem", "goWasmTimestamp").String()
	fmt.Println("Stored timestamp:", timestamp)
	
	// Create a result object
	result := js.Global().Get("Object").New()
	result.Set("timestamp", timestamp)
	result.Set("storageAvailable", true)
	
	return result
}

func main() {
	fmt.Println("DOM and Browser API WebAssembly Module Initialized")
	
	// Register functions to be called from JavaScript
	js.Global().Set("createCanvasAnimation", js.FuncOf(CreateCanvasAnimation))
	js.Global().Set("fetchData", js.FuncOf(FetchData))
	js.Global().Set("storageExample", js.FuncOf(StorageExample))
	
	// Keep the program running
	<-make(chan struct{})
}

Example HTML usage:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Go WASM DOM Example</title>
    <script src="wasm_exec.js"></script>
    <script>
        // Initialize Go WASM
        const go = new Go();
        WebAssembly.instantiateStreaming(fetch("dom_api.wasm"), go.importObject)
            .then((result) => {
                go.run(result.instance);
                
                // Create animation
                const stopAnimation = createCanvasAnimation("animation-container");
                
                // Fetch data example
                fetchData("https://jsonplaceholder.typicode.com/todos/1", function(data) {
                    console.log("Fetched data:", data);
                    document.getElementById("fetch-result").textContent =
                        JSON.stringify(data, null, 2);
                });
                
                // Storage example
                const storageResult = storageExample();
                document.getElementById("storage-result").textContent =
                    "Timestamp: " + storageResult.timestamp;
                
                // Add stop button functionality
                document.getElementById("stop-button").addEventListener("click", function() {
                    stopAnimation();
                    this.disabled = true;
                });
            });
    </script>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
        .section { margin: 20px 0; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
        pre { background-color: #f5f5f5; padding: 10px; border-radius: 4px; overflow: auto; }
    </style>
</head>
<body>
    <h1>Go WebAssembly DOM and Browser API Examples</h1>
    
    <div class="section">
        <h2>Canvas Animation</h2>
        <div id="animation-container"></div>
        <button id="stop-button">Stop Animation</button>
    </div>
    
    <div class="section">
        <h2>Fetch API Example</h2>
        <pre id="fetch-result">Loading...</pre>
    </div>
    
    <div class="section">
        <h2>Storage API Example</h2>
        <div id="storage-result"></div>
    </div>
</body>
</html>

Handling Events and Callbacks

Managing events and callbacks between Go and JavaScript requires careful consideration:

package main

import (
	"fmt"
	"syscall/js"
	"time"
)

// EventEmitter provides a way to register and trigger events
type EventEmitter struct {
	listeners map[string][]js.Func
}

// NewEventEmitter creates a new event emitter
func NewEventEmitter() *EventEmitter {
	return &EventEmitter{
		listeners: make(map[string][]js.Func),
	}
}

// On registers an event listener
func (e *EventEmitter) On(event string, listener js.Func) {
	if _, ok := e.listeners[event]; !ok {
		e.listeners[event] = make([]js.Func, 0)
	}
	e.listeners[event] = append(e.listeners[event], listener)
}

// Off removes an event listener
func (e *EventEmitter) Off(event string, listener js.Func) {
	if listeners, ok := e.listeners[event]; ok {
		for i, l := range listeners {
			// Compare function references
			if l.Value == listener.Value {
				// Remove the listener
				e.listeners[event] = append(listeners[:i], listeners[i+1:]...)
				break
			}
		}
	}
}

// Emit triggers an event
func (e *EventEmitter) Emit(event string, args ...interface{}) {
	if listeners, ok := e.listeners[event]; ok {
		for _, listener := range listeners {
			listener.Invoke(args...)
		}
	}
}

// Release releases all event listeners
func (e *EventEmitter) Release() {
	for _, listeners := range e.listeners {
		for _, listener := range listeners {
			listener.Release()
		}
	}
	e.listeners = make(map[string][]js.Func)
}

// Global event emitter
var emitter = NewEventEmitter()

// JavaScript wrapper functions
func jsOn(this js.Value, args []js.Value) interface{} {
	if len(args) != 2 || args[0].Type() != js.TypeString || !args[1].InstanceOf(js.Global().Get("Function")) {
		return "Error: Expected (event, callback) arguments"
	}
	
	event := args[0].String()
	callback := js.FuncOf(func(this js.Value, callbackArgs []js.Value) interface{} {
		return args[1].Invoke(callbackArgs...)
	})
	
	emitter.On(event, callback)
	return nil
}

func jsOff(this js.Value, args []js.Value) interface{} {
	if len(args) != 2 || args[0].Type() != js.TypeString || !args[1].InstanceOf(js.Global().Get("Function")) {
		return "Error: Expected (event, callback) arguments"
	}
	
	event := args[0].String()
	callback := js.FuncOf(func(this js.Value, callbackArgs []js.Value) interface{} {
		return args[1].Invoke(callbackArgs...)
	})
	
	emitter.Off(event, callback)
	callback.Release()
	return nil
}

func jsEmit(this js.Value, args []js.Value) interface{} {
	if len(args) < 1 || args[0].Type() != js.TypeString {
		return "Error: Expected event name as first argument"
	}
	
	event := args[0].String()
	eventArgs := make([]interface{}, len(args)-1)
	for i := 1; i < len(args); i++ {
		eventArgs[i-1] = args[i]
	}
	
	emitter.Emit(event, eventArgs...)
	return nil
}

// Example: Long-running task with progress updates
func LongRunningTask(this js.Value, args []js.Value) interface{} {
	totalSteps := 10
	if len(args) > 0 && args[0].Type() == js.TypeNumber {
		totalSteps = args[0].Int()
	}
	
	// Create a channel to signal completion
	done := make(chan struct{})
	
	// Start the task in a goroutine
	go func() {
		for i := 1; i <= totalSteps; i++ {
			// Simulate work
			time.Sleep(500 * time.Millisecond)
			
			// Report progress
			progress := float64(i) / float64(totalSteps)
			emitter.Emit("progress", progress, i, totalSteps)
		}
		
		// Signal completion
		emitter.Emit("complete", "Task completed successfully")
		close(done)
	}()
	
	// Return a function to cancel the task
	return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		// Signal completion early
		select {
		case <-done:
			// Task already completed
		default:
			emitter.Emit("cancel", "Task cancelled by user")
			close(done)
		}
		return nil
	})
}

func main() {
	fmt.Println("Event Handling WebAssembly Module Initialized")
	
	// Register JavaScript functions
	js.Global().Set("on", js.FuncOf(jsOn))
	js.Global().Set("off", js.FuncOf(jsOff))
	js.Global().Set("emit", js.FuncOf(jsEmit))
	js.Global().Set("startLongRunningTask", js.FuncOf(LongRunningTask))
	
	// Keep the program running
	<-make(chan struct{})
}

Example JavaScript usage:

// Register event listeners
on("progress", function(progress, step, total) {
    console.log(`Progress: ${Math.round(progress * 100)}% (Step ${step}/${total})`);
    document.getElementById("progress-bar").style.width = `${progress * 100}%`;
    document.getElementById("progress-text").textContent = `${Math.round(progress * 100)}%`;
});

on("complete", function(message) {
    console.log("Task completed:", message);
    document.getElementById("status").textContent = message;
    document.getElementById("start-button").disabled = false;
});

on("cancel", function(message) {
    console.log("Task cancelled:", message);
    document.getElementById("status").textContent = message;
    document.getElementById("start-button").disabled = false;
});

// Start button click handler
document.getElementById("start-button").addEventListener("click", function() {
    this.disabled = true;
    document.getElementById("status").textContent = "Task running...";
    
    // Start the task and get the cancel function
    const cancelTask = startLongRunningTask(20); // 20 steps
    
    // Set up cancel button
    document.getElementById("cancel-button").onclick = function() {
        cancelTask();
        this.disabled = true;
    };
});

Performance Optimization Techniques

While WebAssembly offers significant performance advantages over JavaScript, optimizing Go WASM applications requires specific techniques to achieve the best possible performance.

Memory Management Optimization

Efficient memory management is crucial for WebAssembly performance:

package main

import (
	"fmt"
	"syscall/js"
	"unsafe"
)

// MemoryPool implements a simple object pool to reduce allocations
type MemoryPool struct {
	pool []interface{}
	size int
}

// NewMemoryPool creates a new memory pool with the specified size
func NewMemoryPool(size int, factory func() interface{}) *MemoryPool {
	pool := &MemoryPool{
		pool: make([]interface{}, size),
		size: size,
	}
	
	// Pre-allocate objects
	for i := 0; i < size; i++ {
		pool.pool[i] = factory()
	}
	
	return pool
}

// Get retrieves an object from the pool
func (p *MemoryPool) Get() interface{} {
	if len(p.pool) == 0 {
		// Pool is empty, create a new object
		return nil
	}
	
	// Get the last object
	obj := p.pool[len(p.pool)-1]
	p.pool = p.pool[:len(p.pool)-1]
	return obj
}

// Put returns an object to the pool
func (p *MemoryPool) Put(obj interface{}) {
	if len(p.pool) < p.size {
		p.pool = append(p.pool, obj)
	}
}

// Vector3 represents a 3D vector
type Vector3 struct {
	X, Y, Z float64
}

// Reset resets the vector to zero
func (v *Vector3) Reset() {
	v.X = 0
	v.Y = 0
	v.Z = 0
}

// Set sets the vector components
func (v *Vector3) Set(x, y, z float64) {
	v.X = x
	v.Y = y
	v.Z = z
}

// Add adds another vector to this vector
func (v *Vector3) Add(other *Vector3) {
	v.X += other.X
	v.Y += other.Y
	v.Z += other.Z
}

// Global vector pool
var vectorPool = NewMemoryPool(1000, func() interface{} {
	return &Vector3{}
})

// Example: Particle system with pooled objects
func CreateParticleSystem(this js.Value, args []js.Value) interface{} {
	particleCount := 1000
	if len(args) > 0 && args[0].Type() == js.TypeNumber {
		particleCount = args[0].Int()
	}
	
	// Create typed arrays for particle data
	// This allows direct sharing of memory between Go and JavaScript
	positions := js.Global().Get("Float64Array").New(particleCount * 3)
	velocities := js.Global().Get("Float64Array").New(particleCount * 3)
	
	// Update function that minimizes allocations
	updateFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		// Get delta time
		dt := 0.016 // Default to 16ms
		if len(args) > 0 && args[0].Type() == js.TypeNumber {
			dt = args[0].Float()
		}
		
		// Update particles without allocations
		for i := 0; i < particleCount; i++ {
			// Get position and velocity
			px := positions.Index(i*3).Float()
			py := positions.Index(i*3+1).Float()
			pz := positions.Index(i*3+2).Float()
			
			vx := velocities.Index(i*3).Float()
			vy := velocities.Index(i*3+1).Float()
			vz := velocities.Index(i*3+2).Float()
			
			// Update position
			px += vx * dt
			py += vy * dt
			pz += vz * dt
			
			// Bounce off walls
			if px > 10 || px < -10 {
				vx = -vx
			}
			if py > 10 || py < -10 {
				vy = -vy
			}
			if pz > 10 || pz < -10 {
				vz = -vz
			}
			
			// Update arrays
			positions.SetIndex(i*3, px)
			positions.SetIndex(i*3+1, py)
			positions.SetIndex(i*3+2, pz)
			
			velocities.SetIndex(i*3, vx)
			velocities.SetIndex(i*3+1, vy)
			velocities.SetIndex(i*3+2, vz)
		}
		
		return nil
	})
	
	// Initialize particles
	for i := 0; i < particleCount; i++ {
		// Use pooled vector for initialization
		pos := vectorPool.Get().(*Vector3)
		vel := vectorPool.Get().(*Vector3)
		
		// Set random position and velocity
		pos.Set(
			(js.Global().Get("Math").Call("random").Float()-0.5)*20,
			(js.Global().Get("Math").Call("random").Float()-0.5)*20,
			(js.Global().Get("Math").Call("random").Float()-0.5)*20,
		)
		
		vel.Set(
			(js.Global().Get("Math").Call("random").Float()-0.5)*2,
			(js.Global().Get("Math").Call("random").Float()-0.5)*2,
			(js.Global().Get("Math").Call("random").Float()-0.5)*2,
		)
		
		// Copy to typed arrays
		positions.SetIndex(i*3, pos.X)
		positions.SetIndex(i*3+1, pos.Y)
		positions.SetIndex(i*3+2, pos.Z)
		
		velocities.SetIndex(i*3, vel.X)
		velocities.SetIndex(i*3+1, vel.Y)
		velocities.SetIndex(i*3+2, vel.Z)
		
		// Return vectors to pool
		pos.Reset()
		vel.Reset()
		vectorPool.Put(pos)
		vectorPool.Put(vel)
	}
	
	// Create result object
	result := js.Global().Get("Object").New()
	result.Set("positions", positions)
	result.Set("velocities", velocities)
	result.Set("update", updateFunc)
	
	return result
}

// Example: Direct memory manipulation for performance
func CreateImageProcessor(this js.Value, args []js.Value) interface{} {
	if len(args) < 1 || !args[0].InstanceOf(js.Global().Get("Uint8ClampedArray")) {
		return "Error: Expected Uint8ClampedArray argument"
	}
	
	// Get image data
	imageData := args[0]
	length := imageData.Length()
	
	// Create processor object
	processor := js.Global().Get("Object").New()
	
	// Add grayscale method
	processor.Set("grayscale", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		// Process image data in chunks for better performance
		const chunkSize = 1024
		
		for offset := 0; offset < length; offset += chunkSize*4 {
			end := offset + chunkSize*4
			if end > length {
				end = length
			}
			
			// Process a chunk
			for i := offset; i < end; i += 4 {
				r := imageData.Index(i).Int()
				g := imageData.Index(i+1).Int()
				b := imageData.Index(i+2).Int()
				
				// Calculate grayscale value
				gray := uint8((r*299 + g*587 + b*114) / 1000)
				
				// Set RGB channels to gray
				imageData.SetIndex(i, gray)
				imageData.SetIndex(i+1, gray)
				imageData.SetIndex(i+2, gray)
				// Alpha channel (i+3) remains unchanged
			}
		}
		
		return nil
	}))
	
	// Add invert method
	processor.Set("invert", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		// Process image data in chunks for better performance
		const chunkSize = 1024
		
		for offset := 0; offset < length; offset += chunkSize*4 {
			end := offset + chunkSize*4
			if end > length {
				end = length
			}
			
			// Process a chunk
			for i := offset; i < end; i += 4 {
				// Invert RGB channels
				imageData.SetIndex(i, 255-imageData.Index(i).Int())
				imageData.SetIndex(i+1, 255-imageData.Index(i+1).Int())
				imageData.SetIndex(i+2, 255-imageData.Index(i+2).Int())
				// Alpha channel (i+3) remains unchanged
			}
		}
		
		return nil
	}))
	
	return processor
}

func main() {
	fmt.Println("Performance Optimization WebAssembly Module Initialized")
	
	// Register JavaScript functions
	js.Global().Set("createParticleSystem", js.FuncOf(CreateParticleSystem))
	js.Global().Set("createImageProcessor", js.FuncOf(CreateImageProcessor))
	
	// Keep the program running
	<-make(chan struct{})
}

Minimizing JavaScript/Go Boundary Crossings

Each call between JavaScript and Go incurs overhead, so minimizing these crossings is important:

package main

import (
	"fmt"
	"syscall/js"
)

// BatchProcessor demonstrates batching operations to reduce JS/Go boundary crossings
func CreateBatchProcessor(this js.Value, args []js.Value) interface{} {
	processor := js.Global().Get("Object").New()
	
	// Bad approach: Process items one at a time
	processor.Set("processItemsIndividually", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		if len(args) < 1 || !args[0].InstanceOf(js.Global().Get("Array")) {
			return "Error: Expected array argument"
		}
		
		items := args[0]
		length := items.Length()
		results := js.Global().Get("Array").New(length)
		
		// Process each item individually (many JS/Go crossings)
		for i := 0; i < length; i++ {
			item := items.Index(i)
			
			// Process the item
			result := processItem(item)
			
			// Store the result
			results.SetIndex(i, result)
		}
		
		return results
	}))
	
	// Good approach: Process items in a batch
	processor.Set("processItemsBatch", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		if len(args) < 1 || !args[0].InstanceOf(js.Global().Get("Array")) {
			return "Error: Expected array argument"
		}
		
		items := args[0]
		length := items.Length()
		results := js.Global().Get("Array").New(length)
		
		// Extract all items at once
		goItems := make([]interface{}, length)
		for i := 0; i < length; i++ {
			goItems[i] = items.Index(i).Interface()
		}
		
		// Process all items in Go
		goResults := processItemsBatch(goItems)
		
		// Return all results at once
		for i, result := range goResults {
			results.SetIndex(i, result)
		}
		
		return results
	}))
	
	return processor
}

// Process a single item
func processItem(item js.Value) interface{} {
	// Example processing: square a number
	if item.Type() == js.TypeNumber {
		value := item.Float()
		return value * value
	}
	return item
}

// Process multiple items in a batch
func processItemsBatch(items []interface{}) []interface{} {
	results := make([]interface{}, len(items))
	
	for i, item := range items {
		// Example processing: square a number
		switch v := item.(type) {
		case float64:
			results[i] = v * v
		default:
			results[i] = item
		}
	}
	
	return results
}

// Example: Transferring large data efficiently
func CreateDataTransferer(this js.Value, args []js.Value) interface{} {
	transferer := js.Global().Get("Object").New()
	
	// Transfer data using typed arrays for efficiency
	transferer.Set("processLargeData", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		if len(args) < 1 || !args[0].InstanceOf(js.Global().Get("Float64Array")) {
			return "Error: Expected Float64Array argument"
		}
		
		// Get the typed array
		data := args[0]
		length := data.Length()
		
		// Create a result array of the same size
		result := js.Global().Get("Float64Array").New(length)
		
		// Process the data directly without copying
		for i := 0; i < length; i++ {
			// Example processing: square root
			value := data.Index(i).Float()
			result.SetIndex(i, value * value)
		}
		
		return result
	}))
	
	return transferer
}

func main() {
	fmt.Println("Boundary Crossing Optimization WebAssembly Module Initialized")
	
	// Register JavaScript functions
	js.Global().Set("createBatchProcessor", js.FuncOf(CreateBatchProcessor))
	js.Global().Set("createDataTransferer", js.FuncOf(CreateDataTransferer))
	
	// Keep the program running
	<-make(chan struct{})
}

Performance Benchmarking: WASM vs JavaScript

Comparing WebAssembly and JavaScript performance helps identify where WASM provides the most benefit:

package main

import (
	"fmt"
	"math"
	"syscall/js"
	"time"
)

// BenchmarkResult represents the result of a benchmark
type BenchmarkResult struct {
	Name           string
	ExecutionTimeMs float64
	OperationsPerSec float64
}

// RunBenchmark runs a benchmark function multiple times and returns statistics
func RunBenchmark(name string, iterations int, fn func()) BenchmarkResult {
	// Warm up
	for i := 0; i < 5; i++ {
		fn()
	}
	
	// Run benchmark
	start := time.Now()
	for i := 0; i < iterations; i++ {
		fn()
	}
	elapsed := time.Since(start)
	
	// Calculate statistics
	executionTimeMs := float64(elapsed.Milliseconds()) / float64(iterations)
	operationsPerSec := 1000.0 / executionTimeMs
	
	return BenchmarkResult{
		Name:           name,
		ExecutionTimeMs: executionTimeMs,
		OperationsPerSec: operationsPerSec,
	}
}

// CreateBenchmarkSuite creates a benchmark suite
func CreateBenchmarkSuite(this js.Value, args []js.Value) interface{} {
	suite := js.Global().Get("Object").New()
	
	// Benchmark: Matrix multiplication
	suite.Set("runMatrixMultiplication", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		size := 100
		iterations := 10
		
		if len(args) > 0 && args[0].Type() == js.TypeNumber {
			size = args[0].Int()
		}
		if len(args) > 1 && args[1].Type() == js.TypeNumber {
			iterations = args[1].Int()
		}
		
		// Create matrices
		matrixA := make([][]float64, size)
		matrixB := make([][]float64, size)
		matrixC := make([][]float64, size)
		
		for i := 0; i < size; i++ {
			matrixA[i] = make([]float64, size)
			matrixB[i] = make([]float64, size)
			matrixC[i] = make([]float64, size)
			
			for j := 0; j < size; j++ {
				matrixA[i][j] = float64(i + j)
				matrixB[i][j] = float64(i - j)
			}
		}
		
		// Run Go benchmark
		goBenchmark := RunBenchmark("Go Matrix Multiplication", iterations, func() {
			// Matrix multiplication
			for i := 0; i < size; i++ {
				for j := 0; j < size; j++ {
					sum := 0.0
					for k := 0; k < size; k++ {
						sum += matrixA[i][k] * matrixB[k][j]
					}
					matrixC[i][j] = sum
				}
			}
		})
		
		// Create result object
		result := js.Global().Get("Object").New()
		result.Set("name", goBenchmark.Name)
		result.Set("executionTimeMs", goBenchmark.ExecutionTimeMs)
		result.Set("operationsPerSec", goBenchmark.OperationsPerSec)
		
		return result
	}))
	
	// Benchmark: Fibonacci calculation
	suite.Set("runFibonacci", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		n := 40
		iterations := 10
		
		if len(args) > 0 && args[0].Type() == js.TypeNumber {
			n = args[0].Int()
		}
		if len(args) > 1 && args[1].Type() == js.TypeNumber {
			iterations = args[1].Int()
		}
		
		// Run Go benchmark
		goBenchmark := RunBenchmark("Go Fibonacci", iterations, func() {
			fibonacci(n)
		})
		
		// Create result object
		result := js.Global().Get("Object").New()
		result.Set("name", goBenchmark.Name)
		result.Set("executionTimeMs", goBenchmark.ExecutionTimeMs)
		result.Set("operationsPerSec", goBenchmark.OperationsPerSec)
		
		return result
	}))
	
	// Benchmark: Image processing
	suite.Set("runImageProcessing", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		width := 1000
		height := 1000
		iterations := 5
		
		if len(args) > 0 && args[0].Type() == js.TypeNumber {
			width = args[0].Int()
		}
		if len(args) > 1 && args[1].Type() == js.TypeNumber {
			height = args[1].Int()
		}
		if len(args) > 2 && args[2].Type() == js.TypeNumber {
			iterations = args[2].Int()
		}
		
		// Create image data
		imageData := make([]uint8, width * height * 4)
		for i := 0; i < width*height*4; i += 4 {
			imageData[i] = uint8(i % 256)     // R
			imageData[i+1] = uint8((i+85) % 256) // G
			imageData[i+2] = uint8((i+170) % 256) // B
			imageData[i+3] = 255               // A
		}
		
		// Run Go benchmark
		goBenchmark := RunBenchmark("Go Image Processing", iterations, func() {
			// Apply a simple blur filter
			for y := 1; y < height-1; y++ {
				for x := 1; x < width-1; x++ {
					idx := (y*width + x) * 4
					
					// Average with neighbors
					for c := 0; c < 3; c++ {
						sum := int(imageData[idx+c])
						sum += int(imageData[idx-4+c])  // left
						sum += int(imageData[idx+4+c])  // right
						sum += int(imageData[idx-width*4+c]) // up
						sum += int(imageData[idx+width*4+c]) // down
						
						imageData[idx+c] = uint8(sum / 5)
					}
				}
			}
		})
		
		// Create result object
		result := js.Global().Get("Object").New()
		result.Set("name", goBenchmark.Name)
		result.Set("executionTimeMs", goBenchmark.ExecutionTimeMs)
		result.Set("operationsPerSec", goBenchmark.OperationsPerSec)
		
		return result
	}))
	
	return suite
}

// Recursive Fibonacci implementation
func fibonacci(n int) int {
	if n <= 1 {
		return n
	}
	return fibonacci(n-1) + fibonacci(n-2)
}

func main() {
	fmt.Println("Benchmark WebAssembly Module Initialized")
	
	// Register JavaScript functions
	js.Global().Set("createBenchmarkSuite", js.FuncOf(CreateBenchmarkSuite))
	
	// Keep the program running
	<-make(chan struct{})
}

Example JavaScript benchmark comparison:

// Create benchmark suite
const benchmarkSuite = createBenchmarkSuite();

// JavaScript matrix multiplication implementation
function jsMatrixMultiplication(size) {
    // Create matrices
    const matrixA = Array(size).fill().map((_, i) =>
        Array(size).fill().map((_, j) => i + j));
    const matrixB = Array(size).fill().map((_, i) =>
        Array(size).fill().map((_, j) => i - j));
    const matrixC = Array(size).fill().map(() => Array(size).fill(0));
    
    // Matrix multiplication
    for (let i = 0; i < size; i++) {
        for (let j = 0; j < size; j++) {
            let sum = 0;
            for (let k = 0; k < size; k++) {
                sum += matrixA[i][k] * matrixB[k][j];
            }
            matrixC[i][j] = sum;
        }
    }
    
    return matrixC;
}

// JavaScript Fibonacci implementation
function jsFibonacci(n) {
    if (n <= 1) return n;
    return jsFibonacci(n - 1) + jsFibonacci(n - 2);
}

// Run benchmarks and compare
function runBenchmarks() {
    // Matrix multiplication benchmark
    console.log("Running matrix multiplication benchmark...");
    const size = 100;
    
    console.time("JS Matrix Multiplication");
    jsMatrixMultiplication(size);
    console.timeEnd("JS Matrix Multiplication");
    
    const wasmMatrixResult = benchmarkSuite.runMatrixMultiplication(size);
    console.log(`WASM Matrix Multiplication: ${wasmMatrixResult.executionTimeMs.toFixed(2)}ms`);
    
    // Fibonacci benchmark
    console.log("\nRunning Fibonacci benchmark...");
    const n = 40;
    
    console.time("JS Fibonacci");
    jsFibonacci(n);
    console.timeEnd("JS Fibonacci");
    
    const wasmFibResult = benchmarkSuite.runFibonacci(n);
    console.log(`WASM Fibonacci: ${wasmFibResult.executionTimeMs.toFixed(2)}ms`);
    
    // Display performance comparison
    document.getElementById("benchmark-results").innerHTML = `
        <h3>Performance Comparison</h3>
        <table>
            <tr>
                <th>Benchmark</th>
                <th>JavaScript</th>
                <th>WebAssembly</th>
                <th>Speedup</th>
            </tr>
            <tr>
                <td>Matrix Multiplication</td>
                <td>${jsMatrixTime.toFixed(2)}ms</td>
                <td>${wasmMatrixResult.executionTimeMs.toFixed(2)}ms</td>
                <td>${(jsMatrixTime / wasmMatrixResult.executionTimeMs).toFixed(2)}x</td>
            </tr>
            <tr>
                <td>Fibonacci(40)</td>
                <td>${jsFibTime.toFixed(2)}ms</td>
                <td>${wasmFibResult.executionTimeMs.toFixed(2)}ms</td>
                <td>${(jsFibTime / wasmFibResult.executionTimeMs).toFixed(2)}x</td>
            </tr>
        </table>
    `;
}

Production Deployment and Best Practices

Deploying Go WebAssembly applications to production environments requires careful consideration of several factors to ensure optimal performance, reliability, and user experience.

Binary Size Optimization

WebAssembly binaries can become large, especially with the Go runtime included. Here are techniques to reduce binary size:

// main.go
package main

import (
	"syscall/js"
)

// Minimal Go WASM application
func main() {
	js.Global().Set("goAdd", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		if len(args) != 2 {
			return "Error: Expected exactly 2 arguments"
		}
		
		a := args[0].Int()
		b := args[1].Int()
		return a + b
	}))
	
	// Keep the program running
	select {}
}

Build with size optimization flags:

GOOS=js GOARCH=wasm go build -ldflags="-s -w" -o main.wasm main.go

Further optimize with additional tools:

# Install wasm-opt
npm install -g wasm-opt

# Optimize the WebAssembly binary
wasm-opt -Oz -o optimized.wasm main.wasm

Loading Strategies

Efficient loading strategies improve user experience:

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Go WASM Application</title>
    <script>
        // Show loading indicator
        document.addEventListener("DOMContentLoaded", function() {
            document.getElementById("loading").style.display = "block";
        });
        
        // Progressive loading strategy
        const wasmImport = {
            // Staged loading with progress reporting
            loadWasm: async function() {
                // Check for WebAssembly support
                if (!('WebAssembly' in window)) {
                    document.getElementById("error").textContent =
                        "WebAssembly is not supported in your browser";
                    document.getElementById("error").style.display = "block";
                    document.getElementById("loading").style.display = "none";
                    return;
                }
                
                try {
                    // Fetch WebAssembly dependencies
                    const goWasmExec = await fetch("wasm_exec.js");
                    const goWasmExecText = await goWasmExec.text();
                    eval(goWasmExecText);
                    
                    // Initialize Go runtime
                    const go = new Go();
                    
                    // Fetch and instantiate the WebAssembly module
                    const wasmResponse = await fetch("main.wasm");
                    const wasmBuffer = await wasmResponse.arrayBuffer();
                    const wasmInstance = await WebAssembly.instantiate(wasmBuffer, go.importObject);
                    
                    // Execute the WebAssembly module
                    go.run(wasmInstance.instance);
                    
                    // Hide loading indicator
                    document.getElementById("loading").style.display = "none";
                    document.getElementById("app").style.display = "block";
                    
                    // Initialize application
                    initApp();
                } catch (err) {
                    console.error("Failed to load WebAssembly module:", err);
                    document.getElementById("error").textContent =
                        "Failed to load WebAssembly module: " + err.message;
                    document.getElementById("error").style.display = "block";
                    document.getElementById("loading").style.display = "none";
                }
            }
        };
        
        // Start loading when the page is ready
        document.addEventListener("DOMContentLoaded", wasmImport.loadWasm);
        
        // Initialize application after WASM is loaded
        function initApp() {
            // Application initialization code
            console.log("WebAssembly module loaded successfully");
            
            // Test the exported function
            const result = goAdd(21, 21);
            document.getElementById("result").textContent = `21 + 21 = ${result}`;
        }
    </script>
</head>
<body>
    <div id="loading" style="display: none;">
        Loading WebAssembly module...
        <div class="progress-bar">
            <div class="progress"></div>
        </div>
    </div>
    
    <div id="error" style="display: none; color: red;"></div>
    
    <div id="app" style="display: none;">
        <h1>Go WebAssembly Application</h1>
        <p id="result"></p>
    </div>
</body>
</html>

Cross-Browser Compatibility

Ensuring compatibility across browsers:

// browser-compatibility.js
function checkWasmSupport() {
    const report = {
        supported: false,
        features: {
            basic: false,
            streaming: false,
            threads: false,
            simd: false,
            exceptions: false,
            tailCall: false,
            referenceTypes: false,
            multiValue: false,
            bulkMemory: false,
            simd128: false
        }
    };
    
    // Check basic WebAssembly support
    if (typeof WebAssembly === 'object') {
        report.supported = true;
        report.features.basic = true;
        
        // Check for streaming compilation
        report.features.streaming = typeof WebAssembly.instantiateStreaming === 'function';
        
        // Check for threads
        report.features.threads = typeof SharedArrayBuffer === 'function';
        
        // Check for SIMD support
        WebAssembly.compile(new Uint8Array([
            0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x05, 0x01,
            0x60, 0x00, 0x01, 0x7b, 0x03, 0x02, 0x01, 0x00, 0x07, 0x08, 0x01,
            0x04, 0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x0a, 0x09, 0x01, 0x07,
            0x00, 0xfd, 0x0f, 0x00, 0x00, 0x0b
        ])).then(() => {
            report.features.simd = true;
        }).catch(() => {
            report.features.simd = false;
        });
    }
    
    return report;
}

// Polyfill for browsers without streaming instantiation
if (!WebAssembly.instantiateStreaming) {
    WebAssembly.instantiateStreaming = async (resp, importObject) => {
        const source = await (await resp).arrayBuffer();
        return await WebAssembly.instantiate(source, importObject);
    };
}

// Load appropriate version based on browser capabilities
async function loadAppropriateWasmVersion() {
    const support = checkWasmSupport();
    
    if (!support.supported) {
        // Load fallback JavaScript version
        loadJavaScriptFallback();
        return;
    }
    
    if (support.features.simd) {
        // Load optimized SIMD version
        loadWasmVersion('main-simd.wasm');
    } else {
        // Load standard version
        loadWasmVersion('main.wasm');
    }
}

Error Handling and Debugging

Effective error handling and debugging are crucial for WebAssembly applications:

package main

import (
	"errors"
	"fmt"
	"syscall/js"
)

// ErrorResponse represents a structured error response
type ErrorResponse struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Details string `json:"details,omitempty"`
}

// NewErrorResponse creates a new error response
func NewErrorResponse(code int, message string, details string) ErrorResponse {
	return ErrorResponse{
		Code:    code,
		Message: message,
		Details: details,
	}
}

// ToJSValue converts the error response to a JavaScript object
func (e ErrorResponse) ToJSValue() js.Value {
	result := js.Global().Get("Object").New()
	result.Set("code", e.Code)
	result.Set("message", e.Message)
	if e.Details != "" {
		result.Set("details", e.Details)
	}
	return result
}

// ErrorHandler wraps a function with error handling
func ErrorHandler(fn func(js.Value, []js.Value) (interface{}, error)) js.Func {
	return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		result, err := fn(this, args)
		if err != nil {
			// Log error on Go side
			fmt.Printf("Error: %v\n", err)
			
			// Convert to JavaScript error object
			var errorResp ErrorResponse
			
			// Check for custom error types
			switch e := err.(type) {
			case *ValidationError:
				errorResp = NewErrorResponse(400, "Validation Error", e.Error())
			case *BusinessLogicError:
				errorResp = NewErrorResponse(500, "Business Logic Error", e.Error())
			default:
				errorResp = NewErrorResponse(500, "Internal Error", err.Error())
			}
			
			// Return structured error to JavaScript
			return errorResp.ToJSValue()
		}
		return result
	})
}

// Custom error types
type ValidationError struct {
	Field   string
	Message string
}

func (e *ValidationError) Error() string {
	return fmt.Sprintf("Field '%s': %s", e.Field, e.Message)
}

type BusinessLogicError struct {
	Operation string
	Message   string
}

func (e *BusinessLogicError) Error() string {
	return fmt.Sprintf("Operation '%s' failed: %s", e.Operation, e.Message)
}

// Example function with error handling
func divide(this js.Value, args []js.Value) (interface{}, error) {
	// Validate arguments
	if len(args) < 2 {
		return nil, &ValidationError{
			Field:   "arguments",
			Message: "Expected 2 arguments",
		}
	}
	
	// Extract values
	a := args[0].Float()
	b := args[1].Float()
	
	// Business logic validation
	if b == 0 {
		return nil, &BusinessLogicError{
			Operation: "division",
			Message:   "Cannot divide by zero",
		}
	}
	
	// Perform operation
	return a / b, nil
}

// Debug logging function
func setupDebugLogging() js.Func {
	return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		if len(args) < 2 {
			return nil
		}
		
		level := args[0].String()
		message := args[1].String()
		
		// Additional context if provided
		context := ""
		if len(args) > 2 && args[2].Type() == js.TypeObject {
			context = fmt.Sprintf(" - Context: %v", args[2])
		}
		
		// Log based on level
		switch level {
		case "debug":
			fmt.Printf("[DEBUG] %s%s\n", message, context)
		case "info":
			fmt.Printf("[INFO] %s%s\n", message, context)
		case "warn":
			fmt.Printf("[WARN] %s%s\n", message, context)
		case "error":
			fmt.Printf("[ERROR] %s%s\n", message, context)
		}
		
		return nil
	})
}

// Setup console redirection for debugging
func setupConsoleRedirection() {
	js.Global().Get("console").Set("goLog", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		if len(args) > 0 {
			fmt.Println(args[0].String())
		}
		return nil
	}))
	
	// Inject JavaScript to redirect console.log
	js.Global().Get("eval").Invoke(`
		(function() {
			const originalLog = console.log;
			console.log = function(...args) {
				if (typeof goLog === 'function') {
					goLog(args.map(arg => String(arg)).join(' '));
				}
				return originalLog.apply(console, args);
			};
		})();
	`)
}

func main() {
	fmt.Println("Initializing WebAssembly module with error handling")
	
	// Set up error handling and debugging
	js.Global().Set("goDivide", ErrorHandler(divide))
	js.Global().Set("goDebugLog", setupDebugLogging())
	setupConsoleRedirection()
	
	// Register stack trace capture
	js.Global().Set("captureGoStackTrace", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		// This is a simplified version - in a real app you'd use runtime.Stack
		return "Go Stack Trace: main.main -> main.captureGoStackTrace"
	}))
	
	// Keep the program running
	<-make(chan struct{})
}

JavaScript side for error handling:

// Error handling in JavaScript
function handleGoFunction(goFunc, ...args) {
    try {
        const result = goFunc(...args);
        
        // Check if result is an error object
        if (result && typeof result === 'object' && result.code !== undefined) {
            console.error(`Go error (${result.code}): ${result.message}`);
            if (result.details) {
                console.error(`Details: ${result.details}`);
            }
            
            // Display error to user
            showError(result.message);
            return null;
        }
        
        return result;
    } catch (jsError) {
        console.error("JavaScript error:", jsError);
        
        // Capture Go stack trace if available
        if (typeof captureGoStackTrace === 'function') {
            const goStack = captureGoStackTrace();
            console.error("Go stack trace:", goStack);
        }
        
        // Display error to user
        showError("An unexpected error occurred");
        return null;
    }
}

// Example usage
function calculateAndDisplay() {
    const a = parseFloat(document.getElementById("valueA").value);
    const b = parseFloat(document.getElementById("valueB").value);
    
    // Use the error handling wrapper
    const result = handleGoFunction(goDivide, a, b);
    
    if (result !== null) {
        document.getElementById("result").textContent = `Result: ${result}`;
    }
}

// Debug logging
function logWithContext(level, message, context) {
    // Log to browser console
    console[level](message, context);
    
    // Also log to Go side
    if (typeof goDebugLog === 'function') {
        goDebugLog(level, message, context);
    }
}

// Example debug usage
logWithContext('info', 'Application initialized', { timestamp: Date.now() });
logWithContext('debug', 'Rendering component', { component: 'Calculator' });

Conclusion

Go WebAssembly represents a significant advancement in web application development, offering a powerful alternative to JavaScript for performance-critical components. Throughout this article, we’ve explored the complete lifecycle of Go WASM applications—from compilation to production deployment.

The ability to compile Go code to WebAssembly opens new possibilities for web developers. Computationally intensive tasks that would typically struggle in JavaScript can now leverage Go’s performance characteristics while running directly in the browser. This capability is particularly valuable for applications involving complex calculations, data processing, graphics rendering, or any scenario where performance is paramount.

We’ve seen how Go’s strong type system and concurrency model can be effectively utilized in browser environments through WebAssembly. The interoperability patterns we’ve explored demonstrate that Go and JavaScript can work together seamlessly, combining their respective strengths. JavaScript handles DOM manipulation and user interface concerns, while Go manages complex business logic and performance-critical operations.

As WebAssembly continues to evolve with new features like interface types, garbage collection, and threading, the integration between Go and browsers will become even more powerful and streamlined. The current need for JavaScript glue code will diminish, and the development experience will improve further.

For developers looking to push the boundaries of web application performance, Go WebAssembly provides a compelling path forward. By following the patterns, optimization techniques, and best practices outlined in this article, you can harness the full potential of this technology combination to deliver exceptional user experiences that were previously unattainable in browser environments.

The future of web development increasingly includes multiple languages working in concert, each applied where its strengths provide the most benefit. Go WebAssembly stands as an excellent example of this multi-language approach, bringing Go’s performance and reliability to the ubiquitous platform of the web browser.

Andrew
Andrew

Andrew is a visionary software engineer and DevOps expert with a proven track record of delivering cutting-edge solutions that drive innovation at Ataiva.com. As a leader on numerous high-profile projects, Andrew brings his exceptional technical expertise and collaborative leadership skills to the table, fostering a culture of agility and excellence within the team. With a passion for architecting scalable systems, automating workflows, and empowering teams, Andrew is a sought-after authority in the field of software development and DevOps.

Tags

Recent Posts