ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Vapor Leaf 模板引擎速查清单:从依赖集成、视图渲染到自定义标签的完整实战指南

Vapor Leaf 模板引擎速查清单:从依赖集成、视图渲染到自定义标签的完整实战指南 Vapor Leaf 模板引擎速查清单从依赖集成、视图渲染到自定义标签的完整实战指南【免费下载链接】reference面向开发者的技术速查清单Cheat Sheets集合整理常见技术、工具与开发流程帮助快速查阅关键信息提高开发效率。项目地址: https://gitcode.com/GitHub_Trending/referen/reference本指南以仓库中的 Leaf 备忘清单 为核心骨架系统梳理 Vapor 框架的轻量级模板引擎 Leaf 的完整技术要点涵盖依赖接入、引擎配置、目录规范、视图渲染、模板语法、内置标签以及自定义标签LeafTag的源码级开发实战。读完本文你将能够独立完成一个基于 Leaf 的 Vapor 服务端动态页面项目并能编写出诸如时间格式化、相对路径拼接等可复用的自定义模板标签。Leaf 是什么Leaf 是 Vapor 生态中的轻量级模板引擎用于在服务端生成动态 HTML 页面。它并不是 Vapor 框架的强制组成部分而是一个可以按需接入的可选依赖核心能力由LeafKit独立的 Swift 模板引擎库提供而Leaf则是 LeafKit 与 Vapor 的集成层负责把模板渲染能力无缝对接到 Vapor 的req.view体系当中。Leaf 的模板语言语法受 Swift 启发标签以#开头风格简洁、可读性强天然适合 Swift 开发者上手。本仓库将这份 Leaf 备忘清单收录在 编程分类 下Leaf与 Swift、SwiftUI 等清单共同构成面向中文开发者的技术速查集合。快速开始添加 Leaf 依赖Leaf 以 Swift Package ManagerSPM方式分发。在项目的Package.swift中声明依赖并链接产品即可官方要求的最低版本为 4.0// swift-tools-version:5.2 import PackageDescription let package Package( name: MyApp, platforms: [ .macOS(.v10_15) ], dependencies: [ /// 添加其它依赖 .package(url: https://github.com/vapor/leaf.git, from: 4.0.0), ], targets: [ .target(name: App, dependencies: [ .product(name: Leaf, package: leaf) ]), ] )要点说明platforms声明了macOS(.v10_15)的最低部署目标这是 Leaf 4 及其依赖链的运行前提依赖声明使用from: 4.0.0SPM 会解析到 4.x 系列的最新兼容版本目标产物通过.product(name: Leaf, package: leaf)暴露给Apptarget之后便可在源码中import Leaf。配置 Leaf 模板引擎接入依赖后需要在configure.swift或应用启动配置处完成引擎初始化。首先导入两个模块import Vapor import Leaf接下来按需配置以下四项1. 设置工作目录app.directory.workingDirectory ....工作目录是 Vapor 解析各类相对路径的基准点务必指向项目根目录即包含Package.swift的目录否则后续模板与静态资源的相对定位会出错。2. 设置模板目录app.directory.viewsDirectory ....模板目录用于存放.leaf文件。Vapor 默认约定为Resources/Views/若你的项目遵循该约定通常无需修改只有目录位置与默认值不一致时才需要显式指定。3. 设置模板引擎app.views.use(.leaf)这是最关键的一步将 Vapor 的视图层实现切换为 Leaf。执行后req.view.render(...)的调用才会被路由到 Leaf 渲染器。4. 配置自定义标签app.leaf.tags[relative] CustomTag()Leaf 通过标签注册表tags字典维护所有可用标签。以字符串为键注册自定义标签后模板中即可通过#relative(...)的形式调用。官方内置标签#extend、#if、#for等由 LeafKit 预置无需手动注册。推荐的目录结构与静态资源一个典型的 Vapor Leaf 项目目录结构如下VaporApp ├── Package.swift ├── Resources │ ├── Views │ │ └── hello.leaf ├── Public │ ├── images (images 资源) │ ├── styles (css 资源) └── Sources └── ...Resources/Views/文件夹专门用来存储.leaf模板文件渲染时传入的文件名即相对于该目录Public/目录存放 images、styles 等静态资源。要让它对浏览器可见需要配置FileMiddleware提供静态文件服务app.middleware.use(FileMiddleware( publicDirectory: app.directory.publicDirectory ))FileMiddleware会把Public/目录下的文件直接暴露为静态资源如/styles/main.css从而与 Leaf 渲染出的动态页面配合使用。渲染视图在路由闭包中调用req.view.render(hello, [...])即可渲染模板。Leaf 4 时代同时支持基于EventLoopFuture的传统写法和基于 Swift Concurrency 的async/await写法app.get(hello) { req - EventLoopFutureView in return req.view.render(hello, [ name: Leaf ]) } // 或 app.get(hello) { req async throws - View in return try await req.view.render( hello, [name: Leaf] ) }两种写法效果等价。渲染时第一个参数是模板文件名不含.leaf扩展名第二个参数是传给模板的上下文数据字典。在hello.leaf模板中使用传入的nameHello, #(name)!打开浏览器访问/hello页面将显示Hello, Leaf!。Leaf 模板语法基础一个基本的 Leaf 标签由三部分组成There are #count(users) users.标记#这表示 leaf 解析器开始寻找的标记名称count标签的标识符参数列表 (users)可以接受零个或多个参数。另外某些标签支持通过冒号和结束标签提供可选的正文内容例如#if(condition): ... #endif、#extend(main): ... #endextend。内置标签示例速览以下代码集中展示了最常用的一组内置标签的写法#(variable) #extend(template): 添加到模板中#endextend #export(title): 欢迎使用 Vapor #endexport #import(body) #count(friends) #for(friend in friends): li#(friend.name)/li #endfor引用子目录中的模板时使用相对路径写法#extend(partials/detail-layout): #export(body): 详情页面 #endexport #endextend表达式与运算Leaf 支持在标签中书写表达式常见运算符包括加法/字符串拼接%取模大于相等比较||逻辑或结合#if使用#if(1 1 2): Hello! #endif #if(index % 2 0): This is even index. #else: This is odd index. #endif上下文与数据传递Leaf 推荐用Encodable结构体向模板传数据。数组等集合类型需要作为结构体的属性整体传递而[String: Any]这种字典形式不被支持因为Any不满足Encodable约束struct WelcomeContext: Encodable { var title: String var numbers: [Int] } return req.view.render(home, WelcomeContext( title: Hello!, numbers: [42, 9001] ) )title和numbers将暴露给Leaf模板就可以在标签中使用这些变量h1#(title)/h1 #for(number in numbers): p#(number)/p #endfor条件控制Leaf 的条件标签家族由#if/#elseif/#else/#endif组成支持多种判断形式。判断变量是否存在空值或缺失时视为假#if(title): The title is #(title) #endif比较运算#if(title Welcome): This is a friendly web page. #endif使用另一个标签作为判断条件的一部分此时内部标签应该省略#例如直接写count(users)而不是#count(users)#if(count(users) 0): You have users! #else: There are no users yet :( #endif多个条件同时满足时才渲染使用连接#if(title user count(users) 0): You have users! #endif多分支判断#if(title Welcome): Hello new user! #elseif(title Welcome back!): Hello old user #else: Unexpected page! #endif循环遍历在控制器中以Codable结构体组织数据并传给模板struct SolarSystem: Codable { let planets [Venus, Earth, Mars] } return req.view.render( solarSystem, SolarSystem() )在Leaf中循环它们ul #for(planet in planets): li#(planet)/li #endfor /ul#for(元素 in 集合): ... #endfor是 Leaf 唯一的循环构造支持数组、以及可迭代的上下文属性循环体内可通过元素变量访问每一项数据。模板继承与布局复用#extend/#export/#import三个标签构成了 Leaf 的模板布局体系用于实现页面级复用。入口页面通过#extend(main)将main.leaf模板的内容复制到当前模板中使用并用#export注入自己的内容块#extend(main): #export(body): pWelcome to Vapor!/p #endexport #endextend在公共模板main.leaf中用#import(body)在指定位置占位并接收子模板注入的内容html head title#(name)/title /head body#import(body)/body /html假设渲染时传入了name Leaf最终呈现如下内容html head titleLeaf/title /head body pWelcome to Vapor!/p /body /html工作流程拆解#export在子模板中存储名为body的一段 HTML#import则取回并插入到父模板#extend所指向的模板对应位置。这本质上是一种「子模板声明内容、父模板声明占位」的协作模式适合搭建统一的页面骨架导航、页脚、公共头部等。内置标签详解#count返回数组中项目的数量Your search matched #count(matches) pages.#count标签返回数组中的项目数量。#lowercased将字符串转成小写字母#lowercased(name)#capitalized将字符串中每个单词的首字母大写其他字母小写#capitalized(name)#contains接受一个数组和一个值作为其两个参数如果参数一中的数组包含参数二中的值则返回 true#if(contains(planets, Earth)): Earth is here! #else: Earth is not in this array. #endif#date将日期格式化为可读的字符串。默认情况下它使用 ISO8601 格式render(..., [now: Date()])模板中使用The time is #date(now)可以传自定义日期格式作为第二参数格式语法遵循DateFormatter规范The date is #date(now, yyyy-MM-dd)#unsafeHTML标签就像一个变量标签例如#(variable)但它不会转义变量可能包含的 HTML 标签The time is #unsafeHTML(styledTitle)当上下文中携带的是已经过安全处理的富文本如带样式的标题、markdown 渲染产物时用它直接输出原始 HTML普通场景下应优先使用#(variable)以保留自动转义防止 XSS 风险。#dumpContext将整个上下文渲染为可读的字符串用于调试当前渲染所接收到的全部数据Hello, world! #dumpContext当模板变量取不到值时用它快速定位是键名拼写错误还是数据结构不匹配。自定义标签LeafTag 协议与 LeafContext内置标签无法覆盖所有业务需求时Leaf 提供了开放的自定义标签机制。创建一个类并遵循LeafTag协议struct NowTag: LeafTag { func render(_ ctx: LeafContext) throws - LeafData { ... } }实现render(_:)方法。传递给该方法的LeafContext参数包含了我们需要的所有内容——包括标签参数、渲染上下文数据、当前请求等。完整实战NowTag 时间格式化标签下面实现一个#now()标签无参数时输出默认格式的时间一个参数时将其作为DateFormatter的日期格式超过一个参数则报错。先定义错误类型enum NowTagError: Error { case invalidFormatParameter case tooManyParameters }再实现标签本体struct NowTag: LeafTag { func render(_ ctx: LeafContext) throws - LeafData { let formatter DateFormatter() switch ctx.parameters.count { case 0: formatter.dateFormat yyyy-MM-dd HH:mm:ss case 1: guard let string ctx.parameters[0].string else { throw NowTagError.invalidFormatParameter } formatter.dateFormat string default: throw NowTagError.tooManyParameters } let dateAsString formatter.string(from: Date()) return LeafData.string(dateAsString) } }注册标签设置标签名称为#nowapp.leaf.tags[now] NowTag()现在可以在 Leaf 中使用自定义标签#now了The time is #now()LeafContext 核心属性parameters 与 dataLeafContext包含两个重要的属性parameters包含标签参数的数组struct NowTag: LeafTag { func render( _ ctx: LeafContext ) throws - LeafData { /// ctx.parameters } }ctx.parameters的类型是[LeafData]按模板中参数的书写顺序排列。上面的NowTag就是通过ctx.parameters.count判断参数个数、通过ctx.parameters[0].string读取第一个参数的字符串值。data渲染时传给视图的数据使用render(_:_:)方法传入的数据字典会以ctx.data的形式暴露给自定义标签return try await req.view.render( home, [name: John] )自定义标签内通过键名读取struct NowTag: LeafTag { func render( _ ctx: LeafContext ) throws - LeafData { let name ctx.data[name]?.string } }此外从源码结构看LeafContext还暴露了request等与当前 HTTP 请求相关的信息详见下文RelativePathTag中ctx.request?.url.path的用法这使得自定义标签可以感知请求路径、查询参数等运行时状态是实现路径感知类标签的关键入口。进阶实战相对路径拼接标签在实际项目中模板位于Resources/Views/下的任意子目录而页面 URL 深度可能不同如/与/post/1导致href相对路径的计算变得繁琐。可以编写一个#relative(main.css)标签根据当前请求路径自动拼接出正确的相对前缀struct RelativePathTag: LeafTag { func render(_ ctx: LeafContext) throws - LeafData { guard ctx.parameters.count 1, let filename ctx.parameters[0].string else { throw Missing #relative parameters } if let filepath ctx.request?.url.path, filename.hasPrefix(/) false { return .string(\(relativePrefix(for: filepath, targetFile: filename))) } return .string(\(filename)) } private func relativePrefix(for pagePath: String, targetFile: String) - String { var components pagePath .trimmingCharacters(in: CharacterSet(charactersIn: /)) .split(separator: /) if let last components.last, last.contains(.) { components components.dropLast() } let cleanTarget targetFile.hasPrefix(./) ? String(targetFile.dropFirst(2)) : targetFile return String(repeating: ../, count: components.count) cleanTarget } }实现逻辑拆解参数校验必须恰好传入一个参数且能转换为字符串否则抛出Missing #relative parameters错误路径判断通过ctx.request?.url.path获取当前请求路径若目标文件以/开头绝对路径则直接原样返回前缀计算relativePrefix(for:targetFile:)把请求路径按/拆分若最后一段包含.说明是文件而非目录例如/post/detail中的detail则丢弃该段最终按剩余层级数生成对应数量的../相对路径归一目标文件名若以./开头先去掉前缀再拼接。配置标签app.leaf.tags[relative] RelativePathTag()现在可以在 Leaf 中使用自定义标签了link relstylesheet href#relative(main.css) /注意示例中throw Missing #relative parameters直接抛出字符串字面量实际工程中需要为String扩展Error协议或仿照NowTag定义显式错误枚举以保证代码符合 Swift 错误处理规范。总结Leaf 作为 Vapor 的官方模板引擎凭借「Swift 风格语法 轻量接入 开放标签扩展」的组合成为 Swift 服务端动态页面渲染的高效选择。掌握本文内容后你可以通过Package.swift接入 Leaf并完成工作目录、模板目录、视图引擎的三步配置遵循Resources/Views/Public/的目录规范组织模板与静态资源熟练运用#(variable)、#if/#elseif/#else、#for、#extend/#export/#import等核心语法构建完整页面借助#date、#count、#contains、#unsafeHTML、#dumpContext等内置标签快速完成格式化、判断与调试基于LeafTag协议和LeafContext的parameters、data、request属性开发任意自定义标签。如需快速查阅本文涉及的全部语法与代码可直接回到本仓库的 Leaf 备忘清单关于本仓库速查清单的整体结构与贡献规范可参阅 README.md 与 排版说明。【免费下载链接】reference面向开发者的技术速查清单Cheat Sheets集合整理常见技术、工具与开发流程帮助快速查阅关键信息提高开发效率。项目地址: https://gitcode.com/GitHub_Trending/referen/reference创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表