Casual & Fellow

Jade模板引擎客户端服务端同时使用

在Stackoverflow上面看到一个问题
Client side + Server side templating, feels wrong to me, how to optimize?
对于客户端,服务端同时使用Jade模板引擎怎么优化呢?其实问题作者的原意是如何同时使用,而且能够让代码保持分离,同时易于维护。基于我对模板引擎的理解,以及以往的使用经验,其实这个问题并不难,于是我回答了一下,简单地做了一个英文的教程。搜索以下国内的文章,似乎没有一些简单的教程,可能会对好像我这样的菜鸟造成学习上的困扰,于是就把答案转译成中文。分享以下,也欢迎大家同时提供更好的编码经验,分享一下。

毫无疑问,Jade是一个非常不错的模板引擎,尽管它也有一些Bug,不过你提issue,应该很快就会被解决掉的。而且它的语法基于HAML,甚至更优,提高效率的东西因此值得我们去尝试使用。

在阅读本文之前,我假定你已经使用过其他的模板引擎,或者已经会使用Server端的模板引擎。如果不懂的话,要折腾一下,应该很容易理解的。那么在把Server端和Client端的Jade结合前,我们先尝试把单纯客户端的Jade模板渲染,做一遍。

首先我们要下载Jade到本地

git clone https://github.com/visionmedia/jade.git

接着我们需要
$ make jade.js
事实上,jade的项目下,已经有这两个文件了:jade.js 或者 jade.min.js。这个时候,我们只是需要把这两个文件放到我们喜欢的路径上,然后让浏览器加载他。

第三步,看看我的demo,我加载了jade以及jquery,而且你还看到我加载了一段jade模板代码进来。其中item是一个变量,我们就利用jade模板引擎的api使用方式,逐步地把这个模板渲染出啦。

代码中已经把基本的html元素去掉了,因此自行加上 :
<script type='text/javascript' language='javascript' src="lib/jquery-1.8.2.min.js"></script>
<script type='text/javascript' language='javascript' src="lib/jade/jade.js"></script>
<script type='template' id='test'>
ul
  li hello world 
  li #{item}
  li #{item}
  li #{item}
  //这个是jade的模板引擎代码 我们将会渲染三个item变量出来,值都是 "this is an item"
</script>
<script>
  var compileText = $("#test").text();//获取我们要编译的模板文本,数据类型是string,注意缩进
  console.log( typeof( compileText ) );
  var fn = jade.compile( compileText , { layout : false } );//利用jade编译模板文本
  var out = fn( { item : "this is an item " } );//编译好之后,我们执行该函数,把我们要传的变量,渲染的数据传入
  console.log( out );//最后输出最终编译的结果
  $("body").append( out );//利用javascript渲染到前端页面上
</script>

最终看到的结果是

hello world
this is item
this is item
this is item

就是上面简单的几个步骤就可以把我们需要渲染的东西渲染出来了。很简单吧。那么疑问就来了,要是我们服务端要使用jade,客户端也是使用jade,岂不是有语法冲突?(试想一下,服务端使用jade,在服务端就把模板渲染好了,客户端上面拿到的是已经编译好的代码)

这个时候我们就必须要解决冲突的问题。还好。jade早就提供了这套解决方案了。这里要记住,服务端有服务端的渲染,客户端有客户端的渲染。下面来看看例子:
index.jade

!!!5
html
  head
   title hello world
  body
    ul#list

    script#list-template(type='template')
      |- for( var i in data )
      |    li(class='list') \#{ data[i].name }
      |- }

很明显,上面是服务端需要渲染的jade文件,代码应该很容易看懂了。和上面的例子一样,我们在上面要渲染的模板是id 为 list-template里面的内容。不过写法似乎有点不一样 :-)。首先缩进相同的情况下,在代码的左边都加上了 “|” 。

加上”|”的原因是,为了避免和server端的jade模板代码造成混淆,不然可能会报错,或者无法正常去到客户端渲染。

其次,你会看到在#{ data[i].name }的前面,加上了”\”符号,实质上同样是为了转义,不被污染。

下面再看javascript的代码

index.js

/* you javascript code */
var compileText = $('#list-template').text();
var compile = jade.compile( compileText , { layout : false } );

var data = [{ "name" : "Ben" } , {"name" : "Jack" } , {"name" : "Rose" }];
var outputText = compile( data );

$("#list").append( outputText );

这下很容易理解了吧。最后输出的结果就是

Ben
Jack
Rose

小提示:其实在调试的时候有jade语法错误提示,但是依然挺麻烦的。如果确实遇到jade模板服务端,客户端混淆的时候,不妨尝试一下先在纯html页面中,载入jade,写一些测试代码,然后让jade进行渲染。保证到需要编译的文本,是字符串,而且语法符合编译的要求。这样就没有问题了。

更新部分:
上面的内容基本上围绕服务端,以及客户端同时使用Jade的情况。那么如果单纯在客户端使用Jade呢?其实也是可以的。不过写法有点不一样。这里再从更新部分给大家展示一下。

<script id='list-template' type='template'>
- var type = ['爱情','生活'];//此后还要加入颜色分类
- for( var i in data ){
   li.question-list-button( data-id='#{ data[i].id }' )
     .icon 
       img( src='#{ data[i].icon }' )
     .message
      h3.no-return #{ data[i].title } 
      p( class='message-panel no-return' ) #{ ( 235 - data[i].id ) + '天,' +data[i].use_times+'人测过' } 
      p( class='type-panel no-return' ) 
        span #{ type[data[i].type] } 
- }
</script>

大家可以看到,script标签内就是Jade模板的代码了。对于这里的缩进是有要求的。不然你的编译会出错。提示缩进不正确。正确的缩进如上面展示的代码所述。在script里面,逻辑代码全部靠左,不留可白的地方。对于变量,逻辑,循环等的特殊语句需要在前面加上横杠,这个就好像上面Jade Server Side & Client Side的Example一样了。注意的地方就是靠左以及不留空。估计我现在这样表述或许会有点不清,不过在实际写代码时,你注意一下就可以了。:-)

20130720更新部分:
今天遇到另外一个场景,就是在pyjade下,服务端以及客户端同时使用jade。此前采用的方式是直接在HTML文件里面添加一个script标签,然后写入脚本。但是使用pyjade的时候发现同样的语法会报错,暂时没有办法解决(因为这里可能是pyjade本身存在问题,也可能是我的代码没有针对pyjade写对。这里还有待研究。后来想想,我在这里边要写的jade模板其实还是不少的,全部塞进HTML文件里面似乎也不太适合。这里不如就采用一种方法,让模板文件分开管理,然后采用异步加载的方式将这些文件引入进来。

(下面是一个示例)于是我就省下了写script标签的功夫,直接把这些模板写入list-template.txt文件中。文件中的内容如下:

  • for( var i in listdata ){
    li #{ listdata[i].name }
  • }

    然后在javascript中则是这样引入的:

    <script id=’list-template’ type=’text/javascript’>

var request_template_dir = ‘…’;
var listdata = [{ “name” : “Ben” } , {“name” : “Jack” } , {“name” : “Rose” }];//需要全局变量才可以使用template渲染

$(function (){

$.get( request_template_dir + “list-template.txt”,function ( template_string ){
console.log( template_string );
var compileText = template_string;
console.log( typeof( compileText ) );
var compile = jade.compile( compileText , { layout : false } );
var outputText = compile( listdata );
console.log( outputText );
$(“#option_list”).append( outputText );
});

});

</script>

这样就顺利地把模板渲染出来了。这里有一点是非常值得注意的,就是你会看到我的渲染数据使用的是一个全局变量。这点非常重要,假设这个listdata写在闭包里面的话,渲染的时候会出现 listdata is not define的错误。这里的话大家稍微想多一层就会明白为什么会这样。结合以前的例子,渲染的时候,我们的模板是直接写在HTML中的,如果使用模板引擎进行渲染的时候,会直接把渲染的数据暴露在全局变量中,让操作得以正常运行,这里只是在写脚本的时候,定义好这是全局变量。因此避免出现未定义变量的情况。

其实本文并没有什么技术含量,仅仅是一个小教程以及解决一些小疑惑。jade的强大还远远不止这些呢。不过上面已经含括了挺多的功能啦,具体的语法还需要你亲自去探索。

其实最近我的个人网站也在偷偷地上线啦,www.cashlee.me 上面会属于我的开源项目,以及一些想法的实验。同时我也会对一些服务封装一些api出来,提供给大家使用。希望大家喜欢。当然我也会长期维护,放更多有趣的东西上去提供给大家玩,或者自己玩。