Customized Leaf tags in Vapor 4

[ad_1]

On this article I will present you tips on how to create some helpful customized tags for the Leaf template engine, written in Swift.

Vapor



The right way to prolong Leaf?

With the rebirth of Leaf we are able to really prolong the template engine and customized tags are only a factor of the previous. You realize in earlier variations of Leaf every thing was known as a tag and there was no differentiation between these little bastards beginning with the # image. Now issues have modified. There are lots of totally different entities in Leaf Tau.

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

This can be a good thing and on prime of this you may create your very personal features, strategies and even blocks. This brings us to a totally extensible template engine that may render every thing in a non-blocking asynchronous means. How cool is that? 😎

Did I point out that Leaf you may prolong the context with customized LeafDataGenerators? Sure, that is a factor now, prior to now you can use the userInfo object to set a “world” out there variable in Leaf, that was nicely out there in each single template file.

Now there are some particular variables out there that you would be able to prolong:


The present context in fact is what you cross to your template utilizing the render technique written in Swift. It’s value to say that self is simply an alias to the present $context, so it would not issues which one you utilize. The $app and $req scopes are empty by design, however you may prolong them. You may even register your individual scope for instance $api and set every thing you want globally beneath that variable. I will present you the way to do that in a while.

As you may see there are many choices out there to increase Leaf. You must suppose twice which path you’re taking, however it’s nice that now we have this many alternatives. Now we’ll stroll via of every of these items and I will present you tips on how to write customized extensions for Leaf Tau. 🥳




The right way to prolong Leaf contexts?

Probably the most simple means of extending Leaf is to offer customized context variables. We are able to simply write an extension for the Utility and the Request object and return LeafDataGenerator values with particular keys and in a while we are able to register these as extra context variables.


import Vapor
import Leaf

extension Utility {
    var customLeafVars: [String: LeafDataGenerator] {
        [
            "isDebug": .lazy(LeafData.bool(!self.environment.isRelease && self.environment != .production))
        ]
    }
}

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)
                    ]),
        ]
    }
}


A LeafDataGenerator object could be lazy or rapid. Quick values might be saved immediately, however lazy values will produce generator blocks which can be going to be known as solely when the renderer wants them. Nothing particular, this works just like the lazy key phrase in Swift.


struct ScopeExtensionMiddleware: Middleware {

    func reply(to req: Request, chainingTo subsequent: Responder) -> EventLoopFuture<Response> {
        do {
            attempt req.leaf.context.register(turbines: req.utility.customLeafVars, toScope: "app")
            attempt req.leaf.context.register(turbines: req.customLeafVars, toScope: "req")
        }
        catch {
            return req.eventLoop.future(error: error)
        }
        return subsequent.reply(to: req)
    }
}


We’d like an extension middleware that registers our generator variables to the given scope.


public func configure(_ app: Utility) throws {

    app.middleware.use(ScopeExtensionMiddleware())

    
}

Attempt to print these values in a template file, you may entry child-values utilizing the dot notation.

#(self)
#($context)

#($app)
#($app.isDebug)

#($req)
#($req.url)
#($req.url.host)
#($req.url.isSecure)
#($req.url.path)
#($req.url.port)
#($req.url.question)

Now we’re going to create a customized context to get some details about the host machine.

last class ServerLeafContextGenerator: LeafContextPublisher {

    var osName: String {
        #if os(macOS)
        return "macOS"
        #elseif os(Linux)
        return "Linux"
        #elseif os(Home windows)
        return "Home windows"
        #else
        return "Unknown"
        #endif
        
    }

    lazy var leafVariables: [String: LeafDataGenerator] = [
        "os": .lazy([
            "name": LeafData.string(self.osName),
            "version": LeafData.string(ProcessInfo.processInfo.operatingSystemVersionString),
        ]),
        "cpu-cores": .rapid(ProcessInfo.processInfo.processorCount),
        "reminiscence": .rapid(ProcessInfo.processInfo.physicalMemory),
    ]
}

We are able to merely put this line subsequent to the opposite two within the scope extension middleware.

attempt req.leaf.context.register(turbines: ServerLeafContextGenerator().leafVariables, toScope: "server")

This manner we are able to get some more information concerning the server in our Leaf templates by utilizing the $server scope. One other means is to increase a scope regionally with a generator.

app.get("server-info") { req -> EventLoopFuture<View> in
    var context: LeafRenderer.Context = [
        "title": "Server info",
    ]
    attempt context.register(object: ServerLeafContextGenerator(), toScope: "server")
    return req.leaf.render(template: "server-info", context: context)
}

The distinction is that within the second case the server scope is simply out there for a single endpoint, but when we register it via the middleware then it may be reached globally in each single Leaf file.

I feel scopes are very helpful, particularly Request associated ones. Previously we needed to create a customized Leaf tag to get the trail, however now we are able to use a scope extension and this data might be out there all over the place. With the lazy load we additionally get some free efficiency enhancements.



Customized Leaf features and strategies

You may create customized features and strategies for Leaf, I would say that this new API is the replacemenet of the outdated tag system. There are some variations and at first sight you would possibly suppose that it is more durable to create a perform with the brand new instruments, however in time you will get used to it.


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

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

This can be a very fundamental perform. Each single perform has a name signature, which is only a listing of type-safe arguments. Features can have return varieties, fortuitously there are pre-made protocols for these, so you do not have to implement the required stuff, however you may say that this features is e.g. a StringReturn perform. Invariant implies that the perform will at all times return the identical output for a similar enter. That is what you need more often than not, it additionally lets you keep away from side-effects.

Within the consider perform you will get entry to all of the enter parameters and you need to return with a LeafData kind. If a parameter is lacking or it might’t be casted to the right kind you may at all times return with an error. Consider is prefer to the outdated render technique, however it’s far more superior.

LeafConfiguration.entities.use(Hiya(), asFunction: "Hiya")

You additionally should register this newly created perform beneath a give title.

#Hiya("Leaf Tau")

Oh by the best way strategies are simply particular features so you may construct them the identical means and register them by way of the asMethod: property. If you wish to see extra examples, you need to check out my different submit about what’s new in Leaf Tau or scroll right down to the final part of this text.




The right way to construct customized Leaf blocks?

This can be a very fascinating and sophisticated matter. Blocks are particular sort of LeafFunctions, similar to strategies, however issues are just a bit bit extra difficult on this case. Instance time:

import Vapor
import Leaf


struct MaybeBlock: LeafBlock, VoidReturn, Invariant {
    
    static var parseSignatures: ParseSignatures? = nil
    static var evaluable: Bool = false
    var scopeVariables: [String]? = nil

    static var callSignature: [LeafCallParameter] { [.double(labeled: "chance")] }
         
    static func instantiate(_ signature: String?, _ params: [String]) throws -> MaybeBlock { .init() }

    mutating func evaluateScope(_ params: LeafCallValues, _ variables: inout [String: LeafData]) -> EvalCount {
        params[0].double! > Double.random(in: 0..<1) ? .as soon as : .discard
    }
    
    mutating func reEvaluateScope(_ variables: inout [String : LeafData]) -> EvalCount {
        fatalError("Error: `Perhaps` blocks cannot be re-evaluated.")
    }
}


This block has a name signature with a labeled argument known as probability. It has an instantiate technique which is utilized by the Leaf engine to create this block. It will not have any parseSignatures or scope variables, we’ll go away that for the for block (go and verify the supply in LeafKit in case you are curious & courageous sufficient). We set evaluable to false since we do not need to make it callable by way of the #consider perform. Now let’s speak about scope analysis actual fast.

The evaluateScope technique might be known as first when the block inside your template will get evaluated. You must return an EvalCount on this technique, which can resolve what number of occasions ought to we print out the contents in between your block (#[name]:THIS PART#finish[name]).

Mainly when a LeafBlock is evaluated the primary time, it is by way of evaluateScope. If that returns a consequence reasonably than nil, any additional calls will use reEvaluateScope as an alternative. – tdotclare

If EvalCount is about to discard then the contents might be discarded, in any other case it will be evaluated as many occasions as you come back. If the depend is .as soon as meaning the tip of the story, but when it get’s evaluated a number of occasions and you do not want extra params for additional analysis, then the reEvaluateScope might be known as for all the opposite cycles.


LeafConfiguration.entities.use(MaybeBlock.self, asBlock: "perhaps")


Remember that now we have to register this block with a given title earlier than we may use it.


#perhaps(probability: 0.5):
    <p>Is that this going to occur? 50-50.</p>
#endmaybe


That is it, we have simply prolonged Leaf with a fundamental block, you may attempt to construct your individual A/B testing Chained block if you wish to dig deeper, however that is fairly a sophisticated matter and there are not any docs out there simply but so you have got to check out the LeafKit supply information in many of the circumstances.





Helpful Leaf extensions.

I’ve made a bunch of helpful Leaf extensions out there beneath the LeafFoundation repository. It is a work-in-progress undertaking, however hopefully it will comprise lot extra fascinating extensions by the point Leaf 4 might be formally launched. PR’s are welcomed. 😬









[ad_2]

Leave a Reply