Git Product home page Git Product logo

ij2tpl.js's Introduction

IJ2TPL.js

TypeScript 编写的类 Mustache 模板引擎(uglify后 <= 4kB)。

比Mustache.js更快(至少不会更慢)!

English(Waiting to update) | 中文

已支持

  • ES3(>=IE6)

注:gh-page 的测试模板是可以在 IE6 下正常渲染的,所以理论上是支持 所有2000年之后的浏览器的。

使用方法

注意:本文里混用“{{xxx}}”与“{xxx}”标签风格,但实际上 IJ2TPL.js 默认是“{xxx}”风格标签,实际使用时注意自行切换。

TypeScript(ES6)

import * as IJ2TPL from './ij2tpl';

// 解析一个模板
let renderer = IJ2TPL.parse(`你好,{name}`);

// 然后让我们来渲染它!
renderer.render({{name: 'IJ2TPL'}}); // -> "你好,IJ2TPL!"

NodeJS

const IJ2TPL = require('./dist/ij2tpl.min');

// 解析一个模板
let renderer = IJ2TPL.parse('你好, {name}!');

// 然后让我们来渲染它!
renderer.render({name: 'IJ2TPL'}); // -> "你好, IJ2TPL!"

注释

{- 一条注释 }
{-- 另一条注释 }
{-- 还是一条注释 --}

{-- 错误! }--}

如你所见,IJ2TPL.js 中的注释是以prefix + '-' + suffix形式组成的, 本质上它是一个标签的变种,解析器在解析时匹配到suffix后便将该标签忽略了。

除了注释之外,其还能用于控制单行的缩进。比如我们想让某一行渲染的内容 不受其在源码中的缩进的影响,如下:

    {-}Hello {name}
{-  ^^^ 输出的结果是:“Hello xxx”  }
template.render({name: "chen"}); // -> "Hello chen"

这个特性在旧版本中被叫做“行起始符号”。

学过正则表达式的小伙伴可能会觉得有点耳熟,这个符号就有点类似于“^”, 但只能用于消除单行左侧的缩进。

If 段落

{?valid}
	仅在数据合法时渲染。
{/valid}

If段落将会判断变量的真假,然后再将其作为新的上下文对段落进行渲染。 变量的真假与大多数类C语言类似,但需要注意,IJ2TPL.js 中的空数组是 假值。

空数组在 JavaScript 中判断为真这确实不是一个bug。但是作为轻逻辑类型 的模板引擎,我们大多数时候是希望将空数组作为假值来处理的。

值得一提的是 IJ2TPL.js(以及Mustache.js) 中的段落不光是作为一种判断存在 的,其也可以作为一种遍历。如果变量是一个数组,那么段落会对其进行一次遍历 ,段落代码中的“.”变量奖会引用到每一次被遍历出来的值。

{{?numbers}}{{.}}\n{{/numbers}}就是对变量numbers的遍历。现在我 们用这个模板来渲染一个数组,如下所示:

let template = parse('{{?numbers}}{{.}}\n{{/numbers}}');
template.render({numbers: [1, 2, 3, 4]}); // -> "1\n2\n3\n\4\n"

这里我们讲到了一个名为“.”的变量,这个变量是对当前上下文的引用。

我们可以使用这个特性对上面的模板进行修改,使其变成下面的格式:

let template = parse('{{?.}}{{.}}\n{{/.}}');
template.render([1, 2, 3, 4]); // -> "1\n2\n3\n\4\n"

更高级的使用方法可以参考下面“嵌套段落”章节。

Not 段落

{!valid}
	仅在数据非法时渲染。
{/valid}

Not段落与If段落是类似的,只不过其会在变量值为假时渲染。

因为段落只有在假时被渲染,所以其也不可能像If段落那样对变量进行一次 遍历。这是理所当然的。但不用担心,内部嵌套的If段落并不会被影响到。

如:

{{!valid}}
	{{?errors}}
		{{-}} error: {{.}}
	{{/errors}}
{{/valid}}

详情可以参考下面“嵌套段落”章节。

Raw 格式化器

{-- name = '<b>urain39</b>' --}
你好 {#name}

Raw格式化器是格式化器中的一种。

我们之前在上面看到的问候模板中的{{name}}便是一个格式化器, 其作用是在给出的视图数据中将与之对应的内容展示出来。

普通的格式化器是会被内部的转义函数转义的,以确保内容足够安全, 但这可能会造成渲染的结果并非是你想要的。

这时你就需要使用到Raw格式化器了。

使用方法与其他格式化器一样,你只需要在普通格式化器前加上一个“#” 号就行了。

let template = parse('<div>{#source}<div>');

template.render({source: '<p>Hello World!</p>'});

当然,如果你觉得这样非常麻烦,那么你也可以修改转义函数:

import { setEscapeFunction } from './ij2tpl';

// 使其原封不动的返回
setEscapFunction(v => v);

不过这样会影响到整个 IJ2TPL.js 的模板。如果你有多个模板,我们一般 是不建议你这样做的。

If-Else 段落

{?valid}
	数据合法。
{*valid}
	哎呀,好像出错了?
{/valid}

If-Else段落是一种语法糖。它可以将同一个变量的If段落和Not段落合并 成一个段落。两个分支之间使用“{*xxx}”格式分隔开。

题外话:为什么没有Not-Else段落呢?

是无法实现吗?不是的。虽然没有实际去写过,但就从实现上来说并不难。 我当初在设计时考虑到这样的语法会相对难理解,至少我不喜欢这样的语法。 加之我们的语法主要以符号为主,如果再增加上这样一种取反语法,那对于 用户来说,可读性降低了可不是一点两点……

函数类型(Lambda)

function toHumanReadableSize(size: number): string {
	var i = 0,
	dataUnits = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB', 'BiB', 'NiB', 'DiB'];

	while (size >= 1024)
		i++, size /= 1024;

	return String(size.toFixed(2)) + dataUnits[i];
}

import { Context, Name } from './ij2tpl';

/* 你可以理解为这是一个属性 getter,与其他格式化器相同 */
function humanReadableSize(context: Context) {
	const name: Name = ['downloadedSize', null, null]; 

	let downloadedSize = context.resolve(name);

	return toHumanReadableSize(downloadedSize);
}
已下载 {humanReadableSize}

函数类型指的是通过上下文查找到的变量是可调用类型的格式化器。

一般而言我们是不需要使用到函数类型的。但是如果你遇到了足够复杂的场景, 如我们需要验证某个值的范围是否正常,然后再决定是否渲染时,函数类型就 显得非常重要了。

函数类型中的函数会接受一个Context变量,你可以通过这个变量查找你想要的值。

如最上面我们见到的示例一样。

我们可以看到在上面的源码里我们还导入了一个叫做Name的类型,这个 类型是从v0.1.0时引入的。

import { Context, Name } from './ij2tpl';

其源码定义是:

//                  NAME    NAMES            FILTERS          IS_ACTION
export type Name = [string, string[] | null, string[] | null, boolean];

因为篇幅原因,我不打算做过多的解释。我们只需要关心前两个元素的类型。

其中NAME表示的是一个格式化器的全名,如"obj.key1.key2"。后面的NAMES表示的是分隔 以后的名字,如["obj", "key1", "key2"]。如果名称不包含属性,那么后面的NAMES属性则 需要设置为null

// 包含属性
let name1: Name = ['name.lastName', ['name', 'lastName']];

// 不包含属性
let name2: Name = ['name', null];

这样设计的原因是为了优化查找速度。但相对的也给开发者造成了一定程度的不便。

注意:这里的函数类型会缓存结果,如果这不是你想要的效果,那么你可以考虑下面的过滤器。

定制 前缀 与 后缀 (分隔符)

IJ2TPL.parse('Hello <%name%>', '<%', '%>');
let template = IJ2TPL.parse('Hello ${name}', '${', '}');

template.render({name: 'urain39'}); // -> 'Hello urain39'

此处应该没有什么特别需要讲解的,所以就举一两个简单的例子一笔带过了。

前缀可以是特殊字符,如上面讲到过的“#”,但是后缀不能是特殊字符。 因为这样会让词法分析函数产生误解,让结果不可预料。

片段模板(v0.1.0+)

{? xxxEnabled }
	{@partial_template}
{/ xxxEnabled }
let renderer = IJ2TPL.parse(`  {@partial_template}`),
	renderer2 = IJ2TPL.parse(source2);

let partialMap = {
		partial_template: renderer2
	};

renderer.render(data, partialMap);

片段模板是指在模板中以“{@xxx}”形式引入的另一个模板。

Renderer.render方法接受两个参数,其中除了必要的视图数据data外, 还有一个可选的叫做partialMap的参数。这个参数的类型是IMap<Renderer>

  public render(data: IMap<any>, partialMap?: IMap<Renderer>): string {
    return this.renderTree(
      this.treeRoot, new Context(data, null), partialMap
	);

也就是说我们只需要将被引用的模板作为Map传给Renderer.render方法即可。

有意思的是,如果我们将模板本身作为参数传入给Render.render,那么我们甚至 可以实现递归渲染,参考:https://stackoverflow.com/questions/13408425/mustache-js-recursion

v0.1.3开始,IJ2TPL.js 和 Mustache.js 一样,已经支持缩进片段模板了。

片段模板将会以“{@xxx}”标签的缩进为准,将渲染后的片段模板以行为单位重新进行缩进。

关于缩进的小问题

目前我们的缩进检测机制还有些小问题,会误认为上一个字符标签的空白部分是一个 单独行的开始部分,即缩进。因此我建议片段模板最好还是单行使用为好。不过不用 担心,稍后的版本中我会改进这个问题。

过滤器 与 动作(Action)(v0.1.0+)

Hello { name | no-f-word }
IJ2TPL.setFilterMap({
	'no-f-word': function(word) {
		return word.replace('fuck', '****');
	}
});

这和许多框架中的过滤器或是Unix Shell中的管道是一样的。

不过需要注意一点,IJ2TPL.js 中的过滤器是闭包保存的,一旦加载后 则是多个引用共享一个变量。我不保证所谓的安全克隆后的对象的安全性。

新版的过滤器增加了一个可选的参数项context,使用方法和上面的函数 类型是一样的,这里也就不赘述了。

动作与过滤器基本是一样的, 但是其并不会查找字段(因为“没名字”)

{- 简单的例子 -}
{| report}
IJ2TPL.setFilterMap({
    report: function(_, context) {
        let debugEnabled = context.resolve(['debugEnabled', null]);

        if (debugEnabled) {
            console.log('debugEnabled = true');
        }

        return '';
    }
});

IJ2TPL.parse('{|report}').render({
    debugEnabled: true
});

还记得我们上面提过的函数类型吗?这里的动作类型就类似于函数类型。

只不过它并不会缓存结果,适合对数据实时性要求比较高的场合使用。

但是需要注意context的实现上仍然是缓存结果的,也就说本质上缓存 结果这个功能是由context来完成的,详情可以参考Context的源码。

函数类型 与 动作类型 的不同点

比较 函数类型 动作类型
实时 No Yes
安全 Yes No
效率 Yes No
易用 Yes No

这两个功能各有优缺点,上图只是一个简单的比较,不能随便下定论。

简单而言,函数类型会在单页渲染时缓存第一次的结果,后面的渲染都是引用。 而动作类型会对每一次渲染重新求值,相对来说更实时一些。

嵌套段落

{?valid}
	{-}你的得分:
	{?scores}
		{-}得分:{.}
	{/scores}
{/valid}

段落相当于是一个子模板,如果模板中能够定义模板,那么段落中 能定义新的段落吗?答案是当然可以,这就是所谓的嵌套。

注意:上面我们提到过了,每个段落相当于新的环境,所以在编写 模板时一定要注意名字是否写对了,然后再使用。

If段落将会判断变量的真假,然后再将其作为新的上下文对段落进行渲染

下面是一个稍复杂的示例:

let template = IJ2TPL.parse(`\
{?settings}
	{?account}
		{?username}{username}{/username}
		{?password}{password}{/password}
	{/account}
{/settings}
`);

template.render({
	settings: {
		account: {
			username: "urain39",
			password: "123"
		}
	}
});

这个功能有点类似于with语法,写起来会更便捷。

如果你真的运行了上面的代码,那么你会发现上面的渲染结果并非 你想的那样,这是为什么呢?

template.render(data) // -> 'urain39123'

其实这是因为我们的tokenize函数会将段落标签的换行符和缩进都 忽略掉造成的。所以上面的模板应该改成:

{?settings}
	{?account}
		{?username}
			{username}
		{/username}
		{?password}
			{password}
		{/password}
	{/account}
{/settings}
template.render(data) // -> '\t\t\turain39\n\t\t\t123\n'

自递归模板(v0.1.3+)

自递归模板是从v0.1.3引入的新概念,其主要功能是让模板支持 递归渲染。

在上面我们给出了一个 Mustache.js 递归的实现,这个功能在 IJ2TPL.js 中被简化为你可以使用“@&”表示引用自身:

let template = IJ2TPL.parse(`\
{?contents}
    {-}类型:{type}
    {-}名称:{name}
    {-}{@&}
{/contents}
`);

let data = {
	"contents": [
		{
			"type": "file",
            "name": "file1",
            "contents": null
		},
		{
			"type": "directory",
			"name": "directory1",
			"contents": [
				{
					"type": "file",
                    "name": "file2",
                    "contents": null
				}
			]
		}
	]
};

expected(template.render(data), `\
类型:file
名称:file1
类型:directory
名称:directory1
类型:file
名称:file2
`);

但是如你所见,我们在渲染时必须规定数据中含有一个“null”作为递归的 终结符号,这或许对我们引用第三方数据来说非常不便。

为了应对这种复杂的情况,我们也增加一种相对独立的递归形式“@^”:

let template = IJ2TPL.parse(`\
{?contents}
    类型:{type}
    名称:{name}
    {@^}
{/contents}
`);

let data = {
	"contents": [
		{
			"type": "file",
			"name": "file1"
		},
		{
			"type": "directory",
			"name": "directory1",
			"contents": [
				{
					"type": "file",
					"name": "file2"
				}
			]
		}
	]
};

expected(template.render(data), `\
    类型:file
    名称:file1
    类型:directory
    名称:directory1
        类型:file
        名称:file2
`);

这样我们就省去了手动加上null终结递归的操作了。

这部分的代码:

        if (value === '&') { // Recursive render with parents
          buffer += this.renderTree(this.treeRoot, context, partialMap)
            .replace(BEGINNING_RE, `${indentation}$&`);
        } else if (value === '^') { // Recursive render without parents
          buffer += this.renderTree(this.treeRoot, new Context(context.data, null), partialMap)
			.replace(BEGINNING_RE, `${indentation}$&`);

你可以看见,这里我们其实是将context的数据重新包装了一次,然后将其parent设置为null。 这样设计我们就不会将上层作用域的变量与当前作用域的变量搞混了。

实战:递归树形结构渲染

{{?contents.length}} {{- ul标签只需要插入一次 }}
<ul>
    {{?contents}} {{- 判断是否有子节点 }}
        {{-}}<li><a class="icon {{type | toClass}}">{{name}}</a></li>
        {{-}}{{@^}}
    {{/contents}}
</ul>
{{/contents.length}}

{{?contents.length}}这样的用法是为了保证最外层的ul标签只插入一次。 还记得我们上面学过的吗?数组始终是会被遍历的,那样的话就不是我们想要的结果了。

关于调试

抱歉,我没有考虑到这一点。 为了改进令牌化(tokenizing)速度,我将位置信息移除了。 不过你依然可以从错误信息中猜测是哪里出了问题,它会告诉你段落的名字与类型。

还未实现

  • 函数类型(已在 v0.0.2-dev 支持)
  • 子模板(Partial Section)
  • 格式化管道(又叫做过滤器)

关于自述文件

写错了 / 不能理解?请帮助我改进! 你只需在我的项目主页打开一个新的 issue 或者 PR ,我会尽可能的回复的 :)

上次更新: 2021-01-04

ij2tpl.js's People

Contributors

urain39 avatar dependabot-preview[bot] avatar dependabot[bot] avatar uzilla avatar

Watchers

James Cloos avatar  avatar  avatar

ij2tpl.js's Issues

TODO: merge two adjacent strings

This problem made by tokenize() because it will removes such as empty formatters like {}, comments... But in fact, it split the strings too.

About debugging

The new version of IJ2TPL.js makes i started consider to improve the debugging function, it may possible to be added in next minor update v0.1.2 .

Bug: commetns(tokenize) bug

IJ2TPL.parse(`{?settings}{?account} {-}{?username} {username} {/username}{-} {?password} {password} {/password} {/account}{/settings}`);
ij2tpl.min.js:1 Uncaught Error: Unexpected token '<type=/, value=account>'
    at ij2tpl.min.js:1
    at Object.e.parse (ij2tpl.min.js:1)
    at <anonymous>:1:8

Prettify

let template = parse(`\
{?settings}
	{?account}
		{-}{?username}{username}{/username}
		{-}{?password}{password}{/password}
	{/account}
{/settings}
`);

Feat: Quirks Mode

This new feature allows you use {/} to instead {/<name>} to end a section.

It is also supports {*} for {*<name>} if you want.

Like:

{{#names}}
- {{{.}}}
{{/}}

feat: line-begin-mark tag

Long story short. This is a feature allow you indent a single-line, like following style:

<!--
	-- Auto-generated from <PROJECT ROOT>/changes.json
	-- DO NOT TRY TO MODIFY DIRECTLY!!!
	-->
CHANGELOG
==========

{?.}
	{?version}
		{^}### {version}({date})
		{?changes}
			{^}- {#.}
		{/changes}

		{^}**status**: *{status}*

	{/version}
{/.}

As you can see, we add a {^} to each lines. It is called line-begin-mark or LBM, and it can tell tokenize function we want to ignore the line indentations until the {^}.

Syntax in EBNF(possible wrong)

template ::=
	comment |
	section |
	text |
	formatter |
	partial;

comment ::= prefix comment_symbol anything suffix;

comment_symbol ::= "-";

(**
 * anything ::= /[\S\s]+/
 * Actually it is not contains `prefix` and `suffix`
 *)

section ::=
	section_head
		template
	[section_else
		template]
	section_tail;

section_head ::= prefix compare_symbol name suffix;

section_else ::= prefix else_symbol name suffix;

section_tail ::= pretix end_symbol name suffix;

compare_symbol ::= "?" | "!";

else_symbol ::= "*";

end_symbol ::= "/";

name ::= anything;

text ::= anything;

formatter ::= prefix [raw_symbol] name suffix;

raw_symbol ::= "#";

partial ::= prefix partial_symbol name suffix;

partial_symbol ::= "@";

About README

Sorry, i have noticed that ij2tpl actually can only support ES5+(>=IE8)
But currently i have no time to update it :(

Another way to implement filters

/**
 * @param {string} name
 * @param {((value: any) => any)[]} filters
 * @returns {(context: Context) => any}
 */
function filter(name, filters) {
	/**
	 * @param {Context} context
	 * @returns {any}
	 */
	return function(context) {
		var value = context.resolve(name);

		for (var i = 0, l = filters.length; i < l; i++) {
			value = filters[i](value);
		}

		return value;
	}
}

function toUpperCase(string) {
	return string.toUpperCase();
}

var _v = {
	bookName: filter('name', [toUpperCase]),
	
};

TODO: escape format

function escapeHTML(string) {
	return string.replace(/[&<>"'`=\/]/g, function(key) {
		return ({
			'&': '&amp;',
			'<': '&lt;',
			'>': '&gt;',
			'"': '&quot;',
			"'": '&#39;',
			'`': '&#x60;',
			'=': '&#x3D;',
			'/': '&#x2F;'
		})[key];
	});
}

news: i will stop to updating English docs from the v0.1.3

因为下载量低迷,以及英语不佳等原因,我将从v0.1.3开始停止更新英文文档。取而代之,我将用更多的时间去完善中文文档,以及改善程序运行效率等。

当然如果你喜欢IJ2TPL.js引擎,并且有足够的时间以及耐心,你也可以将翻译过后的英文文档以PR的形式发布给我。PR is welcome :)

MTL:

Due to the low download volume and poor English, I will stop updating English documents from v0.1.3. Instead, I will spend more time to improve the Chinese documents and improve the efficiency of the program.

Of course, if you like IJ2TPL.js Engine, and have enough time and patience, you can also post the translated English documents to me in the form of PR. PR is welcome :)

Bug: Context resolve value bug

当当前context和父级context都含有某个变量A时,v0.0.1的实现上可能会有查找错误的bug。

因为在v0.0.1的实现上会向上级查找A的缓存,假设当前的context有A变量,但没有A的缓存,父级context也有A变量,且A的缓存,则context会直接引用父级的缓存,最终查找结果会出现偏移。

var context = {
  cache: {},
  data: { A: 9 },
  parent: {
    cache: { 'A': 10 },
    data: { A: 10 },
    parent: null
  }
}

Feature: format pipe

template.pipe('formatSize', function formatSize(size) {
    var i = 0,
    dataUnits = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB', 'BiB', 'NiB', 'DiB'];

    while (size >= 1024)
        i++, size /= 1024;

    return String(size.toFixed(2)) + dataUnits[i];
});

Suggest: change `for ... of ...` to ES3/ES5 style(cache length property)

Our compiled source ij2tpl.js use for (let i = 0; i < arr.length; i++) e = arr[i] style to instead for (e of arr) style. It may slow than if we cache the length property: for (let i = 0, l < arr.length; i < l; i++) e = arr[i]. At least that will be work fast on old browser, such like IE6.

feature: render recursive partial

{{?contents}}
Type: {{type}}
Name: {{name}}
    {{@^}} {{- context without parents }}
{{/contents}}
export class Renderer {
  public treeRoot: Token[];
  public recursionDepth: number = 0;

  public constructor(treeRoot: Token[]) {
    this.treeRoot = treeRoot;
  }
      case TokenType.PARTIAL:
        token = token as Partial;
        value = token[TokenMember.VALUE];

        if (value === '^') {
          this.recursionDepth += 1;
          buffer += this.renderTree(this.treeRoot, new Context(context.data, null), partialMap);
        } else if (value === '&') {
          this.recursionDepth += 1;
          buffer += this.renderTree(this.treeRoot, context, partialMap);
        } else if (partialMap && hasOwnProperty.call(partialMap, value))
          buffer += this.renderTree(partialMap[value].treeRoot, context, partialMap);
        else
          throw new Error(`Cannot resolve partial '${value}'`);
        break;
  public render(data: IMap<any>, partialMap?: IMap<Renderer>): string {
    this.recursionDepth = 0; // reset

    return this.renderTree(
      this.treeRoot, new Context(data, null), partialMap
    );
  }

About Primitive Types

I think user given data should always be a Map-like object, not a primitive.
Also in a section, the primitive's properties are can't be access with short names.

{{?.}}{{length}}{{/.}}
template.render("Hello"); // -> ''

XXX: 性能优化

目前来说模板引擎部分场景(如只有formatter)渲染时总体会比mustache.js慢17~20%左右或者持平,原因暂时未知(可能是PIC方面的原因?)。

此外quickjs下总体慢于mustache.js,但在nodejs和mozjs60下速度会比mustache.js快很多。

TODO: Enhance ij2tpl cli

Usage ::= "ij2tpl" [ "-t" ] { template } Options;

Options ::=
    "-D" data |
    "-d" { data } |
    "-o" <output> |
    ExtraOptions;

(**
 * default:
 *     <data prefix> ::= "data."
 *     <template suffix> ::= "ij2[.suffix]"
 *)

ExtraOptions ::=
    "-p" <data prefix> |
    "-s" <template suffix>;

Feature: Raw Source Block

This feature allows you use a double-prefix + double-suffix to tell tokenize function that you want skip parsing between the double-prefix and double-suffix. It may looks like Python's format-string :)

For example:

{{{{
    Hello {{name}}
}}}}

Outputs:

Hello {{name}}

feat: IJ2Loader plugin for rollupjs

// rollup-plugin-ij2tpl.js

import { createFilter } from 'rollup-pluginutils'; 
import { parse } from '../ij2tpl';

export function IJ2Loader(options: any = {}): object {
  if (!options.include)
    options.include = ['**/*.ij2', '**/*.ij2.*'];

  const filter = createFilter(options.include, options.exclude);

  let plugin = {
    name: "IJ2Loader",
    transform: function(code: string, name: string): string | void {
      if (filter(name)) {
        let renderer = parse(code);

        return `export const template = ${JSON.stringify(renderer.treeRoot)}`;
      }
    }
  }

  return plugin;
}
// rollup.config.js

import { IJ2Loader } from './base-loader';

export default {
  input: 'src/main.js',
  output: {
    file: 'bundle.js',
    format: 'umd'
  },
  plugins: [ IJ2Loader() ]
};

Test: Render Test

var ij2tpl = require('./ij2tpl.min'),
	mustache = require('./mustache.min');

mustache.tags = ['{', '}'];

var renderData = (function() {
    var datas = [];
    for (var i = 0; i < 20000; i++) {
        datas.push({
            is_shown_in_index: i % 2,
            author: Math.pow(i, 2),
            tags: String(i).split(''),
            preview: 'https' + String(i)
        });
    }

    return datas;
})();

var timer = {
	startTime: 0,
	start: function() {
		this.startTime = new Date().getTime();
	},
	stop: function() {
		return new Date().getTime() - this.startTime;
	},
};

var engines = {
	ij2tpl: ij2tpl,
	mustache: mustache
};

var templates = {
	ij2tplTemplate: `
{?.}
	{?is_shown_in_index}
		{author} {tags} {preview_url}
	{*is_shown_in_index}
		{author} {tags} {preview_url}
	{/is_shown_in_index}
{/.}
`,
	mustacheTemplate: `
{#.}
	{#is_shown_in_index}
		{author} {tags} {preview_url}
	{/is_shown_in_index}
	{^is_shown_in_index}
		{author} {tags} {preview_url}
	{/is_shown_in_index}
{/.}
`,
	mustacheTemplate4ij2tpl: `
{?.}
	{?is_shown_in_index}
		{author} {tags} {preview_url}
	{/is_shown_in_index}
	{!is_shown_in_index}
		{author} {tags} {preview_url}
	{/is_shown_in_index}
{/.}
`
};

var templateInstances = [];

// Test engine parser
[
	['ij2tpl', 'ij2tplTemplate'],
	['mustache', 'mustacheTemplate'],
	['ij2tpl', 'mustacheTemplate4ij2tpl']
].forEach((tuple) => {
	var engine = tuple[0],
		template = tuple[1];

	timer.start();
	templateInstances.push([
		engine, template, 
		engines[engine].parse(templates[template])
	]);
	console.log(`${engine}.parse(${template}): ${timer.stop()}ms`);
});

// Test engine renderer
templateInstances.forEach((tuple) => {
	var engine = tuple[0],
		template = tuple[1],
		instance = tuple[2];

	timer.start();
	instance.render ?
		instance.render(renderData)
	:
		engines[engine].render(templates[template], renderData)
	;
	console.log(`${engine}.render(${template}, renderData): ${timer.stop()}ms`);
});

// Test engine resolver
[
	['ij2tpl', 'resolve'],
	['mustache', 'lookup']
].forEach(function(tuple) {
	var engine = tuple[0],
		method = tuple[1]
		context = new engines[engine]
			.Context({name: 'urain39' });

	for (let i = 0; i < renderData.length; i++)
		context = new engines[engine].Context({}, context);

	timer.start();
	eval(`context.${method}('name')`);
	console.log(`${engine}.Context.resolve('name'): ${timer.stop()}ms`);
});

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.