What's New in Marko v3 // MarkoImprove this page
We’ve made some exciting improvements to Marko as part of the Marko v3 release. Most notably:
- Improved syntax
- Now supports both a familiar HTML syntax and a concise, indentation-based syntax
- All attribute values are now parsed as JavaScript expressions
- New parser: htmljs-parser (thanks @philidem!)
- Improved directives
- New and improved compiler API
We worked hard to make Marko v3, arguably, the best templating engine ever. This release includes over 250 commits to Marko and almost 100 commits to the parser. Marko is heavily tested with over 600 individual tests for Marko and its new parser. Thank you to all of the contributors who have provided code and feedback along the way (see #90, #211, especially @adammcarth, @BryceEWatson, @crsandeep, @DanCech, @danrichman, @kristianmandrup, @onemrkarthik, @philidem, @pswar, @scttdavs, @SunnyGurnani, @tindli, @vedam and @yomed)!
In order to make these improvements it was necessary to introduce some breaking changes. However, a migration tool is available to automatically convert Marko v2 templates to the new syntax. Please see: github.com/marko-js/marko-migrate
It's also worth mentioning that the new concise syntax was heavily inspired by Jade/Pug. However, we reduced the number of grammar rules with Marko to make the concise syntax easier to grasp and closer to HTML.
While Marko is focused on being the best-in-class templating engine, Marko Widgets aims to be one of the simplest and fastest libraries for building UI components. Marko renders the HTML for UI components, while Marko Widgets adds client-side behavior. Marko Widgets offers advanced features like DOM-diffing, batched updates, stateful widgets, declarative event binding and efficient event delegation.
Please try it out and provide feedback:
npm install marko@3 --save
If you are using Marko Widgets you will need to also install the latest version of Marko Widgets:
npm install marko-widgets@6 --save
Also, the Lasso.js taglib has been updated to be compatible with Marko v3 so you will need to upgrade Lasso.js if you are using that tool:
npm install lasso@2 --save
Please join the Marko community on Gitter and feel free to ask any question or provide feedback.
Cheers!
New features
JavaScript expressions as attribute values
Marko v3 uses a new parser that allows for a much more powerful syntax. With Marko v3, an attribute value is always parsed as a JavaScript expression:
<div class=data.myClassName><input type="checkbox" checked=data.isChecked/><my-component string="Hello"/><my-component number=1/><my-component template-string="Hello "/><my-component boolean=true/><my-component array=1, 2, 3/><my-component object={hello: 'world'}/><my-component variable=name/><my-component function-call=data.foo/><my-component complex-expression=1+2/><my-component super-complex-expression=data.foo + data.bar'a', 'b', 'c'/>
Previously, attribute values were parsed as strings (by default), but a tag definition file (AKA, a schema file) could be used to associate type information with an attribute value to change how the value is interpreted. By replacing the strict HTML parser with a much more flexible htmljs-parser, it now obvious how an attribute value would be interpreted.
Attribute arguments
Marko v3 allows tags and attributes to have an argument as shown below:
<my-custom-tagsome argument><div my-custom-attrsome argument>
Arguments are used to improve the syntax for directives such as looping and conditionals.
Improved Editor and IDE Support
- Atom: language-marko
- Sublime Text: marko-sublime
- WebStorm: marko.tmbundle (See: Importing TextMate Bundles) (New!)
- TextMate: marko.tmbundle
- CodeMirror/Brackets (New!)
Concise indentation-based syntax
Marko v3 supports both a familiar HTML syntax and a concise, indentation-based syntax. Both syntaxes are equally supported and both syntaxes can even be mixed in the same Marko template.
The code snippets below show how each syntax variant compares.
HTML syntax
<html lang="en"><head><title>Marko Templating Engine</title></head><body><!-- Welcome to Marko! --><h1>Hello !</h1><ul ifdata.colors.length><li forcolor in data.colors></li></ul><div else>No colors!</div></body></html>
Concise syntax
The following concise template is equivalent to the previous HTML template:
html lang="en"headtitle - Marko Templating Enginebody// Welcome to Marko!h1 - Hello !ul ifdata.colors.lengthli forcolor in data.colorsdiv else- No colors!
There is one "gotcha" that you need to be aware of. The Marko parser starts out in the concise mode. Therefore, given the following template:
Hello WorldWelcome to Marko
The output would be the following:
<Hello World></Hello><Welcome to Marko></Welcome>
To parse correctly, you would need to prefix each HTML line with a hyphen:
- Hello World- Welcome to Marko
Alternatively, a multi-line, delimited HTML block can be used:
---Hello WorldWelcome to Marko---
Mixed syntax
You can even mix and match the concise syntax with the HTML syntax within the same document. The following template is equivalent to the previous templates:
html lang="en"head<title>Marko Templating Engine</title>body// Welcome to Marko!<h1>Hello !</h1>ul ifdata.colors.lengthli forcolor in data.colorsdiv else- No colors!
Improved syntax for directives
Macros
Old syntax:
<def function="greeting(name, count)">Hello ! You have new messages.</def><invoke function="greeting" name="John" count=""/><invoke function="greeting('Frank', 20)"/>
New syntax:
<macro greetingname, count>Hello ! You have new messages.</macro><greeting name="Frank" count=30/><greeting'Frank', 30/>
Macros that allow nested body content are now more cleanly supported using the <macro-body>
tag:
<macro section-headingclassName><h1 class=className><macro-body/></h1></macro><section-heading className="foo">Hello World!</section-heading><!-- Output: --><h1 class="foo">Hello World!</h1>
Includes
Old syntax:
<include template="./include-target.marko" name="Frank" count=""/><include template="./include-target.marko" template-data="{name: 'Frank', count: 20}"/>
New syntax:
<include"./include-target.marko" name='Frank' count=20/><include"./include-target.marko", {name: 'Frank', count: 20}/>
Includes also support body content:
<include"./include-nested-content.marko" name="Frank" count=20>Have a<b>wonderful</b>day!</include>
When body content is provided, a special data.renderBody = function(out) { ... }
function will be added to the template data for the target template.
Dynamic templates are also supported:
<script marko-init>var templateA = ;var templateB = ;</script><includefoo ? templateA : templateB name='Frank' count=20/>
Issue #178 - Marko v3: include tag
Conditionals
Old syntax:
<!-- Applied as tags: --><if test="a > b">...</if><else-if test="b < a">...</else-if><else>...</else><!-- Applied as attributes: --><div if="a > b">...</div><div else-if="b < a">...</div><div else>...</div>
New syntax:
<!-- Applied as tags: --><ifa > b>...</if><else-ifb < a>...</else-if><else>...</else><!-- Applied as attributes: --><div ifa > b>...</div><div else-ifb < a>...</div><div else>...</div>
for(item in items)
Old syntax:
<!-- Applied as a tag: --><ul><for test="color in colors"><li></li></for></ul><!-- Applied as an attribute: --><ul><li for="color in colors"></li></ul>
New syntax:
<!-- Applied as a tag: --><ul><forcolor in colors><li></li></for></ul><!-- Applied as an attribute: --><ul><li forcolor in colors></li></ul>
Additional options are now passed in after a |
symbol as shown below:
<forcolor in 'red', 'green', 'blue' | separator=", "></for>
Output:
red, green, blue
- Issue #189 - Marko v3: Improve syntax of the "for" directive by keeping everything in parens
- Issue #193 - Marko v3: Custom iterators
Iterator function as target
Marko v3 now allows the target to be an iterator function:
<script marko-init>{;;;}</script><ul><forcolor in myColorsIterator><li></li></for></ul>
Output:
redgreenblue
Issue #193 - Marko v3: Allow for target to be an iterator function
for(<init>
; <test>
; <update>
)
Marko v3 now supports native for-loops as shown below:
<forvar i=1; i<=3; i++></for>
Output:
123
Issue #191 - Marko v3: Allow native JavaScript for loops
for(key, value in object)
Old syntax:
<for each="(name,value) in {'foo': 'low', 'bar': 'high'}">:</for>
New syntax:
<forname,value in {'foo': 'low', 'bar': 'high'}>:</for>
Issue #175 - Marko v3: Looping over object properties
for(<range>
)
Old syntax:
<for each="i from 0 to 9"></for>
New syntax:
<fori from 0 to 9></for>
while(<test>
)
Marko v3 now supports native while loops:
<!-- Applied as a tag: --><var n=0/><ul><whilen < 4><li></li></while></ul><!-- Applied as an attribute: --><var n=0/><ul><li whilen < 4></li></ul>
Issue #228 - Marko v3: while loop support
Imports
Old syntax:
<require module="change-case" var="changeCase"/>
New syntax:
Marko v3 now supports adding JavaScript initialization code at the top of the compiled template. This code will only load the first time the compiled template is loaded and it can be used to import other JavaScript modules and introduce new static variables.
<script marko-init>var reverse = reverse;var changeCase = ;</script>
Issue #214 - Marko v3: <script marko-init>
Variables
Old syntax:
<var name="foo" value="'bar'" /><var name="count" value="0" /><assign var="count" value="count+1" />
New syntax:
<var foo="bar" count=0/><assign count=count+1/>
Scoped variables are also supported:
<var name="Frank">Hello !</var><!-- The "name" variable will be `undefined` here -->
Thank you, @BryceEWatson, for working on this feature!
Issue #169 - Marko v3: var tag Issue #171 - Marko v3: assign tag
Scriptlets
Marko v3 has adopted the more universal <% ... %>
syntax for scriptlets. Scriptlets are there if you need to inject arbitrary JavaScript code into the compiled template.
Old syntax:
{% if (true) { %}HELLO{% } %}{% if (false) { %}WORLD{% } %}
New syntax:
<% console; %><% if true %>HELLO<% %><% if false %>WORLD<% %>
Issue #181 - Marko v3: Scriptlets
Layout taglib
Old syntax:
<layout-use template="./layout-default.marko" show-header="$false"><layout-put into="body">BODY CONTENT</layout-put><layout-put into="footer">FOOTER CONTENT</layout-put></layout-use>
New syntax:
The <layout-use>
tag now expects a template argument:
<layout-use"./layout-default.marko" show-header=false><layout-put into="body">BODY CONTENT</layout-put><layout-put into="footer">FOOTER CONTENT</layout-put></layout-use><!-- The layout template can also be dynamic and you can also pass a data object: --><layout-usedata.layoutDynamic, {showHeader: false}>...</layout-use>
Issue #209 - Marko v3: Re-introduce support for the layout taglib
Invoke tag
Old syntax:
<invoke function="test('World')"/><invoke function="console.log('Hello World')"/>
New syntax:
<invoke test'World' /><invoke console.log'Hello World'/>
Issue #179 - Marko v3: invoke tag
Empty closing tag
The tag name in the closing tag is now optional:
<my-custom-tag>Hello world!</>
htmljs-parser
- Issue #30 - Allow an empty closing tag
Shorthand ID and class names
Assigning IDs and class names is very common so Marko v3 introduces a shorthand syntax that matches the CSS selector syntax as shown below:
#foo
➔<div id="foo">
#foo.bar
➔<div id="foo" class="bar">
.bar
➔<div class="bar">
#sectionul.colorsli.color - redli.color - greenli.color - bluebutton#submitButton.enabled - Submit Form
Output:
redgreenblueSubmit Form
The shorthand syntax also works with the more verbose HTML syntax:
<#section><ul.colors><li.color>red</li><li.color>green</li><li.color>blue</li></ul.colors><button#submitButton.enabled>Submit Form</button></>
Issue #220 - Marko v3: Support expansion of CSS selector shorthand for tag names htmljs-parser - Issue #24 - Expand CSS selector shorthand
Miscellaneous improvements
Validating parser
The new parser used by Marko v3 will no longer allow mismatched opening and closing tags. Instead of letting problems in the original template pass through, the new parser will report the problem as errors. For example, with the following template:
<div>Hello World</foo>
You will get a friendly error message:
The closing "foo" tag does not match the corresponding opening "div" tag
All JavaScript expressions are validated at compile time
Previously, Marko v2 would not throw an error at compile time if a JavaScript expression was invalid. Now, with Marko v3, all JavaScript expressions are parsed and validated at compile time using esprima. These extra compile-time checks make development a lot easier and it prevents errors from showing up at runtime.
Style attribute
The value of the style attribute can now resolve to an object expression (in addition to a string value) as shown below:
<div style={color: 'red', 'font-weight': 'bold'}>
Output:
Issue #229 - Marko v3: Special case style attribute to allow object expression
Class attribute
The value of the class attribute can now be an object expression or an array expression as shown below:
<!-- array: --><div class='a', null, 'c'><!-- object: --><div class={a: true, b: false, c: true}>
In both cases, the output will be the same:
Issue #230 - Marko v3: Special case class attribute to allow object or array expression
Dynamic attributes
Old syntax:
<div attrs="myAttrs"/>
New syntax:
<var myAttrs={'class': 'foo', 'style': 'background-color: red'}/><div />
Output:
Issue #198 - Marko v3: Replace <div attrs(myAttrs)>
with <div ${myAttrs}>
Dynamic tag names
Dynamic tag names are now supported by putting a placeholder in the tag name:
<>Hello World!</>
Output:
<!-- If foo is true: --><div>Hello World!</div><!-- If foo is false: --><span>Hello World!</span>
Issue #226 - Marko v3: Allow placeholders in tag name
Input data for custom tags:
<greeting{name: 'Frank'}/><!-- Equivalent to: --><greeting name='Frank' />
Issue #173 - Marko v3: Input data object for custom tags
Tag body content parsing
The marko-body
attribute can be used to control how body content is parsed. The following values are supported:
"html"
- Body content will be parsed HTML (the default)"static-text"
- Body content will be parsed as static text (HTML tags will be ignored). Placeholders will be ignored."parsed-text"
- Body content will be parsed as text (HTML tags will be ignored). Placeholders will not be ignored.
<div marko-body="static-text">This is just one<span iffoo>Hello !</span>big text block</div>
Output:
This is just oneHello ${THIS IS NOT VALID}!big text block
Preserve whitespace
Old syntax:
<div c-space="preserve">All of thiswhitespace willbe preserved.</div>
New syntax:
Whitespace can be preserved using the marko-preserve-whitespace
attribute:
<div marko-preserve-whitespace>All of thiswhitespace willbe preserved.</div>
Compiler options
The <marko-compiler-options>
tag can be used to enable whitespace preservation and/or HTML comments preservation for the entire template.
Old syntax:
<compiler-options whitespace="preserve" />
New syntax:
<marko-compiler-options preserve-whitespace preserve-comments />
Issue #205 - Marko v3: Provide full control over whitespace Issue #206 - Marko v3: HTML comments should be handled correctly
Open tag only
Marko v3 allows tags to be declared as "open tag only". If a custom tag is declared as being "open tag only" then the parser will report an error if an ending tag is found or if the tag has nested body content.
Tag definition:
"<my-custom-tag>":"open-tag-only": true...
Usage:
<!-- Allowed: --><my-custom-tag><my-custom-tag/><!-- Not allowed: --><my-custom-tag>Foo</my-custom-tag>
Issue #222 - Marko v3: Allow open only tags to be defined in tag definition
Improved readability of compiled code
A lot of attention was put on producing very clean output JavaScript code. If you ever find that you need to debug through the code of a compiled template you will find that the code is very well formatted and readable.
For example, given the following input template:
<my-custom-tag name="World"/><ul ifdata.colors.length><li forcolor in data.colors></li></ul><div else>No colors!</div>
The compiled output will be similar to the following:
{var str = __helperssempty = __helpersenotEmpty = __helpersneescapeXml = __helpersx__loadTag = __helperstmy_custom_tag =forEach = __helpersf;return {;if datacolorslengthout;;out;elseout;};}moduleexports = ;
Improved performance
The Marko runtime has been slightly tweaked to improve performance and it is now faster and smaller! You can find the updated benchmarks here: https://github.com/marko-js/templating-benchmarks
Here's a partial snippet of the results that shows how Marko stacks up to the competition:
Template Engine | Results |
---|---|
marko | 187,729 op/s (fastest) |
dot | 183,161 op/s (2.43% slower) |
handlebars | 104,634 op/s (44.26% slower) |
dust | 83,773 op/s (55.38% slower) |
swig | 54,866 op/s (70.77% slower) |
jade | 32,929 op/s (82.46% slower) |
nunjucks | 32,306 op/s (82.79% slower) |
react | 3,651 op/s (98.06% slower) |
Removed features
Removed: $
Use ${<variable-name>}
instead. We found that allowing $
without the curly braces was problematic when parsing inline script that used the $
variable from libraries like jQuery.
Removed: $!
Use $!{<variable-name>}
instead.
Removed: "simple conditional" syntax
Marko v3 removes support for the following "simple conditional" syntax:
Old syntax:
<div class="{?data.isActive; active; inactive}">
New syntax:
In Marko v3, JavaScript is favored over a new syntax so, instead, use a JavaScript conditional expression:
<div class=data.isActive ? 'active' : 'inactive'>
Alternatively, when only a value is needed when true
:
<div class=data.isActive && 'active'>
Removed: nested attributes
Nested attributes are no longer supported (likely never used):
<test-popover><attr name="title">Popover Title</attr><attr name="content">Popover Content</attr>Link Text</test-popover>
Removed: <require>
tag
Use <script marko-init>
instead.
Old syntax:
<require module="./my-include-target.marko" var="myIncludeTarget" />
New syntax:
<script marko-init>var myIncludeTarget = ;</script>
Removed: c-data
/c-input
attribute
Custom tag data should be passed using an argument:
<my-custom-tag{name: 'Frank'}/>
Removed: c-space
/c-whitespace
attribute
Use marko-preserve-whitespace
attribute instead:
<div marko-preserve-whitespace>This whitespacewill be preserved.</div>
Removed: c-escape-xml
attribute
No longer applicable.
Removed: c-parse-body-text
attribute
Use the marko-body="<body-type>"
attribute instead.
Removed: attrs
attribute
Use placeholder within open tag instead:
<div >
Removed: with
tag and attribute
Instead, use <var>
tag with nested content to create scoped variables.
Removed: JavaScript operator aliases
The following JavaScript operator aliases are no longer supported:
JavaScript Operator | Marko Equivalent |
---|---|
&& |
and |
|| |
or |
=== |
eq |
!== |
ne |
< |
lt |
> |
gt |
<= |
le |
>= |
ge |
Instead, you must use valid JavaScript operators. For example:
Old syntax:
<div if="searchResults.length gt 100">Show More</div>
New syntax:
<div ifsearchResults.length > 100>Show More</div>
A dynamic include target must resolve to a loaded template (not a string path)
If using dynamic templates, the expression must resolve to a fully loaded template instance (not a string path). In Marko v2, the following was allowed:
var templateData =includeTarget: './include.marko'
<include template=""/>
This is no longer allowed in Marko v3 and, instead, you must do the following:
var includeTemplate = ;// ...var templateData =includeTarget: includeTemplate
<includedata.includeTarget}/>
Other notable changes
marko-taglib.json → marko.json
Taglib definition files must now be named marko.json
(not marko-taglib.json
).
In addition, the rules for resolving marko.json
files have changed (see: Marko v3: Improve taglib discovery #224)
Issue #216 - Marko v3: Transition from marko-taglib.json to marko.json
Nested tags separator changed
The symbol for separating nested tags has changed from .
(period) to :
(colon). This was done to avoid conflicts with the new shorthand class syntax (e.g. <div.foo>
).
Old syntax:
<tabs><tabs.tab title="Tab 1">Content for tab 1</tabs.tab><tabs.tab title="Tab 2">Content for tab 2</tabs.tab></tabs>
New syntax:
<tabs><tabs:tab title="Tab 1">Content for tab 1</tabs:tab><tabs:tab title="Tab 2">Content for tab 2</tabs:tab></tabs>
Issue #219 - Marko v3: Use ":" instead of "." for nested tags
Node.js v4+ is now required for compiling
Unlike Marko v2, Marko v3 requires Node.js v4+ to compile Marko templates. The new Marko compiler was written using ECMAScript 2015 (ES6) features that are only found in Node.js v4+. The Marko runtime, however, still works in all JavaScript runtimes.
Case-sensitive HTML parser
The Marko parser and compiler are now case sensitive. The following tags are not equal with Marko v3:
<my-custom-tag/><My-CUSTOM-Tag/>
Issue #215 - Marko v3: Marko should be case sensitive with tag names and attributes
New compiler API
The Marko compiler went through a major refactor with Marko v3 as a result of introducing a new parser that recognizes types at compile time. The new compile-time API is much simpler and more powerful. For more information on the Marko compiler and extending Marko at compile-time, please check out:
- Compiler Advanced
- The Compiler API (work-in-progress)
- Compile-time Tags
Next steps
If you have ideas on how to improve Marko please let us know. We welcome new contributors so if you would like to help out please join us in the Gitter chat room for Marko, file an issue on Github or send us a pull request.