jade效率基准测试&预编译&nodejs工作流
发布在全端工作流2014年6月19日view:6881
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

jade效率基准测试&预编译&nodejs工作流

本文会提到jade的执行效率基准测试, 讲解提升jade执行效率的方法—预编译, 以及如何将优化应用到持续开发中.

jade是TJ大神的作品, 和EJS,stylus,mocha,co,koa,express,node-convas … 一样. So…

I use like 300 of his open source projects , I feel like I owe him money or something. — How is TJ Holowaychuk so insanely productive?

秉承生命在于折腾的至理名言, 我在自己的blog的开发过程中也将模板引擎从EJS切换到了简洁优雅的jade. 如果有同学遇到类似的模板引擎迁移的需求,可以尝试html2jade, google一下会有两个工具可用,不过用下来的结果是都不那么得心应手,trust me, 前段时间和同事一起把前端html页面用这俩工具都转到jade, 转完之后又全部过了一遍才再编译出正常的html.一方面有语法解析的错误,另一方面也有过于老时的语法混杂在其中.工具就是工具,能解决大部分的重复劳动就不错了.

从多方听说jade效率不够好,在自己的blog换用jade做模板引擎之后,个人感觉慢了不少,但究竟是不是真的慢了,又慢了多少,得”跑分”才知道.

下面就使用OSX自带的apache bench来测试一下本机以及vps生产环境(那是还是便宜的位于米国的buyvm)中的模板执行效率.具体使用情况是:不启用cluster, mongoDB中有四篇blog, 获取的首页会从数据库中读取四篇post的基本信息和摘要(markdown原文),然后循环渲染出一个post list. 本机中的jade和之前使用的ejs逻辑基本一致, 使用方法也是最原始的res.render(page, data), 这次的测试算是两种模板引擎的效率的对比. 而对vps中jade的测试则是看看jade在实际的网络环境中的表现.

ab -n 1000 -c 100 http://localhost:3000/

1000个请求, 分10次发送, 并发为100.

下面是三次运行bench的结果, 分别是本地jade, server jade和本地ejs.

本地jade 本地jade

本地ejs 本地ejs

server jade server ade

可见jade的效率确实是略低, 尤其是在它本身的功能限制下: 不支持动态过滤器, 即不能给filter传递变量.为实现在页面中渲染markdown摘要/正文的功能, 只好把方法当做data的一个属性传了过去,然后在jade文件内访问方法,处理变量.

By the way, 这里有nodejs支持的模板引擎feature的对比列表: node-templates.

这么大的差距让笔者都怀疑是不是应该换回ejs. 但是受简洁优雅的语法诱惑, 不换! 赶紧寻找提升jade执行效率的方法.

jade api中介绍了可以将jade文件编译成js function, 然后在处理请求时将查询数据传给这个function, 生成html串.

jade.compile(source, options): Compile some jade source to a function which can be rendered multiple times with different locals.

将生成的html string res.end出去, 速度应该是比较快的, 下面是本机benchmark:

预编译的本地jade 预编译的jade

Wow~ 效率可以和高效的ejs模板引擎媲美了~

把jade预编译融入到nodejs的程序中, 就需要在启动程序时读取jade文件, 编译成function, exports出来, 在程序处理请求,获得data后传给编译的function,生成html, 然后response出去. 具体的实现可以按照个人喜好,甚至可以直接替换掉res.render方法. 读者感兴趣的话可以去到我的blog的实现render功能的代码看看我的实现方法.

下面说说怎么在”持续开发”中使用预编译.

上篇讲到gulp实现livereload很方便, 监控文件实时刷新so easy. jade的基本应用(直接用res.render发送页面和数据)也可以和gulp配合.但到了这里的场景,很明显不适用了: jade pre-compile和nodejs程序的运行紧紧的绑在了一起, 响应请求是从运行环境内存里调用compile好的方法来生成html.

个人认为, 在实际的应用场景中解决实际问题的能力, 就是大牛们所说的”工程能力”.

我想出的满足当下场景下需求(在使用了预编译的同时实现实时刷新)的办法是: nodejs中fs.watch监控jade文件进行re-compile + gulp中watch文件然后延时触发lr.changed().

nodejs程序

这负责预编译jade的文件中列出所有会在response中发送的页面,然后读取文件,预编译,将编译出来的function导出, 下面是伪代码:

$coffeescript$
// 需要预编译的页面
pages =
  index: "index.jade"
  photo: "photo.jade"
  post: "post.jade"
  dashboard: "dashboard.jade"
  404: "error/404.jade"
  500: "error/500.jade"

// 最后会导出的render对象
render = {}
// 负责preCompile的函数
preCompile = (filePath, pageName) ->
  fs.readFile filePath, (err, jadeStr) ->
    if err
      logger.error "read jade file err: #{filePath}"
    else
      compiledJade[pageName] = jade.compile(jadeStr, compileOption)
      render[pageName] = (res, data) ->
        html = compiledJade[pageName](data)
        sendPage res, html

至于批量预编译,可以用朴灵大大的EventProxy或者其他异步流程控制库,甚至自己简单的实现,来保证所有任务都完成后在终端输出个success的消息.

下面判断环境为dev时添加fs.watch,然后重新预编译.nodejs的require机制和js的引用类型特性保证别的脚本文件引用导出的render对象时,这里进行修改的也是同样的render对象.

$coffeescript$
if env === 'dev'
  logger.info "developing..."
  fs.watch viewPath ->
    console.time "\tre compile jade views"
    rePreCompile()

fs.watch: Watch for changes on filename, where filename is either a file or a directory.

程序启动时预编译,文件变更时重新编译 fs.watch监控编译jade

gulpfile需要做的设置

gulpfile只需要在watch任务中添加jade视图文件的watch, 然后在核实的时间触发lr.changed:

$javascript$
// watch jade
gulp.watch( Conf.jade.watch, function( file ){
    // jade file 没有在页面上的映射, 所以会导致全面刷新
    // timeout是为了给app中jade编译留时间
    setTimeout( function(){
        lr.changed( file.path );
    }, 800 );
});

800ms的时间设置是多次监控了rePreCompile()执行耗时(一般为400ms以下)后定下的时间,因为页面比较少,所以不会有多少浮动.当然如果文件变多或者想更精准的控制触发的时机的话,干脆可以专门设置这么一个jade文件,gulp任务中watch这个特殊的文件,而在nodejs程序中每次rePreCompile完成后改变以下这个小文件.变通的办法多的是,猿们的想象力是无穷的~

以上代码都来自我的blog项目,欢迎大家去查阅代码,欢迎拍砖~

到这里就把所有的技术/技巧讲完了, 下面说一说”思想”上的东西. 这里的优化是建立在”简单的个人blog”的项目基础上的, 我开始搭建blog的时候就是用的最原始的expressjs, 没有像”nodejs最佳实践”中描述的那样:用ningx处理静态资源,node只做数据处理. 要是为了最优的话那可以做的优化就多了去,单是内存数据缓存就会提供特别大的提升. 在特定的量级用最合适的技术来实现,这是前辈同事说过的超理性的话. 我牢记在心,也希望能有更多的同学记住, 然后坚持践行, 把空出来的时间用来做更有意义的事,比如陪身边的朋友, 比如写技术文分享出来共同进步.

OK, that’s all~

评论
发表评论
5年前

@幽谷疾风 不适合团队开发… 体现在哪里? 我所在的上一个团队和现在的团队, 都在用jade啊~

5年前

通过Chrome开发者查看jade编写的页面时等待时间异常的长,在排除服务器的因素后确定了是每次打开编译jade的原因。jade加上预编译用起来确实爽,但是不适合团队开发是它的硬伤。

5年前

通过Chrome开发者查看jade编写的页面时等待时间异常的长,在排除服务器的因素后确定了是每次打开编译jade的原因。jade加上预编译用起来确实爽,但是不适合团队开发是它的硬伤。

5年前

@题叶 文章并没有要对比所有模板引擎效率的意思,只是笔者在做自己的小项目时经历了ejs -> jade的迁徙, 对比了他俩的效率. EJS在同样有cache选项,所以肯定是可以缓存编译后的拼接字符串的function的,所以也有预编译.

之所以没跟doT和handlerbars比较,还因为看了node-template列出来的所有模板引擎的语法后,觉得还是jade最爽.所以ejs和jade之外的模板都没有去尝试,也就没有对比一说了.

诚然doT号称最快,但还是那句话,在指定的量级做最合适的事.没有这么高的速度要求,为何要自己找麻烦写那么多<tagname></tagname>呢~

5年前

预编译以后的 Jade 应该和其他预编译的比如 doT 或者 Handlebars 比才靠谱吧.. 另外 EJS 是否能预编译?

WRITTEN BY
chenllos
志向远大的前端~
TA的新浪微博
PUBLISHED IN
全端工作流

前端/全端工程师的高效工作流程

我的收藏