当前位置: 首页 > 科技观察

自己动手:实现Dustjs中间件

时间:2023-03-18 00:39:31 科技观察

Dustjs是我个人更喜欢的JS模板引擎,原因有两个。首先,它同时支持客户端和服务器端渲染。模板编译成JS使用,性能良好;是的,在大公司的支持下,Linkedin有专门的Dustjs版本(本文都是这个版本),而且已经在线测试过了。Dustjs本文不赘述(见文档),直接进入正题。1、为什么要写一个中间件Dustjs作为Express的ViewEngine是官方支持的,但是我个人更喜欢用它来做客户端渲染,这样可以减少服务端的性能损失,充分利用客户端的机器性能。目前Dustjs没有类似less-middleware的插件,可以按需编译模板供客户端参考,所以才有了这个Dustjs中间件。2.显示代码2.1。中间件中间件代码很简单,只有几十行,无非就是拦截HTTP请求,如果发现获取模板,就按需编译。//引入依赖模块varurl=require('url'),fs=require('fs'),extend=require('node.extend'),dust=require('dustjs-linkedin'),beautify=require('js-beautify').js_beautify,iconv=require('iconv-lite'),path=require('path');//遵循模块定义,将模块暴露给用户module.exports=function(source,options){//使用node.extend模块提供默认值options=extend(true,{format:false,//是否格式化代码方便阅读encoding:'utf-8'//代码编码格式,支持中文},options||{});//source参数用于指定模板代码的存放路径,编译后的JS代码和模板源码放在一起if(!source){thrownewError('dustjs-middlewarerequires`source`directory');}returnfunction(req,res,next){if('GET'!=req.method.toUpperCase()&&'HEAD'!=req.method.toUpperCase()){//只处理Get和Head请求returnnext();}varpathname=url.parse(req.url).pathname;if(!/^\/dust\/[\S]+\.js$/.test(pathname)){//notforJSfiles这里不处理请求returnnext();}varjsPath=源+路径名;vardustPath=jsPath.replace(/\.js$/,'.dust');varerror=function(err){returnnext('ENOENT'==err.code?null:err);};//编译模板函数varcompile=function(){fs.readFile(dustPath,function(err,buf){if(err){returnerror(err);}//使用指定的编码解析出模板源码vardata=iconv.decode(buf,options.encoding);//将模板编译成文件名作为模板名varname=path.basename(dustPath,'.dust');vartemplate=dust.compile(数据,名称);if(options.format){//必要时格式化代码,基于js-beautifytemplate=beautify(template,{indent_size:2});}//写入指定编码的编译后JS代码buf=iconv.encode(template,options.encoding);fs.writeFile(jsPath,buf,next);});};fs.stat(dustPath,function(dustErr,dustStats){//判断模板代码是否存在,不存在则不处理请求if(dustErr){if('ENOENT'==dustErr.code){returnnext();}else{returnnext(dustErr);}}if(dustStats.isDirectory()){//模板代码为文件,不处理returnnext();}fs.stat(jsPath,function(jsErr,jsStats){if(jsErr){if('ENOENT'==jsErr.code){//JS文件不存在,直接编译returncompile();}else{returnnext(jsErr);}}elseif(dustStats.mtime>jsStats.ctime){//模板有变化,重新编译returncompile();}});});};};需要注意的是,中间件使用文件名作为模板的名称。使用模板时,需要指定模板名称,示例如下

这里一个隐含的约束是同一个页面不能引入同名模板,这将必要时可以在模板文件命名时加上Namespace来区分。2.2.编码问题Dustjs的编码问题比较简单,先看一个编译好的Dust模板。(function(){dust.register("hello",body_0);functionbody_0(chk,ctx){returnchk.write("Helloworld!");}returnbody_0;})();所有的Dust模板在加载时都会注册到dust全局对象中,模板之间的相互引用是通过全局对象完成的,不像Less需要将各个组件的代码合并在一起。所以,要解决Dustjs的编码问题,只需要保证单个文件的编码是正确的即可(详见代码)。2.3.Node模块定义除了代码之外,还需要补充Node模块的定义,才能正常依赖和使用。{//作者信息"author":{"name":"JoshuaZhan","email":"daonan.zhan@gmail.com","url":"http://home4j.duapp.com/"},//模块信息"name":"dustjs-middleware","description":"Dustjsmiddlewareforexpress.","version":"0.0.1","repository":{"type":"git","url":"http://git.oschina.net/joshuazhan/dustjs-middleware.git"},//模块代码入口"main":"index.js",//依赖"dependencies":{"dustjs-linkedin":"~2.3.4","node.extend":"~1.0.8","iconv-lite":"~0.2.11","js-beautify":"~1.5.1"}...}最重要的是指定模块的入口,否则模块加载不出来。同时,由于还没有加入npm仓库,所以现阶段还不能直接使用。需要通过git导入。示例“dustjs-middleware”:“git+http://git.oschina.net/joshuazhan/dustjs-middleware.git”。3.一些想法3.1。回调得益于事件驱动和非阻塞IO接口,Nodejs有非常好的性能,同时也带来了编码方式的改变。随处可见的匿名函数和回调函数,看着让人不舒服。幸运的是,有一些有效的方法可以在很大程度上缓解这个问题。给大家推荐一篇文章(http://callbackhell.com/),这篇文章对此做了很好的总结,希望对大家有所帮助。3.2.Express类似于JavaWebFilter。Express中间件也以链式处理请求。一个典型的中间件如下:function(request,response,next){...returnnext();}各个中间件之间的联系就是next()回调函数。如果中间件没有输出内容到response,则请求必须通过回调交给下一个中间件处理。一般来说,回调函数只能调用一次,多次调用可能会引发异常;如果不调用,请求将得不到响应,占用宝贵的链接资源和内存空间。问题在于Node充满了回调和匿名函数,使得next()很容易被遗忘或调用错误。这个目前好像没有很好的解决办法。只能靠开发经验和测试了。一个好习惯是尽可能在调用回调后立即返回returnnext()。这样可以有效避免多次调用的问题。本文来自:http://home4j.duapp.com/index.php/2014/06/01/diy-writing-a-dust-middleware.html