Vugu Program Structure

Vugu takes a number of steps to make the development process easy to rapidly get started and prototype user interfaces. That said, it's important to understand the overall program structure and how a Vugu program works (at least by default), so you can customize it to your needs when building a sophisticated application. The basics are fairly simple.

When you use the HTTP handler in development mode (as shown on the Getting Started page), upon page load Vugu will convert your .vugu files into .go and also by default attempt to generate a main_wasm.go if it does not exist. (You can edit main_wasm.go as needed. It is only a template to get your started. Vugu will not overwrite it.)

WebAssembly main()

Like any Go program, it starts with main(). The build constraint at the top (see Dual-Build below) indicates this is the WebAssembly entry point:

// +build wasm

package main

import (
	// ...
	"github.com/vugu/vugu"
)

func main() {

Root Component

To render a page to HTML, you need to have "root" component. This is the top level component that houses everything else. By default this component lives in root.vugu, gets code generated to root_vgen.go, which has a Root struct type. The generated main_wasm.go file creates an instance of this Root struct and uses it as the beginning of the tree of components to be rendered.

BuildEnv

BuildEnv keeps track of component state across render cycles. Component re-use from render to render and detecting which components are changed are handled here. You can get a new instance by calling vugu.NewBuildEnv.

JSRenderer

Once we have a root component instance, we need an environment. There are two environments currently implemented: the DOM renderer for use in WebAssembly applications, and the static renderer which can be used for server-side rendering and tests. domrender.JSRenderer is what performs the syncing of the virtual DOM from our root component and any nested components to the browser DOM.

Render Loop

The render loop is where the magic happens. Your components' virtual DOM output (see BuildOut) is generated and then synchronized with the browser's DOM to give you a matching HTML page. This involves various optimizations including keeping track of which components have changed and caching those that haven't.

for ok := true; ok; ok = renderer.EventWait() {
    buildResults := buildEnv.RunBuild(rootBuilder)
    err = renderer.Render(buildResults)
    if err != nil {
        panic(err)
    }
}

This will call RunBuild and then Render immediately the first time and then wait for renderer.EventWait() to return and render again.

EventWait will return false if it detects something wrong with the environment and the program should exit. This should release any resources and be a clean exit from the program when the page goes away.

main_wasm.go

If we take a look at the main_wasm.go file that is generated by default, it gives us a pretty good idea of how these fit together:

// +build wasm

package main

import (
    "log"
    "fmt"
    "flag"

    "github.com/vugu/vugu"
    "github.com/vugu/vugu/domrender"
)

func main() {

    mountPoint := flag.String("mount-point", "#vugu_mount_point", "The query selector for the mount point for the root component, if it is not a full HTML component")
    flag.Parse()

    fmt.Printf("Entering main(), -mount-point=%q\n", *mountPoint)
    defer fmt.Printf("Exiting main()\n")

    rootBuilder := &Root{}

    buildEnv, err := vugu.NewBuildEnv()
    if err != nil {
        log.Fatal(err)
    }

    renderer, err := domrender.NewJSRenderer(*mountPoint)
    if err != nil {
        log.Fatal(err)
    }
    defer renderer.Release()

    for ok := true; ok; ok = renderer.EventWait() {

        buildResults := buildEnv.RunBuild(rootBuilder)

        err = renderer.Render(buildResults)
        if err != nil {
            panic(err)
        }
    }
	
}

The Dual-Build Approach

The discussion above is only about the WebAssembly side of your application.

Using Go's build constraints it is easy to output two different executables from your same package directory. The common case is that you want a client-side build that compiles to WebAssembly (as discussed above), as well as a server-side executable to act as a web server. These each need different main() and likely other functions, but your components and other functionality should be available both in your WebAssembly output and in your server program.

This is why the main() function for your client-side application lives in main_wasm.go. The "_wasm" part indicates that the file should be included during a WebAssembly build. You can and should include a server-side main() in another file and use // +build !wasm at the top as the inverse build constraint. See Building and Distribution for more info.