Examples
Go libraries. GOX components. Code for your next project.
TypeScript
Transform TypeScript into browser-ready JavaScript.
Source
1const button = document.querySelector<HTMLButtonElement>("#ts-counter");
2let count: number = 0;
3
4button?.addEventListener("click", () => {
5 count += 1;
6 button.textContent = `Clicks: ${count}`;
7});
Result
Compilation runs in the background and writes a static file under public/generated. On the first visit the browser may receive a 404 before it is ready; reload after compilation. Errors are logged on the server.
1. Add the Go library
Run this command from your project root.
gox get github.com/evanw/esbuild/pkg/[email protected]
2. Copy the component
Save the complete GOX source from the Code tab as pages/@components/typescript.gox.
3. Create the source file
content/examples/counter.ts
1const button = document.querySelector<HTMLButtonElement>("#ts-counter");
2let count: number = 0;
3
4button?.addEventListener("click", () => {
5 count += 1;
6 button.textContent = `Clicks: ${count}`;
7});
Use it in your page
module index
import (
"github.com/gox-lang/gox"
scripts "@components/typescript.gox"
)
func GET(ctx *gox.Context) gox.Page {
return <main>
<button id="ts-counter" type="button">Clicks: 0</button>
<&scripts.Typescript src="content/examples/counter.ts" />
</main>
}
When the source changes
The memory cache avoids reading or compiling on repeated renders. After replacing the file, invalidate its entry:
scripts.Invalidate("content/examples/counter.ts") This is a single-file example: no dependency watching or multi-file bundling. public/generated must be writable. Existing generated files are reused; failed background jobs are removed from memory so later renders can retry.
esbuild removes TypeScript types and generates a JavaScript module. It does not type-check your program. The button and script must appear once per page.
pages/@components/typescript.gox
This is the complete component used by the live preview. Copy this single GOX file into your project.
module typescriptcomponent
import (
"crypto/sha256"
"fmt"
"log"
"os"
"path/filepath"
"sync"
"github.com/evanw/esbuild/pkg/api"
"github.com/gox-lang/gox"
)
type Job struct {
URL string
Ready bool
}
var cache map[string]*Job = make(map[string]*Job)
var cacheMu sync.RWMutex
var preparation sync.Mutex
var conversion sync.Mutex
func Typescript(src string) gox.Component {
url, err := Build(src)
if err != nil {
log.Printf("Typescript: %v", err)
return nil
}
return <script type="module" src={{ url }}></script>
}
// Single-file examples: project-owned local paths, with manual invalidation.
func Build(src string) (string, error) {
key := filepath.Clean(src)
cacheMu.RLock()
job, exists := cache[key]
cacheMu.RUnlock()
if exists {
return job.URL, nil
}
preparation.Lock()
defer preparation.Unlock()
cacheMu.RLock()
job, exists = cache[key]
cacheMu.RUnlock()
if exists {
return job.URL, nil
}
source, err := os.ReadFile(key)
if err != nil {
return "", err
}
hash := sha256.Sum256(append(source, []byte("typescript-v1")...))
name := fmt.Sprintf("%x.js", hash)
directory := filepath.Join("public", "generated", "typescript")
destination := filepath.Join(directory, name)
url := "/generated/typescript/" + name
if info, err := os.Stat(destination); err == nil && info.Mode().IsRegular() && info.Size() > 0 {
cacheMu.Lock()
cache[key] = &Job{URL: url, Ready: true}
cacheMu.Unlock()
return url, nil
}
job = &Job{URL: url}
cacheMu.Lock()
cache[key] = job
cacheMu.Unlock()
compile := Compile
Start(key, job, func() error {
output, err := compile(source)
if err != nil {
return err
}
if err := os.MkdirAll(directory, 0755); err != nil {
return err
}
file, err := os.CreateTemp(directory, ".asset-*")
if err != nil {
return err
}
defer os.Remove(file.Name())
defer file.Close()
if _, err := file.Write(output); err != nil {
return err
}
if err := file.Chmod(0644); err != nil {
return err
}
if err := file.Close(); err != nil {
return err
}
return os.Rename(file.Name(), destination)
})
return url, nil
}
func Compile(source []byte) ([]byte, error) {
result := api.Transform(string(source), api.TransformOptions{
Loader: api.LoaderTS,
Format: api.FormatESModule,
Target: api.ES2020,
MinifyWhitespace: true,
MinifySyntax: true,
})
if len(result.Errors) > 0 {
return nil, fmt.Errorf("TypeScript: %s", result.Errors[0].Text)
}
return result.Code, nil
}
func Invalidate(src string) {
preparation.Lock()
defer preparation.Unlock()
cacheMu.Lock()
defer cacheMu.Unlock()
delete(cache, filepath.Clean(src))
}
func Finish(key string, job *Job, err error) {
if err != nil {
log.Printf("Typescript %s: %v", key, err)
}
cacheMu.Lock()
defer cacheMu.Unlock()
if cache[key] != job {
return
}
if err != nil {
delete(cache, key)
return
}
job.Ready = true
}
func Start(key string, job *Job, work func() error) {
finish := Finish
lock := &conversion
go func() (err error) {
defer func() {
if recovered := recover(); recovered != nil {
err = fmt.Errorf("conversion panicked: %v", recovered)
}
finish(key, job, err)
}()
lock.Lock()
defer lock.Unlock()
return work()
}()
}