Awesome
Mark Template
Mark Template is a new JS template engine inspired by React JSX and XSLT. It's template syntax is based on Mark, a new markup notation that has unified support of JSON, HTML and XML.
Features of Mark Template:
- Clean template syntax based on Mark notation.
- Wide-range of input/output formats support, including JSON, HTML, XML, DOM, virtual-dom, etc.
- Completely functional transformation.
- Powerful high-order transformation (like React high-order components).
- Powerful selector-based template matching (like XSLT, but uses CSS selector).
Template Reference
1. Template root
{template}
- The entire Mark template is wrap inside this root-level element
{template}
.
- The entire Mark template is wrap inside this root-level element
2. Top-level elements
Like React JSX and XSLT, each Mark Template script is actually a collection of transformation templates and functions. The top-level elements that can directly appear under the {template}
root are:
-
{import at:'url'}
- Import functions from template at the specified URL.
-
{component name:'...', match:'selector', extend:'class', prop:value
... // template contents
}
- Defines a component template.
- Multiple component templates, with the same name can be defined, but they should have different match
selector
. The template matching process is similar to that in XSLT. - When the component is being composed, the template engine maintains a transformation context that can be accessed through JS expression in the template body. The context fields are:
this.model.*
: the current matched model object;this.context.*
: global context for the entire transformation;this.children.*
: content nodes of the current component this is being composed;this.apply.*
: the current list of model objects that are being transformed;this.apply.length
: the length of the list that is being transformed;this.apply.index
: the index of the model object in the list that is currently being transformed;
this.*
: properties defined in the component template;
-
{comp props ...}
- A shorthand for component template that matches elements with name
comp
.
- A shorthand for component template that matches elements with name
-
{function name:'...' ...}
- Defines a function template.
- Function name must be unique.
Components and functions can only be defined at the root-level of the template. Their definition cannot be nested inside the body of a component or function template.
3. Component Transformation
The following section describes the element that can nest inside the component/function body, including pre-defined elements that carry out specific transformation logic, and literal elements. Elements other than these pre-defined ones are treated as literal elements. Transformation elements and literal elements can nest within each other.
3.1 Processing of element content
The contents a template element are processed in the following prioritized way:
- if it is an element, then
- if it is one of the pre-defined transform elements, including
{if}
,{else}
,{for}
,{let}
,{apply}
,{compose}
, process it accordingly; - if its name starts with
this.
, it is processed as an inline expression; - if its name matches the local variable defined by outer
{let}
element, it is processed as an inline expression; - if there's any component/function defined with the same name as the element name, if so invoke the function;
- else it is processed an a literal element;
- if it is one of the pre-defined transform elements, including
- else if it is a Mark pragma, then it is processed as an inline JS expression;
- else any text nodes are copied literally;
3.2 Pre-defined transformation elements
{if is:expr ...}
or{if not:expr ...}
- When the condition expression is evaluated to
true
, then the template body is executed, otherwise the body of the following{else}
branch is executed, if there's any.
- When the condition expression is evaluated to
{else if:expr ...}
or{else ...}
- The else branch of a
if
element. It should always follow aif
element immediately. - If the
if:expr
property is defined, then it is a nested else-if branch, otherwise, it is a simple else branch.
- The else branch of a
{let name:expr ...}
- Defined a scoped variable with the given name. The variable is only effective within the body of the
let
element.
- Defined a scoped variable with the given name. The variable is only effective within the body of the
{context name:expr ...}
- Shadows the value of a context variable.
{for each:expr ...}
- Loop through the objects returned from evaluating the
expr
, and execute the body of thefor
element.
- Loop through the objects returned from evaluating the
{apply}
or{apply to:expr}
- Apply template transformation a list of model objects. If optional to:expr is specified, it is applied to the objects returned from evaluating the
expr
, otherwise it is applied to the
- Apply template transformation a list of model objects. If optional to:expr is specified, it is applied to the objects returned from evaluating the
{compose}
- Compose the body of the current high-order component, by applying transformation to the child elements of the component.
3.3 Component and function invocation
-
{comp prop1:value prop2:expr ...}
- Invokes a component template, through a template matching process, like XSLT.
-
{func prop1:value prop2:expr ...}
- Invokes a function.
The differences are:
- when a component is invoked, it changes the current component in the context; when a function is invoked, it does not change the current model object or the current component.
- component invocation goes through a template matching process like XSLT, which is more powerful and flexible (but also slower in performance), whereas function is a straight invocation without template matching.
3.4 Inline expressions
Elements whose name start with this
and Mark pragmas are treated as inline JS expressions.
{this.*}
(expr as Mark pragma)
The result of evaluating the expression is passed to the template engine output adaptor. It may be casted into string or preserved as value, depending on the output adaptor.
3.5 Literal elements
{elmt prop1:value prop2:expr ...}
- Constructs an output element of same name. Properties are evaluated as defined in 3.4, and contents processed as defined in 3.1.
4. Output Adaptors
Mark Template can support wide-range of output, through built-in and custom adaptors. The built-in adaptors can support Mark, JSON, HTML, XML, Text, DOM, virtual-dom.
Usage
Install from NPM:
npm install mark mark-template --save
Then in your node script, use it as:
const Mark = require('mark');
const Template = require('mark-template');
var tmpl = Template.compile(`... template source ...`);
var model = Mark.parse(`... model data ...`);
var output = Template.apply(tmpl, model);
console.log("Output: " + Mark.stringify(output));
To use the library in browser, you can include the mark-template.js
under /dist
directory into your html page, like:
<script src='mark.js'></script>
<script src='mark-template.js'></script>
<script>
var tmpl = MarkTemplate.compile(`... template source ...`);
var model = Mark.parse(`... model data ...`);
var output = MarkTemplate.apply(tmpl, model);
console.log("Output: " + Mark.stringify(output));
</script>