Budibase 字符串模板引擎全解析:基于 Handlebars 的跨端模板系统实战指南
【免费下载链接】budibaseAI agents, automations and apps that run your operations. Model agnostic.项目地址: https://gitcode.com/GitHub_Trending/bu/budibase
@budibase/string-templates是 Budibase 平台中统一负责字符串模板渲染的核心包,Builder 设计器、客户端页面与服务端自动化共用同一套模板能力。本文以该包的 README 为主体,结合源码实现,系统讲解其模板语法、九大类 Helper、日期格式化、核心 API 与内部处理管线,帮助你直接在自己的模板字符串中运用{{...}}表达式、条件块与数组迭代,并理解每一次渲染背后发生的预处理、Helper 注册与后处理过程。
一、包定位:一套模板,三端共用
Budibase 是一个低代码应用构建平台,其页面、自动化、邮件通知等场景大量依赖"模板字符串 + 上下文数据"的渲染方式。string-templates包正是为此设计的公共基础设施,位于 packages/string-templates/:
- 模板引擎内核为 Handlebars(Mustache 的逻辑增强扩展),具备
{{variable}}、{{#if}}、{{#each}}等能力; - 在 Handlebars 之上通过
@budibase/handlebars-helpers注册了一批扩展 Helper,覆盖数学、数组、字符串、比较、URL、正则等常见操作; - 包以 UMD 形式构建,既能被 Node 服务端(worker/server)引用,也能被浏览器端(builder/client)直接加载。
从 package.json 可以看到其依赖组合:handlebars4.7.9 作为引擎、@budibase/handlebars-helpers提供扩展 Helper、dayjs承担日期解析与格式化(取代了传统 moment 方案,显著降低打包体积)。
二、九大 Helper 集合:官方没有全部引入,只注册了精选子集
README 明确指出:并没有实现 handlebars-helpers 包提供的全部 Helper,只挑选了对 Budibase 场景有价值的 9 个集合。这一点在 helpers/constants.ts 中得到印证——源码中的EXTERNAL_FUNCTION_COLLECTIONS仅包含:
export const EXTERNAL_FUNCTION_COLLECTIONS = [ "math", "array", "number", "url", "string", "comparison", "object", "regex", "uuid", ]注册逻辑在 helpers/external.ts:遍历这些集合调用helperscollection将 Helper 挂载到 Handlebars 实例,同时跳过与内置 Helper 重名的条目(如if、each、with等,见HelperFunctionBuiltin)。
1. Math —— 数值运算
用于对数字执行逻辑运算:avg、add、abs、ceil、floor、divide、multiply、subtract、modulo、round、sum、random、remainder等。典型用法:
{{ add 1 2 }} -> 3 {{ avg 1 2 3 4 5 }} -> 3 {{ round 10.3 }} -> 10 {{ modulo 10 5 }} -> 02. Array —— 数组操作
对数组进行切片、拼接、迭代、过滤、排序。README 强调这类 Helper 在自动化中非常实用:
{{ after ['a','b','c','d'] 2 }} -> c,d {{ before ['a','b','c','d'] 3 }} -> a,b {{ first [1,2,3,4] 2 }} -> 1,2 {{ last [1,2,3] }} -> 3 {{ join array "-" }} -> 以 - 连接成字符串 {{#forEach array}} {{name}} {{/forEach}} -> 迭代数组forEach是典型的块级迭代 Helper,块内会暴露index、total、isFirst、isLast等变量,同时@index作为 Handlebars 私有变量可用。
3. Number —— 数字展示格式化
与 Math 不同,Number 集合侧重"把数字转成适合展示的格式":bytes(字节单位)、addCommas(千分位)、toPrecision(精度)、toFixed、toExponential、toAbbr(缩写)、phoneNumber(电话格式):
{{ addCommas 1000000 }} -> 1,000,000 {{ bytes 1386 1 }} -> 1.4 kB {{ toFixed 1.1234 2 }} -> 1.124. URL —— URL 处理
用于构建自动化中要请求的 URL:encodeURI、escape、decodeURI、stripQueryString、stripProtocol、urlResolve、urlParse:
{{ encodeURI 'https://myurl?Hello There' }} -> https%3A%2F%2Fmyurl%3FHello%20There {{ stripQuerystring 'https://myurl/api/test?foo=bar' }} -> 'https://myurl/api/test' {{ stripProtocol 'https://myurl/api/test' }} -> '//myurl/api/test'5. String —— 字符串构建与展示
这是日常使用最频繁的集合:append、prepend、camelcase、capitalize、capitalizeAll、downcase、upcase、lowercase、uppercase、ellipsis、truncate、trim、replace、remove、split、titleize、sentence、pascalcase、snakecase、dashcase、startsWith、occurrences等:
{{ append 'index' '.html' }} -> index.html {{ camelcase 'foo bar baz' }} -> fooBarBaz {{ ellipsis 'foo bar baz' 7 }} -> foo bar… {{ remove 'a b a b a b' 'a ' }} -> b b b {{ uppercase 'aBcDef' }} -> ABCDEF6. Comparison —— 条件逻辑
主要用于按条件构造字符串,是条件块{{#gte score "50"}}的主力:and、or、not、eq、is、isnt、gt、gte、lt、lte、compare、contains、default、has、ifEven、ifOdd、ifNth、isTruthy、isFalsey等,绝大多数支持块级与内联两种用法:
{{#and a b}}both{{else}}no{{/and}} {{#gte 4 3}}greater or equal{{else}}less{{/gte}} {{#contains ['a','b','c'] 'd'}}found{{else}}not found{{/contains}}7. Object —— 对象解析与 JSON 输出
将对象转为 JSON 字符串,便于在模板中输出结构化数据。源码在 helpers/index.ts 中直接注册为:
new Helper(HelperFunctionNames.OBJECT, (value: any) => { return new Handlebars.SafeString(JSON.stringify(value)) })测试用例 helpers.spec.ts 验证了其行为:
{{ object obj }} -> {"a":1}8. Regex —— 正则测试
对字符串执行正则匹配,可用于条件语句:
{{#match 'foobar' 'foo'}}matched{{else}}not{{/match}}9. Date —— 日期格式化
基于 moment 的helper-date改造而来,可把 ISO/时间戳日期格式化为人类可读文本:
{{ date dateProperty "DD-MM-YYYY" }}需要注意:源码中的日期实现并未使用 moment,而是以 dayjs 重写了helper-date的逻辑(见 helpers/date.ts 头部注释),原因有二:原包同时依赖 moment 与 date.js 在简单语法上产生怪异 bug;换用 dayjs 后大幅削减 bundle 体积。该文件额外注册了duration、difference、durationFromNow三个时间差值/时长 Helper,默认格式为MMMM DD, YYYY。
三、日期格式化语法
本包采用标准的日期时间格式化记号,下表完整摘录自 README(YYYY、YY、Y、Q、M/MM、MMM/MMMM、D/DD、Do、DDD/DDDD、X、x):
| 输入 | 示例 | 描述 |
|---|---|---|
| YYYY | 2014 | 4 位或 2 位年份。注意:严格模式下只有 4 位可解析 |
| YY | 14 | 2 位年份 |
| Y | -25 | 任意位数并带正负号的年份 |
| Q | 1..4 | 年份季度。会把月份设为该季度首月 |
| M MM | 1..12 | 月份数字 |
| MMM MMMM | Jan..December | 由 moment.locale() 设定的语言环境下的月份名 |
| D DD | 1..31 | 月份中的第几天 |
| Do | 1st..31st | 带序数后缀的日 |
| DDD DDDD | 1..365 | 一年中的第几天 |
| X | 1410715640.579 | Unix 时间戳(秒) |
| x | 1410715640579 | Unix 毫秒时间戳 |
从源码看,dateHelper 支持更多细节:不传参数时返回当前时间按默认格式输出;传入字符串时区参数(如"utc")会切换到 UTC,否则使用dayjs.tz.guess()推测的本地时区;pattern传空字符串时返回toISOString()结果。
四、模板格式:内联表达式与条件/迭代块
模板系统支持两种主要写法。
4.1 单语句(类 Mustache)
Hello I'm building a {{uppercase adjective}} string with Handlebars!给定上下文{adjective: "cool"},输出:
Hello I'm building a COOL string with Handlebars!可以看到字符串 Helper 直接把结果嵌入了句子。这类语句还可以用圆括号堆叠多个 Helper:
{{ uppercase (remove string "bad") }}内层remove先移除子串,外层uppercase再对结果转大写。
4.2 条件块语句
Hello I'm building a {{ #gte score "50" }}Great{{ else }}Bad{{ /gte }} string with Handlebars!根据上下文变量score与"50"的比较结果,插入Great或Bad。Comparison、String、Array 集合中的部分 Helper 都支持这种条件式块级用法。
4.3 迭代块
与条件块语法相近但产生循环操作,例如forEach数组 Helper:
{{#forEach orders}}{{name}} - {{total}}{{/forEach}}这正是自动化中对一组记录逐条拼装文本的核心手段。
五、核心 API:七个主函数 + 若干进阶能力
包的对外使用方式是通过入口 src/index.ts 导出的函数。README 列出的七个主函数如下:
1.processString(string, object)(async)
异步处理单个模板字符串。给定模板字符串与上下文对象,经由预处理(pre-processors)、Handlebars 渲染、后处理(post-processors)三步产出结果字符串。从源码看,其内部直接委托给processStringSync:
export async function processString(string, context, opts) { return processStringSync(string, context, opts) }2.processObject(object, object)(async)
对对象内所有字符串属性递归执行processString的能力,常用于一次处理整份数据/配置。源码 index.ts 会先调用testObject用JSON.stringify探测循环引用(存在环会直接抛错,防止无限递归),然后逐键递归;大对象因深度递归可能较慢。
3.processStringSync(string, object)
同步版字符串处理,功能较异步版精简,风格类似 Node 的readdirSync。所有 JS 绑定、异步能力在同步模式下不可用。
4.processObjectSync(object, object)
processObject的同步版本,同样递归遍历对象并处理每个字符串属性。
5.makePropSafe(string)
处理 Handlebars 无法直接访问的属性名。例如Table 1含空格不是合法标识符,调用后变为[Table 1](字面量说明符包裹)。README 强调:对象访问的每一层都应调用此函数,即[Table 1].[property name]才是 Handlebars 需要的完整语法。源码实现非常简洁:
export function makePropSafe(property: any): string { return `[${property}]`.replace("[[", "[").replace("]]", "]") }6.isValid(string)
检测给定字符串是否为合法模板,返回布尔值。源码 index.ts 的判定方式是直接编译并渲染一次模板,捕获 Handlebars 的语法错误信息,再通过错误消息特征(expecting '等无效特征与cannot read property、undefined等有效特征)判断合法性。
7.getManifest()
返回描述全部 Helper 及其参数的清单 JSON。该文件由脚本从 Helper 元数据生成,位于 src/manifest.json,为 Builder 设计器提供"该 Helper 有哪些参数、示例是什么"的自动提示数据,例如add的参数为a、b,示例{{ add 1 2 }} -> 3。
进阶能力:源码中额外导出的实用函数
除 README 列出的七项外,源码还提供了一批进阶工具,适合在集成开发时使用:
processJsonStringSync(template, context, opts):专为 JSON 字符串模板设计,先把模板中{{...}}块替换为占位 token 以保证 JSON 语法有效,再解析、渲染、还原;多对象模板(如 Mongo 的{filter} {update})会按顶层对象拆分处理,注释明确说明"失败即关闭"策略——多对象模板必须是合法 JSON 对象,避免引号破坏导致的操作符注入(index.ts);encodeJSBinding(javascript)/decodeJSBinding(handlebars)/isJSBinding(handlebars):将任意 JS 代码编码为{{ js "base64..." }}模板表达式(或反向解码),这是 Budibase 在前端执行自定义 JS 绑定的机制;findHBSBlocks(string):提取字符串中所有{{...}}/{{{...}}}块;doesContainString(template, string)/doesContainStrings(template, strings):判断模板中是否存在包含指定词的绑定,检测 JS 绑定时会先解码再匹配;disableEscaping(string):把{{ name }}形式的双重花括号改写为{{{ name }}}三花括号,从而关闭 Handlebars 默认的 HTML 转义(由noEscaping处理选项触发);convertToJS(hbs):把 Handlebars 模板编译为可执行的 JS 模板字符串,用于前端 JS 执行环境。
六、渲染管线:预处理 → Helper 执行 → 后处理
理解processString的完整行为需要看三层处理。渲染入口在 index.ts 的processStringSyncInternal:模板编译时注入当前时间戳now与内部选项__opts,执行后统一走postprocess/postprocessWithLogs。
6.1 预处理(Preprocessors)
定义在 processors/preprocessor.ts,按顺序执行四个步骤:
- SWAP_TO_DOT:把
[Table 1]这类字面量写法自动规范为.[Table 1]点号链访问语法; - FIX_FUNCTIONS:修正
{ #gte、{ else、{ /gte等带空格的块语法为{#gte标准写法,容忍用户在模板中手写的多余空格; - NORMALIZE_SPACES:将
{{(多个连续空格)规整为{{; - FINALISE:把绝大多数内联语句包装成
{{ all ... }}形式——all是包内置的通用 Helper,负责统一处理空值输出、对象转 JSON、&还原以及<、>的 HTML 安全转义(helpers/index.ts)。函数类块语句(#、else、/开头)不会被包装。
此外在createTemplate中还有一个细节:如果上下文对象里的键与某个 Helper 重名(称为 overlapping helpers),会把这些键自动加上./前缀以避免与 Helper 冲突(index.ts)。
6.2 Helper 注册
helpers/index.ts 中registerAll分两层完成注册:
registerMinimum:注册包内置 Helper——object(对象转 JSON)、js(JS 绑定执行)、decodeId(解码 URL 编码的 ID)、all(全语句通用包装)、literal(为后处理留下字面量标记,如{{%LITERAL% number-42}});registerAll:在最低集基础上追加外部 Helper 集合与date、duration、difference、durationFromNow四个日期增强 Helper。
同时,包维护两个Handlebars 实例:完整实例hbsInstance(注册全部 Helper)与最小实例hbsInstanceNoHelpers(仅registerMinimum)。当处理选项noHelpers: true时走最小实例——测试 helpers.spec.ts 验证了{{ avg 1 1 1 }}在noHelpers下输出为空字符串。
6.3 后处理(Postprocessors)
定义在 processors/postprocessor.ts:CONVERT_LITERALS识别%LITERAL%标记,根据标记中携带的类型(string、number、boolean、object、js_result)把渲染结果还原为对应类型——这正是模板能输出数字、布尔和对象而非纯字符串的关键机制。
6.4 处理选项(ProcessOptions)
createTemplate中的默认选项(index.ts)反映了可调行为:
| 选项 | 默认值 | 作用 |
|---|---|---|
noHelpers | false | 为true时禁用全部外部 Helper,仅保留内置最小集 |
cacheTemplates | false | 为true时按"模板字符串 + 选项"缓存编译结果,复用模板函数 |
noEscaping | false | 为true时把双花括号改为三花括号,关闭 HTML 转义 |
escapeNewlines | false | 为true时把换行符替换为\n字面量 |
noFinalise | false | 为true时跳过all包装(isValid内部使用) |
onlyFound | - | 只处理字符串中已找到的 HBS 块,未命中时保留原输入 |
noThrow | true | 渲染失败时不抛错,返回原始输入 |
七、开发与构建
该包与仓库中其他包一样由lerna管理,使用Rollup构建(Budibase 多数包的标准做法),产物为UMD格式以便 Node 与浏览器同时使用,同时生成TypeScript 声明文件,供 IDE 在引用包时提供代码补全。package.json 中的命令:
yarn build—— 依次执行tsc --emitDeclarationOnly(生成类型声明)与rollup -c(打包出dist/bundle.cjs、dist/bundle.mjs);yarn test—— 运行 Jest 测试套件,覆盖各 Helper 的核心行为与若干典型用例。测试文件位于 test/,例如 helpers.spec.ts 验证了 object Helper、noHelpers模式、math 的abs/add/avg、array 的after/before/filter/itemAt/join/sort/unique、number 的addCommas/phoneNumber等;basic.spec.ts 与 hbsToJs.spec.ts 覆盖基础渲染与 HBS 转 JS 的逻辑;yarn dev—— 内部命令,由 lerna 在仓库根目录执行yarn dev时调用rollup -cw监听文件变化并持续重建。
此外还提供yarn manifest(运行 scripts/gen-collection-info.ts 重新生成 Helper 清单 JSON)与yarn check:types(类型检查)。
八、典型应用场景与实战要点
结合 Budibase 的整体架构,这套模板系统主要出现在三类场景:
- 自动化节点中的文本/URL 组装:利用 Math、Array、URL、String 集合将上游数据拼装为 API 请求地址、通知内容或输出消息;
- 页面数据绑定的安全访问:通过
makePropSafe把Table 1这类带空格的表名/属性名包装为[Table 1]再嵌入模板,避免 Handlebars 解析失败; - Builder 中的 Helper 提示:
getManifest()返回的清单驱动设计器向用户展示每个 Helper 的参数与示例。
实战中还需留意几个来自源码的行为边界:
- 同步与异步的能力差异:
processStringSync是精简功能版,JS 绑定等完整能力以异步/后端路径为准(index.ts 的注释明确说明); - 循环引用防护:
processObject/processObjectSync对含环对象直接抛错; - 上下文与 Helper 同名:会自动加
./前缀消除歧义; - JS 绑定限制:
processStringWithLogsSync在后端服务中会被禁用(Logging disabled for backend bindings),而isJSAllowed()在设置了环境变量NO_JS时会关闭 JS 执行能力(utilities.ts)。
综上,string-templates是一个"小而全"的模板基础设施:以 Handlebars 为引擎、以精选 Helper 集合为扩展、以三层处理管线保证输出安全与类型正确,并通过 UMD + 类型声明兼顾了三端复用与开发体验。无论是阅读 README 快速上手,还是深入 src/index.ts、src/helpers/ 与 src/processors/ 理解实现细节,都能为你在 Budibase 中编写、调试模板提供完整的知识闭环。
【免费下载链接】budibaseAI agents, automations and apps that run your operations. Model agnostic.项目地址: https://gitcode.com/GitHub_Trending/bu/budibase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考