[ad_1]
All the things you must know concerning the upcoming Leaf template engine replace and easy methods to migrate your Vapor / Swift codebase.
Vapor
Utilizing Leaf 4 Tau
Earlier than we dive in, let’s make a brand new Vapor mission with the next bundle definition.
import PackageDescription
let bundle = Package deal(
identify: "myProject",
platforms: [
.macOS(.v10_15)
],
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", from: "4.30.0"),
.package(url: "https://github.com/vapor/leaf", .exact("4.0.0-tau.1")),
.package(url: "https://github.com/vapor/leaf-kit", .exact("1.0.0-tau.1.1")),
],
targets: [
.target(name: "App", dependencies: [
.product(name: "Vapor", package: "vapor"),
.product(name: "Leaf", package: "leaf"),
]),
.goal(identify: "Run", dependencies: ["App"]),
.testTarget(identify: "AppTests", dependencies: [
.target(name: "App"),
.product(name: "XCTVapor", package: "vapor"),
])
]
)
The very very first thing I might like to point out you is that we have now a brand new render methodology. Prior to now we have been ready to make use of the req.view.render operate to render our template information. Take into account the next actually easy index.leaf file with two context variables that we’ll give show actual quickly.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta identify="viewport" content material="width=device-width, initial-scale=1">
<title>#(title)</title>
</head>
<physique>
#(physique)
</physique>
</html>
Now in our Vapor codebase we may use one thing like this to render the template.
import Vapor
import Leaf
public func configure(_ app: Utility) throws {
app.views.use(.leaf)
app.get() { req -> EventLoopFuture<View> in
struct Context: Encodable {
let title: String
let physique: String
}
let context = Context(title: "Leaf 4", physique:"Hi there Leaf Tau!")
return req.view.render("index", context)
}
}
We are able to use an Encodable object and cross it round as a context variable. This can be a handy approach of offering values for our Leaf variables. Earlier than we proceed I’ve to let you know that each one of this can proceed to work in Leaf Tau and you do not have to make use of the brand new strategies. 👍
New render strategies
So let me present you the very same factor utilizing the brand new API.
import Vapor
import Leaf
public func configure(_ app: Utility) throws {
app.views.use(.leaf)
app.get() { req -> EventLoopFuture<View> in
let context: LeafRenderer.Context = [
"title": "Leaf 4",
"body": "Hello Leaf Tau!",
]
return req.leaf.render(template: "index", context: context)
}
}
That is not a giant deal you could possibly say at first sight. Nicely, the factor is that this new methodology offers type-safe values for our templates and that is simply the tip of the iceberg. You must neglect concerning the view property on the request object, since Leaf began to outgrow the view layer in Vapor.
import Vapor
import Leaf
public func configure(_ app: Utility) throws {
app.views.use(.leaf)
app.get() { req -> EventLoopFuture<View> in
let identify = "Leaf Tau"
let context: LeafRenderer.Context = [
"title": "Leaf 4",
"body": .string("Hello (name)!"),
]
return req.leaf.render(template: "index",
from: "default",
context: context,
choices: [.caching(.bypass)])
}
}
In case you take a better have a look at this comparable instance, you discover out that the context object and the values are representable by varied varieties, but when we attempt to use an interpolated string, we have now to be somewhat bit extra sort particular. A LeafRenderer.Context object is considerably a [String: LeafData] alias the place LeafData has a number of static strategies to initialize the built-in fundamental Swift varieties for Leaf. That is the place the type-safety characteristic is available in Tau. You need to use the static LeafData helper strategies to ship your values as given varieties. 🔨
The from parameter generally is a LeafSource key, in case you are utilizing a number of template areas or file sources then you may render a view utilizing a particular one, ignoring the supply loading order. There’s one other render methodology with out the from parameter that’ll use the default search order of sources.
There’s a new argument that you need to use to set predefined choices. You possibly can disable the cache mechanism with the .caching(.bypass) worth or the built-in warning message by way of .missingVariableThrows(false) if a variable isn’t outlined in your template, however you are attempting to make use of it. You possibly can replace the timeout utilizing .timeout(Double) or the encoding through .encoding(.utf8) and grant entry to some nasty entities by together with the .grantUnsafeEntityAccess(true) worth plus there’s a embeddedASTRawLimit possibility. Extra about this in a while.
Additionally it is potential to disable Leaf cache globally by way of the LeafRenderer.Context property:
if !app.surroundings.isRelease {
LeafRenderer.Choice.caching = .bypass
}
If the cache is disabled Leaf will re-parse template information each time you attempt to render one thing. Something that may be configured globally for LeafKit is marked with the @LeafRuntimeGuard property wrapper, you may change any of the settings at software setup time, however they’re locked as quickly as a LeafRenderer is created. 🔒
Context and information illustration
You possibly can conform to the LeafDataRepresentable protocol to submit a customized sort as a context worth. You simply must implement one leafData property.
struct Consumer {
let id: UUID?
let e-mail: String
let birthYear: Int?
let isAdmin: Bool
}
extension Consumer: LeafDataRepresentable {
var leafData: LeafData {
.dictionary([
"id": .string(id?.uuidString),
"email": .string(email),
"birthYear": .int(birthYear),
"isAdmin": .bool(isAdmin),
"permissions": .array(["read", "write"]),
"empty": .nil(.string),
])
}
}
As you may see there are many LeafData helper strategies to signify Swift varieties. Each single sort has built-in non-obligatory assist, so you may ship nil values with out spending extra effort on worth checks or nil coalescing.
app.get() { req -> EventLoopFuture<View> in
let consumer = Consumer(id: .init(),
e-mail: "root@localhost.com",
birthYear: 1980,
isAdmin: false)
return req.leaf.render(template: "profile", context: [
"user": user.leafData,
])
}
You possibly can assemble a LeafDataRepresentable object, however you continue to have to make use of the LeafRenderer.Context as a context worth. Fortuitously that sort might be expressed utilizing a dictionary the place keys are strings and values are LeafData varieties, so this can cut back the quantity of code that you need to sort.
Constants, variables, nil coalescing
Now let’s transfer away somewhat bit from Swift and speak concerning the new options in Leaf. In Leaf Tau you may outline variables utilizing template information with actual dictionary and array assist. 🥳
#var(x = 2)
<p>2 + 2 = #(x + 2)</p>
<hr>
#let(consumer = ["name": "Guest"])
<p>Hi there #(consumer.identify)</p>
<hr>
#(non-obligatory ?? "fallback")
Similar to in Swift, we are able to create variables and constants with any of the supported varieties. If you inline a template variables might be accessed in each templates, that is fairly helpful as a result of you do not have to repeat the identical code time and again, however you need to use variables and reuse chunks of Leaf code in a clear and environment friendly approach. Let me present you the way this works.
Additionally it is potential to make use of the coalescing operator to offer fallback values for nil variables.
Outline, Consider, Inline
One of many greatest debate in Leaf is the entire template hierarchy system. In Tau, the complete strategy is rebuilt below the hood (the entire thing is extra highly effective now), however from the end-user perspective just a few key phrases have modified.
Inline
Lengthen is now changed with the brand new inline block. The inline methodology actually places the content material of a template into one other. You possibly can even use uncooked values if you happen to do not wish to carry out different operations (reminiscent of evaluating Leaf variables and tags) on the inlined template.
<!-- index.leaf -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta identify="viewport" content material="width=device-width, initial-scale=1">
<title>Leaf 4</title>
</head>
<physique>
#inline("house", as: uncooked)
</physique>
</html>
<!-- house.leaf -->
<h1>Hi there Leaf Tau!</h1>
As you may see we’re merely placing the content material of the house template into the physique part of the index template.
Now it is extra attention-grabbing once we skip the uncooked half and we inline a daily template that comprises different expressions. We’re going to flip issues just a bit bit and render the house template as an alternative of the index.
app.get() { req -> EventLoopFuture<View> in
req.leaf.render(template: "house", context: [
"title": "Leaf 4",
"body": "Hello Leaf Tau!",
])
}
So how can I reuse my index template? Ought to I merely print the physique variable and see what occurs? Nicely, we are able to strive that…
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta identify="viewport" content material="width=device-width, initial-scale=1">
<title>#(title)</title>
</head>
<physique>
#(physique)
</physique>
</html>
<!-- house.leaf -->
<h1>Hi there Leaf Tau!</h1>
#inline("index")
Wait a minute… this code isn’t going to work. Within the house template first we print the physique variable, then we inline the index template and print its contents. That is not what we would like. I wish to use the contents of the house template and place it in between the physique tags. 💪
Consider
Meet consider, a operate that may consider a Leaf definition. You possibly can consider this as a block variable definition in Swift. You possibly can create a variable with a given identify and in a while name that variable (consider) utilizing parentheses after the identify of the variable. Now you are able to do the identical skinny in Leaf by utilizing the consider key phrase or straight calling the block like a operate.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta identify="viewport" content material="width=device-width, initial-scale=1">
<title>#(title)</title>
</head>
<physique>
#consider(bodyBlock) (# or you need to use the `#bodyBlock()` syntax #)
</physique>
</html>
On this template we are able to consider the bodyBlock and in a while we’ll be capable to outline it some other place.
Outline
Definitions. Lastly arrived to the final element that we’ll must compose templates. Now we are able to create our physique block within the house template.
#outline(bodyBlock):
<h1>#(physique)</h1>
#enddefine
#inline("index")
Now if you happen to reload the browser (Leaf cache should be disabled) every little thing ought to work as it’s anticipated. Magic… or science, no matter, be happy to decide on one. 💫
Particular thanks goes to tdotclare who labored day and night time to make Leaf higher. 🙏
So what is going on on right here? The #outline(bodyBlock) part is chargeable for constructing a block variable known as bodyBlock that’s callable and we are able to consider it in a while. We merely print out the physique context variable inside this block, the physique variable is a context variable coming from Swift, that is fairly simple. Subsequent we inline the index template (think about copy-pasting complete content material of the index template into the house template) which can print out the title context variable and evaluates the bodyBlock. The bodyBlock might be obtainable since we have simply outlined it earlier than our inline assertion. Simple peasy. 😝
<!-- var, let -->
#var(x = 10)
#let(foo = "bar")
<!-- outline -->
#outline(resultBlock = x + 1)
#outline(bodyBlock):
<h2>Hi there, world!</h2>
<p>I am a multi-line block definition</p>
#endblock
<!-- consider -->
#consider(resultBlock)
#bodyBlock()
I am actually blissful about these adjustments, as a result of Leaf is heading into the correct path, and people individuals who haven’t used the pre-released Leaf 4 variations but these adjustments will not trigger that a lot hassle. This new strategy follows extra like the unique Leaf 3 conduct.
Goodbye tags. Hi there entities!
Nothing is a tag anymore, however they’re separated to the next issues:
- Blocks (e.g. #for, #whereas, #if, #elseif, #else)
- Features (e.g. #Date, #Timestamp, and so on.)
- Strategies (e.g. .rely(), .isEmpty, and so on.)
Now you can create your very personal capabilities, strategies and even blocks. 🔥
public struct Hi there: LeafFunction, StringReturn, Invariant {
public static var callSignature: [LeafCallParameter] { [.string] }
public func consider(_ params: LeafCallValues) -> LeafData {
guard let identify = params[0].string else {
return .error("`Hi there` should be known as with a string parameter.")
}
return .string("Hi there (identify)!")
}
}
public func configure(_ app: Utility) throws {
LeafConfiguration.entities.use(Hi there(), asFunction: "Hi there")
}
Now you need to use this operate in your templates like this:
#Hi there("Leaf Tau")
You possibly can occasion overload the identical operate with totally different argument labels
public struct HelloPrefix: LeafFunction, StringReturn, Invariant {
public static var callSignature: [LeafCallParameter] { [
.string(labeled: "name"),
.string(labeled: "prefix", optional: true, defaultValue: "Hello")]
}
public func consider(_ params: LeafCallValues) -> LeafData {
guard let identify = params[0].string else {
return .error("`Hi there` should be known as with a string parameter.")
}
let prefix = params[1].string!
return .string("(prefix) (identify)!")
}
}
public func configure(_ app: Utility) throws {
LeafConfiguration.entities.use(Hi there(), asFunction: "Hi there")
LeafConfiguration.entities.use(HelloPrefix(), asFunction: "Hi there")
}
This fashion you need to use a number of variations of the identical performance.
#Hi there("Leaf Tau")
#Hi there(identify: "Leaf Tau", prefix: "Hello")
This is one other instance of a customized Leaf methodology:
public struct DropLast: LeafNonMutatingMethod, StringReturn, Invariant {
public static var callSignature: [LeafCallParameter] { [.string] }
public func consider(_ params: LeafCallValues) -> LeafData {
.string(String(params[0].string!.dropLast()))
}
}
public func configure(_ app: Utility) throws {
LeafConfiguration.entities.use(DropLast(), asMethod: "dropLast")
}
You possibly can outline your personal Leaf entities (extensions) through protocols. You do not have to recollect all of them, as a result of there may be various them, however that is the sample that you must search for Leaf*[Method|Function|Block] for the return varieties: [type]Return. If you do not know invariant is a operate that produces the identical output for a given enter and it has no negative effects.
You possibly can register these entities as[Function|Method|Block] by way of the entities property. It’ll take some time till you get accustomed to them, however fortuitously Leaf 4 comes with fairly a great set of built-in entities, hopefully the official documentation will cowl most of them. 😉
public struct Path: LeafUnsafeEntity, LeafFunction, StringReturn {
public var unsafeObjects: UnsafeObjects? = nil
public static var callSignature: [LeafCallParameter] { [] }
public func consider(_ params: LeafCallValues) -> LeafData {
guard let req = req else { return .error("Wants unsafe entry to Request") }
return .string(req.url.path)
}
}
public func configure(_ app: Utility) throws {
LeafConfiguration.entities.use(Path(), asFunction: "Path")
}
Oh, I virtually forgot to say that if you happen to want particular entry to the app or req property you need to outline an unsafe entity, which might be thought-about as a nasty observe, however fortuitously we have now one thing else to exchange the necessity for accessing these items…
Scopes
If you could cross particular issues to your Leaf templates it is possible for you to to outline customized scopes.
extension Request {
var customLeafVars: [String: LeafDataGenerator] {
[
"url": .lazy([
"isSecure": LeafData.bool(self.url.scheme?.contains("https")),
"host": LeafData.string(self.url.host),
"port": LeafData.int(self.url.port),
"path": LeafData.string(self.url.path),
"query": LeafData.string(self.url.query)
]),
]
}
}
extension Utility {
var customLeafVars: [String: LeafDataGenerator] {
[
"isDebug": .lazy(LeafData.bool(!self.environment.isRelease && self.environment != .production))
]
}
}
struct ScopeExtensionMiddleware: Middleware {
func reply(to req: Request, chainingTo subsequent: Responder) -> EventLoopFuture<Response> {
do {
strive req.leaf.context.register(turbines: req.customLeafVars, toScope: "req")
strive req.leaf.context.register(turbines: req.software.customLeafVars, toScope: "app")
}
catch {
return req.eventLoop.makeFailedFuture(error)
}
return subsequent.reply(to: req)
}
}
public func configure(_ app: Utility) throws {
app.middleware.use(ScopeExtensionMiddleware())
}
Lengthy story brief, you may put LeafData values right into a customized scope, the great factor about this strategy is that they are often lazy, so Leaf will solely compute the corresponding values if when are getting used. The query is, how will we entry the scope? 🤔
<ul>
<li><b>ctx:</b>: #($context)</li>
<li><b>self:</b>: #(self)</li>
<li><b>req:</b>: #($req)</li>
<li><b>app:</b>: #($app)</li>
</ul>
You must know that self is an alias to $context, and you’ll entry your personal context variables utilizing the $ signal. It’s also possible to construct your personal LeafContextPublisher object that may use to change the scope.
closing class VersionInfo: LeafContextPublisher {
let main: Int
let minor: Int
let patch: Int
let flags: String?
init(main: Int, minor: Int, patch: Int, flags: String? = nil) {
self.main = main
self.minor = minor
self.patch = patch
self.flags = flags
}
var versionInfo: String {
let model = "(main).(minor).(patch)"
if let flags = flags {
return model + "-" + flags
}
return model
}
lazy var leafVariables: [String: LeafDataGenerator] = [
"version": .lazy([
"major": LeafData.int(self.major),
"minor": LeafData.int(self.minor),
"patch": LeafData.int(self.patch),
"flags": LeafData.string(self.flags),
"string": LeafData.string(self.versionInfo),
])
]
}
public func configure(_ app: Utility) throws {
app.views.use(.leaf)
app.middleware.use(LeafCacheDropperMiddleware())
app.get(.catchall) { req -> EventLoopFuture<View> in
var context: LeafRenderer.Context = [
"title": .string("Leaf 4"),
"body": .string("Hello Leaf Tau!"),
]
let versionInfo = VersionInfo(main: 1, minor: 0, patch: 0, flags: "rc.1")
strive context.register(object: versionInfo, toScope: "api")
return req.leaf.render(template: "house", context: context)
}
}
What if you wish to lengthen a scope? No downside, you are able to do that by registering a generator
extension VersionInfo {
var extendedVariables: [String: LeafDataGenerator] {[
"isRelease": .lazy(self.major > 0)
]}
}
let versionInfo = VersionInfo(main: 1, minor: 0, patch: 0, flags: "rc.1")
strive context.register(object: versionInfo, toScope: "api")
strive context.register(turbines: versionInfo.extendedVariables, toScope: "api")
return req.leaf.render(template: "house", context: context)
There’s an app and req scope obtainable by default, so you may lengthen these by way of an extension that may return a [String: LeafDataGenerator] variable.
Abstract
As you may see Leaf improved quite a bit in comparison with the earlier variations. Even within the beta / rc interval of the 4th main model of this async template engine introduced us so many nice stuff.
Hopefully this text will assist you throughout the migration course of, and I consider that it is possible for you to to make the most of most of those built-in functionalities. The model new render and context mechanism offers us extra flexibility with out the necessity of declaring extra native constructions, Leaf variables and the redesigned hierarchy system will assist us to design much more highly effective reusable templates. By way of entity and the scope API we can carry Leaf to a totally new stage. 🍃
[ad_2]
