v3 Release Notes
Textwire v3 is one of the most important releases, introducing several significant features and improvements. This release includes the introduction of the global variable object, a new defined() function (#56), the ability to register custom functions for the OBJECT type, and improvements to error handling, performance (#61 #60 #59), and usability #35.
If you are transitioning from Textwire v2, you can follow this guide for all the instructions. v3 contains several breaking changes, so make sure you don't miss any parts of the guide.
Table of Contents
New Features
Textwire v3 brings several new features to the language. Let's explore these features, starting with a new concept in Textwire - global functions.
1. Function defined
Textwire v3 introduces the global function defined() that returns a boolean value. This function should be used only with variables and allows you to check if a variable is defined to prevent Textwire from creating an error.
<header class="header">
<h2>{{ page.title }}</h2>
<p>{{ page.desc }}</p>
@if(defined(user))
<small>The user is {{ user.name }}</small>
@end
</header>Here are the commits 0a19b, f1d3f, 264e7.
2. Global Data
In v3 you can now pass values from your Go code to any Textwire template using the global object. Here is an example of passing some values:
import (
"os"
"github.com/textwire/textwire/v3"
"github.com/textwire/textwire/v3/config"
)
tpl, err = textwire.NewTemplate(&config.Config{
DebugMode: true,
GlobalData: map[string]any{
"env": os.Getenv("APP_ENV"),
},
})In your templates, you can access global object like this:
@if (global.env == "development")
<p>You are currently in development mode</p>
@endRead more about Global Data in our docs, or look at the commit.
3. Custom Functions for Object Type
In Textwire v2 you could define custom functions for all types except objects; in v3 you can now do that. This was added with this commit. Here is a simple example:
err := textwire.RegisterObjFunc("_addProp", func(obj map[string]any, args ...any) any {
key := args[0].(string) // first arg
value := args[1] // second arg
obj[key] = value
return obj
})You can now use it in Textwire:
input := `{{ obj = {name: "Anna"}; obj = obj._addProp("age", 25); obj.age }}`
out, err := textwire.EvaluateString(input, nil)
// Result: "25"4. Embed Templates into Binary
Now, in Textwire v3 you can use Go's embedded package to embed Textwire template files into a final, compiled binary. You can read about how to use this functionality in our docs. But it looks something like this:
package main
import (
"embed"
"github.com/textwire/textwire/v3"
"github.com/textwire/textwire/v3/config"
)
//go:embed templates/*
var templateFS embed.FS
func main() {
tpl, err := textwire.NewTemplate(&config.Config{
TemplateFS: templateFS,
})
// other logic here ...
}Improvements
1. Error Handling
- Improve error handling with this commit when trying to use
@use,@insert,@reserveor@componentdirectives in simpleEvaluateStringorEvaluateFilefunction calls. These directives are only allowed inside template files withtextwire.NewTemplate. - Now, you'll get a clear error message like
@use, @insert, @reserve, @component only allowed in templatesif you try to use them. Previously, the error wasn't clear. - Improved all error messages in this commit to make them clearer and more straightforward.
- You'll get a proper error instead of panic when you try to use
@each(item in false)directive on non-array type. - You'll get a clear error when using 2 or more
@usestatements in the same template.
There are much more improvements to error handling in this release, so make sure to upgrade to version 3.
2. Memory Performance
Improve memory and performance with optimized data structures and reduced memory allocations. Here are the optimizations that you can expect in Textwire v3:
| Improved target | Speed | Memory usage | Allocations |
|---|---|---|---|
| Function arr.join() | ⚡ 18.5× faster | 💾 97.8% less | 📉 33% fewer |
| Tokenizing (lexing) directives | ⚡ 1.24× faster | 💾 46.9% less | 📉 84.9% fewer |
| Parsed AST evaluation | No change | 💾 9.3% less | 📉 2.5% fewer |
Here is a GitHub issue for array.join() if you are interested.
Bug Fixes
Like any software, Textwire has its share of bugs. We found and fixed several bugs in this release. You can find the list of fixed bugs below.
1. Using Component Statement inside Layout
Fixed bug where you couldn't use @component directive inside of a layout file like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@reserve('title')</title>
<meta name="description" content="@reserve('description')">
</head>
<body>
@component('~navbar') {{-- using components/navbar here --}}
<main class="container mx-auto max-w-200">
@reserve('content')
</main>
</body>
</html>You also couldn't use @component inside of other components. Now, it all has been fixed.
2. Incorrect File Path
Fixed incorrect file path in error messages when an error occurs inside the @insert directive. It used to point to the wrong file.
3. Function contains()
- Fixed
containsfunction for strings,{{ !"aaa".contains("a") }}now returns correct result. - Fixed
containsfunction for arrays,{{ ![{}, 21].contains({age: 21}) }}now returns correct result. They both used to work incorrect because of incorrect precidence, which was fixed.
4. Replace Panic with Error
Now you will get a proper error when trying to access propery on non object type like {{ "str".nice }}. Before, in Textwire v2 you would get a panic with weird error message and long stacktrace.
Breaking Changes
Textwire v3 introduces several breaking changes. We're implementing these changes now rather than later, as it's better to make significant updates when the user base is smaller.
This transition would be much harder with tens of thousands of users than with hundreds. Thank you for choosing Textwire - we'll provide the least painful transition experience with our detailed upgrade guide.
I'll discuss each breaking change briefly because they are already explained in the Upgrade Guide, so there's no need to repeat that detailed information here.
1. Custom Functions Return Type
When you define a custom function, it now returns the type any. If you have any registered custom functions, make sure to change their return type to any.
2. Reserved Variable Name
Variable global is now reserved, you cannot use this name for your variables.
3. Precedence Fix
Fixed precedence for prefix expressions and function call expressions. In Textwire v3, function calls now have higher precedence over prefix expressions. Instead of ((!var).func()), we now have (!(var.func())).
4. Default Extension Change
Changed the default file extension from .tw.html to .tw. If you still want to support .tw.html, add the field TemplateExt: ".tw.html" to your configuration in NewTemplate or Configure. This will behave the same way as in Textwire v2.
5. Minimal Go Version
In Textwire v3, the minimum supported Go version is now 1.25.0. In Textwire v2, it was 1.22.0. Upgrade your project to the latest version to use Textwire v3.
6. Components Scope Fix
Components in Textwire v2 would pass variables to their children automatially without manual passing. It was a bug. In Textwire v3 each component has its scope. You need to pass data manually:
{{ name = "Anna" }}
@component('user')
@component('user', { name })7. Variable Leak Fix
Fixed variable leak from template to layout non-explicitly. In Textwire v2, if you had a variable in your template, it would be accessible in your layout without passing it explicitly. In Textwire v3, this is not available anymore.
Use the Global Data to pass variables into all of your Textwire files.
