Quick reference for using Templar's vendoring and SourceLoader features. See docs/vendoring.md for full documentation.
Templar supports loading templates from external sources (like GitHub repositories) using the @source/ prefix syntax. This enables:
- Sharing template libraries across projects
- Explicit dependency management
- Reproducible builds with lock files
In your templates directory:
sources:
goapplib:
url: github.com/panyam/goapplib
path: templates
ref: main
vendor_dir: templar_modules
search_paths:
- .
- ./templar_modulescd templates
templar getThis creates:
templates/
├── templar.yaml
├── templar.lock # Lock file (auto-generated)
├── templar_modules/
│ └── goapplib/ # Vendored templates
│ ├── BasePage.html
│ └── components/
└── MyPage.html # Your templates
Use @sourcename/ prefix to reference vendored templates:
{{# namespace "Base" "@goapplib/BasePage.html" #}}
^^^^^^^^^^
Source name from templar.yaml
{{ define "MyPage" }}
{{ template "Base:BasePage" . }}
{{ end }}import (
"path/filepath"
tmplr "github.com/panyam/templar"
)
templates := tmplr.NewTemplateGroup()
// CRITICAL: Use absolute path
configPath, _ := filepath.Abs(filepath.Join(TEMPLATES_FOLDER, "templar.yaml"))
sourceLoader, err := tmplr.NewSourceLoaderFromConfig(configPath)
if err != nil {
// Fallback to basic loader
templates.Loader = tmplr.NewFileSystemLoader(TEMPLATES_FOLDER)
} else {
templates.Loader = sourceLoader
}// CORRECT
configPath, _ := filepath.Abs(filepath.Join(TEMPLATES_FOLDER, "templar.yaml"))
sourceLoader, _ := tmplr.NewSourceLoaderFromConfig(configPath)
// WRONG - Relative paths may fail
sourceLoader, _ := tmplr.NewSourceLoaderFromConfig("./templates/templar.yaml")Why? The SourceLoader resolves vendor_dir and search_paths relative to the config file location. With relative paths, the working directory affects resolution.
<!-- CORRECT -->
{{# namespace "EL" "@goapplib/components/EntityListing.html" #}}
<!-- WRONG - Looks in search_paths, not vendored sources -->
{{# namespace "EL" "goapplib/components/EntityListing.html" #}}sources:
GoAppLib: # This name...
url: github.com/panyam/goapplib<!-- Must match exactly -->
{{# namespace "EL" "@GoAppLib/..." #}} <!-- Correct -->
{{# namespace "EL" "@goapplib/..." #}} <!-- Wrong - case mismatch -->If templar_modules/ isn't committed:
git clone myrepo
cd myrepo/templates
templar get # Fetch dependenciesCreates a SourceLoader from a templar.yaml file:
func NewSourceLoaderFromConfig(configPath string) (*SourceLoader, error)Searches for templar.yaml starting from a directory:
func NewSourceLoaderFromDir(dir string) (*SourceLoader, error)type VendorConfig struct {
Sources map[string]SourceConfig // Named sources
VendorDir string // Where vendored files live
SearchPaths []string // Template search order
RequireLock bool // Require lock file
}
type SourceConfig struct {
URL string // Repository URL
Path string // Subdirectory within repo
Version string // Semantic version (takes precedence)
Ref string // Git ref (branch/commit)
Include []string // Glob patterns to include
Exclude []string // Glob patterns to exclude
}The SourceLoader resolves templates in this order:
- @source/path - Look up source, resolve to vendored location
- ./relative/path - Relative to current template
- path - Search in
search_pathsorder
# templar.yaml
sources:
goapplib:
url: github.com/panyam/goapplib
path: templates
ref: main
vendor_dir: templar_modules
search_paths:
- .
- ./templar_modules{{# namespace "Base" "@goapplib/BasePage.html" #}}Resolves to:
./templar_modules/goapplib/BasePage.html
Note: The flat structure uses just the source name, not the full GitHub path.
Cause: Using @xyz/ but no source named xyz in config
Fix: Add source to templar.yaml
Cause: templar_modules not fetched, or wrong path
Fix: Run templar get, verify file exists in templar_modules
Cause: templar.yaml not found or relative path issue
Fix: Use filepath.Abs() for configPath
Cause: Using @xyz without a path
Fix: Use full path like @xyz/file.html
git add templar_modules/
git add templar.lockPros: Reproducible builds, no network needed at deploy time
echo "templar_modules/" >> .gitignore
git add templar.lockIn CI:
templar get # Fetches using lock filePros: Smaller repo, cleaner diffs
When building a tool that embeds templar as a library (e.g. slyds, a presentation tool), you can customize all file names and generated content using ToolInfo:
import tmplr "github.com/panyam/templar"
info := tmplr.ToolInfo{
Name: "slyds",
ConfigNames: []string{".slyds.yaml"},
VendorDir: "./.slyds-modules",
LockFile: ".slyds.lock",
FetchCmd: "slyds update",
ProjectURL: "https://github.com/panyam/slyds",
}
// Find config with custom names (searches ".slyds.yaml" instead of "templar.yaml")
configPath, err := tmplr.FindVendorConfigWithNames(dir, info.ConfigNames)
// Load config with custom defaults (VendorDir defaults to ".slyds-modules" if not specified)
config, err := tmplr.LoadVendorConfigWithDefaults(configPath, info)
// Write generated content branded for your tool
tmplr.WriteVendorReadmeFor(config.VendorDir, info)
tmplr.WriteLockFileFor(lockPath, lock, info)The original functions (FindVendorConfig, LoadVendorConfig, WriteVendorReadme, WriteLockFile) continue to work with templar's defaults — no breaking changes.
| Default (templar) | Configurable (embedding) |
|---|---|
FindVendorConfig(dir) |
FindVendorConfigWithNames(dir, names) |
LoadVendorConfig(path) |
LoadVendorConfigWithDefaults(path, info) |
WriteVendorReadme(dir) |
WriteVendorReadmeFor(dir, info) |
WriteLockFile(path, lock) |
WriteLockFileFor(path, lock, info) |
- excaliframe/site/templates/ - Simple site with goapplib dependency
- lilbattle/web/templates/ - Full app with multiple template sources