DEV Community

kasu
kasu

Posted on

Go's template engine “templ” is convenient (it also works with TinyGo)

Synopsis

I wanted to run an application in Go that returned plain HTML,
I decided to use Cloudflare Workers, which can convert it to Wasm and deploy it.

I recommend syumai/workers's template for deploying Go applications to Cloudflare Workers.

syumai/workers: Go package to run an HTTP server on Cloudflare Workers.

Go + text/template

First, let's build using text/template in a straightforward manner.

ls -lh . /build
   total 15656
   -rwxr-xr-x 1 ergofriend staff 7.6M 8 8 20:12 app.wasm
   -rw-r--r-- 1 ergofriend staff 1.2K 8 8 20:12 shim.mjs
   -rw-r--r-- 1 ergofriend staff 16K 8 8 20:12 wasm_exec.js
   -rw-r--r-- 1 ergofriend staff 160B 8 8 20:12 worker.mjs
Enter fullscreen mode Exit fullscreen mode

It was almost 8 MB.

https://developers.cloudflare.com/workers/platform/limits/#account-plan-limits

Cloudflare Workers has worker size limits depending on the plan.

The free slot is 1MB, and the paid slot ($5~) is 10MB.

Even with the final limit based on size after compression, it will be tough to fit into the free quota starting at 8MB.

TinyGo + text/template

So I decided to switch to TinyGo, which is supposed to be for WebAssembly (Wasm).

After building, the size is about 0.75 MB, which seems to fit into the free frame.

ls -lh . /build
total 1160
-rwxr-xr-x 1 ergofriend staff 556K 8 8 20:23 app.wasm
-rw-r--r-- 1 ergofriend staff 1.2K 8 8 20:23 shim.mjs
-rw-r--r-- 1 ergofriend staff 15K 8 8 20:23 wasm_exec.js
-rw-r--r-- 1 ergofriend staff 160B 8 8 20:23 worker.mjs
Enter fullscreen mode Exit fullscreen mode

Oops!

But here is where tragedy strikes.

When I try to access the built application, I get an error.

[wrangler:inf] GET / 200 OK (35ms)[ERROR] Uncaught (in response) RuntimeError: unreachable

      at main.runtime._panic (wasm://wasm/main-0022bc46:wasm-function[35]:0x2b4a)
      at main.(*text/template.state).evalField
  (wasm://wasm/main-0022bc46:wasm-function[540]:0x6c5f4)
      at main.(*text/template.state).evalFieldChain
  (wasm://wasm/main-0022bc46:wasm-function[531]:0x697fe)
      at main.(*text/template.state).evalFieldNode
  (wasm://wasm/main-0022bc46:wasm-function[530]:0x6959a)
      at main.(*text/template.state).evalPipeline
  (wasm://wasm/main-0022bc46:wasm-function[535]:0x6a1d2)
      at main.(*text/template.state).walk (wasm://wasm/main-0022bc46:wasm-function[569]:0x72cdd)
      at main.(*text/template.state).walk (wasm://wasm/main-0022bc46:wasm-function[569]:0x730b0)
      at main.main$1 (wasm://wasm/main-0022bc46:wasm-function[261]:0x2ac52)
      at main.(net/http.HandlerFunc).ServeHTTP
  (wasm://wasm/main-0022bc46:wasm-function[463]:0x5973a)
      at
  main.interface:{ServeHTTP:func:{named:net/http.ResponseWriter,pointer:named:net/http.Request}{}}.ServeHTTP$invoke
  (wasm://wasm/main-0022bc46:wasm-function[459]:0x56f72)

panic: unimplemented: (reflect.Value).MethodByName()
Enter fullscreen mode Exit fullscreen mode

The method MethodByName, which is called when a template variable is passed in template.ExecuteTemplate(), is not yet It seems to be unimplemented.

Since TinyGo is a subset of the original, there are many unsupported features,
I found that text/template was calling unsupported methods.

https://github.com/tinygo-org/tinygo/blob/1154212c15e6e97048e122068730dab5a1a9427f/src/reflect/type.go#L1086-L1088

Now, I would like to be cool and say p-r for TinyGo, but this time I'm going to look for a quick alternative.

TEMPL

So I was looking for another template engine to replace text/template and came across templ.

GitHub logo a-h / templ

A language for writing HTML user interfaces in Go.

templ

An HTML templating language for Go that has great developer tooling.

templ

Documentation

See user documentation at https://templ.guide

Go Reference xc compatible Go Coverage Go Report Card

Tasks

build

Build a local version.

go run ./get-version > .version
cd cmd/templ
go build
Enter fullscreen mode Exit fullscreen mode

nix-update-gomod2nix

gomod2nix
Enter fullscreen mode Exit fullscreen mode

install-snapshot

Build and install current version.

# Remove templ from the non-standard ~/bin/templ path
# that this command previously used.
rm -f ~/bin/templ
# Clear LSP logs.
rm -f cmd/templ/lspcmd/*.txt
# Update version.
go run ./get-version > .version
# Install to $GOPATH/bin or $HOME/go/bin
cd cmd/templ && go install
Enter fullscreen mode Exit fullscreen mode

build-snapshot

Use goreleaser to build the command line binary using goreleaser.

goreleaser build --snapshot --clean
Enter fullscreen mode Exit fullscreen mode

generate

Run templ generate using local version.

go run ./cmd/templ generate -include-version=false
Enter fullscreen mode Exit fullscreen mode

test

Run Go tests.

go run ./get-version > .version
go run ./cmd/templ generate -include-version=false
go test ./...
Enter fullscreen mode Exit fullscreen mode

test-short

Run Go tests.

go run ./get-version > .version
go run ./cmd/templ generate -include-version=false
go test ./... -short
Enter fullscreen mode Exit fullscreen mode

test-cover

Run…




Features

Here is a summary of TEMPL.

Own DSL

It is not difficult to write a unique program, but it can be written like templ ≈ Go + JSX.

templ hello() {
  <div>Welcome back!
}
Enter fullscreen mode Exit fullscreen mode

It looks like a very familiar writing style.

templ login(isLoggedIn bool) {
  if isLoggedIn {
    @hello() <! -- @ to use other components -->.
  } else {
    <input name="login" type="button" value="Log in"/>
  }
}
Enter fullscreen mode Exit fullscreen mode

When you use it, you call Go functions generated from the DSL by templ generate on a component-by-component basis.

func login(isLoggedIn bool) templ.Component {
  return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5
  ...
}
Enter fullscreen mode Exit fullscreen mode
Handle("/", templ.Handler(login(false))))
Enter fullscreen mode Exit fullscreen mode

The function of the generated component inherits the argument types defined in the template, so it can be safely called.
Unlike some ExecuteTemplate, this is safe.

VSCode Extensions

https://marketplace.visualstudio.com/items?itemName=a-h.templ

Syntax Highlight and LSP completion will be very useful.

TinyGo + templ

Just to be sure, I checked and found that it only depends on reflect's TypeOf, which is already implemented in TinyGo.

https://github.com/tinygo-org/tinygo/blob/1154212c15e6e97048e122068730dab5a1a9427f/src/reflect/type.go#L494-L500

Now let's try using templ.
The build seemed to have enough room.

ls -lh . /build
total 1008
-rwxr-xr-x 1 ergofriend staff 477K 8 8 20:35 app.wasm
-rw-r--r-- 1 ergofriend staff 1.2K 8 8 20:35 shim.mjs
-rw-r--r-- 1 ergofriend staff 15K 8 8 20:35 wasm_exec.js
-rw-r--r-- 1 ergofriend staff 160B 8 8 20:35 worker.mjs
Enter fullscreen mode Exit fullscreen mode

Here is the actual application.

goworkers-demo.ergofriend.workers.dev

When accessed, the HTML can be returned without error.

> wrangler deploy
Total Upload: 493.48 KiB / gzip: 187.91 KiB
Total Upload: 493.48 KiB / gzip: 187.91 KiB
Enter fullscreen mode Exit fullscreen mode

The final deployed size is also 187.91 KiB, so there is plenty of room to expand the application.

This verification is left in this repository.
ergofriend/goworkers-demo


This article is a translation from Japanese.
https://ergofriend.hatenablog.com/entry/2024/08/08/230603

Top comments (0)