Releases: vapor/leaf-kit
LeafKit 1.0.0
LeafKit 1.0.0
1.0.0 Zeta 1
This release reverses the Tau changes and reflects what (should) be the final release for LeafKit 1.0.0 barring any glaring bugs. If you want to keep using Tau, we strongly encourage you to do so! You can find it (currently) in the fork here
Parse & Serialize Bug Fixes
This patch was authored and released by @tdotclare.
- Properly respect
invariant
flag on entities during parsing to avoid pre-resolving - Correct behavior on nested inline variable/define resolution
- Correct behavior of declaring variables referencing previous stack definition
- Improved error handling on
define
style mismatches in resolution
LeafKit 1.0.0-tau
tau
is the final prerelease of LeafKit 1.0 and Leaf4.
This is a massive rebuild from the previous prerelease version and entirely replaces the architecture for extending the language; as such, there is no direct transition path for converting LeafTag
objects to the new architecture directly.
Migration documents are coming soon to address how that functionality has improved and changed
If you currently use the Leaf4 pre-release versions, pin to the final
rc
branch in your Swift Package Manifest to avoid updating to this state if you are not ready to transition:dependencies: [ .package(url: "https://github.com/vapor/leaf-kit.git", .exact("1.0.0-rc.1.17")), .package(url: "https://github.com/vapor/leaf.git", .exact("4.0.0-rc.1.4")) ]
Leaf4 is a dynamic language templating engine for (and inspired by) Swift with a unique hybrid design to allow significant extensibility, customization, performance, and optimizations for a broad range of applications. Leaf templates function primarily as the View component of Model-View-Controller architectures.
As the successor to Leaf3, it greatly expands the language's capabilities and introduces significant changes that are oriented towards: simplfying integration of Leaf into applications; broader use beyond web-templating; robust handling of compiling templates; and improved, more powerful, and safer extensibility of templates at runtime.
LeafKit is the core architecture of Leaf4
Leaf is the bindings of LeafKit to Vapor and Vapor-specific configuration.
Internal Bug Fix
This patch was authored and released by @tdotclare.
Internal Only
Fixed malformed assert checks in LeafConfiguration
LeafConfiguration Encoding and Formatting Options
This patch was authored and released by @tdotclare.
This release adds configuration methods for setting the default presentation output of data in rendered Leaf templates.
These options are all settable before LeafKit starts running.
Property Options
Stored property options in a LeafConfiguration
may be used in various ways by a LeafRenderer
which the configuration object was for, and changes to them after the object was provided to a specific LeafRenderer
will have no affect.
.rootDirectory: String // The default file directory used for file-system based `LeafSource`s
Static Options
Static options on LeafConfiguration
are effectively constant once any LeafRenderer
has been instantiated and attempts to change them will assert in Debug
and silently fail in Release
to prevent inconsistent behavior.
// The global tag indicator for LeafKit
.tagIndicator: Character == "#"
// Encoding used when a template is serialized
.encoding: String.Encoding == .utf8
// Formatters for converting the base internal data types to Strings for serialization
.boolFormatter: (Bool) -> String = { $0.description } // Bool.description
.intFormatter: (Int) -> String = { $0.description } // Int.description
.doubleFormatter: (Double) -> String = { $0.description } // Double.description
.nilFormatter: () -> String = { "" } // Empty string (Optional containing .none)
.voidFormatter: () -> String = { "" } // Empty string (Tag with no return value)
.stringFormatter: (String) -> String = { $0 } // Identity return
.dataFormatter: (Data) -> String? =
{ String(data: $0, encoding: Self._encoding) } // Data using .encoding
// Note: Array & Dictionaries elements will already have been converted to Strings
.arrayFormatter: ([String]) -> String = // Array: [element, ..., element]
{ "[\($0.map {"\"\($0)\""}.joined(separator: ", "))]" }
.dictFormatter: ([String: String]) -> String = // Dictionary: [key: value, ..., key: value]
{ "[\($0.map { "\($0): \"\($1)\"" }.joined(separator: ", "))]" }
API Changes
-
Character.tagIndicator
can no longer be directly set - it must be configured throughLeafConfiguration
-
LeafData.NaturalType
represents eight concrete Swift data types Leaf handles:- Instantiable data types: [Bool, Int, Double, String, Array, Dictionary, Data]
- Non-instantiable: [Void]
-
LeafData
static initializers from Swift data types now take Optional values:- If input is
.none
, the returnedLeafData
represents aOptional.none
state of the specific type .nil
static initializer now requires a concrete type fromLeafData.NaturalType
be specified
- If input is
-
LeafData
objects present the following informational states:celf
: NaturalType of the objectisNil
: whether the object contains.none
isCollection
: if the object's type isarray
ordictionary
.isCastable(to type: NaturalType)
: whether the type is implicitly castable to a second typeisCoercible(to type: NaturalType)
: whether the type has an implicit one-way conersion path to the second type- NOTE both above methods are inherently true when the two types are the same type, or castable when asking if coercible.
hasUniformType
: whether the object consists of one concrete type (true for all non-containers), false if it can be determined that it does not, and nil in unusual cases where an object is storing an internal dynamic data generator that returns a container itself.uniformType
: If the object can be determined to have auniformType
, returns that type or nil if not determinable.
-
LeafDataRepresentable
adherence now requires.leafData
returnLeafData
rather thanLeafData?
- Updated static initalizers mentioned above will automatically handle creating
LeafData
holding.none
where otherwise a nil return would occur - Generic conformances for various Swift base types are updated to reflect this:
-
let invalidValue: Float80 = Float80.max let leafData = invalidValue.leafData // leafData now represents the equivalent of Double? containing nil
- Default implementations for protocols
FixedWidthInteger
andBinaryFloatingPoint
- Adherance via above for
Bool
,String
,Int/Int8/Int32/Int64/UInt/UInt8/UInt16/UInt32/UInt64
,Float/Double/Float80
,Data
,UUID
(via String),Date
(via Double),Array<LeafDataRepresentable>
,Dictionary<String, LeafDataRepresentable>
,Set<LeafDataRepresentable>
via Array
-
- Updated static initalizers mentioned above will automatically handle creating
Internal Only
Substantial internal changes to LeafData
, LeafDataRepresentable
Protocol, LeafDataStorage
:
LeafData
conversion between types is now explicitly handled bycastable
andcoercible
rules where underlying types are different. The default behaviors allow only implicit conversion viacastable
rules where a clearly established bi-directional rule exists between the two types.LeafDataStorage
signifcantly modified to allow above behaviors- Internal
Lazy
resolvable data generators must now state their concrete return type and whether they have variant return behavior; such generators will never be tested in any kind of comparison states to prevent side-effects - they will only be called when a template serializes. - Various state evaluation, data conversion, and related behaviors are moved internally to the enum definition to prevent confusing situations
- Internal
- Various internal changes reflecting the above modifications
Fixes
- Throws an error if overflowing add/subtract on integer values
Fix Performance Regression
This patch was authored and released by @tdotclare.
This release fixes a performance regression from an internal testing function being used by public render()
calls
Performance Improvements
This patch was authored and released by @tdotclare.
This release significantly improves performance of reading raw template files
Internal Notes
Template source documents being parsed previously involved mutating an array copy of the template source; now scans a constant String copy and pre-allocates chunked reading method [Character] storage
Improved Rendering Performance
This patch was authored and released by @tdotclare.
This release substantially improves the rendering performance of Leaf on typical calls where the template is fully resolved and cached.
Performance Comparison
- Linear test - 10 flat templates, 1 million render calls against them
- Random test - 10 flat templates (layer 3), 20 templates referencing one of the flat templates (layer 2), 100 templates referencing two random layer 2 templates (layer 1). 1 million render calls against a random one of the 130 total templates.
- Each test was run 50 times on an 4GHz i7 Retina iMac
Linear
Branch | Min | Avg | Max | Avg Baseline | Avg CPU Time | CPU Baseline |
---|---|---|---|---|---|---|
1.0.0rc-1.13 | 4.27s | 4.72s | 5.14s | 44.83% | 1m 14s | 40.72% |
1.0.0rc-1.12 | 10.4s | 10.52s | 11.4s | 100% | 3m 1s | 100% |
Random
Branch | Min | Avg | Max | Avg Baseline | Avg CPU Time | CPU Baseline |
---|---|---|---|---|---|---|
1.0.0rc-1.13 | 4.43s | 4.82s | 5.17s | 49.9% | 1m 18s | 45.3% |
1.0.0rc-1.12 | 9s | 9.66s | 10.61s | 100% | 2m 51s | 100% |
NOTE this is purely a pipeline measurement - the templates used are lightweight and require near-zero time to serialize
Set Default File Extension for `NIOLeafFiles`
This patch was authored and released by @tdotclare.
This release adds an initialization parameter to NIOLeafFiles
to allow setting the default extension to be used for Leaf files.
public struct NIOLeafFiles: LeafSource {
...
/// Initialize `NIOLeafFiles` with a NIO file IO object, limit options, and sandbox/view dirs
/// - Parameters:
/// - fileio: `NonBlockingFileIO` file object
/// - limits: Options for constraining which files may be read - see `NIOLeafFiles.Limit`
/// - sandboxDirectory: Full path of the lowest directory which may be escaped to
/// - viewDirectory: Full path of the default directory templates are relative to
/// - defaultExtension: The default extension inferred files will have (defaults to `leaf`)
///
/// `viewDirectory` must be contained within (or overlap) `sandboxDirectory`
public init(fileio: NonBlockingFileIO,
limits: Limit = .default,
sandboxDirectory: String = "/",
viewDirectory: String = "/",
defaultExtension: String = "leaf") {...}
Usage:
// Use "leaf4" instead of "leaf" as the implied file extension
let source: LeafSource = NIOLeafFiles(..., defaultExtension: "leaf4")
Internal Changes
- Various documentation of public protocols/objects
- General rearrangement of code for clarity