博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
自己动手——实现Dustjs中间件
阅读量:5894 次
发布时间:2019-06-19

本文共 4538 字,大约阅读时间需要 15 分钟。

hot3.png

原文地址:

是我个人比较喜欢的一个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();        }      });    });  };};

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

这里隐含的一个约束是同一个页面不能引入同名的模版,这会导致冲突,有必要时可以在模版文件命名时加上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有着很好的性能,同时也带来了编码方式的变更。随处可见的匿名函数和回调函数看起来让人不太舒服,庆幸的是有一些有效的方法能在很大程度上缓解这个问题,推荐一篇文章给大家(),该文章对此做了很好的整理总结,希望能有所帮助。

3.2. Express

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

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

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

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

转载于:https://my.oschina.net/joshuazhan/blog/280829

你可能感兴趣的文章
哈希切片(Hashed Sharding)
查看>>
Android基础:翻页控件Viewpager的使用方法,viewpager+fragment混合使
查看>>
工厂模式学习心得
查看>>
JAVA对象实例化过程研究笔记(二)
查看>>
ModelState验证部分属性
查看>>
xcode 插件失效 解决办法
查看>>
可见面判别算法---区域细分算法
查看>>
ansible安装配置
查看>>
清理恢复文本框的默认值
查看>>
【原创】如何在vim中使用tab进行python代码补全
查看>>
Struts秘籍之起式:第1.3式:迁移至Struts 1.1
查看>>
绿色PLSQL/Developer搭配Oracle精简客户端使用
查看>>
ViewPager Banner(广告墙)
查看>>
程序员是如何一步步走向平庸的?
查看>>
Spring Cloud 入门教程(二): 服务消费者(rest+ribbon)(Greenwich.RELEASE)
查看>>
iOS开发20:Navigation Bar的简单设置
查看>>
iOS开发24:使用SQLite3存储和读取数据
查看>>
GMF树形布局 2 实现展开/折叠
查看>>
Cocos2dx 2.0x Touch事件
查看>>
php判断是否登录
查看>>