dom.tpl

DOM模版通用翻译器,用于将DOM模版翻译成其他模版语言,接口类似Q.js。

Usage no npm install needed!

<script type="module">
  import domTpl from 'https://cdn.skypack.dev/dom.tpl';
</script>

README

dom.tpl

DOM模版通用翻译器,用于将DOM模版翻译成其他模版语言,接口类似Q.js

模版简史

jQuery作者John Resig的著名文章JavaScript Micro-Templating,开启了前端模版引擎的序章。

之后,模版引擎走向了两个方向:

  • 强大的自定义语法,代表有著名的handlebars
  • 性能优先,追求极速体验,支持Javascript语法,代表有doT

但随着MVVMWeb Component的崛起,模版引擎又有了一些有趣的新成员:

  • DOM Base Template
  • Markup Template,代表有plates

为了解决什么需求?

  • DSLs (Domain Specific Languages) ,例如<%=foo%>{{foo}}的可移植性较差
  • 逻辑和模版真正分离,模版本身就是一个标准HTML片段
  • DOM结构不依赖于数据,使得DOM在没有数据时是可分析的

当然DOM Base Template也有一些很难逾越的问题,比如性能,由于基于DOM树的建立以及对DOM树的遍历,所以对性能并不友好,这也正是Markup Template出现的原因。

dom.tpl

实际上是一个DOM Base Template翻译器,我们的思想是既然DOM Base Template有性能问题,那么我们通过一次编译将其翻译成性能更好的模版,例如:

<p q-text="message"></p>

翻译成:

<p q-text="message"><%=it.message%></p>

例子可见Qtpl.js

使用

语法上和Q.js,在元素上通过自定义属性来映射指令。

directive

告知翻译器如何对节点进行操作,遵循Vuejs写法:

<element
  prefix-directiveId="[argument:] value [| filters...]">
</element>

例如模版:

<p q-text="message"></p>

引擎会找到对应的text指令来对该p元素进行操作,例如velocity.js

var domTpl = new DOMTpl({
  directives: {
    // text指令是发现q-text后在其内插入${value}
    'text': function (value) {
      value = postFix(value);
      var dom = htmlparser.parseDOM('${' + value + '}');
      DomUtils.appendChild(this.el, dom[0]);
    }
  }
});

再例如Qtpl.js的例子:

var domTpl = new DOMTpl({
  // 所有value都会经过get方法预处理,例如message预处理后变成it.message
  get: function (value) {
    return 'it.' + value;
  },
  // 所有filter的组装方法
  // filters是字符串数组,value是上面prefix的结果
  applyFilters: function (filters, value) {
    var args, foo;
    if (filters.length) {
      filters.forEach(function (filter) {
        args = filter.split(/ +/);
        foo = args.shift()
        value = [
          value
        ];
        args = args.map(function (arg) {
          return "\'" + arg + "\'";
        });
        args.unshift(1, 0);
        value.splice.apply(value, args);
        // 我们可以看到这个方法输入如果是filters = ['filter1 arg', 'filter2'], value = message,则输出为:opt.filters.filter2(opt.filters.filter1(value, 'arg'))
        value = 'opt.filters.' + foo + '(' + value.join(', ') + ')';
      })
    }
    return value;
  },
  directives: {
    // 最后到directive,输出结构
    'text': function (value) {
      var dom = htmlparser.parseDOM('{{=' + value + '}}');
      DomUtils.appendChild(this.el, dom[0]);
    }
  }
});
默认方法
  • get的默认为:
// 即直接返回值,不进行prefix
get: function (value) {
  return value;
}
  • applyFilters默认为:
// 如果输入为filters = ['filter1 arg', 'filter2'], value = message,则输出为:filter2(filter1(message, 'arg'))
applyFilters: function (filters, value, options) {
   var args, foo;
   if (filters.length) {
     filters.forEach(function (filter) {
       args = filter.split(/ +/);
       foo = args.shift();
       if (options.filters[foo]) {
         value = [value]
         value.push.apply(value, args);
         value = options.filters[foo].apply(this, value);
       } else {
         value = [
           value
         ];
         args = args.map(function (arg) {
           return "\'" + arg + "\'";
         });
         args.unshift(1, 0);
         value.splice.apply(value, args);
         value = foo + '(' + value.join(', ') + ')';
       }
     })
   }
   return value;
 }