ecomfe / etpl Goto Github PK
View Code? Open in Web Editor NEWETPL是一个强复用、灵活、高性能的JavaScript模板引擎,适用于浏览器端或Node环境中视图的生成。
Home Page: http://ecomfe.github.io/etpl/
License: BSD 3-Clause "New" or "Revised" License
ETPL是一个强复用、灵活、高性能的JavaScript模板引擎,适用于浏览器端或Node环境中视图的生成。
Home Page: http://ecomfe.github.io/etpl/
License: BSD 3-Clause "New" or "Revised" License
<!-- 这里是注释 -->
<!-- target: 1-->
xxx1
<!-- /target -->
<! -- 这里是注释 -->
<!-- target: 2 -->
xxx2
target 1把注释内容也给包含进去了,但是我不想让注释内容插入到html中。。。所以尝试用了手工闭合target,结果貌似不管用。。。
var etpl = require('etpl')
etpl.addFilter('trim', function (s, space) { return s.substring(space) })
var render = etpl.compile('${id | trim(${space})}')
var data = { id: 'abc', space: 1 }
render(data)
// -> "${id | trim(1)}"
已经修复了 #8 的错误保证能顺利执行
预期输出应该是abc
在截取掉1位(space
控制)后的字符串,即"bc"
,但真实输出是"${id | trim(1)}"
,逻辑不大能分析出来……似乎trim
本身也没有执行
是设计如此吗?我理解应该返回一个“永远返回空字符串”的函数
直接将resourceId
切开,并忽略了.
和..
,导致relative id无法正确解析出对应的URL
后续发PR过来
在ETPL3.0中计划target
和master
归一化,只保留target
,删除master
。也就是说,未来的模板继承(母版功能),会这么写:
<!-- target: masterTarget -->
<!-- target: myTarget[master=masterTarget] -->
在ETPL2中,target
内通过content
,覆盖master
中contentplaceholder
的部分。在smarty和nunjucks中,模板继承的内容覆盖是通过block
来实现的。block
可以表达contentplaceholder
,也可以被继承的block
覆盖。
但是,ETPL提供了命令标签的自闭合功能,当contentplaceholder
没有默认内容时,是可以不写闭合的。而content
也是可以不写闭合的(content允许不写闭合还有一个弊端,content之间无法嵌套)。
所以,在ETPL3.0中,需要对使用contentplaceholder
+content
,或使用block
进行选择。
contentplaceholder
+content
,使用block
使用该方案,在任何情况下,block
都需要手工闭合
<!-- target: masterTarget -->
<header><!-- block: header --><!-- /block --></header>
<div><!-- block: content -->default content<!-- /block --></div>
<!-- target: myTarget[master=masterTarget] -->
<!-- block: header -->my header<!-- /block -->
<!-- block: content -->my content<!-- /block -->
contentplaceholder
+content
和ETPL2使用方法一致
<!-- target: masterTarget -->
<header><!-- contentplaceholder: header --></header>
<div><!-- contentplaceholder: content -->default content<!-- /contentplaceholder --></div>
<!-- target: myTarget[master=masterTarget] -->
<!-- content: header -->my header
<!-- content: content -->my content
请投票:
contentplaceholder
+content
,使用block
contentplaceholder
+content
无论使用哪种方案,ETPL3的升级都会推出模版自动升级工具。投票时无需考虑迁移成本。
比如
<!-- master: sidebar -->
<div class="sidebar">
<!-- contentplaceholder: content -->
</div>
<!-- target: main -->
<header>Hello</header>
<!-- use: sidebar -->
<!-- content: content -->
This is Sidebar
<!-- /use -->
现在得这么写
<!-- target: sidebarHead -->
<div class="sidebar">
<!-- target: sidebarTail -->
</div>
<!-- target: main -->
<header>Hello</header>
<!-- import: sidebarHead -->
This is Sidebar
<!-- import: sidebarTail -->
希望支持如下的语法:
<!-- var: foo = ${bar|filter1|filter2}; -->
<!-- if: ${foo} -->
...
<!-- /if -->
初次
提问,心情忐忑
,不知大大是否空闲。
某年某月的某一天,为了真爱, 于是默默的写下了纠结一生的代码:
// filter
etpl.addFilter('kiss', function (source) {
return 'duang~duang~ ' + source + ' too.';
});
// view
<p>${'i love u'|kiss}</p>
// output
<p>duang~duang~ too.</p>
心都碎了, 求
大大
指点 ~
用etpl时,想要直接输出${xxx}
,而不是用data.xxx
来代替似乎做不了,希望可以有类似这样的语法
$${xxx}
输出成${xxx}
简单来说,现在的etpl
并不支持backbone
的Collection
,也不支持emc
的Collection
,因为它们封装了数组,并且没有暴露出类数组的索引下标访问功能
我们当然可以在一切这样的场景下先把Collection
对象变成纯数组,但这会使得应用系统变得非常复杂,我们使用Collection
是希望使用它的变化通知的相关事件,转为纯数组会失去这一功能,或者会变成每次收到集合变化时再转一次纯数组交给etpl
我总结了一下几个知名库的封装数组类的接口:
backbone.Collection
:有.length
属性,有each
和forEach
方法,使用at(index)
访问元素ember.Array
:有.length
属性,有forEach
方法,使用objectAt(index)
访问元素ko.ObservableArray
:有.length
属性,能直接下标访问jQuery
:有.length
,能直接下标访问我们的emc.Collection
有.length
属性,没有任何遍历的方法,使用get(index)
访问元素
因此我希望可以支持这一类的封装数组,从上面来看,大家都会有.length
属性,但访问方法各不相同,其中以each
和forEach
最为通用,emc
可以添加该方法作支持
个人的建议是当具备.length
属性时,增加检测forEach
及each
方法,存在的话也认为是一个数组并使用方法进行循环
最终这一需求会影响多少的体积,是否合适,还是交给 @errorrik 来判断吧
etpl部分命令标签提供了标签自闭和功能,为了方便模板编写时能够减少输入。
为了评估该功能带来的收益,评估自闭和功能设计是否达到了想像中的便利,为了接下来etpl的设计,发起该调查。
该调查为多选
A
使用target自闭和,target不编写结束标签B
使用master自闭和,master不编写结束标签C
使用content自闭和,content不编写结束标签D
使用contentplaceholder自闭和,contentplaceholder不编写结束标签addFilter
传递过来的参数是个string,如果原始参数是个Object
,那么传过来的就会是[object Object]
,这样根本没法做任何处理吧!?比如我想加个stringify
的filter
:
etpl.addFilter('stringify', function(source) {
console.log(source); // "[object Object]"
});
稳跪。。。
应用中发现for循环写死不太方便,有时后端数据可能没有做限制,建议可以增加break功能,使使用更灵活
语法可以是这样:
{{ break: conditional-expression }}
在for循环中插入命令语句
我自测时实现了一个break command,测试用例如 {{ break: 3 }} {{ break: ${list.length} - 2 }} 等均能通过
当值为 url 的时候会产生很多 404 错误
compile-time.html
93ms
83ms
157ms
110ms
84ms
render-time.html?5000*10|start
etpl 811ms
juicer 834ms
mustache 2236ms
hogan 843ms
handlebars 827ms
ejs 2762ms
dot 3515ms
artTemplate 1162ms
baiduTemplate 1984ms
compile-time.html
140ms
96ms
78ms
81ms
75ms
render-time.html?5000*10|start
etpl 692ms
juicer 638ms
mustache 2480ms
hogan 726ms
handlebars 603ms
ejs 2446ms
dot 1327ms
artTemplate 776ms
baiduTemplate 1759ms
compile-time.html
368ms
353ms
361ms
495ms
357ms
render-time.html?5000*10|start
etpl 3461ms
juicer 3752ms
mustache 7704ms
hogan 4754ms
handlebars 4728ms
ejs 11429ms
dot 9086ms
artTemplate 2ms
baiduTemplate 8997ms
compile-time.html
234ms
194ms
172ms
137ms
150ms
render-time.html?5000*10|start
etpl 623ms
juicer 567ms
mustache 1557ms
hogan 563ms
handlebars 712ms
ejs 1875ms
dot 2182ms
artTemplate 714ms
baiduTemplate 1324ms
compile-time.html
59ms
57ms
54ms
53ms
55ms
render-time.html?5000*10|start
etpl 489ms
juicer 361ms
mustache 606ms
hogan 416ms
handlebars 301ms
ejs 762ms
dot 283ms
artTemplate 351ms
baiduTemplate 847ms
compile-time.html
84ms
84ms
83ms
84ms
80ms
render-time.html?5000*10|start
etpl 1173ms
juicer 940ms
mustache 2040ms
hogan 2036ms
handlebars 1064ms
ejs 1159ms
dot 2323ms
artTemplate 1374ms
baiduTemplate 3143ms
compile-time.html
197ms
165ms
178ms
168ms
170ms
render-time.html?5000*10|start
etpl 2395ms
juicer 1782ms
mustache 3475ms
hogan 1638ms
handlebars 1615ms
ejs 2815ms
dot 2089ms
artTemplate 3330ms
baiduTemplate 3450ms
compile-time.html
261ms
260ms
268ms
272ms
265ms
render-time.html?5000*10|start
etpl 5212ms
juicer 4153ms
mustache 7485ms
hogan 7052ms
handlebars 4383ms
ejs 5497ms
dot 6865ms
artTemplate 6820ms
baiduTemplate 10792ms
注:
compile-time.html
的数据为测试5次的结果未来在node server环境,可以通过某种机制,target file和file path可以有一些对应映射关系,更方便server环境中模板的管理
现在的变量是${
开头和}
结束,不可自定义。
现在的命令open和close是可以通过engine的option:commandOpen
&commandClose
来自定义的。
如果变量的open和close也能自定义,使用者就能很大程度上使用自己喜欢的模板语法。
支持option:variableOpen
, variableClose
在所有中间页中,所有的静态资源都是放在vs-static域下的,与主域不同。etpl/tpl是通过ajax来加载模板的,无法跨域。
在后端无法配合的条件下,etpl模板是没办法跨域加载到的。
如题~
官方例子只有这么简单一个
var render = etpl.compile('Hello ${name}!');
var text = render({ name: 'etpl' });
那个example页面,也没有如何调用的js代码。对于一个一开始没接触过模板的人来说,完全无法下手。
比如这个循环,我改怎么写js,完全不知道
例如遍历一个Array
<!-- for: ${array} as ${value}, ${index} -->
<!-- if ${@last} -->
<!-- else -->
<!-- /if -->
<!-- /for -->
这个需求应该很常见吧?
${data[$key]} 这样的不支持么?
原因是因为,for command的语法抽取正则是写死的。
Issue方便讨论,就在这里了:
<!-- master: masterName(master=masterName) -->
<!-- contentplaceholder: x -->
<!-- /contentplaceholder -->
Additional content
<!-- contentplaceholder: y -->
<!-- /contentplaceholder -->
<!-- /master -->
母板即建立一个模板,里面放一些“变量”,继承母板的目标或者子母板通过填充这些“变量”来实现输出。
母板可以指定一个父母板,形成多层的母板关系。但父母板中的contentplaceholder
不会再传递下去,即contentplaceholder
只在一层有效。
自闭合。内部可嵌套任何条件和循环语句。
<!-- contentplaceholder: name -->
default content
<!-- /contentplaceholder -->
内容片用在母板中,为一个占位空间。
内容片 必须 闭合。内部可嵌套任何条件和循环语句。
内容片的起始和结束之间放的东西为“默认内容”,即使用此母板的目标或母板未使用content
片段定义这个内容片时,使用此默认内容。
<!-- target: targetName(master=masterName) -->
<!-- content: x -->
Replaced content
<!-- /content -->
<!-- /target -->
<!-- target: targetName -->
target content could be any HTML
<!-- /target -->
目标就是最简单的一个模板块,使用getRenderer
或render
,提供targetName
即可使用。
master
参数可选。
自闭合。内部可嵌套任何条件和循环语句。
<!-- content: x -->
Content
<!-- /content -->
用于覆盖母板中定义的一个内容片,内容的名称必须与母板定义的对应。如果一个内容名称在母板中未定义,则报错。
自闭合。内部可嵌套任何条件和循环语句。
<!-- mixin: mixinName($arg1, $arg2...) -->
Content
<!-- /mixin -->
混合就是个辅助函数,不能单独被getRenderer
或render
使用,只能在其它的母板、目标中引入。
引入混合使用如下语法:
<!-- use: mixinName -->
使用use
与import
区分,前者负责混合的引入,后者负责目标的引入。
目标可以当混合用,但目标是不能传递参数的,而混合可以传递参数。
同Linux的概念;
${x|date}
则将x
这个数据,调用date
这个管道处理器(在模板引擎中我们叫filter
)。
管道处理器可以有参数:
${x|date('yyyy-MM-dd', true)}
则调用date
管道处理器时会把参数跟上,如这么实现:
function dateFilter(value, format, useLocale) {
if (!useLocale) {
value = value.getUTCDate();
}
return formatDate(value, format)
}
关于管道处理器怎么设计我们另开Issue讨论。
给管道传参还可以用另一个动态数据:
${x|date(${dateFormat}, true)}
这个实现不知道代价有多大,但很实用。
既然叫管道,自然可以连着用:
${x|raw|customEncode|highlightKeyword(${keyword})|html}
意思就是:先原样输出(禁用默认的HTML编码),再通过customEncode
进行自定义编码,再高亮关键词(使用keyword
为关键词),再进行HTML编码。
当然语法上要适应空格,不然粘一起太丑:
${x | raw | customEncode | highlightKeyword(${keyword}) | html}
相比管道对单个数据生效,块处理器弥补2个问题:
块处理器在定义后能这么写:
<!-- filter: markdown(${useExtra}, true) -->
## markdown document
This is the content, also I can use `${variables}`
<!-- /filter -->
当然这么一写就能嵌套组合了,不再详述。
块处理器一样能传参数。任何管理处理器都能当块处理器用,纯粹是调用语法上的变化而已。
<!-- for: ${list} as ${item}, ${index} -->
<!-- for: ${object} as ${value}, ${key} -->
<!-- if: condition -->
、<!-- elif: condition -->
、<!-- else -->
与原tpl相比,主要变化在:
master
可以有master,这在多级布局中很重要contentplaceholder
有默认内容,可以不被覆盖,我实际使用中也倍感需求mixin
,主要为了使用的时候可以传参,非常重要<!-- var ${i} = 1 -->
,我认为有必要,在循环中格外有用,但作用域怎么控制是个问题(要不要块作用域)render
后会消失,建议<!-- // xxx -->
<input value="${value}" />
,则如果${value}
为空时,输出的是<input />
,这个太高级了我觉得不用做,但有这功能的话模板用着会非常非常舒服。ASP.NET的Razor是有这功能的,jade也有通过{RegExp} commandSyntax
参数,可以制定command语法。
{RegExp} commandSyntax
的match点:
比如我们现在的if是这样的
<!-- if: ${v} == 1 -->
如果commandSyntax
是/^\s*(\/)?([a-z]+)\s?([\s\S]*)$/
,就能这样写if:
<!-- if ${v} == 1 -->
如果想绘制个“menu”(或者“tree”),比如:
<ul>
<li><a href="#">Aberdeen</a></li>
<li><a href="#">Ada</a></li>
<li>
<a href="#">Salzburg</a>
<ul>
<li><a href="#">Perch</a></li>
<li><a href="#">D</a></li>
<li>
<a href="#">Delphi</a>
<ul>
<li><a href="#">Ada</a></li>
<li><a href="#">Saarland</a></li>
</ul>
</li>
</ul>
</li>
</ul>
有什么好办法绘出?
例如,recursively
使用 target来绘制tree:
<script id="tpl" type="text/html">
{{ target: some }}
{{ use: menu(children=${menu}) }}
{{ /target }}
{{ target: menu }}
<ul>
{{ for: ${children} as ${item} }}
<li>
<a href="#">${item.text}</a>
{{ if: ${item.children} && ${item.children.length} }}
{{ use: menu(children=${item.children}) }}
{{ /if }}
</li>
{{ /for }}
</ul>
{{ /target }}
</script>
<script id="self" type="text/javascript">
etpl.config({
commandOpen: '{{',
commandClose: '}}'
});
etpl.compile($('#tpl').html());
alert(
etpl.render(
'some',
{
menu: [
{ text: 'Aberdeen' },
{ text: 'Ada' },
{
text: 'Salzburg',
children: [
{ text: 'Perch' },
{ text: 'D' },
{
text: 'Delphi',
children: [
{ text: 'Ada' },
{ text: 'Saarland' }
]
}
]
}
]
}
)
);
</script>
当然,现在是不行的,compile时会 Maximum call stack size exceeded
支持这种事情容易么?
或者,有什么别的方法?
具体方案见 #30
由于etpl3的语法相对etpl2有一些变化,需要写一个工具,方便使用者转换
tool/etpl2to3.js
比如希望:
<!-- target: pc -->
<head>
<!-- import: meta -->
</head>
<!-- /target -->
<!-- target: meta -->
<meta charset="utf-8" />
<meta name="renderer" content="webkit" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<!-- /target -->
希望生成:
<head>
<meta charset="utf-8" />
<meta name="renderer" content="webkit" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
</head>
【保留空格】,更复杂的情况是,有部分 target 需要被插入到任意深度的结构中
不知道这样描述的够不够清楚。
RT
${xxx.xxx}
传给了data的get方法,但是get方法并不知道上下文,所以无法正常读取哎呀,又是我的问题,关闭吧,thx
如题,需求大概是这样:
<!--use: ${someTargetName}(someVariable=${conf.name})-->
<!--import: ${yetAnotherTargetName}-->
应用场景是这样的:
schema
来描述这个表单的每个字段。etpl
的target
模板,模板的内容根据字段类型与schema
配置而不同。schema
中对应字段配置,使用use
命令动态渲染出dom结构。<!--target:form-->
<!--for:${fields} as ${field}-->
<!--use:${field.type}(conf=${field})-->
<!--/for-->
<!--target:TextBox-->
<div class="ui-textbox"> ... </div>
var formRenderer = etpl.getRenderer('form');
var html = formRenderer({
fields: [{
type: 'TextBox',
...
}]
});
document.getElementById('main').innerHTML = html;
当然这里可以先用js来按字段一个一个render,就是比较笨拙呢。所以请大神们看看是否合适在etpl中支持此功能。
我打算在 node 环境下使用 etpl
,看到目前 npm
上的版本只是占个位
既然都支持了,发个版呗
匿名target
以及
返回第一个target编译后的renderer函数
的什么是 第一
似乎不看代码不能理解。
“默认生成target” 这一功能,似乎是为了 “与其他模板引擎使用方式类似” 而引入
比如这个模板:
<script id="tpl" type="text/html">
bbbbbegin
{{ use: t0 }}
{{ target: t0 }}
asdf
{{ /target }}
eeeeeend
</script>
那么我一上来就认为
var renderer = etpl.compile($('#tpl').html());
返回的renderer就应该涵盖了 整个的模板片段的渲染。
但是事实上,返回的是一个只含有用于渲染“bbbbbegin”的renderer。
而由于是“匿名”的target,导致渲染“eeeeend”的render没有其他办法能够得到。
所以,是否
or
不知我的理解是否有问题
我个人希望是可以有这样的功能:
<!-- target: page -->
<!-- import: header -->
<!-- block: username -->
Gray Zhang
<!-- /block -->
<!-- /import -->
<div id="main">
<!-- import: sidebar -->
<!-- block: currentLink -->
<a href="#/">Main Page</a>
<!-- /block -->
<!-- /import -->
Body Content
</div>
<!-- import: footer -->
<!-- block: copyRight -->
All Rights Reserved Baidu .Inc
<!-- /block -->
<!-- /import -->
重点:
import
和/import
间可以通过block
来覆写target
被import
时,其内部的block
仅作用于import
和/import
之间,出了/import
之后就没这个block
了这种方式可以非常灵活地组装各个target
,相比master
的机制灵活性有一定提升,当然master
的存在也可以用于布局稳定的场景来减少重复代码
因为RIA是专注比较重型解决方案,所以有些东西并不一定与etpl的理念相符,如果此Issue并不符合etpl希望提供的内容,直接Close就好
在系统中,我们会有一些经常会用到的数据,这些数据希望可以在任何的代码中使用,即全局的。其中比较典型的就是枚举,比如我们会在JavaScript中有这样的代码:
var Status = require('foo/enum').Status;
if (entity.status === Status.ENABLED) {
// ...
}
由于JavaScript有AMD的模块管理,自然代码很容易。但是放在模板中,就比较难了,我们希望模板中有这样的代码:
<!-- if: ${entity.status} === ${slot.enum.Status.ENABLED} -->
WTF
<!-- /if -->
为了达到这个目标,我们会有一些方案:
Model
中加入Status
这个属性,但这样需要不少额外的编码,在各种Model
中都要有类似的代码放进许许多多的静态对象View#getTemplateData
,通过重写get
方法来解析变量名称,通过一定的规则来获取常量。这种方案看上去美好,但仅限于View
中使用的模板,在其它地方使用etpl就没有这样的效果,事实是我们确实有很多地方使用着etpl因此,我希望etpl
可以有一个方法:
{*} TemplateEngine#getVariableValue({string} variableName})
这个方法默认的实现etpl内部肯定是有的,开放到prototype
上以便系统去定制,通过实现自身的TemplateEngine
子类来增加各种不同的功能
对于这一功能我希望支持的原因是,我相信etpl内部本身就有这样的逻辑,只是从内部转到prototype
上来,应该不会影响etpl的体积
现在用处不大,但总会用上的,这样:
var engine = new template.Engine();
engine.config('encodeHTML', false);
表示默认不要进行HTML转义。
当然也有template.config('encodeHTML', false);
的写法。
另外还要全局配置(区别于template.config
,那个只给默认引擎生效),因此要有
template.global('encodeHTML', false);
如 #1 所说,管道模型中的filter
我觉得是个核心。
filter
的管道模式肯定没问题,一个接一个调用就行了,关键在于filter
可以传参。
一般这个时候,有2种设计:
engine.addFilter(
'date',
function (value, format, useUTC, includeTime) {
// ...
}
);
这种设计暴力简单,输入的内容放在第1个参数,其它放在后面。另一种:
engine.addFilter(
'date',
function (format, useUTC, includeTime) {
return function (value) {
// 用到format等参数
}
}
);
典型的函数工厂式实现,mustache应该是这么实现的。代价是每次用要先调用函数返回真正的filter
,然后再调用filter
输出内容。
优点是对参数的变化适应性要强一些,比如我们要增强模板,使用filter
时不仅仅给value
,还会给当时的Scope
对象,当时的各种上下文,那第1种方案基本没法动了,第2种则很容易,内存函数多几个参数而已,爱用不用。
我建议是第2种,感觉filter
多一次函数调用不应该会产生太多的性能问题,如果真要性能了,filter
的实现者有义务做好memoize工作
下下来是源码,里面的test/run.html 也无法运行。。。
我只想使用,不打算研究js,对js不是很熟悉,能否行个方便?
如沟通,类似这样的:
<!--
use: someTarget(
a = 1,
b = 2
)
-->
解析不了,原谅我洁癖……
比如这样的一段模板
<!-- target: ok -->
OK!
直接渲染的时候会变成
[有个空行]
OK!
在OK!
前面多出一个空行来。这在组装HTML的时候不会出现太多问题,但是如果用etpl来做代码生成之类的工作,空行就显得尤其重要
因此希望可以至少把各命令前后的换行给去掉,比如
<!-- if: x -->
this is x
则要直接输出this is x
,前面不会有2个空行
<!-- for: ${list} as ${unit} -->
<div class="text-center">
<script type="text/javascript">
// 这样没问题
window["list" + '${unit.TId}'] = ${unit.TId};
// 这样就报错,导致我没法获取到对象本身
window["list" + '${unit.TId}'] = ${unit};
</script>
<a href="javascript:void(0)" onclick="editOne('${unit.TId}')"> 编辑</a>
</div>
<!-- /for -->
请教各位大牛,如果用此模板,SEO的问题如何处理?
我的意思是这样的,在页面上有两块区域,一个是隐藏的,写模板,一个是填充数据,如下
<div id="template"> // 模板写在这里 </div>
<div id="result"> // 把结果插入到这里 </div>
是不是每个页面都至少需要这么两块地方?
假设合并成一块,那么第一次 compile 没问题,第二次 compile就不行了,因为已经没有模板了(被数据填充了)?
<div id="templateANDresult"> // 第一次模板写在这里,如果把结果也插入到这里,那么模板就没了。 </div>
不知道 eptl 是不是那个缓存是不是解决这种问题的?
2.0.10版,遇到个问题~
<script id="tpl" type="text/html">
{{ target: ta1 }}
ta1ta1ta1ta1
{{ /target }}
{{ target: ta2 }}
ta2ta2ta2ta2
{{ use: ta1 }}
ta2ta2ta2ta2
{{ /target }}
</script>
·
etpl.config({
commandOpen: '{{',
commandClose: '}}'
});
etpl.compile($('#tpl').html());
etpl.get('ta2');
get时抛异常,因为UseCommand没有getContent方法
你懂得,不符合analyze-module.js的要求
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.