/mus

🚀 A high performance server-side javascript template library.

Primary LanguageJavaScriptMIT LicenseMIT

Mus

NPM version Build Status Appveyor status Coverage Status

A server-side javascript template library, high performance and extending easily.

Benchmark

Mus#renderExtend x 40,578 ops/sec ±2.35% (84 runs sampled)
Nunjucks#renderExtend x 14,102 ops/sec ±2.45% (80 runs sampled)
Swig#renderExtend x 20,764 ops/sec ±2.86% (88 runs sampled)
Fastest is Mus#renderExtend
Mus#renderNested x 53,453 ops/sec ±1.69% (85 runs sampled)
Nunjucks#renderNested x 32,862 ops/sec ±1.75% (86 runs sampled)
Swig#renderNested x 27,586 ops/sec ±1.76% (86 runs sampled)
Fastest is Mus#renderNested
Mus#renderSimple x 708,375 ops/sec ±1.16% (87 runs sampled)
Nunjucks#renderSimple x 343,384 ops/sec ±1.79% (85 runs sampled)
Swig#renderSimple x 97,091 ops/sec ±1.76% (85 runs sampled)
Fastest is Mus#renderSimple

Quick start

npm install node-mus --save

Or

yarn add node-mus --save

Simple demo

const mus = require('node-mus');
mus.renderString('{{ mus }}', { mus: 'hello mus' }); // hello mus;

Apis

configure(options)

options

  • baseDir String, default: __dirname
  • blockStart String, default: {%
  • blockEnd String, default: %}
  • variableStart String, default: {{
  • variableEnd String, String, default: }}
  • noCache Boolean, default: false
  • ext String, default: tpl
  • autoescape Boolean, default: true

e.g.

const mus = require('node-mus');
mus.configure({
   baseDir: 'template',
   blockStart: '<%',
   blockEnd: '%>',
   variableStart: '<%=',
   variableEnd: '%>',
   ext: 'ejs',
});
const template = '<% if test %><div><%= test %></div><% endif %>';
mus.renderString(template, { test: '123' });
// '<div>123</div>'

mus.render('test', { test: '123' });
// render template/test.ejs to '<div>123</div>'

render(path[, args])

render template file

mus.render('test', { text: 'hello' });
// render test.tpl to string

renderString(html[, args])

render template string

mus.renderString('asd{{ text }}', { text: 'hello' });
// output: asdhello

setFilter(name, cb)

create a custom filter.

mus.setFilter('join', arr => arr.join(','));

using

mus.renderString('{{ text | join }}', { text: [1, 2] });
// output: 12

setTag(name, tagOptions)

create a custom tag.

tagOptions

  • unary Boolean, if true, endtag was needed
  • attrName String, default attribute name, default is 'default'
  • render(attr, scope, compiler) Function, render function

render function args

  • attr Object, attribute in tag
  • scope Object, render scope
  • compiler Object, compiler object

compiler property

  • fileUrl String, template file url
  • include(templateUrl, scope) Function, include other template file, would return rendered string
  • compile(ast, scope) Function, compile ast to string, would return rendered string.

e.g.

mus.setTag('css', {
  unary: true,
  attrName: 'href',
  render(attr, scope, compiler) {
    return `<link href="${attr.href}" rel="stylesheet">`;
  }
});

using

mus.renderString('{% css "style.css" %}');
// output: <link href="style.css" rel="stylesheet">

compile child node, this in render function was current tag object.

mus.setTag('style', {
  render(attr, scope, compiler) {
    return `<style>${compiler.compile(this.children, scope)}</style>`;
  }
});

using

mus.renderString('{% style %}.text{margin: 10px;}{% endstyle %}')
// output: <style>.text{margin: 10px;}</style>

custom tag example

Base Feature

variable

mus.renderString('{{ obj.hello }}{{ obj.mus }}', {
  obj: {
    hello: 'hello',
    mus: 'mus'  
  }
}); // hello mus;

expression

mus.renderString('{{ !test ? text : "nothing" }}', {
  test: false,
  text: 'hello mus',
}); // hello mus;

using function

mus.renderString('{{ add(1, 2) }}', {
  add: (a, b) => a+b,
}); // 3;

builtin function

  • range(start[, end])
  • regular(str[, flag]) create a regular object

regular expression

It needs to be prefixed with r.

mus.renderString('{{ test | replace(r/[a-z]/gi, 'b') }}', {
  test: 'aBc11cc'
}); // bbb11bb;

smarty style

and or not

mus.renderString('<div>{{ not test1 and test3 or test2 }}</div>', {
   test1: false,
   test2: '123'
}) // <div>123</div>;

if condition. but I extremely suggested using a ? b : c instead.

mus.renderString('<div>{{ "123" if test1 else "321" }}</div>', {
 test1: false 
}); // <div>321</div>

comment

mus.renderString('11{# {{ test }} #}', {
  test: 'hello mus',
}); // 11;

filter

// expression would be autoescape
// use safe filter to prevent escape
mus.renderString('{{ text | nl2br | safe }}', {
  text: 'hello \n mus',
}); // hello <br/> mus;

custom filter

mus.setFilter('add', (input, a) => {
  return +input + a;
});

mus.renderString('{{ text | add(2) }}', {
  text: 1,
}); // 3;

builtin filter

  • nl2br replace '\n' or '\r\n' to <br/>
  • json JSON.stringify
  • escape escape html tag
  • reverse Array#reverse
  • replace String#replace
  • abs Math.abs
  • join Array#join
  • lower String#lower
  • upper String#upper
  • slice Array#slice
  • trim String#trim
  • safe use to prevent escape

source

Tags

for

{% for item in list %}
    ({{ loop.index0 }}:{{ item }})
{% endfor %}
mus.render('test', {
    list: [1, 2],
}); // (0:1)(1:2)

if

{% if test > 1 %}
    {{ test }}
{% endif %}
mus.render('test', {
    test: 2
}); // 2

Or

{% if test > 2 %}
    {{ test }}
{% elseif test === 2 %}
    111
{% else %}
    333
{% endif %}
mus.render('test', {
    test: 2
}); // 111

set

{% set test = { say: 'hello' } %}

{{ test.say }}
mus.render('test');
// hello

raw

{% raw %}
    {{ test }}
{% endraw %}
mus.render('test', {
    test: 2
}); // {{ test }}

filter

{% filter replace(123, 321) %}
  {% for item in list %}
    {{ item }}
  {% endfor %}
{% endfilter %}
mus.render('test', { list: [123, 12, 123] });
// output: 32112321

macro

{% macro test %}
    123
{% endmacro %}

{{ test() }}
mus.render('test'); 
// 123

with arguments

{% macro test(a, b = '123') %}
  {{ a }}{{ b }}
{% endmacro %}

{{ test('123') }}
mus.render('test'); 
// 123123

import

import other template's macro

{% macro test(a, b = '123') %}
  {{ a }}{{ b }}
{% endmacro %}
{% import "test" %}
{{ test(123) }}

Or

{% import "test" as item %}
{{ item.test(123) }}

extends & block

template 1: test.tpl

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>{{ title }}</title>
</head>
<body>
  {% block main %}
    test.tpl content
  {% endblock %}
</body>
</html>

template 2: test2.tpl

{% extends './test.tpl' %}

{% block main %}
  test2.tpl content
{% endblock %}

render

mus.render('test2.tpl'); 
// <!doctype html> ... test2.tpl content ...

include

template 1: test.tpl

{% include './test2.tpl' test=obj.text %}

template 2: test2.tpl

hello {{ test }}

render:

mus.render('test.tpl', { obj: { text: 'mus' } }); 
// hello mus

Debug

friendly error

/Users/wanghx/Workspace/my-project/mus/test/template/test7.tpl:14:3

     12      {% endraw %}
     13    
     14      {{ num.replace('aaaa') }}
             ^^^^^^^^^^^^^^^^^^^^^^^^^
     15    </div>

Error: num.replace is not a function
    at Object.genError (/Users/wanghx/Workspace/my-project/mus/lib/utils/utils.js:107:19)
    at Object.throw (/Users/wanghx/Workspace/my-project/mus/lib/utils/utils.js:122:16)

Command

test

npm test

benchmark

npm run benchmark

example

npm run example

Author

wanghx

License

MIT