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

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

时间: 2024-11-03 21:56:45

自己动手——实现 Dustjs 中间件的相关文章

专访曾宪杰:大型网站系统与Java中间件实践

摘要:淘宝近10年来历次技术飞跃的参与者.贡献者和带领者曾宪杰做客了CSDN社区问答栏目,担任第四期的嘉宾,带您了解大型网站系统与Java中间件的实践.在活动开始之前,我们采访到了曾老师,一窥他的技术和人生. 编者按:淘宝技术部总监.淘宝技术委员会Java分会会长曾宪杰将携他的新书<大型网站系统与Java中间件实践>做客我们社区问答栏目,担任第四期的问答嘉宾,届时会接受广大网友的提问,欢迎各位网友前来与淘宝网中间件大牛曾宪杰一起碰撞思想的火花.以下为采访正文:  淘宝技术部总监曾宪杰,他是淘宝

《淘宝技术这十年》读书笔记 (四). 分布式时代和中间件

        前面两篇文章介绍了淘宝的发展历程.Java时代的变迁和淘宝开始创新技术:            <淘宝技术这十年>读书笔记 (一).淘宝网技术简介及来源            <淘宝技术这十年>读书笔记 (二).Java时代的脱胎换骨和坚若磐石            <淘宝技术这十年>读书笔记 (三).创造技术TFS和Tair        这篇文章主要讲述分布式时代和中间件相关知识,包括服务化.HSF.Notify和TDDL.同时里面有我们经常遇见的编

自己动手做一个SQL解释器

自己动手做一个SQL解释器在一些小型的应用中,完全没有必要使用大型数据库软件.自己做一个SQL解释器就能用数据库的方式来管理了.这个解释器,能解释常用的SQL命令.你可以自行添加其他功能. <?phpclass DB_text {  var $conn;  var $classname = "db_text";  var $database;  function on_create() {  }  function connect($database_name) {    $th

自己动手写ASP.NET ORM框架(二):AdoHelper支持多数据库操作的封装(2)

在上一篇文章中已经分析了AdoHelper的部分代码,接下来将继续分析剩余的部分代码,这里分析ExecuteNonQuery方法的实现,代码块1-1: // <summary>//通过提供的参数,执行无结果集的数据库操作命令// 并返回执行数据库操作所影响的行数.// </summary>// <param name="connectionString">数据库连接字符串</param>// <param name="co

用户洞察的秘密武器:阿里中间件ARMS前端监控功能正式上线

近日,阿里中间件(Aliware)旗下的业务实时监控产品(ARMS)推出了前端监控服务.该技术通过对网站页面上动态数据的采集监测和实时反馈,可帮助企业更高效地进行运营决策. 实时获知站点真实情况 前端监控的重要性 随着互联网的高速发展,现在的网络环境千变万化,网站往往会遇到意想不到的情况.因此我们需要了解的更多,如用户实际访问本站点遇到的错误:各个国家.地区的用户访问本站点的真实速度是多少:每个应用内有大量的异步数据调用的成功率有多高等. 如今互联网产品获取流量的渠道越来越多样化,通常是一个后端

数据库相关中间件收录集

数据库中间件 这里主要介绍互联网行业内有关数据库的相关中间件.数据库相关平台主要解决以下三个方面的问题: 为海量前台数据提供高性能.大容量.高可用性的访问 为数据变更的消费提供准实时的保障 高效的异地数据同步 应用层通过分表分库中间件访问数据库,包括读操作(Select)和写操作(update, insert和delete等,DDL, DCL).写操作会在数据库上产生变更记录,MySQL的变更记录叫binlog, Oracle的称之为redolog, 增量数据订阅与消费中间件解析这些变更,并以统

阿里中间件技术专家魏鹏:基于Java容器的多应用部署技术实践

首届阿里巴巴在线技术峰会(Alibaba Online Technology Summit),将于7月19日-21日 20:00-21:30 在线举办.本次峰会邀请到阿里集团9位技术大V,分享电商架构.安全.数据处理.数据库.多应用部署.互动技术.Docker持续交付与微服务等一线实战经验,解读最新技术在阿里集团的应用实践. 阿里巴巴在线技术峰会专题:https://yq.aliyun.com/activity/97峰会统一报名链接:https://yq.aliyun.com/webinar/j

给你机会挑战双十一实时计算,你敢来吗?【阿里中间件性能挑战赛来袭】

快速报名通道,加入挑战,尽情展示你的才华: 赛题赛制:https://tianchi.shuju.aliyun.com/programming/introduction.htm?raceId=231533 大赛介绍:https://tianchi.shuju.aliyun.com/promotion-programming 主办方: About us! 我不会告诉你什么叫高性能,我只会告诉你我们承载了全球巨大的电商流量:我不会告诉你什么叫大数据,我只会告诉你,阿里的数据都会经由我们来创造:我不会

自己动手删除各种病毒遗留文件

很多人说病毒清除掉后留下很多尸体文件,虽然可以删除,但是他们无处不在,一个个干掉实在是太麻烦了. 比如viking留下的_desktop.ini:比如欢乐时光留下的desktop.ini.folder.htt:还比如病毒经常在你的硬盘跟目录下放上属性为系统+隐藏的autorun.inf,以及这个文件里run=字段后面的程序,数不胜数-- 那么如何来干掉这些垃圾?实际上你可以利用搜索功能从"我的电脑"范围内将这些垃圾搜索出来,然后CTRL+A全选,DEL删除!当然要记得搜索时在"