İçindekiler

GoUI, Go’nun native html/template katmanı üzerine Blade benzeri bir template motoru sunar. .goui.html dosyalarına tanıdık direktifler yazarsınız (@if, @extends, @component, …); uygulama açılışında bunlar bir kez native Go template’e çevrilir ve bellekte tutulur. Runtime’da string eval yoktur; Go ifade dili yeniden yazılmaz — {{ .Field }} pipeline’ları olduğu gibi kopyalanır, böylece context-aware auto-escaping korunur.

Ayrıca: RenderTemplate migrasyonu, örnek examples/counter-view.

1. Felsefe

Motor bir yapısal önişlemcidir:

GoUI’nin işihtml/template’e bırakılan
@if / @foreach / @extends / @include / @component{{ .Field }}, pipeline, eq, fonksiyonlar
Dizin → dot-path adlarıAuto-escaping
Derleme zamanı bağımlılık grafiğiÇalıştırma

2. Kurulum

  • Uzantı: .goui.html
  • Dot-path: views/pages/home.goui.html"pages.home" (Root’a göre)
import gouitemplate "github.com/zatrano/goui/template"

reg, err := gouitemplate.NewRegistry(gouitemplate.Config{
    Root:        "./views",
    StrictProps: true, // prod’da önerilir
})
if err != nil {
    log.Fatal(err)
}
defer reg.Close()

html, err := reg.Render("pages.home", data)

3. Direktif referansı

Koşullar

@if(.User.IsAdmin)
  <span>Admin</span>
@elseif(.User.Moderator)
  <span>Mod</span>
@else
  <span>User</span>
@endif

@unless(.Hidden)
  görünür
@endunless

Döngüler

Her zaman $ ile isimlendirin (. yeniden bağlamaya güvenmeyin):

@foreach(.Items as $item)
  <li>{{ $item.Name }}</li>
@empty
  <li>Yok</li>
@endforeach

@foreach(.Items as $key, $item)
  <li>{{ $key }}: {{ $item }}</li>
@endforeach

Switch

@switch(.Status)
@case("ok")
  OK
@break
@default
  Diğer
@endswitch

Çıktı

{{ .Name }}           <!-- escape -->
{!! .TrustedHTML !!}  <!-- ham; Güvenlik bölümüne bakın -->
{{-- yorum --}}
@@literal             <!-- tek @ yazar -->

Yardımcılar (BaseFuncMap)

{{ default "Guest" .User.Name }}
{{ dict "Type" "submit" "Label" "Save" }}
{{ list 1 2 3 }}

4. Layout (@extends / @section / @yield)

layouts/app.goui.html:

<html>
<head><title>@yield("title", "App")</title></head>
<body>
  @yield("content")
</body>
</html>

pages/home.goui.html:

@extends("layouts.app")
@section("title", "Home")
@section("content")
  <h1>Hoş geldiniz</h1>
@endsection

@extends varken kök seviyede yalnızca @section (ve boşluk) serbesttir.

5. Include

@include("partials.nav")
@include("partials.user", .User)
@includeIf("partials.optional")  <!-- yoksa derleme zamanında atlanır -->

Eksik @include hedefi NewRegistry’yi düşürür. @includeIf düşürmez.

6. Component ve slot

components/card.goui.html:

@props(Title string)
<div class="card">
  @if(.Slots.header)
    <header>{{ .Slots.header }}</header>
  @endif
  <div>{{ .DefaultSlot }}</div>
</div>

Çağıran:

@component("components.card", dict "Title" "Hi")
  @slot("header")
    {{ .PageTitle }}
  @endslot
  Varsayılan gövde
@endcomponent

Component içinde . bir Dot’tur: .Props.*, .Slots.name, .DefaultSlot. Slot gövdeleri çağıranın veri bağlamında render edilir.

İç içe component desteklenir.

7. @props ve StrictProps

@props(Name string, Count int = 0)

StrictProps: true iken NewRegistry, dosyadaki her .Props.X kullanımının beyan edildiğini kontrol eder (yazım hataları “did you mean …?” ile açılışta yakalanır). Kullanılmayan beyanlar reg.Warnings() ile yumuşak uyarıdır.

StrictProps false (varsayılan) ise bu kontroller çalışmaz.

8. Hot reload (geliştirme)

hub := ws.NewHub()
reg, err := gouitemplate.NewRegistry(gouitemplate.Config{
    Root:            "./views",
    WatchForChanges: true, // prod’da false
    OnReload: func() {
        hub.Broadcast(ws.PushMessage{
            Kind: "reload",
            Text: "templates updated",
        })
    },
    OnReloadError: func(err error) {
        log.Printf("template reload: %v", err)
    },
})
defer reg.Close()

template paketi ws import etmez; callback’i siz bağlarsınız. Başarısız reload son iyi derlemeyi korur.

9. ViewComponent entegrasyonu

type Counter struct {
    core.BaseComponent
    Count int
}

func (c *Counter) View() string { return "counter" }
func (c *Counter) Render() (string, error) {
    return "", gouitemplate.ErrViewRenderDirect
}

tmplReg, _ := gouitemplate.NewRegistry(gouitemplate.Config{Root: "./views"})
coreReg.Register("counter", gouitemplate.Wrap(tmplReg, func() core.Component {
    return &Counter{}
}))

Örnek: examples/counter-view.

10. Güvenlik

  • Tercihen {{ }} (escape’li).
  • {!! !!} / raw auto-escape’i kapatır — yalnızca güvenilir HTML.
  • Kullanıcı girdisini sanitize etmeden raw’dan geçirmeyin.

11. Blade karşılaştırması

BladeGoUI
@if / @foreach / @extendsAynı fikir → native html/template
@component / @slotDesteklenir (iki aşamalı render)
@includeIfDerleme zamanı
@propsOpt-in isim kontrolü (StrictProps)
@php / keyfi PHPYok (bilinçli tasarım) — template’de keyfi kod yok
İstek başına mtime cacheProcess ömrü boyunca bellek içi derleme

@php tarzı kaçışların olmaması kasıtlıdır: template veri + yapı, mantık Go’da kalır.

Performans

Tipik bir dizüstünde mertebe (go test ./template/ -bench=.):

İşlemKabaca maliyet
Render düz sayfadüşük µs / op
Render + extendsdüşük–orta µs / op
Render ~20 componentonlarca µs / op
NewRegistry ~100 dosyaonlarca–yüzlerce ms

Açılışta bir kez derleyin (veya hot reload); Render disk I/O yapmaz.