BoxLang 🚀 A New JVM Dynamic Language Learn More...
Stubble is a Mustache-inspired engine for rendering Mustache template syntax in CFML. It tokenizes templates, parses them into an AST, and renders against CFML data (view). When enabled, it also caches parsed templates in a thread-safe LRU cache.
Why not? :D There have been a handful of Mustache options in the JVM world that can integrate with CFML applications (with some exceptions), but the goal was for a native implementation that aligns as close to complete with the syntax specs, and then build on top of it.
The real experiment was that a vast majority of this project is built using AI Agents in an effort to test the capabilities of building functional, yet efficient CFML tooling in an ever-changing AI ecosystem.
Older CFML engines may still work, providing they support the functionality leveraged, but this project targets supported runtimes.
For a high-level overview of the syntax itself, see the Mustache manual.
The initial implementation of Stubble has put a good amount of focus on the existing Mustache spec which includes some of the following tag types/features:
Install the latest stable release from ForgeBox:
box install stubble
Stubble supports being instantiated directly or used as a ColdBox
module. The main API is the render() method which accepts
a template string, view data, and optional partials.
<cfscript>
stubble = new models.Stubble();
template = "Hello {{name}}!";
view = { name: "World" };
result = stubble.render(template, view);
writeOutput(result); // Outputs: Hello World!
</cfscript>
component {
property name="stubble" inject="Stubble@stubble";
any function index( event, rc, prc ) {
// Renders "Hello ColdBox!"
prc.greeting = variables.stubble.render(
template = "Hello {{name}}!",
view = { name : "ColdBox" }
);
event.setView( "main/index" );
}
}
<cfset template = "Hello {{name}}!">
<cfset view = { name: "ColdBox" }>
<!--- Outputs: Hello ColdBox! --->
<cfoutput>#renderMustache(template, view)#</cfoutput>
Stubble renders template strings which contain any number of Mustache
tags. File-based workflows read template and partial contents first
and then pass those strings to render().
<cfscript>
template = fileRead( expandPath( "./examples/templates/releaseReport.mustache" ) );
partials = {
projectCard: fileRead( expandPath( "./examples/templates/partials/projectCard.mustache" ) )
};
result = stubble.render( template, view, partials );
writeOutput( result );
</cfscript>
View:
{
name: "Stubble",
version: "1.0.0"
}
Template:
{{name}} v{{version}}
Output:
Stubble v1.0.0
View:
{
engines: [
{ name: "ColdFusion", org: "Adobe" },
{ name: "Lucee", org: "Lucee Association" },
{ name: "BoxLang", org: "Ortus Solutions" }
]
}
Template:
{{#engines}}
{{name}} by {{org}}
{{/engines}}
Output:
ColdFusion by Adobe
Lucee by Lucee Association
BoxLang by Ortus Solutions
A note on sections in CFML:
- Native Mustache syntax in
.mustachefiles works as expected.- Inside
.cfcand.cfmstring literals, a literal#must be escaped as##, so native Mustache section syntax appears in source as{{##people}}.
View:
{
engines: [
{ name: "ColdFusion" },
{ name: "Lucee" },
{ name: "BoxLang" }
]
}
Template:
{{#engines}}
{{name}}
{{/engines}}
Output:
ColdFusion
Lucee
BoxLang
View:
{
engines: [
"ColdFusion",
"Lucee",
"BoxLang"
]
}
Template:
{{#engines}}
{{.}}
{{/engines}}
Output:
ColdFusion
Lucee
BoxLang
View:
{
greeting: "Hello",
name: function() {
return "World";
}
}
Template:
{{greeting}} {{name}}
Output:
Hello World
View:
{
calc: () => { return 2 * 4; }
}
Template:
2 * 4 = {{calc}}
Output:
2 * 4 = 8
{{=<% %>=}}(<%text%>)
{{>*currentPartial}}
Resolve currentPartial from the current context and
render the matching partial from the partials struct.
{{<layout}}
{{$body}}Hello {{name}}{{/body}}
{{/layout}}
Use parent templates with block overrides to compose layouts while keeping rendering inside the current context.
There are runnable examples included in the examples/
directory that demonstrate various features of Stubble, including
basic variable interpolation, sections, partials, lambdas, and
file-based templates. You can access these examples by starting a
local server pointed to the project root and navigating to http://127.0.0.1:8520/examples/index.cfm.
examples/index.cfm lists the shipped demos.examples/basic-demo.cfm shows variables, sections,
partials, lambdas, and cache stats.examples/file-template-partial-demo.cfm shows
file-backed templates and partials with nested data.render( required string template, any data = {}, struct
partials = {} ) Renders a template string against the
supplied data and partial map.tokenize( required string template, string openDelimiter =
"{{", string closeDelimiter = "}}" )
Returns low-level tokens and supports custom starting delimiters.parse( required array tokens, required string template
) Parses tokens into the AST used by the renderer.configureCache( boolean enabled = true, numeric maxEntries =
200 ) Enables or disables the cache and sets the maximum
LRU size. Values below 1 are normalized to 1.clearCache() Removes all cached parsed templates.getCacheStats() Returns enabled,
maxEntries, and currentEntries.Tests are located in the test-harness/tests/specs/
directory and are written with TestBox in a BDD style, covering both
unit and integration scenarios for the standalone engine, official
Mustache specs, and ColdBox module.
The recommended way to run tests is via CommandBox and the provided
server configs (configured to run on port 8520). Once
dependencies have been installed and the server is running, navigate
to http://127.0.0.1:8520/tests/runner.cfm or run
box testbox run.
box run-script install:dependencies
box server start serverConfigFile="[email protected]"
The following supported server configs are available in the root of the repository:
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
Stubble was heavily based and inspired by the vast Mustache ecosystem and the many developers who have made it all possible. Thanks!
$
box install stubble