Skip to content

Latest commit

 

History

History
551 lines (432 loc) · 19.2 KB

File metadata and controls

551 lines (432 loc) · 19.2 KB

Template Vendoring with External Sources

Templar supports loading templates from external sources like GitHub repositories. This enables sharing template libraries across projects while maintaining explicit dependency management and reproducible builds.

Overview

┌─────────────────────────────────────────────────────────────────────────────┐
│  Your Project                                                               │
│                                                                             │
│  templates/                                                                 │
│  ├── pages/                                                                 │
│  │   └── dashboard.html    ──► {{# namespace "EL" "@goapplib/..." #}}       │
│  └── components/                                                            │
│      └── custom.html                                                        │
│                                                                             │
│  templar.yaml              ──► sources:                                     │
│                                  goapplib:                                  │
│                                    url: github.com/panyam/goapplib          │
│                                    path: templates                          │
│                                    ref: v1.2.0                              │
│                                                                             │
│  templar_modules/          ──► Vendored dependencies (after templar get)    │
│  └── github.com/                                                            │
│      └── panyam/                                                            │
│          └── goapplib/                                                      │
│              └── templates/                                                 │
│                  └── EntityListing.html                                     │
└─────────────────────────────────────────────────────────────────────────────┘

Quick Start

1. Configure Sources

Create a templar.yaml in your project root:

# Define external template sources
sources:
  goapplib:
    url: github.com/panyam/goapplib
    path: templates              # Subdirectory within repo
    ref: v1.2.0                  # Tag, branch, or commit hash

  shared:
    url: github.com/myorg/shared-templates
    ref: main

# Where vendored templates are stored
vendor_dir: ./templar_modules

# Template search paths (in order)
search_paths:
  - ./templates                  # Local templates first
  - ./templar_modules            # Then vendored dependencies

2. Fetch Dependencies

templar get

This downloads all configured sources to templar_modules/:

templar_modules/
├── github.com/
│   ├── panyam/
│   │   └── goapplib/
│   │       └── templates/
│   │           ├── EntityListing.html
│   │           └── components/
│   │               └── Grid.html
│   └── myorg/
│       └── shared-templates/
│           └── ...
└── templar.lock                 # Lock file with exact versions

3. Use in Templates

Reference external templates with the @sourcename prefix:

{{# namespace "EL" "@goapplib/components/EntityListing.html" #}}
{{# include "@shared/layouts/base.html" #}}

{{ define "MyPage" }}
    {{ template "EL:EntityListing" .Items }}
{{ end }}

Template Reference Syntax

┌─────────────────────────────────────────────────────────────────────────────┐
│  Reference Syntax                                                           │
│                                                                             │
│  ┌────────────────────────┬─────────────────────────────────────────────┐   │
│  │ Syntax                 │ Resolution                                  │   │
│  ├────────────────────────┼─────────────────────────────────────────────┤   │
│  │ "@goapplib/foo.html"   │ Vendored: templar_modules/.../foo.html      │   │
│  │ "./components/bar.html"│ Relative to current template                │   │
│  │ "layouts/base.html"    │ Searched in search_paths order              │   │
│  └────────────────────────┴─────────────────────────────────────────────┘   │
│                                                                             │
│  The @ prefix maps to a configured source in templar.yaml                   │
└─────────────────────────────────────────────────────────────────────────────┘

Resolution Example

With this configuration:

sources:
  goapplib:
    url: github.com/panyam/goapplib
    path: templates
    ref: v1.2.0

This template reference:

{{# namespace "EL" "@goapplib/components/EntityListing.html" #}}

Resolves to:

./templar_modules/github.com/panyam/goapplib/templates/components/EntityListing.html

CLI Commands

templar init - Initialize Configuration

# Create templar.yaml in current directory
templar init

# Overwrite existing configuration
templar init --force

This creates a minimal templar.yaml with sensible defaults and a templates/ directory.

templar get - Fetch Dependencies

# Fetch all configured sources
templar get

# Update to latest versions matching refs
templar get --update

# Fetch only a specific source
templar get @goapplib

# Verify local files match lock file
templar get --verify

# Show what would be fetched (dry run)
templar get --dry-run

templar sources - List Sources

# Show configured sources and their status
templar sources

# Output:
# SOURCE      URL                              REF      STATUS
# goapplib    github.com/panyam/goapplib       v1.2.0   ✓ vendored (abc123)
# shared      github.com/myorg/shared-templates main    ✗ not fetched

Configuration Reference

templar.yaml

# External template sources
sources:
  # Source name (used with @ prefix in templates)
  goapplib:
    # Repository URL (GitHub shorthand supported)
    url: github.com/panyam/goapplib

    # Subdirectory within repo containing templates (optional)
    path: templates

    # Git ref: tag, branch, or commit hash
    ref: v1.2.0

  # Another source example
  company-templates:
    url: github.com/mycompany/templates
    ref: main

# Directory for vendored templates (default: ./templar_modules)
vendor_dir: ./templar_modules

# Template search paths (in order of priority)
search_paths:
  - ./templates              # Check local first
  - ./templar_modules        # Then check vendored

# Optional: Require lock file for reproducible builds
require_lock: true

templar.lock

Auto-generated lock file with exact versions:

# AUTO-GENERATED - Do not edit manually
# Run 'templar get' to regenerate

version: 1
sources:
  goapplib:
    url: github.com/panyam/goapplib
    ref: v1.2.0
    resolved_commit: abc123def456789...
    fetched_at: 2024-12-08T10:30:00Z

  shared:
    url: github.com/myorg/shared-templates
    ref: main
    resolved_commit: def456abc789012...
    fetched_at: 2024-12-08T10:30:05Z

Deployment Strategies

Strategy 1: Vendor and Check In (Recommended)

Check vendored templates into version control for reproducible builds:

┌─────────────────────────────────────────────────────────────────────────────┐
│  Development                                                                │
│                                                                             │
│  1. templar get                    # Fetch dependencies                     │
│  2. git add templar_modules/       # Check in vendored files                │
│  3. git add templar.lock           # Check in lock file                     │
│  4. git commit                                                              │
│                                                                             │
├─────────────────────────────────────────────────────────────────────────────┤
│  Production                                                                 │
│                                                                             │
│  1. git clone / pull               # Get code + vendored templates          │
│  2. go build                       # Build app                              │
│  3. ./app                          # Run - no network needed                │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Pros:

  • Reproducible builds without network access
  • See template changes in diffs during code review
  • No external service dependencies during build

Cons:

  • Larger repository size
  • Potential merge conflicts when updating

Strategy 2: Lock File Only

Check in only the lock file, fetch during build:

┌─────────────────────────────────────────────────────────────────────────────┐
│  Development                                                                │
│                                                                             │
│  1. templar get                    # Fetch dependencies                     │
│  2. git add templar.lock           # Only check in lock file                │
│  3. echo "templar_modules/" >> .gitignore                                   │
│  4. git commit                                                              │
│                                                                             │
├─────────────────────────────────────────────────────────────────────────────┤
│  CI / Production Build                                                      │
│                                                                             │
│  1. git clone / pull                                                        │
│  2. templar get                    # Fetch using lock file                  │
│  3. go build                                                                │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Pros:

  • Smaller repository
  • No merge conflicts on vendored files

Cons:

  • Requires network during build
  • Depends on external service availability

Strategy 3: CI-Generated Vendor

Generate vendored files in CI, cache or artifact them:

# .github/workflows/build.yml
jobs:
  build:
    steps:
      - uses: actions/checkout@v4

      - name: Cache templar modules
        uses: actions/cache@v4
        with:
          path: templar_modules
          key: templar-${{ hashFiles('templar.lock') }}

      - name: Fetch templates
        run: templar get --verify || templar get

      - name: Build
        run: go build ./...

Comparison with Go Modules

┌─────────────────────────────────────────────────────────────────────────────┐
│  Concept              │ Go Modules          │ Templar Vendoring             │
├───────────────────────┼─────────────────────┼───────────────────────────────┤
│  Config file          │ go.mod              │ templar.yaml                  │
│  Lock file            │ go.sum              │ templar.lock                  │
│  Fetch command        │ go mod download     │ templar get                   │
│  Update command       │ go get -u           │ templar get --update          │
│  Vendor command       │ go mod vendor       │ (automatic with get)          │
│  Vendor directory     │ vendor/             │ templar_modules/              │
│  Reference syntax     │ import "pkg/..."    │ @source/path/...              │
└─────────────────────────────────────────────────────────────────────────────┘

Complete Example

Project Structure

myapp/
├── templar.yaml
├── templar.lock
├── templates/
│   ├── pages/
│   │   └── product-list.html
│   └── base.html
└── templar_modules/
    └── github.com/
        └── example/
            └── uikit/
                └── templates/
                    └── components/
                        └── card.html

templar.yaml

sources:
  uikit:
    url: github.com/example/uikit
    path: templates
    ref: v1.0.0

vendor_dir: ./templar_modules

search_paths:
  - ./templates
  - ./templar_modules

templates/pages/product-list.html

{{# include "base.html" #}}
{{# namespace "UI" "@uikit/components/card.html" #}}

{{/* Custom product card preview - shows product image */}}
{{ define "productPreview" }}
{{ if .ImageUrl }}
<img src="{{ .ImageUrl }}" alt="{{ .Name }}" class="w-full h-32 object-cover">
{{ else }}
<div class="w-full h-32 bg-gray-200 flex items-center justify-center">
    <span class="text-gray-400">No image</span>
</div>
{{ end }}
{{ end }}

{{/* Extend Card to use product-specific preview */}}
{{# extend "UI:Card" "ProductCard"
           "UI:CardPreview" "productPreview" #}}

{{# extend "UI:CardGrid" "ProductGrid"
           "UI:Card" "ProductCard" #}}

{{ define "content" }}
<main class="max-w-7xl mx-auto px-4 py-8">
    <h1>Products</h1>
    {{ template "ProductGrid" .Products }}
</main>
{{ end }}

{{ define "ProductListPage" }}
{{ template "base" . }}
{{ end }}

Go Code

package main

import (
    "github.com/panyam/templar"
)

func main() {
    // Create SourceLoader with configuration
    config := &templar.VendorConfig{
        Sources: map[string]templar.SourceConfig{
            "uikit": {
                URL:  "github.com/example/uikit",
                Path: "templates",
                Ref:  "v1.0.0",
            },
        },
        VendorDir:   "./templar_modules",
        SearchPaths: []string{"./templates"},
    }

    group := templar.NewTemplateGroup()
    group.Loader = templar.NewSourceLoader(config)

    // Load template - @uikit references resolve automatically
    tmpl := group.MustLoad("pages/product-list.html", "")

    // Render
    group.RenderHtmlTemplate(w, tmpl[0], "ProductListPage", data, nil)
}

Gotchas and Tips

1. Always run templar get after cloning

If vendored files aren't checked in, fetch them before building:

git clone myrepo
cd myrepo
templar get          # Fetch dependencies
go build ./...

2. Lock file should always be checked in

Even if you check in templar_modules/, the lock file ensures:

  • Exact commit hashes are recorded
  • templar get --verify can validate local files
  • Reproducible fetches if re-vendoring is needed

3. Use specific refs for stability

# Avoid (unstable):
sources:
  lib:
    url: github.com/example/lib
    ref: main                    # May change unexpectedly

# Prefer (stable):
sources:
  lib:
    url: github.com/example/lib
    ref: v1.2.3                  # Specific version
    # or
    ref: abc123def               # Specific commit

4. The @ prefix is required for external sources

{{/* WRONG - looks in search_paths */}}
{{# namespace "EL" "goapplib/components/EntityListing.html" #}}

{{/* CORRECT - looks up source "goapplib" */}}
{{# namespace "EL" "@goapplib/components/EntityListing.html" #}}

5. Source names are case-sensitive

sources:
  GoAppLib:              # This name...
    url: ...
{{/* Must match exactly */}}
{{# namespace "EL" "@GoAppLib/..." #}}    {{/* Correct */}}
{{# namespace "EL" "@goapplib/..." #}}    {{/* Wrong - case mismatch */}}

Debugging

Check source resolution

# See how a template path resolves
templar debug --trace templates/pages/WorldListingPage.html

# Output shows resolution chain:
# @goapplib/components/EntityListing.html
#   → source: goapplib
#   → url: github.com/panyam/goapplib
#   → path: templates
#   → resolved: templar_modules/github.com/panyam/goapplib/templates/components/EntityListing.html

Verify vendored files

# Check if local files match lock file
templar get --verify

# Output:
# ✓ goapplib: matches lock (abc123def)
# ✗ shared: modified locally (expected def456, found ghi789)

List what would be fetched

templar get --dry-run

# Output:
# Would fetch:
#   goapplib: github.com/panyam/goapplib@v1.2.0 → templar_modules/...
#   shared: github.com/myorg/shared@main → templar_modules/...

Library Embedding

When using templar as a library in another tool, all file names and generated content are customizable via ToolInfo. The WithNames, WithDefaults, and For function variants accept a ToolInfo to override templar's defaults. See the Integration Guide for details.