Introduction to the

Go-app is a library for writing progressive Web applications using GO + WebAssembly technology. WebAssembly is a new type of code that runs in modern browsers. WebAssembly technology has made great progress in the past two years. We can now write WebAssembly code in high-level languages such as C/C++/Rust/Go. I’m here to introduce Go-App, a library that makes it easy to write WebAssembly code using go.

Quick to use

The Go-app has high requirements for the Go language version (Go 1.14+) and must use the Go Module. Create a directory and initialize Go Module (Win10 + Git Bash) :

$ mkdir go-app && cd go-app
$ go mod init
Copy the code

Then download and install the Go-App package:

$ go get -u -v github.com/maxence-charriere/go-app/v6
Copy the code

For a detailed use of the Go Module, check out The Ultimate Primer on Go Modules from Fish Fry.

First, we’ll write a WebAssembly program:

package main

import "github.com/maxence-charriere/go-app/v6/pkg/app"

type Greeting struct {
  app.Compo
  name string
}

func (g *Greeting) Render(a) app.UI {
  return app.Div().Body(
    app.Main().Body(
      app.H1().Body(
        app.Text("Hello, "), app.If(g.name ! ="",
          app.Text(g.name),
        ).Else(
          app.Text("World"),
        ),
      ),
    ),
    app.Input().
      Value(g.name).
      Placeholder("What is your name?").
      AutoFocus(true).
      OnChange(g.OnInputChange),
  )
}

func (g *Greeting) OnInputChange(src app.Value, e app.Event) {
  g.name = src.Get("value").String()
  g.Update()
}

func main(a) {
  app.Route("/", &Greeting{})
  app.Run()
}
Copy the code

Components are used to divide functional modules in go-App, and app.Com Po must be embedded in each component structure. The component implements the Render() method, which is called when the component needs to be displayed to return to the displayed page. The Go-app uses a declarative syntax, so you can write HTML pages entirely using GO. The HTML section above is easier to understand. The above code also implements the function of an input box and adds a listener to it. The OnInputChange method is called whenever the contents of the input box are modified, and g.update () causes the component to be rerendered.

Finally, mount the component to the path /.

Once you have written a WebAssembly program, you need to cross-compile it into a. Wasm file:

$ GOARCH=wasm GOOS=js go build -o app.wasm
Copy the code

If compilation errors occur, use the go version command to check if go is 1.14 or later.

Next, we need to write a Go Web application that uses this app. Wasm:

package main

import (
  "log"
  "net/http"

  "github.com/maxence-charriere/go-app/v6/pkg/app"
)

func main(a) {
  h := &app.Handler{
    Title:  "Go-App",
    Author: "dj",}if err := http.ListenAndServe(": 8080", h); err ! =nil {
    log.Fatal(err)
  }
}
Copy the code

Go-app provides an app.handler structure that automatically looks for app.wasm in the same directory (which is why the target file is app.wasm). We then put the app. Wasm generated by the previous compilation into the same directory and execute the program:

$ go run main.go
Copy the code

Default display “Hello World” :

After you enter something in the input box, the display changes:

As you can see, the Go-App sets up some basic styles, web ICONS, etc.

Simple principle

GitHub provides a good example of how HTTP requests flow:

User requests go to the app.handler layer, which goes to app.wASM to perform routing logic and find static files on disk. The response is relayed back to the user via app.handler. The user then sees the page rendered by App.wasm. In fact, for the purposes of this article, we just need to write a Go Web program, and each time we write a new WebAssembly, copy the app.wasm file generated by the new compilation into the Go Web directory and rerun the program. Note that if the page does not refresh in a timely manner, caching may be to blame and try cleaning the browser cache.

component

Customizing a component is as simple as embedding the app.Com Po into the structure. Implement the Render() method to define the appearance of the component. App.Com Po actually has a default appearance, which we can view like this:

func main(a) {
  app.Route("/app", &app.Compo{})
  app.Run()
}
Copy the code

After compiling and generating app.wasm, the initial Go Web application does not need to be modified.

The event processing

In Quick Start, we also showed you how to use events. Use the declarative syntax app.input ().onchange (handler) to listen for content changes. The Event handler must be of type func (SRC app.Value, e app.Event), where app.Value is the trigger object and app.Event is the content of the Event. App. Value can be used to obtain information about input box contents and selection box options, etc. App. Event can be used to obtain information about events, such as mouse events, keyboard events or other events:

type ShowSelect struct {
  app.Compo
  option string
}

func (s *ShowSelect) Render(a) app.UI {
  return app.Div().Body(
    app.Main().Body(
      app.H1().Body(
        app.If(s.option == "",
          app.Text("Please select!"),
        ).Else(
          app.Text("You've selected "+s.option),
        ),
      ),
    ),
    app.Select().Body(
      app.Option().Body(
        app.Text("apple"),
      ),
      app.Option().Body(
        app.Text("orange"),
      ),
      app.Option().Body(
        app.Text("banana"),
      ),
    ).
      OnChange(s.OnSelectChange),
  )
}

func (s *ShowSelect) OnSelectChange(src app.Value, e app.Event) {
  s.option = src.Get("value").String()
  s.Update()
}

func main(a) {
  app.Route("/", &ShowSelect{})
  app.Run()
}
Copy the code

The code above shows a selection box that changes the text displayed when an option changes. When the initial:

After the choice:

Nested components

Components can be used in nesting, that is, using one component within another. Render internal components as part of external components:

type Greeting struct {
  app.Compo
}

func (g *Greeting) Render(a) app.UI {
  return app.P().Body(
    app.Text("Hello, "),
    &Name{name: "dj")}},type Name struct {
  app.Compo
  name string
}

func (n *Name) Render(a) app.UI {
  return app.Text(n.name)
}

func main(a) {
  app.Route("/", &Greeting{})
  app.Run()
}
Copy the code

The above code inserts a Name component in the component Greeting, which runs the display:

The life cycle

Go-app provides three life-cycle hook functions for a component:

  • OnMount: called when a component is inserted into the DOM;
  • OnNav: called when a component’s page is loaded and refreshed.
  • OnDismount: called when a component is removed from the page.

Such as:

type Foo struct {
  app.Compo
}

func (*Foo) Render(a) app.UI {
  return app.P().Body(
    app.Text("Hello World"),)}func (*Foo) OnMount(a) {
  fmt.Println("component mounted")}func (*Foo) OnNav(u *url.URL) {
  fmt.Println("component navigated:", u)
}

func (*Foo) OnDismount(a) {
  fmt.Println("component dismounted")}func main(a) {
  app.Route("/", &Foo{})
  app.Run()
}
Copy the code

Compile and run, open the page in the browser, open the browser console and observe the output:

component mounted
component navigated: http://localhost:8080/
Copy the code

Write HTML

In the previous example we saw how to write HTML pages using declarative syntax. Go-app provides related types for all standard HTML elements. The names of the methods that create these objects are also easy to remember, which is to capitalize the element names. For example app.div () creates a Div element, app.p () creates a P element, app.h1 () creates an H1 element, and so on. In go-App, these structures are exposed to the corresponding interface for developers to use, such as div to HTMLDiv interface:

type HTMLDiv interface{ Body(nodes ... Node) HTMLDiv Class(vstring) HTMLDiv
  ID(v string) HTMLDiv
  Style(k, v string) HTMLDiv

  OnClick(h EventHandler) HTMLDiv
  OnKeyPress(h EventHandler) HTMLDiv
  OnMouseOver(h EventHandler) HTMLDiv
}
Copy the code

You can see that each method returns the HTMLDiv itself, so chained calls are supported. These methods can be called to set various aspects of the element’s attributes:

  • Class: Add CSS Class;
  • ID: Sets the ID attribute.
  • Style: Set the built-in style;
  • Body: Sets the element content, which can be nested at will.divContained in theh1andp.pContained in theimgAnd so on;

And set event listening:

  • OnClick: Click event;
  • OnKeyPress: key event;
  • OnMouseOver: Mouse over event.

For example:

app.Div().Body(
  app.H1().Body(
    app.Text("Title"),
  ),
  app.P().ID("id").
    Class("content").Body(
      app.Text("something interesting"),),)Copy the code

Equivalent to HTML code:

<div>
  <h1>title</h1>
  <p id="id" class="content">
    something interesting
  </p>
</div>
Copy the code

Native element

We can write the HTML code directly in app.raw (), and app.raw () will generate the corresponding app.UI:

svg := app.Raw(` 
       
        
       `)
Copy the code

But this is unsafe because you don’t check the structure of the HTML.

conditions

We already used conditional statements in our original example, which correspond to three methods: If()/ElseIf()/Else().

If and ElseIf accept two arguments, the first of which is a bool. If true, the second argument (of type app.ui) is displayed, otherwise not.

Else must be used after If or ElseIf, and If none of the previous conditions are met, the app.UI passed in the Else method is displayed:

type ScoreUI struct {
  app.Compo
  score int
}

func (c *ScoreUI) Render(a) app.UI {
  return app.Div().Body(
    app.If(c.score >= 90,
      app.H1().
        Style("color"."green").
        Body(
          app.Text("Good!"),
        ),
    ).ElseIf(c.score >= 60,
      app.H1().
        Style("color"."orange").
        Body(
          app.Text("Pass!"),
        ),
    ).Else(
      app.H1().
        Style("color"."red").
        Body(
          app.Text("fail!"),
        ),
    ),
    app.Input().
      Value(c.score).
      Placeholder("Input your score?").
      AutoFocus(true).
      OnChange(c.OnInputChange),
  )
}

func (c *ScoreUI) OnInputChange(src app.Value, e app.Event) {
  score, _ := strconv.ParseUint(src.Get("value").String(), 10.32)
  c.score = int(score)
  c.Update()
}

func main(a) {
  app.Route("/", &ScoreUI{})
  app.Run()
}
Copy the code

Above we display the corresponding text according to the score entered, 90 and above shows the green Good! , display orange Pass between 60 and 90! , less than 60 shows red Fail! . Here is the result:

Range

Suppose we want to write an HTML list that currently has a slice of a string. Writing one by one is too cumbersome, inflexible, and error-prone. Here we can use the Range() method:

type RangeUI struct {
  app.Compo
  name string
}

func (*RangeUI) Render(a) app.UI {
  langs := []string{"Go"."JavaScript"."Python"."C"}
  return app.Ul().Body(
    app.Range(langs).Slice(func(i int) app.UI {
      return app.Li().Body(
        app.Text(langs[i]),
      )
    }),
  )
}

func main(a) {
  app.Route("/", &RangeUI{})
  app.Run()
}
Copy the code

Range() can generate an app.ui for each item in a slice or map, which is then tiled in the Body() method of an element.

Running results:

Context menu

In go-app, we can easily customize the pop-up menu by right-clicking and writing responses to menu items:

type ContextMenuUI struct {
  app.Compo
  name string
}

func (c *ContextMenuUI) Render(a) app.UI {
  return app.Div().Body(
    app.Text("Hello, World"),
  ).OnContextMenu(c.OnContextMenu)
}

func (*ContextMenuUI) OnContextMenu(src app.Value, event app.Event) {
  event.PreventDefault()

  app.NewContextMenu(
    app.MenuItem().
      Label("item 1").
      OnClick(func(src app.Value, e app.Event) {
        fmt.Println("item 1 clicked")
      }),
    app.MenuItem().Separator(),
    app.MenuItem().
      Label("item 2").
      OnClick(func(src app.Value, e app.Event) {
        fmt.Println("item 2 clicked")}),)}func main(a) {
  app.Route("/", &ContextMenuUI{})
  app.Run()
}
Copy the code

We call event.preventDefault () in OnContextMenu to prevent the default menu from popping up. See the results:

Click the menu item and observe the console output ~

app.Handler

We used the go-App built-in app.handler to handle client requests above. We set only two simple properties Author and Title. App.handler has a number of other fields that you can customize:

type Handler struct {
  Author string
  BackgroundColor string
  CacheableResources []string
  Description string
  Env Environment
  Icon Icon
  Keywords []string
  LoadingLabel string
  Name string
  RawHeaders []string
  RootDir string
  Scripts []string
  ShortName string
  Styles []string
  ThemeColor string
  Title string

  UseMinimalDefaultStyles bool
  Version string
}
Copy the code
  • Icon: Set the app icon.
  • Styles: CSS style file;
  • Scripts: JS script file.

CSS and JS files must be declared in app.handler. Here is an example app.handler:

h := &app.Handler{
  Name:        "Luck",
  Author:      "Maxence Charriere",
  Description: "Lottery numbers generator.",
  Icon: app.Icon{
    Default: "/web/icon.png",
  },
  Keywords: []string{
    "EuroMillions"."MEGA Millions"."Powerball",
  },
  ThemeColor:      "# 000000",
  BackgroundColor: "# 000000",
  Styles: []string{
    "/web/luck.css",
  },
  Version: "wIKiverSiON",}Copy the code

In this paper, the code

The WebAssembly code in this article is in its own directory. The Go Web demo code is in the Web directory. Go to a directory and compile with the following command:

$ GOARCH=wasm GOOS=js go build -o app.wasm
Copy the code

Then copy the generated app.wasm to the web directory:

$ cp app.wasm .. /web/Copy the code

Switch to the Web directory and start the server:

$ cd .. /web/ $go run main.go
Copy the code

conclusion

This article describes how to write a WebAssembly-based Web application using go-App. Some might find the way go-App writes HTML a bit cumbersome. But we can write a conversion program to turn normal HTML code into Go-app code, if you’re interested. WebAssembly technology deserves a lot of attention

If you find a fun and useful Go library, please Go to GitHub and submit issue😄

reference

  1. Go – GitHub:github.com/maxence-cha app…
  2. GitHub: github.com/darjun/go-d…

I

My blog is darjun.github. IO

Welcome to follow my wechat public account [GoUpUp], learn together, progress together ~