这周发了个etpl的node包装器,到npm上,以及对应的github地址: https://github.com/wslx520/etpl-wrap
etpl是我用过的一款非常强大的模板引擎,该有的功能都有。
etpl是可以用在node环境下的,但用法与我想像中的不一样。
这就要从我学习koa说起了,我学的koa2,他有一个koa-views的中间件,用来接入渲染模板文件的功能。但理所当然的是,无法与etpl配合起来使用。
之前express也是如此,如果有某引擎要与express配合起来,需要额外设置,也许还要在源码里专门做些工作。
我觉得这样很别扭,web框架与模板引擎关系大吗?不应该是互相独立的吗?
比如koa,使用了koa-views并接入模板引擎用,为的就是在渲染模板的时候,可以使用
ctx.body = ctx.render(‘index’,data)
这样的语法来渲染。但我想,假如我有一个完全独立的模板引擎,难道不可以这样用?:
ctx.body = etpl.render(‘index’,data)
这样的用法,模板引擎与web框架完全无关。你甚至可以在express中,按同样的语法使用:
res.end(etpl.render(‘index’,data))
这样不才是更好的状态吗?为啥模板引擎必须要注入到web框架里?
Node好歹也是服务器环境,如果他有模板文件 ,一般也是一个文件。但 etpl不具备直接渲染文件的能力。
如果我们使用etpl来渲染一个Node下的模板文件,按正常流程,我们需要先读取到文件的内容,转换成字符串,然后传给etpl.compile,然后render。
但这样太不智能了,我不能接受。
所以我就想写这么一个包装器,用来在node.js下方便的使用etpl.
包装器有个原则:不能改动原始的源代码。也就是说,我不能对etpl本身做什么,不然etpl以后升级了,我就难办了。
我粗看了一个etpl的源代码,但复杂了,而我时间有限,看完他不现实(其实是我看不太懂)
本来我打算的是,在render时,动态读取对应的模板文件,如果未编译,就编译,再渲染,并要把编译结果缓存起来(避免重复编译的消耗);如果已编译,就马上渲染
但这样有些难了,而且说不定必须改etpl源码。
后来我自我安慰说,一个网站的模板文件,加起来应该也不超过10M吧,小case,我 从一进来就读取所有的模板文件,并依次调用etpl.compile编译了,以后etpl.render的时候,就不用担心“有没有编译过”的问题。
虽然这样肯定 在启动服务器时,就会有额外的消耗,但应该可以接受。
etpl为了方便编译不同的“模块”,使用了target。比如我们新建了一个index.html的模板文件,如果以后想etpl.render(‘index’,data)的话,则index.html的第一句必须是一个target声明,如:
但这样对服务器端显然不合理,因为在服务器端,每个不同的文件,就是不同的模块。
所以包装器做了个处理:
如果模板文件第一句不是target声明,则以当前模板文件的名字作为target的名字
还有,如果你非要在模板文件第一句声明target,则target name需要与模板文件名一致
这么一算还是不写target划算
另外,在浏览器端,用户需要自行规避target同名的问题。但在服务器端,根目录下可以有list.html,子目录下也可以有,二者理论上是完全不冲突的
2016/5/5下午想明白一个原则:“ 同目录下的所有target都是同级的”。这原则相信没人会反对,基于本原则,很多关于子目录下的模板问题都能简单解决
如果子目录下有多个模板文件,则默认以index当作主文件。如有子目录:main,则其下的index会自动变成target:main
此时如有想引用main/index的,引用main就行了。
,其他文件如main/content,则会变成target: main/content
但其实main与main/content是同级的(因为main=main/index)
可以 考虑增加一个“默认主模板”的参数,可以设置为index,main等
服务器环境的模板,还有个问题,就是子目录下的模板。
比如根目录下的index.html可能会 import: list/list1.html
而子目录下的模板也可能会引用父级目录的模板如:import : ../header
这是浏览器端的etpl不会碰到的情况
所幸如果 直接把target名字取为一个路径的话,在etpl里也能正常使用,如:
target: header/head-detail
etpl当然是允许一个模板文件下有多个target的,所以我不能去掉这个功能
那么,在一个模板文件中,有import其他target时,此时就要考虑:
是引用的本文件的其他某target?
是引用的本文件同级下的其他模板文件?
如果引用的target,在同级或父级的其他模板文件中,也有,优先级是?–本文件最高
按最简单的原则,优先级应该是:同文件下的target==> 同级下的其他文件里target
比如有个header.html,会被默认当作target:header,但他后面有另一个target: li,此时,有另一个模板文件list.html,也想使用target:li,该如何引用?
本来我考虑在import时,使用 import: header.li 来引入,但测试发现这样的target名字会导致etpl报错(后来查看源码,可以通过修改源码修正这个问题,但这不合我的要求)
如果允许这个引用,可能会出现: import: head/head-left/logo.image的情况
暂时打算禁止 从一个模块文件引入其他模板文件中的非主target。(不过我想,有的人会给非主target取个不会冲突的名字,其他模板文件当成一个全局target一样引用,应该是合法的呢,不应该禁止)
2016/5/5:
今天想到一个思路:同文件下多target,非主target会被当作主target的子target。如:主target:main,则本文件中的其他target分别是main/li, main/p等等。这样一来,有外部文件想引用他们,则直接import: main/li 即可
但这样就有一个要求: 须保证同级目录下的所有模板文件里的所有target,不能重名。但这点是可以做到的。
下午,又想到个问题:
如子目录main下的主模板文件index,里面有其它target: good,编译时会被编译成target: main/good
而main目录下有另一个模板文件content.html,里面也有其他target:other,此时应编译成 main/content/other,还是main/other?
暂时决定是后者,因为需要保证“ 同目录下的所有target都是同级的”,即使你的target是在其他次模板文件里声明的
因为node端有了目录概念,所以target也有就有路径,不能像浏览器一样,import:target1,import:target2,而可能会变成:import:list/li1, import:list/li2, import:../main/list2
但前面也说了,target中出现.会让etpl报错
所以我决定在包装器里,将../这种相对路径换算成从 模板根目录出发的绝对路径。比如,root/main/list/ul.html模板文件,import:../list2/li,则会换算成:import:/main/list2/li
鉴于fn.call这样执行函数,效率会低几倍,所以给其中几个函数加入了额外的参数,以避免用call执行