Pug Template
Get Started
In your Flamingo project, run:
mkdir frontend
cd frontend
npm init
npm i flamingo-carotene-core
npm i flamingo-carotene-pug
mkdir -p src/page
echo "h1 hello world" > src/page/index.pug
npx flamingo-carotene build
Add the index route in config/routes.yml
:
- path: /
name: home
controller: flamingo.render(tpl="index")
See the folder example
for this example.
Pug js
Pug is a JavaScript template rendering engine.
Flamingo Pug Template
The Flamingo flamingo.me/pugtemplate
package is a flamingo template module to use Pug templates.
Pug.js is by default compiled to JavaScript, and executed as HTML. This mechanism is used to render static prototypes
for the templates, so the usual HTML prototype is
just a natural artifact of this templating, instead of an extra workflow step or custom tool.
This allows frontend developers to start templating very early with very few backend support, and without the need to
rewrite chunks or even learn a new template language.
The static prototype can be used to test/analyze the UI in early project phases, when the backend might not be ready
yet.
The way pug.js works is essentially the following:
template -[tokenizer]-> tokens -[parser]-> AST -[compiler]-> JavaScript -[runtime]-> HTML
To integrate it in Flamingo, we save the AST (abstract syntax tree) in a JSON representations. Pugtemplate will use a
parser to build an in-memory tree of the concrete building blocks. It then will use a renderer to transform these blocks
into HTML Go templates as follows:
AST -[parser]-> Block tree -[render]-> go template -[go-template-runtime]-> HTML
It is possible to view the intermediate result by https://your_flamingo_url/_pugtpl/debug?tpl=home/home
Templating
One feature of pug.js is the possibility to use arbitrary JavaScript in cases where the template syntax does not provide
the some functionality. For example, loops can be written as below:
ul
each val, index in ['zero', 'one', 'two']
li= index + ': ' + val
In this example the term ['zero', 'one', 'two']
is in JavaScript. Developers will be able to use more advanced code:
- var prefix = 'foo_'
ul
each val, index in [prefix+'zero', prefix+'one', prefix+'two']
li= index + ': ' + val
The pug_template module takes this JavaScript and uses the Go-based JS engine, otto, to parse the JavaScript and
transpile it into Go code.
While this works for most standard statements and language constructs (default data types such as maps, list, etc), it
does not support certain things such as Object Oriented Programming or the JavaScript standard library. Only snippets of
JavaScript code can be run.
However, it is possible to recreate such functionalities in a third-party module via Flamingo's template functions.
For example pug_template itself has a substitute for the JavaScript Math
library with the min
, max
and ceil
functions. Please note that these function have to use reflection and it's up to the implementation to properly reflect
the functionality and handle different inputs correctly.
Nevertheless, extensive usage of JavaScript is not advised.
Dynamic JavaScript
The Pug Template engine compiles a subset of JavaScript (ES2015) to Go templates.
This allows frontend developers to use known a syntax and techniques, instead of learning a complete new template
engine.
To make this possible Flamingo rewrites the JavaScript to Go, on the fly.
Supported JavaScript
Standard Data types
- var object = {"key": "value"}
- var array = [1, 2, 3, 4, 5]
- var concat_string = "string" + "another string"
- var add = 1 + 2
- var multiply = 15 * 8
Those types have been reflected in Go in a form structs as Pugjs.Object, Pugjs.Map, Pugjs.Array, Pugjs.String and
Pugjs.Number.
Supported prototype functions
Array
The technical documentation of the array struct can be found
on GoDoc. As a short summary, the following functions are
publicly available:
- var myArray = [1,2,3,4,5]
- myArray.indexOf(3) // returns 2
- myArray.length // return 5
- myArray.join(', ') // joins the values of the array by the given separator: "1, 2, 3, 4, 5"
- myArray.push(6) // returns [1,2,3,4,5,6]
- myArray.pop() // returns 6 and myArray equals [1,2,3,4,5]
- myArray.splice(4) // return [5] and myArray equals [1,2,3,4]
- myArray.slice(2) // returns [1,2]
- myArray.sort() // sorts alphabetically or numerically the array
Note that the splice and slice functions only have a start index.
Objects
Javascript Object have been transcribed as Go Maps. The technical documentation of the objects/Pugjs.Maps struct can be
found on GoDoc. As a short summary, the following functions are
publicly available:
- var myObject = {"a": 1, "b": 2}
- myObject.keys() // returns an array of keys ["a","b"]
- myObject.assign("c", 3) // assigns a new property by key myObject equals {"a": 1, "b": 2, "c":3}
- myObject.hasMember(a) // True
- myObject.hasMember(z) // False
Strings
The technical documentation of the string struct can be found
on GoDoc. As a short summary, the following functions are
publicly available:
- var myString = "This is a nice string"
- myString.length // returns 21
- myString.indexOf("h") // returns 1
- myString.charAt(4) // returns "s"
- myString.toUpperCase() // returns "THIS IS A NICE STRING"
- myString.toLowerCase() // returns "this is a nice string"
- myString.replace("is", "was") // returns "this was a nice string"
- myString.split(" ") // returns ["this","was","a","nice","string"]
- myString.slice(1, 4) // returns "his"
Supported template functions
TODO
Supported Pug
Interpolation
- var a = "this"
p Text #{a} something #{1 + 2}
Conditions
- var user = { description: 'foo bar baz' }
- var authorised = false
if user.description
p {user.description}
else if authorised
p Authorised
else
p Not authorised
Loops
each value, index in ["a", "b", "c"]
p value #{value} at #{index}
Mixins
mixin mymixin(arg1, arg2="default")
p.something(id=arg2)= arg1
+mymixing("foo")
+mymixin("foo", "bar")
Includes
include /mixin/mymixin
+mymixing("foo")
Debugging
Templates can be debugged via /_pugtpl/debug?tpl=pages/product/view
Partials Rendering
The template engine supports rendering of partials.
The idea of partials is to support progressive enhancement by being able to request just a chunk of content from the
browser. The partial will still be rendered server side and should be requested by an ajax call from the browser.
Partials are requested by setting the HTTP Header X-Partial
The requested partials are searched in a subfolder "{templatename}.partials"
So if you have a response that will normally render like this:
return cc.responder.Render("folder/template")
And you request that page with the Header X-Partial: foo,bar
The engine will search for partials in folder/template.partials/foo.pug
and folder/template.partials/bar.pug
and
just render them and return them wrapped in a JSON response:
{
"partials": {
"foo": "content rendered",
"bar": "content rendered"
}
}
If you call the template function setPartialData
in this templates, you can add additional data to the json response.
For example:
setPartialData("cartamount", 4)
Will result in this response:
{
"partials": {
"foo": "content rendered",
"bar": "content rendered"
},
"data" : {
"key": 4
}
}
Loading mechanism
In production mode, all templates are loaded at once on application startup. Incoming requests are blocked until
the loading process has finished.
This module registers a route /pugjs/ready
on
the systemendpoint
which gives an HTTP 200 response only if the template loading has finished. This endpoint can for example be used as
kubernetes Startup probe.
In Flamingo's debug mode (flamingo.debug.mode: true
), all templates are loaded on demand on each render request.
This setting should be used when working on templates locally.