自己动手——实现 Dustjs 中间件
Dustjs是我个人比较喜欢的一个JS模版引擎,原因有两个,一是,同时支持客户端和服务端渲染,模版编译成JS后使用,性能好;二是,有大公司的支持,Linkedin有专门的Dustjs版本(本文所说的都是该版本),而且经过线上考验。
关于Dustjs本文不再赘述(可参看文档),直接进入正题。
- 为什么要写一个中间件
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(); ,这个可以有效的避免多次调用的问题。
相关文章:
如何利用离散Hopfield神经网络进行高校科研能力评价(2)
如何利用离散Hopfield神经网络进行高校科研能力评价,应用部分

yyyy-MM-dd HH:mm:ss和yyyy-MM-dd hh:mm:ss
做项目,需要24小时制式的时间,误用了yyyy-MM-dd hh:mm:ss,导致出现一个很难发现的bug,现记录下,方便以后查阅。 yyyy-MM-dd hh:mm:ss 12小时制 yyyy-MM-dd HH:mm:ss 24小时制

Asp.Net开发架构设计(二)
上回说到,我们配置了一下UnityConfig层,在这个层中定义了一个IContainerAccessor的接口和一个返回IUnityContainer类型的方法,这个方法的主要作用就是把Service层中的接口类和Business层中的接口实现类装配到UnityContainer中并返回ÿ…
Matlab与线性代数 -- 稀疏矩阵的创建
本文详细介绍了在Matlab中创建稀疏矩阵的三种方法。

uva 10491 Cows and Cars
https://vjudge.net/problem/UVA-10491 题意: a头牛,b辆车,每扇门后面都有一头牛或一辆车 开始选手选择一扇门 然后主持人打开c扇有牛的门(选中的除外) 然后选手换一扇门 问最后选手选的门后面是车的概率 开始选牛的概…
ssh免密码登录的原理
工作第一天,就需要登录各种服务器,免密码登录无疑能大大的提高工作效率。以前只知道配置,今天了解了下原理。 免密码登录原理 图解,server A免登录到server B: 1.在A上生成公钥私钥。 2.将公钥拷贝给server B,要重命…

mysql-cluster 安装配置
mysql-cluster免编译包下载:https://dev.mysql.com/downloads/file/?id469881 新版本支持只把索引和用到的数据加载到内存,而不是老版本的整个数据库都得加到内存中wget https://cdn.mysql.com//Downloads/MySQL-Cluster-7.5/mysql-cluster-gpl-7.5.6-l…

ExtJs学习笔记(5)_Ajax示例
ExtJs对于Ajax的使用非常简单,看下面的代码: 1.Html页: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns"http://www.w3.o…
Matlab与线性代数 -- 稀疏矩阵的图形显示
本文详细说明了Matlab中稀疏矩阵可视化的过程。

浏览器及时感知服务端数据变化的方式
需求 在公司,有一个需求,是浏览器实时获取服务端数据变化,然后根据变化做相应的动作。需求场景如下:手机端扫描二维码,然后获取待取件订单列表,点击取件,进行取件,同时远端打印机打…

拥抱Node.js 8.0,N-API入门极简例子
本文摘录自《Nodejs学习笔记》,更多章节及更新,请访问 github主页地址。欢迎加群交流,群号 197339705。 N-API简介 Node.js 8.0 在2017年6月份发布,升级的特性中,包含了N-API。编写过或者使用过 node扩展的同学&#x…
什么是标记符控制的分水岭算法
本文首先指出传统分水岭算法的不足,然后介绍了标记符控制的分水岭算法,最后通过实例演示了该算法在图像分割中的应用。该微信图文由安晟提供。

.NE 后退刷新验证码
Response.Buffer true; Response.Expires -1; Response.ExpiresAbsolute DateTime.Now.AddDays(-1); Response.Expires0; Response.CacheControl "no-cache"; 转载于:https://www.cnblogs.com/lmjob/archive/2008/09/01/128096…
intellij打开工程在每个java文件上有个红色的无效符的解决办法
说明该类不是可编译文件。在project Structure中(快捷键ctrlaltshifts)选Modules将你带红圈的文件添加上,将你带红圈的文件夹设为Sources。一般是src文件夹,点Sources文件夹,然后点击OK。设置为可编译文件再去工程中看…

[zt] petshop4.0 详解之三
三、PetShop数据访问层之消息处理在进行系统设计时,除了对安全、事务等问题给与足够的重视外,性能也是一个不可避免的问题所在,尤其是一个B/S结构的软件系统,必须充分地考虑访问量、数据流量、服务器负荷的问题。解决性能的瓶颈&a…

windows下Bullet 2.82编译安装(Bullet Physics开发环境配置)
平台:Win7,VS2010 1. Bullet库的组织 下图是Bullet_User_Manual中的截图: 从中可见,Bullet的LinearMath(线性数学模块),其上是BulletCollision(碰撞检测模块),…
Matlab与线性代数 -- 寻找矩阵的非零元素
本微信图文详细介绍了Matlab中find函数的用法。

java重新回顾
很不幸,自己的移动硬盘坏掉了,之前自己所做的学习笔记都没了,大概有300多篇,是大学学习java的时候整理复习的,这样的损失是巨大的,这将花费我更多的时间重新去整理,但是事实已经是这样的了。 基…

BestCoder 1st Anniversary ($) 1002.Hidden String
Hidden String Accepts: 437 Submissions: 2174 Time Limit: 2000/1000 MS (Java/Others)Memory Limit: 262144/262144 K (Java/Others)问题描写叙述今天是BestCoder一周年纪念日. 比赛管理员Soda有一个长度为n的字符串s. 他想要知道是否能找到s的三个互不相交的子串s[l1..r1],…

动态生成GridView时,加入DataKeyNames属性,回调时出错解决方法
早上看到有位同学问关于自动生成GridVie并且添加DataKeyNames属性后 回调页面时,出现错误异常.下面是我实现的方法。注意一点。就是数据绑定的时间。 要是先绑定,再添加到div1中,回调页面时,就会出错。 页面代码: <…
如何制作风格迁移图?
本微信图文介绍了实现风格迁移图的基本原理并在此基础上制作了效果展示图。

leetcode--链表的设计--python
leetcode--链表的设计--python题目题目详情示例提示解题代码代码运行结果体会题目 设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向…

从空间数据库中删除所有拓扑对象
/// <summary> /// //从空间数据库中删除所有拓扑对象 /// </summary> /// <returns></returns> public bool DeleteALLTopolgyFromGISDB() { bool rbc true; try { …
Altium Desgner软件,PCB设计中铺铜的作用
PS原文出自http://mp.weixin.qq.com/s/5mLNXzCDm1hGOXiKNE8Ddg 问1:为何要铺铜? 答:一般铺铜有几个方面原因。 1、EMC.对于大面积的地或电源铺铜,会起到屏蔽作用,有些特殊地,如PGND起到防护作用。ÿ…
如何利用自组织竞争网络进行患者癌症发病预测
如何利用自组织竞争网络进行患者癌症发病预测 数据下载: http://download.csdn.net/detail/lsgo_myp/9711473

leetcode--反转链表--python
文章目录题目题目详情示例提示解题代码代码运行结果体会题目 题目详情 反转一个单链表。 示例 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL提示 你可以迭代或递归地反转链表。你能否用两种方法解决这道题? 解题代码 …

非递归一次性加载分类数据到TreeViw
不是经常用,发现再次用到时又要再写一遍,这次花点时间记录一下 代码:------------------------------------- private void InitView() { DataView dv CommodityClassBLL.Query().Tables[0].DefaultView; if (dv.Count > 1) { dv.Sort …
什么是SESSION?(二)
本篇图文讨论了SESSION多服务器共享问题以及SESSION操作的效率问题。本篇微信图文由钟锦提供。

leetcode--两数之和--python
文章目录题目题目详情示例解题代码代码运行结果体会题目 题目详情 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 你可以假设每种输入只会对应一个答案。但是,你不能重复…

关于 OpenIdConnect 认证启用 HTTPS 回调 RedirectUri 不生效问题
在搭建 IdentityServer 服务端后,我们尝试使用了 OIDC(OpenID Connect) 的中间件来代替了原先的 Session 系统认证方式,起初采用的是 HTTP 协议,一切都没有什么问题,最近启用全站 HTTPS 后,发现登陆会跳转到 HTTP的页面…