trellis 0.10.2
trellis: ^0.10.2 copied to clipboard
Template engine for Dart, using natural HTML templates. Fragment-first for hypermedia-driven frameworks like HTMX. Inspired by Thymeleaf.
Changelog #
0.10.2 #
Fixed #
- Dev-mode template watching now sees changes in nested template directories on Linux.
FileSystemLoader(devMode: true)relied onDirectory.watch(recursive: true), which dart:io implements with inotify on Linux — where therecursiveflag is silently ignored, so hot reload only ever noticed edits directly in the template root. On Linux the loader now watches each directory in the tree itself, adding watches for directories that appear later (including ones created or moved in already carrying templates) and dropping them when they go away; the per-directory walk tolerates unreadable directories, so one of them does not stop the rest of the tree from being watched. macOS and Windows keep the single native recursive watch. No API change. - A template updated by rename now triggers a reload on Linux and Windows. Atomic-save tools (
sed -i, rsync, some editors) writepage.html.tmpand rename it overpage.html; that arrives as a single move event whose path is the source, so the template-extension filter — which checked only that path — never fired. macOS reports renames as delete + create and was unaffected. - A template directory the OS refuses to watch — most often Linux's
fs.inotify.max_user_watcheslimit on a large template tree — now prints one warning naming the directory, instead of capping hot reload silently. (Per-directory watching, so Linux; macOS/Windows keep the native watch's existing error behaviour.) close()on a dev-modeFileSystemLoadercan no longer leave a watch behind: an event arriving while it was cancelling could install a new directory watch that the shutdown had already passed by.listTemplates()no longer follows symlinks, so every name it returns is oneload()will actually serve. It previously listed templates reached through a symlink out of the template tree, whichload()then rejected as a boundary escape — turning up as spuriouswarmUpAll()failures. A symlink pointing back inside the tree is also no longer listed: its target stays listed under the real path, andload()still serves the alias name, but the alias itself disappears from enumeration-driven features (warmUpAll(),trellis_dev's validator).
0.10.1 #
Breaking #
ProcessorContext.domProcessoris now typedFragmentHost(non-nullable) instead ofdynamic. This is a source break for code that constructsProcessorContextdirectly — anullor duck-typed stub that compiled against 0.10.0 no longer does. Shipped as a patch deliberately: Trellis is pre-1.0 with no known external users,ProcessorContextis documented as something aProcessorreceives (never constructs), andpackage:trellis/testing.dartis the supported test path. If you do construct one, pass aFragmentHostimplementation.
Added #
FragmentHost— the narrow fragment-resolution contract (evaluator,processFragmentContent,querySelectorFromDoc,lookupFragment,pushFragmentRegistry,popFragmentRegistry) implemented by the engine's DOM processor. Declaredabstract interface class, so future members can be added without breaking implementers.
Changed #
- Fragment-aware processors now get static checking on
context.domProcessorrather than unchecked dynamic dispatch; the internalas DomProcessorcast is gone.
0.10.0 #
Changed #
- Lockstep version bump to keep all Trellis SDK packages on a single shared version. No functional changes in this package.
0.9.1 #
Changed #
- Lockstep version bump to keep all Trellis SDK packages on a single shared version. No functional changes in this package.
0.9.0 #
Changed #
- Lockstep version bump to keep all Trellis SDK packages on a single shared version. No functional changes in this package.
All notable changes to trellis are documented here. This project follows Semantic Versioning.
0.8.2 #
Added #
trellis:validate --strictCI gate: the--strictflag (alias--fatal-warnings) makes the CLI exit1on warnings, not only errors. This letsdart run trellis:validategate CI on the silent HTML5-parser mutations surfaced in 0.8.1 (duplicatetl:attr,<table>/<select>foster-parenting) — these are reported as warnings, so a plain run exits0even when present.trellis:validateaccepts the target directory positionally —dart run trellis:validate templatesnow works alongside--dir templates.
0.8.1 #
Fixed #
TemplateValidatorsurfaces silent HTML5 parser mutations: templates are parsed bypackage:htmlbeforetl:*processors run, and the parser can silently rewrite malformed input – dropping duplicate attributes and foster-parenting elements out of<table>/<select>– strippingtl:*directives with no error. The validator now reports these as warnings:- Duplicate
tl:attron one element – only the first is kept (HTML forbids duplicate attribute names); use a single comma-separatedtl:attrinstead. tl:each/tl:*on a<tl:block>(or other unknown tag) inside<table>/<select>– foster-parented out of the table, detaching the loop scope; put the directive directly on<tr>/<option>.
- Duplicate
0.8.0 #
Added #
- Expression utility objects:
${#strings.*},${#numbers.*},${#dates.*},${#lists.*}— 53 built-in methods for common string, number, date, and list operations#strings:capitalize,upperCase,lowerCase,trim,isEmpty,isNotEmpty,length,contains,startsWith,endsWith,replace,substring,indexOf,split,join,repeat#numbers:formatDecimal,formatCurrency,formatPercent,abs,min,max,round,floor,ceil,isOdd,isEven#dates:format,formatDate,formatTime,now,year,month,day,hour,minute,second,isBefore,isAfter#lists:size,isEmpty,isNotEmpty,first,last,contains,sort,sortBy,reverse,take,skip,where,map,join,flatten
UtilityCallExprAST node and parser rule for${#name.method(args)}syntax#dates.formatand#numbers.formatusepackage:intlwhen available, English-only fallback otherwise- Unknown utility object or method produces
ExpressionExceptionwith a message listing available options - Testing utilities — merged
trellis_testinto core aspackage:trellis/testing.dart:testEngine()— preconfigured engine factory for testing withMapLoader, strict mode enabled, and caching disabled- CSS-selector HTML matchers:
hasElement,hasNoElement,hasAttribute,elementCount,hasTextContent - Snapshot golden file testing:
expectSnapshot,expectSnapshotFromSource— auto-creates on first run, fails with readable diff on mismatch;TRELLIS_UPDATE_GOLDENS=trueregenerates all golden files - Fragment isolation helpers:
testFragment,testFragmentFile normalizeHtml()— parse-and-serialize round-trip for stable snapshot comparison
Changed #
- Added
matcherdependency to support the mergedtesting.dartmatchers
0.7.0 #
Added #
- Template inheritance:
tl:extendsandtl:definefor layout-based template composition — child templates extend parents and override named blocks - Contextual escaping: URL-encoding for
@{}expressions,tl:href, andtl:srcattributes — values are properly percent-encoded for safe URL construction - Inheritance validation:
TemplateValidatorrecognizestl:extendsandtl:defineattributes, warns on duplicate block names, validates non-empty values
Changed #
InheritanceResolverruns as a pre-pass between DOM cloning and fragment collection — transparent to existing render pipelineloadSync()method onTemplateLoaderfor synchronous parent template loading (maintains sync-first contract ofrender())
0.6.0 #
Added #
- Expression AST cache: parsed expressions are now cached per
Trellisinstance and exposed viacacheStats.expressionCacheSize - Warm-up APIs:
warmUp()andwarmUpAll()pre-load templates into the DOM cache withWarmUpResultreporting for failures and evictions - Template discovery:
listTemplates()onFileSystemLoaderandMapLoaderfor startup warm-up workflows - Template validation toolkit:
TemplateValidator,ValidationError, andValidationSeverityfor static template checks - Testing helper:
package:trellis/testing.dartexportsisValidTemplate()for unit-test assertions - CLI validator:
dart run trellis:validatevalidates template directories for CI usage
0.5.0 #
Added #
devModeparameter onFileSystemLoader— file watching viadart:ioDirectory.watch()devModeparameter onTrellis— automatic cache invalidation on template file changesclose()onFileSystemLoaderandTrellisfor async resource disposalFileSystemLoader.changesstream for change notifications
0.4.1 #
- Added logo to README
0.4.0 #
- Bumped minimum Dart SDK from 3.7 to 3.10
- Applied Dart 3.10 dot shorthand syntax throughout
lib/src/(zero behavioral changes)
0.3.0 #
Added #
- Processor interface & pipeline:
Processorabstract class,ProcessorPriorityenum (8 priority slots),ProcessorContextclass — all built-in processors implement the interface; pipeline iterates a sorted processor list - Custom processor registration:
DomProcessor(processors: [...])registers customProcessorinstances with auto-prefixed attributes, priority-sorted merge, error wrapping, andautoProcessChildrencontrol - Dialect system:
Dialectabstract class andStandardDialect;DomProcessor(dialects: [...], includeStandard: false)composes processors and filters across multiple dialects - Filter arguments:
| filterName:arg1:arg2syntax for parameterized filters; supports string (\'escape), int, double, bool, null, and bare identifier args; backward compatible with existingFunction(dynamic)filters - i18n message expressions:
#{key}expression type withMessageSourceabstract class andMapMessageSourceimplementation; parameterized messages#{key(arg1, arg2)}with{0}/{1}positional replacement; locale support via engine config and_localecontext override; strict/lenient missing-key behavior AssetLoader: loads templates from Dart package assets viaIsolate.resolvePackageUriCompositeLoader: tries delegate loaders in order, falling back onTemplateNotFoundExceptionTrellisconstructor params:processors,dialects,includeStandard,messageSource,locale- Framework Integration Guide:
docs/guides/framework-integration.mdcovering shelf, dart_frog, and HTMX patterns - Todo app example:
example/todo_app/— full Shelf + HTMX app demonstrating v0.3 features
Changed #
example/restructured intoexample/basic/andexample/todo_app/sub-packages
0.2.1 #
Fixed #
tl:blockself-closing:<tl:block/>no longer swallows subsequent siblings — normalizer now uses a quote-aware scanner instead of a regex, correctly handling>inside attribute values (e.g.tl:if="${count > 0}")tl:fragmentontl:block:renderFragment()andrenderFragments()now correctly unwrap block elements, returning inner content instead of empty outputtl:eachwith null/missing iterable: gracefully removes the host element instead of throwing; consistent with lenient-mode semantics
Added #
!negation operator:!is now supported as an alias fornotin expressions (e.g.tl:if="!${active}")
0.2.0 #
Added #
- Expression enhancements: arithmetic operators (
+ - * / %), literal substitution (|Hello, ${name}!|), dynamic index expressions (${list[index]}), selection expressions (*{field}withtl:object), comparison aliases (gt,lt,ge,le,eq,ne), no-op token (_) tl:switch/tl:case: multi-branch conditional renderingtl:classappend/tl:styleappend: append to existing class/style attributestl:block: virtual element that renders only its children (no host element in output)tl:remove: remove elements or content from output (all,body,tag,all-but-first,none)tl:inline: inline expression processing in text, JavaScript, and CSS contexts ([[${expr}]]escaped,[(${expr})]unescaped)tl:object/*{}: object context and selection expressions for scoped field access; auto-conversion viatoMap()/toJson()- Parameterized fragments:
tl:fragment="card(title, body)"with argument passing at inclusion time - CSS selector targeting:
tl:insert="~{file :: #id}"andtl:insert="~{file :: .class}" - Cycle detection: fragment inclusion stack replaces depth-only guard — recursive inclusions detected immediately
renderFragments(): render multiple named fragments from a single template string in one callrenderFileFragments(): async variant loading from the filesystem- Strict mode:
Trellis(strict: true)— undefined variables, members, and keys throwExpressionException - LRU cache: configurable max cache size via
maxCacheSizeparameter; evicts least-recently-used entries CacheStats: expose cache hit/miss/size metrics viaengine.cacheStatsclearCache(): clear DOM cache and reset statisticsTrellisContext: fluent builder for constructing rendering context mapsdata-tl-*prefix mode:Trellis(prefix: 'data-tl')for strict HTML5-valid attribute names
Fixed #
- Expression parser: alias/keyword words (
gt,eq,and,true, etc.) now work as member names after.— e.g.${obj.eq},${stats.gt} - README: corrected
TrellisContextexample,renderFragmentsreturn types,tl:switchcase syntax,tl:classappendternary,maxCacheSizedefault, removed nonexistentseparatorparameter
Changed #
- Fragment registry entries now carry parameter names for parameterized fragment resolution
- Inclusion depth guard replaced by cycle detection stack (still enforces max depth 32 as hard limit)
0.1.0 #
Added #
- Core template engine with 15
tl:*attributes for natural HTML templating - Text substitution:
tl:text(escaped) andtl:utext(unescaped HTML) - Conditionals:
tl:ifandtl:unless - Iteration:
tl:eachwith status variables (index, count, size, first, last, odd, even, current) - Fragment system:
tl:fragment,tl:insert,tl:replacewith cross-file inclusion - Local variable binding:
tl:with - Attribute setting:
tl:attr,tl:href,tl:src,tl:value,tl:class,tl:id - Expression evaluator:
${var}variables,@{/url}URL expressions, string literals, ternary, Elvis, comparisons, boolean operators - Four public API methods:
render(),renderFile(),renderFragment(),renderFileFragment() - DOM caching with deep-clone for performance
- Configurable attribute prefix (default
tl) FileSystemLoaderwith security boundary enforcement (path traversal, symlink escape protection)MapLoaderfor in-memory templates and testing- Typed exception hierarchy:
TemplateException,ExpressionException,FragmentNotFoundException,TemplateNotFoundException,TemplateSecurityException