Dustjs是我个人比较喜欢的一个JS模版引擎,原因有两个,一是,同时支持客户端和服务端渲染,模版编译成JS后使用,性能好;二是,有大公司的支持,Linkedin有专门的Dustjs版本(本文所说的都是该版本),而且经过线上考验。

关于Dustjs本文不再赘述(可参看文档),直接进入正题。

  1. 为什么要写一个中间件

Dustjs 官方支持作为Express的View Engine使用,但个人倾向用于客户端渲染,能减少服务端的性能损耗,充分利用客户端的机器性能。目前Dustjs没有类似于less- middleware的插件,能够在按需的对模版进行编译,供客户端引用,因此才有了这个Dustjs中间件。

2. Show Me The Code

2.1. 中间件

中间件代码很简单,只有几十行,无非是拦截HTTP请求,如发现是获取模版,则按需的进行编译。

// 依赖模块的引入
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-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) {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();}});});};
};

需要注意的是中间件以文件名作为模版的名字,使用模版时,需要指定该模版名,示例如下。

<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做区分。

2.2. 编码问题

Dustjs的编码问题相对简单,先来看一个编译后的Dust模版。

(function() {dust.register("hello", body_0);function body_0(chk, ctx) {return chk.write("Hello world!");}return body_0;
})();

所有的Dust模版在加载时都会注册到dust 全局对象中,模版间的互相引用都是通过该全局对象完成,不像Less那样需要把组件的代码合并到一起。因此解决Dustjs的编码问题只要保证单个文件的编码正确即可(详见代码)。

2.3. Node模块定义

除了代码,还需要补充Node模块的定义,才能被正常的依赖和使用。

{// 作者信息"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" 。

3. 一些感想

3.1. Callback

得益于事件驱动和非阻塞的IO接口,Nodejs有着很好的性能,同时也带来了编码方式的变更。随处可见的匿名函数和回调函数看起来让人不太舒服,庆幸的是有一些有效的方法能在很大程度上缓解这个问题,推荐一篇文章给大家(http://callbackhell.com/),该文章对此做了很好的整理总结,希望能有所帮助。

3.2. Express

和Java Web的Filter类似,Express中间件也是链式的处理请求,一个典型的中间件如下:

function(request, response, next) {...return next();
}

衔接各个中间件的则是next() 回调函数,如果中间件没有把内容输出到response 中,则必通过回调把请求交给下一个中间件处理。一般而言回调函数只能调用一次,多次调用可能产生异常;不调用则请求得不到响应,占用宝贵的链接资源和内存空间。

麻烦之处在于,Node中充斥着各种回调和匿名函数,使得next() 非常容易被遗忘或是错误的调用。这个目前貌似没有很好的解决办法,只能靠开发的经验和测试,一个好的习惯是尽可能的在调用回调后就立即返回return next(); ,这个可以有效的避免多次调用的问题。

自己动手——实现 Dustjs 中间件相关推荐

  1. 定义咯一个枚举变量枚举变量怎么打印出来 linux c,以C语言的字符串形式输出枚举变量...

    2014年11月13日15: 17: 20 haifeilang阅读次数: 6295 每个枚举常量都对应一个整数. 很多时候它可以像整数一样使用. 每个人都知道这一点:但是,如果要打印枚举变量名称的字 ...

  2. Redis源码学习(20),学习感悟

      最近学习Redis源码也有半个月的时间了,有不少收获也有不少感悟,今天来好好聊聊我学习的感悟. 1 发现问题   人非圣贤孰能无过,只要是人难免会犯错,回顾我之前的学习历程,其实是可以发现不少的问 ...

  3. 自己动手写一个分库分表中间件(三)数据源路由实现

    相关文章: 自己动手写一个分库分表中间件(一)思考 自己动手写一个分库分表中间件(二)数据源定义和分片代理层设计 排查项目中读写分离失效原因 小议 Java 内省机制 注:本文内容暂不涉及事务相关的问 ...

  4. 自己动手写一个服务网关

    什么是网关?为什么需要使用网关? 如图所示,在不使用网关的情况下,我们的服务是直接暴露给服务调用方.当调用方增多,势必需要添加定制化访问权限.校验等逻辑.当添加API网关后,在第三方调用端和服务提供方 ...

  5. 阿里巴巴P9大佬雷卷与中间件小哥重新定义:高段位程序员的学习之道

    每次看到和程序员相关的段子,我都会会心一笑:这是时代的进步啊! 上个世纪,桌面时代 Bacis.6502汇编 一切都是面向对象 Turbo Pascal 信息学奥林匹克竞赛的常用编程语言 那时候能搞编 ...

  6. .net core 一个避免跨站请求的中间件

    前提: 前几天看到博客园首页中有这么一篇文章:跨站请求伪造(CSRF),刚好前段时间自己一直也在搞这个东西,后来觉得每次在form表单里添加一个@Html.AntiForgeryToken,在对应的方 ...

  7. Python Web开发:开发wsgi中间件

    本文参考了: github.com/alanctkc/ws- Youtube : Creating WSGI Middleware 上篇文章简要提到:wsgi 规范中的 app 是一个可调用对象,可以 ...

  8. 学习 redux 源码整体架构,深入理解 redux 及其中间件原理

    如果觉得内容不错,可以设为星标置顶我的公众号 1. 前言 你好,我是若川.这是学习源码整体架构系列第八篇.整体架构这词语好像有点大,姑且就算是源码整体结构吧,主要就是学习是代码整体结构,不深究其他不是 ...

  9. 一年增加1.2w星,Dapr能否引领云原生中间件的未来?

    作者 | 敖小剑 Dapr 将引领云原生时代应用和中间件的未来. Dapr 是由微软发起的云原生开源新项目,在今年 2 月份刚刚发布了 v1.0 正式版本.虽然推出至今不过一年半时间,但 Dapr 发 ...

最新文章

  1. 07.LoT.UI 前后台通用框架分解系列之——强大的文本编辑器
  2. 你负责人工智能哪部分?人工那部分:知识图谱的构建主要靠人工还是机器?...
  3. 2010最后一篇:使用PyQt4开发的一个开源小程序QaoBa
  4. 《计算机组成原理》第05章在线测试
  5. 内附PPT下载 | 阿里云资深技术专家 陈长城:一站式数据管理DMS及最新解决方案解读
  6. navicat创建计算列_Tableau Part 9 计算字段amp;粒度聚合比率amp;表计算
  7. 如何在mfc主对话框中再显示子对话框_win10扩展显示器设置方法
  8. 自用shell命令搜集
  9. linux版Nacos安装、集群配置
  10. linux —— ubuntu 初次安装问题
  11. java 调用net remoting_获取 org.springframework.remoting.RemoteAccessException: 在进行调用时无法访问远程服务?...
  12. 在线搜索全网音乐支持歌曲外链下载等源码[免费开源]
  13. 矩阵求导法则,梯度求导方式
  14. cad画多段线时不显示轨迹_为什么CAD绘图编辑拖动时看不到预览效果?
  15. 基于java的高速公路收费系统——计算机毕业设计
  16. android studio的旅游APP的开发和设计
  17. matlab绘制动画保存
  18. Android 12源码单手模式
  19. PADS Layout添加工艺边和Mark的方法和步骤
  20. Echarts可视化MySQL数据

热门文章

  1. Marty Cagan:怎样寻找出色的产品经理
  2. git-flow工作流说明
  3. Spring源码分析【3】-SpingWebInitializer的加载
  4. Gin源码解析和例子——路由
  5. 一种使用GDI+对图片尺寸和质量的压缩方法
  6. Windows上通过VLC播放器搭建rtsp流媒体测试地址操作步骤
  7. 【SVN】svn“E155017工作副本的参考文件损坏、E200014文件校验和不匹配”的解决方法
  8. 【视频】对RTSP抓包,分析通讯流程
  9. valgrind概述及错误分析
  10. java多线程工具类_Java多线程系列之:线程的并发工具类