What’s new in Leaf 4 (Tau)?

[ad_1]

Every thing you need to know in regards to the upcoming Leaf template engine replace and learn how to migrate your Vapor / Swift codebase.

Vapor

Utilizing Leaf 4 Tau

Earlier than we dive in, let’s make a brand new Vapor challenge with the next bundle definition.



import PackageDescription

let bundle = Package deal(
    title: "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(title: "Run", dependencies: ["App"]),
        .testTarget(title: "AppTests", dependencies: [
            .target(name: "App"),
            .product(name: "XCTVapor", package: "vapor"),
        ])
    ]
)


The very very first thing I would like to indicate you is that now we have a brand new render technique. Prior to now we have been in a position to make use of the req.view.render perform 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 title="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:"Good day 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 can say at first sight. Properly, the factor is that this new technique offers type-safe values for our templates and that is simply the tip of the iceberg. It is best to neglect in regards to 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 title = "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)])
    }
}

When you take a better take a look at this comparable instance, you discover out that the context object and the values are representable by numerous sorts, but when we attempt to use an interpolated string, now we have to be somewhat bit extra kind 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 sorts for Leaf. That is the place the type-safety function is available in Tau. You should utilize the static LeafData helper strategies to ship your values as given sorts. πŸ”¨

The from parameter could be a LeafSource key, if you’re 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 technique with out the from parameter that’ll use the default search order of sources.


There’s a new argument that you should utilize to set predefined choices. You’ll be able to 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’ll be able to 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 afterward.


It’s also potential to disable Leaf cache globally by way of the LeafRenderer.Context property:

if !app.atmosphere.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 utility setup time, however they’re locked as quickly as a LeafRenderer is created. πŸ”’




Context and information illustration

You’ll be able to conform to the LeafDataRepresentable protocol to submit a customized kind as a context worth. You simply need to implement one leafData property.

struct Consumer {
    let id: UUID?
    let electronic 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 symbolize Swift sorts. Each single kind has built-in elective help, so you may ship nil values with out spending extra effort on worth checks or nil coalescing.

app.get() { req -> EventLoopFuture<View> in
    let person = Consumer(id: .init(),
                electronic mail: "[emailΒ protected]",
                birthYear: 1980,
                isAdmin: false)

    return req.leaf.render(template: "profile", context: [
        "user": user.leafData,
    ])
}

You’ll be able to assemble a LeafDataRepresentable object, however you continue to have to make use of the LeafRenderer.Context as a context worth. Luckily that kind may be expressed utilizing a dictionary the place keys are strings and values are LeafData sorts, so this can cut back the quantity of code that it’s important to kind.



Constants, variables, nil coalescing

Now let’s transfer away somewhat bit from Swift and discuss in regards to the new options in Leaf. In Leaf Tau you may outline variables utilizing template information with actual dictionary and array help. πŸ₯³

#var(x = 2)
<p>2 + 2 = #(x + 2)</p>
<hr>
#let(person = ["name": "Guest"])
<p>Good day #(person.title)</p>
<hr>
#(elective ?? "fallback")

Similar to in Swift, we will create variables and constants with any of the supported sorts. Whenever you inline a template variables may 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 should utilize variables and reuse chunks of Leaf code in a clear and environment friendly approach. Let me present you the way this works.

It’s also potential to make use of the coalescing operator to supply fallback values for nil variables.




Outline, Consider, Inline

One of many largest debate in Leaf is the entire template hierarchy system. In Tau, your complete method is rebuilt beneath the hood (the entire thing is extra highly effective now), however from the end-user perspective just a few key phrases have modified.




Inline

Prolong is now changed with the brand new inline block. The inline technique actually places the content material of a template into one other. You’ll be able to even use uncooked values for those who do not wish to carry out different operations (comparable to evaluating Leaf variables and tags) on the inlined template.


<!-- index.leaf -->
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta title="viewport" content material="width=device-width, initial-scale=1">
        <title>Leaf 4</title>
    </head>
    <physique>
        #inline("house", as: uncooked)
    </physique>
</html>

<!-- house.leaf -->
<h1>Good day 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 fascinating once we skip the uncooked half and we inline an everyday template that comprises different expressions. We’re going to flip issues just a bit bit and render the house template as a substitute 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? Properly, we will attempt that…


<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta title="viewport" content material="width=device-width, initial-scale=1">
        <title>#(title)</title>
    </head>
    <physique>
        #(physique)
    </physique>
</html>

<!-- house.leaf -->
<h1>Good day 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 perform that may consider a Leaf definition. You’ll be able to consider this as a block variable definition in Swift. You’ll be able to create a variable with a given title and afterward name that variable (consider) utilizing parentheses after the title 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 perform.


<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta title="viewport" content material="width=device-width, initial-scale=1">
        <title>#(title)</title>
    </head>
    <physique>
        #consider(bodyBlock) (# or you should utilize the `#bodyBlock()` syntax #)
    </physique>
</html>


On this template we will consider the bodyBlock and afterward we’ll have the ability to outline it someplace else.




Outline

Definitions. Lastly arrived to the final part that we’ll have to compose templates. Now we will create our physique block within the house template.


#outline(bodyBlock):
<h1>#(physique)</h1>
#enddefine

#inline("index")

Now for those who reload the browser (Leaf cache have to be disabled) every thing ought to work as it’s anticipated. Magic… or science, no matter, be at liberty to decide on one. πŸ’«

Particular thanks goes to tdotclare who labored day and evening to make Leaf higher. πŸ™

So what is going on on right here? The #outline(bodyBlock) part is accountable for constructing a block variable referred to as bodyBlock that’s callable and we will consider it afterward. We merely print out the physique context variable inside this block, the physique variable is a context variable coming from Swift, that is fairly easy. Subsequent we inline the index template (think about copy-pasting whole content material of the index template into the house template) which is able to print out the title context variable and evaluates the bodyBlock. The bodyBlock might be out there since we have simply outlined it earlier than our inline assertion. Straightforward peasy. 😝


<!-- var, let -->
#var(x = 10)
#let(foo = "bar")

<!-- outline -->
#outline(resultBlock = x + 1)
#outline(bodyBlock):
    <h2>Good day, world!</h2>
    <p>I am a multi-line block definition</p>
#endblock

<!-- consider -->
#consider(resultBlock)
#bodyBlock()


I am actually comfortable about these adjustments, as a result of Leaf is heading into the proper path, and people individuals who haven’t used the pre-released Leaf 4 variations but these adjustments will not trigger that a lot bother. This new method follows extra like the unique Leaf 3 habits.



Goodbye tags. Good day entities!

Nothing is a tag anymore, however they’re separated to the next issues:

  • Blocks (e.g. #for, #whereas, #if, #elseif, #else)
  • Capabilities (e.g. #Date, #Timestamp, and many others.)
  • Strategies (e.g. .depend(), .isEmpty, and many others.)

Now you can create your very personal features, strategies and even blocks. πŸ”₯

public struct Good day: LeafFunction, StringReturn, Invariant {
    public static var callSignature: [LeafCallParameter] { [.string] }

    public func consider(_ params: LeafCallValues) -> LeafData {
        guard let title = params[0].string else {
            return .error("`Good day` have to be referred to as with a string parameter.")
        }
        return .string("Good day (title)!")
    }
}

public func configure(_ app: Utility) throws {

    LeafConfiguration.entities.use(Good day(), asFunction: "Good day")
    
}

Now you should utilize this perform in your templates like this:

#Good day("Leaf Tau")

You’ll be able to occasion overload the identical perform 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 title = params[0].string else {
            return .error("`Good day` have to be referred to as with a string parameter.")
        }
        let prefix = params[1].string!
        return .string("(prefix) (title)!")
    }
}

public func configure(_ app: Utility) throws {


    LeafConfiguration.entities.use(Good day(), asFunction: "Good day")
    LeafConfiguration.entities.use(HelloPrefix(), asFunction: "Good day")

    
}

This fashion you should utilize a number of variations of the identical performance.

#Good day("Leaf Tau")
#Good day(title: "Leaf Tau", prefix: "Hello")

This is one other instance of a customized Leaf technique:


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’ll be able to outline your individual Leaf entities (extensions) through protocols. You do not have to recollect all of them, as a result of there may be numerous them, however that is the sample that you need to search for Leaf*[Method|Function|Block] for the return sorts: [type]Return. If you do not know invariant is a perform that produces the identical output for a given enter and it has no unwanted side effects.

You’ll be able to register these entities as[Function|Method|Block] by way of the entities property. It may take some time till you get accustomed to them, however thankfully Leaf 4 comes with fairly a superb 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 nearly forgot to say that for those who want particular entry to the app or req property it’s important to outline an unsafe entity, which might be thought-about as a foul follow, however thankfully now we have one thing else to switch the necessity for accessing these items…



Scopes

If it’s worthwhile to 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 {
            attempt req.leaf.context.register(turbines: req.customLeafVars, toScope: "req")
            attempt req.leaf.context.register(turbines: req.utility.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 method 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>

It is best to know that self is an alias to $context, and you’ll entry your individual context variables utilizing the $ signal. You may as well construct your individual LeafContextPublisher object that may use to change the scope.


remaining 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")
        attempt context.register(object: versionInfo, toScope: "api")
        return req.leaf.render(template: "house", context: context)
    }

    

}

What if you wish to prolong a scope? No drawback, 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")
attempt context.register(object: versionInfo, toScope: "api")
attempt context.register(turbines: versionInfo.extendedVariables, toScope: "api")
return req.leaf.render(template: "house", context: context)


There’s an app and req scope out there by default, so you may prolong these by way of an extension that may return a [String: LeafDataGenerator] variable.




Abstract

As you may see Leaf improved rather a lot 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 enable you to in the course of the migration course of, and I imagine 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 help us to design much more highly effective reusable templates. By means of entity and the scope API we can deliver Leaf to a very new stage. πŸƒ



[ad_2]

Leave a Reply