Migration from Sprig
Coming from Sprig and looking to use Sprout? You're in the right place for a complete guide on making the transition. This guideline will help you navigate the differences.
This page evolves with each version based on modifications and community feedback. Having trouble following this guide?
Open an issue to get help, and contribute to improving this guide for future users. 🌱 💜
Introduction
Sprout is a modern templating engine inspired by Sprig but with enhanced features and a more modular approach. Migrating from Sprig to Sprout involves understanding these differences and adjusting your templates and code accordingly.
Key Differences
1. Registry System
Sprig: Functions are globally available.
Sprout: Functions are grouped into registries for modularity. Each registry can be added to the handler as needed.
Migration Tip
List the registries needed for your project and register them with the handler. If you're unsure, you can safely register all built-in registries.
2. Handler and Function Management
Sprig: Functions are accessed directly.
Sprout: Functions are managed by a handler, allowing for better control over function availability, error handling, and logging.
Migration Top
Nothing to do, using the new handler is enough.
3. Error Handling
Sprig: Limited and inconsistent error handling, with some functions causing panics (see Panicking Functions), and not fully adhering to Go template standards.
Sprout: Offers configurable error handling strategies, including returning default values, or return error to stop template generation (default), providing a more consistent and flexible approach.
Migration Tip
You can learn mote about the safe strategy here: Safe Functions
4. Function Aliases
Sprig: Functions are accessed by a single name, deprecated functions are duplicated in code.
Sprout: Supports function aliases, allowing multiple names for the same function.
Migration Tip
Use WithAlias or WithAliases to set up function aliases as needed when creating your handler.
5. Following the Go Template Convention
Sprig: No error for naming convention are followed.
Sprout: Design to adhere closely to Go's native template conventions, ensuring compatibility and a consistent experience for developers familiar with Go templates. This adherence helps reduce surprises and ensures that templates behave as expected according to Go's standard practices.
Migration Tip Takes a look over renamed functions to change it in your template or use aliases.
How to Transition
Choose your migration path based on your situation:
Option A: New Projects or Breaking Changes Acceptable
If you're starting a new project or your users accept breaking changes, use Sprout directly:
This gives you all the improvements (bug fixes, proper piping support, better error handling) without any compatibility overhead.
Option B: Non-Breaking Migration for End-Users
If you have existing end-users and need a gradual migration path:
Phase 1: Replace Sprig with Sprigin
Sprigin provides full backward compatibility while logging deprecation warnings. Your end-users will see warnings in logs about deprecated functions and signature changes, giving them time to update their templates.
Phase 2: Keep Sprigin for X Versions/Months
Maintain sprigin for your defined deprecation period (e.g., 3-6 months or 2-3 versions) to:
Respect your breaking change policy
Allow end-users to see and act on deprecation warnings
Ensure a smooth transition
Phase 3: Switch to Sprout
Once your deprecation period ends, replace sprigin with sprout:
How to Transition for your end-users
You use Sprig or Sprout for end-users and want to migrate? Here's a detailed plan to ensure confidence during the migration:
Need more information? Contact maintainers or open a discussion on the repository.
The Sprigin Compatibility Layer
The sprigin package is specifically designed for this use case. It:
Supports both signatures: Automatically detects whether you're using the old Sprig signature (
get $dict "key") or the new Sprout piping signature ($dict | get "key")Logs deprecation warnings: When old signatures or deprecated functions are used, warnings are logged to inform your end-users
Preserves Sprig behavior: Bug-for-bug compatible to avoid breaking existing templates
Migration Steps
Communicate the Purpose of the Migration Explain the reasons for switching to Sprout, emphasizing improvements such as better performance, modular function registries, enhanced error handling, and new features like function notices and safe functions.
Replace Sprig with Sprigin Simply replace your import and function call:
Monitor Deprecation Warnings Sprigin logs warnings for deprecated functions and signature changes. Share these logs with your end-users so they can update their templates.
Keep Sprigin for X Versions/Months Maintain the compatibility layer according to your breaking change policy. We recommend 3-6 months or 2-3 versions.
Final Switch to Sprout Once your deprecation period ends and end-users have migrated their templates, switch to Sprout directly.
As a library developer, you can extend Sprout by creating your own function registry. Additionally, you can use notices to inform your end-users about important updates during template execution.
Our maintainers and collaborators can assist you if you have questions. Don't hesitate to open a discussion on GitHub!
Migrating Common Functions
Many functions in Sprig have direct equivalents in Sprout, but they might be organized differently or require registration in a handler.
Example: Simple Function Migration
Sprig:
Sprout:
Example: Using Aliases
Sprig:
Sprout:
You can continue to use the same function name inside your template
Panicking Functions
In Sprig, errors within certain functions cause a panic. Both Sprout and Sprigin fix all panics, returning errors properly instead.
Old Behavior (Sprig): Triggers a panic on error
New Behavior (Sprout & Sprigin): Returns nil or an empty value on error
Migration Tip
Whether you use sprout or sprigin, all panics are fixed. You can safely migrate without worrying about panics crashing your application.
Methods that previously caused a panic in Sprig:
DeepCopy
MustDeepCopy
ToRawJson
Append
Prepend
Concat
Chunk
Uniq
Compact
Slice
Without
Rest
Initial
Reverse
First
Last
Has
Dig
RandAlphaNumeric
RandAlpha
RandAscii
RandNumeric
RandBytes
Function-Specific Changes
Signature Changes (Argument Order)
Sprout reorders function arguments to support Go template piping conventions. The target (map/list) is now the last argument instead of the first.
If you use sprigin.FuncMap(), both signatures are supported automatically. Sprigin detects which signature you're using and logs a warning when the old Sprig signature is detected.
Map Functions: get, set, unset, hasKey, pick, omit
get
{{ get $dict "key" }}
{{ $dict | get "key" }}
set
{{ set $dict "key" "value" }}
{{ $dict | set "key" "value" }}
unset
{{ unset $dict "key" }}
{{ $dict | unset "key" }}
hasKey
{{ hasKey $dict "key" }}
{{ $dict | hasKey "key" }}
pick
{{ pick $dict "k1" "k2" }}
{{ $dict | pick "k1" "k2" }}
omit
{{ omit $dict "k1" "k2" }}
{{ $dict | omit "k1" "k2" }}
List Functions: append, prepend, slice, without
append
{{ append $list "value" }}
{{ $list | append "value" }}
prepend
{{ prepend $list "value" }}
{{ $list | prepend "value" }}
slice
{{ slice $list 1 3 }}
{{ $list | slice 1 3 }}
without
{{ without $list "a" "b" }}
{{ $list | without "a" "b" }}
Dig Function
Sprig:
{{ dig "key1" "key2" "default" $dict }}- default value is the second-to-last argumentSprout:
{{ $dict | dig "key1" "key2" | default "default" }}- usedefaultfilter separately
Additional differences:
Sprig: Dots in keys are treated literally (
dig "a.b"looks for key"a.b")Sprout: Keys are split on dots (
dig "a.b"is equivalent todig "a" "b")
Behavior Changes
MustDeepCopy
Sprig: Accepts
nilinput, causing an internal panic.Sprout: Returns
nilif input isnil, avoiding panic.
Rand Functions
Sprig: Causes an internal panic if the length parameter is zero.
Sprout: Returns an empty string if the length is zero, ensuring stability.
DateAgo
Sprig: Does not support int32 and *time.Time; returns "0s".
Sprout: Supports int32 and *time.Time and returns the correct duration.
DateRound
Sprig: Returns a corrected duration in positive form, even for negative inputs.
Sprout: Accurately returns the duration, preserving the sign of the input.
Date
Sprig: Uses local timezone for formatting.
Sprout: Uses the timezone from the time value itself.
If you use sprigin.FuncMap(), the date function uses local timezone like Sprig for backward compatibility.
Base32Decode / Base64Decode
Sprig: Returns the error message string when decoding fails.
Sprout: Returns an empty string when decoding fails.
If you use sprigin.FuncMap(), the decoding functions return the error message like Sprig for backward compatibility.
ToCamelCase / ToPascalCase
Sprig: The
camelcasefunction returns PascalCase (e.g.,"hello_world"→"HelloWorld"). No true camelCase function exists.Sprout:
toCamelCasereturns true camelCase (e.g.,"hello_world"→"helloWorld")toPascalCasereturns PascalCase (e.g.,"hello_world"→"HelloWorld")The alias
camelcasepoints totoPascalCasefor backward compatibility.
ToTitleCase / Title
Sprig: Uses the deprecated
strings.Titlefunction which:Doesn't lowercase letters before capitalizing (
"HELLO"stays"HELLO")Treats apostrophes as word separators (
"it's"becomes"It'S")
Sprout: Uses proper Unicode title casing (
"HELLO"→"Hello","it's"→"It's")
If you use sprigin.FuncMap(), the toTitleCase/title function uses the old strings.Title behavior with a warning about the upcoming change.
Substr
Sprig:
substr 0 0 "hello"returns""(empty string)Sprout:
substr 0 0 "hello"returns"hello"(full string, as end=0 means "to the end")
If you use sprigin.FuncMap(), the old behavior is preserved with warnings about the upcoming change.
KindOf
Sprig:
kindOf nilreturns"invalid"Sprout:
kindOf nilreturns an error
If you use sprigin.FuncMap(), the old behavior is preserved with a warning about the upcoming change.
EllipsisBoth / Abbrevboth
Sprig: Has a bug where offset <= 4 is ignored (truncates from right only)
Sprout: Respects the offset parameter correctly
If you use sprigin.FuncMap(), the buggy behavior is preserved with a warning when offset <= 4.
Nospace / Ellipsis (Abbrev)
Sprig: Has bugs with Unicode characters (e.g.,
nospace "α β γ")Sprout: Correctly handles Unicode characters
Merge / MergeOverwrite
Sprig: Dereferences when the second value is the default Go value (e.g.,
0for int is treated as "not set").Sprout: Does not dereference; keeps the second value as-is (e.g.,
0for int is preserved).
Deprecated Features
Functions to be Removed
The following functions are marked with // ! DEPRECATED and will be removed in the next major version:
fail
Limited use case
Use Go error handling
urlParse
Moving to dedicated URL package
Will be replaced
urlJoin
Moving to dedicated URL package
Will be replaced
getHostByName
Non-deterministic, security concern
Handle DNS outside templates
Perform cryptographic operations (listed in crypto package) outside of templates. The crypto registry will be dropped in future versions.
Renamed Functions (Deprecated Aliases)
The following function names are deprecated. They still work but will log deprecation warnings. Use the new names instead:
String Functions
upper, toupper, uppercase
toUpper
lower, tolower, lowercase
toLower
title, titlecase
toTitleCase
camelcase
toPascalCase
snake, snakecase
toSnakeCase
kebab, kebabcase
toKebabCase
swapcase
swapCase
abbrev
ellipsis
abbrevboth
ellipsisBoth
trimall
trimAll
List Functions
push
append
mustPush
mustAppend
tuple
list
biggest
max
Encoding Functions
b64enc
base64Encode
b64dec
base64Decode
b32enc
base32Encode
b32dec
base32Decode
Path Functions
base
pathBase
dir
pathDir
ext
pathExt
clean
pathClean
isAbs
pathIsAbs
Date Functions
date_modify
dateModify
date_in_zone
dateInZone
must_date_modify
mustDateModify
ago
dateAgo
Type Conversion Functions
int, atoi
toInt
int64
toInt64
float64
toFloat64
toDecimal
toOctal
toStrings
strSlice
Math Functions
addf
add
add1f
add1
subf
sub
Other Functions
expandenv
expandEnv
Migration Tip
All deprecated aliases are flagged with // ! deprecated (lowercase) in sprigin for renamed functions.
Functions marked for total removal use // ! DEPRECATED (uppercase) in the codebase.
Conclusion
Migrating from Sprig to Sprout offers significant benefits, including improved error handling, modular function management, and enhanced compatibility with modern Go practices. While the sprigin package provides a bridge for backward compatibility, fully embracing Sprout’s native capabilities will lead to a more stable and maintainable codebase. For further details on Sprout’s features and API, consult the official Sprout documentation.
Last updated