Vugu Files: Code (Go)

Go code can be included in your component with a <script type="application/x-go"> tag. This code is copied from your .vugu file into the resulting code generated .go file. It is an appropriate place to include structs and methods needed by your component. Any import statements for packages referenced throughout your vugu file must be included in this tag.

A component corresponds to a single, exported struct in Go. By default, the name of the file is converted from snake case to camel case. For example some-thing.vugu corresponds to a type SomeThing struct. If you do not define this struct in the package, the code generator will output its definition as an empty struct.

In writing Go code blocks for your components, it is important to observe the following naming conventions:

  • component-name.vugu results in a code generated file called component-name_vgen.go in the same directory
  • component-name.vugu implies a struct called ComponentName which corresponds to the type of the component.
  • ComponentName can and should have data and methods needed by the component.

Let's take a look at a working example of a component that makes more use of the Go code section and see all of this work together. This example shows a button which, when clicked, will cause the browser to fetch data from the CoinDesk Bitcoin Price Index API, parse the result, and display it.

root.vugu:

<div class="demo">
    <div vg-if='c.IsLoading'>Loading...</div>
    <div vg-if='len(c.PriceData.BPI) > 0'>
        <div>Updated: <span vg-content='c.PriceData.Time.Updated'></span></div>
        <ul>
            <li vg-for='c.PriceData.BPI' vg-key='key'>
                <span vg-content='key'></span> <span vg-content='fmt.Sprint(value.Symbol, value.RateFloat)'></span>
            </li>
        </ul>
    </div>
    <button @click="c.HandleClick(event)">Fetch Bitcoin Price Index</button>
</div>

<script type="application/x-go">
import "encoding/json"
import "net/http"
import "log"

type Root struct {
    PriceData bpi  `vugu:"data"`
    IsLoading bool `vugu:"data"`
}

type bpi struct {
    Time struct {
        Updated string `json:"updated"`
    } `json:"time"`
    BPI map[string]struct {
        Code string `json:"code"`
        Symbol string  `json:"symbol"`
        RateFloat float64 `json:"rate_float"`
    } `json:"bpi"`
}

func (c *Root) HandleClick(event vugu.DOMEvent) {

    c.PriceData = bpi{}

    ee := event.EventEnv()

    go func() {

        ee.Lock()
        c.IsLoading = true
        ee.UnlockRender()
        
        res, err := http.Get("https://api.coindesk.com/v1/bpi/currentprice.json")
        if err != nil {
            log.Printf("Error fetch()ing: %v", err)
            return
        }
        defer res.Body.Close()

        var newb bpi
        err = json.NewDecoder(res.Body).Decode(&newb)
        if err != nil {
            log.Printf("Error JSON decoding: %v", err)
            return
        }

        ee.Lock()
        defer ee.UnlockRender()
        c.PriceData = newb
        c.IsLoading = false

    }()
}

</script>
  • Since this example lives in root.vugu, the corresponding struct is Root. (And for example in a component in file sample-comp.vugu, this would be SampleComp.) This is where a component's instance data is stored.
  • On Root we have a variable called IsLoading of type bool. This provides a simple way to vg-if a div, which then shows only during loading. (More below on how IsLoading is updated.) The `vugu:"data"` struct tag indicates that this variable should be examined when determining if this component should be re-rendered. (See Modification Tracking for more info.)
  • A struct bpi is defined for the data we're getting from the API call. This makes it easy to read and display. Remember, components are converted to regular Go code, and heeding the fact that your code runs in the browser, many of the usual approaches you would do in any Go program apply here.
  • We're looping over one of the members of bpi using vg-for in our markup to display each individual item returned.
  • A handler for the DOM click event is enabled with @click. Notice that the method it calls is a member of c (of type *Root). This call does not have to be to a methd on c, but this is the most common case.
  • When the click handler is invoked, we start a goroutine to fetch data from the server in the background. It is important we don't block here and return from this method quickly.
  • The EventEnv is used to synchronize access to data, and works well from goroutines. When we modify any information on c or that could potentially have concurrency issues, you can use EventEnv.Lock() to get an exclusive lock, and EventEnv.UnlockRender() to release the lock and tell render loop that the page needs to be updated. See DOM Events for more info. As of this writing, goroutines do not execute concurrently in WebAssembly, but nonetheless we want to follow best practices regarding locking. The idiom of acquiring a Lock and then calling UnlockRender when done combines proper locking (and future-proofs our code for when parallel execution is available in WebAssembly) with a clear indication of when re-rendering should occur.

github.com/vugu/vugu/js

In order to ease the development of server-side page rendering along side client-side Vugu page rendering, the package github.com/vugu/vugu/js exists and is a lightweight wrapper around Go's syscall/js package, but with the distinct difference than it will compile on non-wasm platforms and all operations will return appropriate zero or error values (or in some cases panic). The point is that you don't have to separate out your files with a build tag every time you need to declare a variable of type js.Value.

All code generated Vugu code uses github.com/vugu/vugu/js instead of syscall/js and you are encouraged to do this as well. It's very lightweight and there are no known bugs.

Import Deduplication

Go code emitted from vugugen will have its import statements deduplicated.