原文地址:http://home4j.duapp.com/index.php/2014/06/01/diy-writing-a-dust-middleware.htmlphp
Dustjs是我我的比较喜欢的一个JS模版引擎,缘由有两个,一是,同时支持客户端和服务端渲染,模版编译成JS后使用,性能好;二是,有大公司的支持,Linkedin有专门的Dustjs版本(本文所说的都是该版本),并且通过线上考验。html
关于Dustjs本文再也不赘述(可参看文档),直接进入正题。node
Dustjs 官方支持做为Express的View Engine使用,但我的倾向用于客户端渲染,能减小服务端的性能损耗,充分利用客户端的机器性能。目前Dustjs没有相似于less- middleware的插件,可以在按需的对模版进行编译,供客户端引用,所以才有了这个Dustjs中间件。jquery
中间件代码很简单,只有几十行,无非是拦截HTTP请求,如发现是获取模版,则按需的进行编译。git
// 依赖模块的引入 var url = 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) { throw new Error('dustjs-middleware requires `source` directory'); } return function(req, res, next) { if ('GET' != req.method.toUpperCase() && 'HEAD' != req.method.toUpperCase()) { // 只处理Get和Head请求 return next(); } var pathname = url.parse(req.url).pathname; if (!/^\/dust\/[\S]+\.js$/.test(pathname)) { // 不是对JS文件的请求这里不处理 return next(); } var jsPath = source + pathname; var dustPath = jsPath.replace(/\.js$/, '.dust'); var error = function(err) { return next('ENOENT' == err.code ? null : err); }; // 编译模版的函数 var compile = function() { fs.readFile(dustPath, function(err, buf){ if (err) { return error(err); } // 用指定的编码解析出模版源码 var data = iconv.decode(buf, options.encoding); // 编译模版,以文件名做为模版名 var name = path.basename(dustPath, '.dust'); var template = dust.compile(data, name); if (options.format) { // 有须要则进行代码格式化,基于js-beautify template = 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) { return next(); } else { return next(dustErr); } } if (dustStats.isDirectory()) { // 模版代码是个文件,也不处理 return next(); } fs.stat(jsPath, function(jsErr, jsStats) { if (jsErr) { if ('ENOENT' == jsErr.code) { // JS文件不存在,直接编译 return compile(); } else { return next(jsErr); } } else if (dustStats.mtime > jsStats.ctime) { // 模版有变更,从新编译 return compile(); } }); }); }; };
须要注意的是中间件以文件名做为模版的名字,使用模版时,须要指定该模版名,示例以下。github
<div id="demo"></div> <script src="https://home4j.duapp.com/share/jquery/jquery-2.min.js"></script> <!-- 引入dust --> <script src="https://home4j.duapp.com/share/linkedin-dustjs/dist/dust-core.min.js"></script> <!-- 引入编译后的模版 --> <script src="context.js"></script> <script> $(function() { // 准备数据 var data = { ... }; // 调用模版,模版名为文件名 dust.render("context", data, function(err, out) { $('#demo').replaceWith(out); }); }); </script>
这里隐含的一个约束是同一个页面不能引入同名的模版,这会致使冲突,有必要时能够在模版文件命名时加上Namespace作区分。express
Dustjs的编码问题相对简单,先来看一个编译后的Dust模版。npm
(function() { dust.register("hello", body_0); function body_0(chk, ctx) { return chk.write("Hello world!"); } return body_0; })();
全部的Dust模版在加载时都会注册到dust 全局对象中,模版间的互相引用都是经过该全局对象完成,不像Less那样须要把组件的代码合并到一块儿。所以解决Dustjs的编码问题只要保证单个文件的编码正确便可(详见代码)。app
除了代码,还须要补充Node模块的定义,才能被正常的依赖和使用。less
{ // 做者信息 "author": { "name": "Joshua Zhan", "email": "daonan.zhan@gmail.com", "url": "http://home4j.duapp.com/" }, // 模块信息 "name": "dustjs-middleware", "description": "Dustjs middleware for express.", "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" 。
得益于事件驱动和非阻塞的IO接口,Nodejs有着很好的性能,同时也带来了编码方式的变动。随处可见的匿名函数和回调函数看起来让人不太舒服,庆幸的是有一些有效的方法能在很大程度上缓解这个问题,推荐一篇文章给你们(http://callbackhell.com/),该文章对此作了很好的整理总结,但愿能有所帮助。
和Java Web的Filter相似,Express中间件也是链式的处理请求,一个典型的中间件以下:
function(request, response, next) { ... return next(); }
衔接各个中间件的则是next() 回调函数,若是中间件没有把内容输出到response 中,则必经过回调把请求交给下一个中间件处理。通常而言回调函数只能调用一次,屡次调用可能产生异常;不调用则请求得不到响应,占用宝贵的连接资源和内存空间。
麻烦之处在于,Node中充斥着各类回调和匿名函数,使得next() 很是容易被遗忘或是错误的调用。这个目前貌似没有很好的解决办法,只能靠开发的经验和测试,一个好的习惯是尽量的在调用回调后就当即返回return next(); ,这个能够有效的避免屡次调用的问题。