4 Star 0 Fork 0

cyfan / ij2tpl.js

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
Apache-2.0

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

Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

简介

TypeScript 编写的类 Mustache 模板引擎(uglify后仅 4kB)。 展开 收起
JavaScript 等 4 种语言
Apache-2.0
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
JavaScript
1
https://gitee.com/urain39/ij2tpl.js.git
git@gitee.com:urain39/ij2tpl.js.git
urain39
ij2tpl.js
ij2tpl.js
master

搜索帮助