文档章节

自己动手——实现Dustjs中间件

joshuazhan
 joshuazhan
发布于 2014/06/17 20:36
字数 1553
阅读 2194
收藏 41
点赞 2
评论 1

原文地址:http://home4j.duapp.com/index.php/2014/06/01/diy-writing-a-dust-middleware.html

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-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();
        }
      });
    });
  };
};

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

<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(); ,这个可以有效的避免多次调用的问题。

© 著作权归作者所有

共有 人打赏支持
joshuazhan

joshuazhan

粉丝 33
博文 18
码字总数 16034
作品 0
杭州
程序员
加载中

评论(1)

在Expressjs4.0中使用dustjs模板引擎

引言 [dustjs][1]是一款js模板,最早由个人开发维护,后来由linkin接手,发展的更加迅速,说实话js模板这块有很多选择,都非常优秀,和paypal的选择一样,最终我使用dustjs作为我的首选模板语...

假正经哥哥 ⋅ 2014/10/19 ⋅ 0

自己动手——让Less支持中文编码

源Blog链接:http://home4j.duapp.com/index.php/2014/05/25/diy-a-less-with-multi-encoding-support.html Node近几年发展得非常迅猛,但还是有些小缺憾,比如对中文编码的支持。由于底层A...

joshuazhan ⋅ 2014/05/26 ⋅ 0

nodejs框架之express

Express介绍 npm提供了大量的第三方模块,其中不乏许多Web框架,比如我们本章节要讲述的一个轻量级的Web框架 ——— Express。 Express是一个简洁、灵活的node.js Web应用开发框架, 它提供一...

笔阁 ⋅ 2015/10/12 ⋅ 0

架构设计:系统间通信(41)——自己动手设计ESB(2)

==================================== (接上文《架构设计:系统间通信(40)——自己动手设计ESB(1)》) 4、Broker Server设计 那么如何赋予ESB中间件原子服务整合、服务路由编排的关键能...

yinwenjie ⋅ 2016/07/26 ⋅ 0

架构设计:系统间通信(40)——自己动手设计ESB(1)

1、概述 在我开始构思这几篇关于“自己动手设计ESB中间件”的文章时,曾有好几次动过放弃的念头。原因倒不是因为对冗长的文章产生了惰性,而是ESB中所涉及到的技术知识和需要突破的设计难点实...

yinwenjie ⋅ 2016/07/21 ⋅ 0

神马是中间件? 只谈概念不谈操作。

先来了解下为什么你要知道中间件? 中间件可以应用于以下情形: 如连接公司 LAN和早期系统、交换两个邮件系统间的信息、支持 web 客户机与数据库服务器交换信息等。 由于标准接口对于可移植性...

konakona ⋅ 2011/10/25 ⋅ 2

蓝色天空/bootstrap-treegrid

功能:依赖bootstrap-table实现treegrid, 来源:参照JS组件系列——自己动手扩展BootstrapTable的treegrid功能 例子做了部分改动 作者:lds2013@163.com 日期:2017年10月5日 修改:2018年4...

蓝色天空 ⋅ 04/24 ⋅ 0

dustjs-helpers 1.7.0 发布

dustjs-helpers 是 dustjs-linkedin 包的额外功能。你可以利用它来编写自己的助手工具和扩展 Dust 模板引擎的功能。 dustjs-helpers 1.7.0 发布,此版本值得关注的更新: 新增两个 behavior...

oschina ⋅ 2015/04/19 ⋅ 0

dustjs-helpers 1.7.3 发布

dustjs-helpers 1.7.3 发布,更新内容如下: #140 Fix logic helpers inside any and none blocks. (@sethkinast) 详细信息请查看发行日志。 下载地址请戳:v1.7.3。 dustjs-helpers 是 dust...

oschina ⋅ 2015/07/29 ⋅ 0

dustjs-helpers 1.74 发布,bug 修复

dustjs-helpers 1.74 发布了,dustjs-helpers 是 dustjs-linkedin 包的额外功能。你可以利用它来编写自己的助手工具和扩展 Dust 模板引擎的功能。 此次更新内容: 修复bug: #146 Fix Node ...

周其 ⋅ 2017/12/10 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

vim编辑模式、vim命令模式、vim实践

vim编辑模式 编辑模式用来输入或修改文本内容,编辑模式除了Esc外其他键几乎都是输入 如何进入编辑模式 一般模式输入以下按键,均可进入编辑模式,左下角提示 insert(中文为插入) 字样 i ...

蛋黄Yolks ⋅ 28分钟前 ⋅ 0

大数据入门基础:SSH介绍

什么是ssh 简单说,SSH是一种网络协议,用于计算机之间的加密登录。 如果一个用户从本地计算机,使用SSH协议登录另一台远程计算机,我们就可以认为,这种登录是安全的,即使被中途截获,密码...

董黎明 ⋅ 47分钟前 ⋅ 0

web3j教程

web3j是一个轻量级、高度模块化、响应式、类型安全的Java和Android类库提供丰富API,用于处理以太坊智能合约及与以太坊网络上的客户端(节点)进行集成。 汇智网最新发布的web3j教程,详细讲解...

汇智网教程 ⋅ 54分钟前 ⋅ 0

谷歌:安全问题机制并不如你想象中安全

腾讯科技讯 5月25日,如今的你或许已经对许多网站所使用的“安全问题机制”习以为常了,但你真的认为包括“你第一个宠物的名字是什么?”这些问题能够保障你的帐户安全吗? 根据谷歌(微博)安...

问题终结者 ⋅ 55分钟前 ⋅ 0

聊聊spring cloud gateway的RedisRateLimiter

序 本文主要研究下spring cloud gateway的RedisRateLimiter GatewayRedisAutoConfiguration spring-cloud-gateway-core-2.0.0.RELEASE-sources.jar!/org/springframework/cloud/gateway/con......

go4it ⋅ 今天 ⋅ 0

169. Majority Element - LeetCode

Question 169. Majority Element Solution 思路:构造一个map存储每个数字出现的次数,然后遍历map返回出现次数大于数组一半的数字. 还有一种思路是:对这个数组排序,次数超过n/2的元素必然在中...

yysue ⋅ 今天 ⋅ 0

NFS

14.1 NFS介绍 NFS是Network File System的缩写 NFS最早由Sun公司开发,分2,3,4三个版本,2和3由Sun起草开发,4.0开始Netapp公司参与并主导开发,最新为4.1版本 NFS数据传输基于RPC协议,RPC...

派派菠菜 ⋅ 今天 ⋅ 0

18.进入编辑模式 vim命令模式 实践

5.5 进入编辑模式 5.6 vim命令模式 5.7 vim实践 5.5 进入编辑模式: i 在当前字符前插入 I 在光标所在行的行首插入 a 在当前字符后插入 A 在光标所在行的行尾插入 o 在当前所在行的下一行插入...

王鑫linux ⋅ 今天 ⋅ 0

阻塞队列(2)--LinkedBlockingDeque底层实现

2.1 LinkedBlockingQueue是什么? 1.1 LinkedBlockingQueue是一个阻塞式的队列,继承自AbstractBlockingQueue,间接的实现了Queue接口和Collection接口。底层以链表的形式保存数据(双向链表,...

yokol ⋅ 今天 ⋅ 0

NFS介绍 NFS服务端安装配置 NFS配置选项

14.1 NFS介绍 14.2 NFS服务端安装配置 14.3 NFS配置选项 NFS介绍 NFS是Network File System的缩写;这个文件系统是基于网路层面,通过网络层面实现数据同步 NFS最早由Sun公司开发,分2,3,4三...

lyy549745 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部