Examples
Go libraries. GOX components. Code for your next project.
Sass
Compile styles into files for the browser.
Source
1$accent: #8250df;
2$space: 20px;
3
4.sass-demo {
5 padding: $space;
6 border: 2px solid $accent;
7 border-radius: 12px;
8
9 strong {
10 color: $accent;
11 }
12}
Result
Variables + nesting.
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/bep/golibsass/[email protected]
GoLibSass includes LibSass and requires CGO plus a C++ compiler. LibSass is deprecated: this example covers classic SCSS variables and nesting, not modern Sass modules such as @use. Dart Sass requires a separate executable.
2. Copy the component
Save the complete GOX source from the Code tab as pages/@components/sass.gox.
3. Create the source file
content/examples/card.scss
1$accent: #8250df;
2$space: 20px;
3
4.sass-demo {
5 padding: $space;
6 border: 2px solid $accent;
7 border-radius: 12px;
8
9 strong {
10 color: $accent;
11 }
12}
This example uses SCSS, the Sass syntax with braces and semicolons. The indented .sass syntax omits them. Both panels show the same .scss source; the browser receives compiled CSS.
Use it in your page
module index
import (
"github.com/gox-lang/gox"
styles "@components/sass.gox"
)
func GET(ctx *gox.Context) gox.Page {
return <main>
<&styles.Sass src="content/examples/card.scss" />
<div class="sass-demo"><strong>Hello, Sass!</strong></div>
</main>
}
When the source changes
The memory cache avoids reading or compiling on repeated renders. After replacing the file, invalidate its entry:
styles.Invalidate("content/examples/card.scss") 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.
pages/@components/sass.gox
This is the complete component used by the live preview. Copy this single GOX file into your project.
module sasscomponent
import (
"crypto/sha256"
"fmt"
"log"
"os"
"path/filepath"
"sync"
"github.com/bep/golibsass/libsass"
"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 Sass(src string) gox.Component {
url, err := Build(src)
if err != nil {
log.Printf("Sass: %v", err)
return nil
}
return <link rel="stylesheet" href={{ url }} />
}
// 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("sass-v1")...))
name := fmt.Sprintf("%x.css", hash)
directory := filepath.Join("public", "generated", "sass")
destination := filepath.Join(directory, name)
url := "/generated/sass/" + 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) {
compiler, err := libsass.New(libsass.Options{OutputStyle: libsass.CompressedStyle})
if err != nil {
return nil, err
}
result, err := compiler.Execute(string(source))
if err != nil {
return nil, err
}
return []byte(result.CSS), 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("Sass %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()
}()
}