Hello World
Spiga

使用Node.js编写Shell脚本,暨Jscex 0.6.5版本发布

2012-06-19 00:12 by 老赵, 4425 visits

昨天不得不花时间做了点保护博客阅读体验的事情,但其实这篇才是我真正想写的。上个星期在香港出差,晚上的活动大都是喝酒,回到酒店便借着些许酒劲改进Jscex。如今虽然Jscex的开发工作并没有详细的时间计划,但我正在使用GitHub的Issues页面记录需要制作的任务点,因此每天都是朝着目标逐步前进的。按照计划,Jscex的0.6.5的主要目标是对Jscex的模块机制进行改进,统一辅助方法,并使用Node.js重新编写发布脚本。这些工作的目的都是为接下来的0.7.0版本作准备,它将会是Jscex在项目功能与质量,以及专业性上有重大突破的版本。

Jscex的模块化划分十分细致,目前正式对外发布的也已经有六个模块(还有一个是命令)。模块化的目的是为了选择性的加载,例如在前端开发过程时需要加载所有的模块,而真正部署时则无需最大的两个模块:JIT编译器及解析器(前者依赖后者),这既照顾到了JavaScript编程体验,也考虑到前端部署时的尺寸。但是模块化便对模块管理提出了要求,这在Node.js平台下问题不大,因为NPM自带包管理功能,但在浏览器上便没有那么好的支持了。例如,模块依赖时还会涉及到版本,版本过高或者过低都是问题,如何在模块没有加载,或是加载了错误的版本时清晰地告诉用户,这便是0.6.5版本中想要解决的问题。此外,Jscex还直接支持不同的包加载环境(如AMD环境,之前我也谈过这方面的单元测试),这也是模块机制的重要责任。有了核心模块机制以后,各模块只需要注册自己的信息以及初始化方法,之前提过的错误提示,包加载环境支持等功能,便可以一并使用了。之后如果需要修复问题,或提供更多的功能,也只需要修改这个核心模块机制——简单地说,其实就是DRY原则。

更重要的一点是,这个各个模块在使用这个模块机制的时候,其实已经提供了自己的元信息,例如模块名称,当前版本,它所依赖的模块及其版本等等。换句话说,在发布这些脚本的时候,我们完全可以直接读取这些信息,而无需额外的配置文件。因此,我也使用Node.js替换了原来的Shell编写发布脚本。使用Node.js来代替Shell脚本在某些情况下的确会更为方便,对于一些常见逻辑表达(条件判断,循环等等)或是操作(文件处理,字符串分析等等)来讲,JavaScript语言的可读性无疑会比Shell高很多(因此也有很多人使用Ruby或Python来代替Shell脚本),对于熟悉JavaScript的同学来说则更不用说了。

大量使用Node.js来编写脚本并不如想象中的麻烦,它受JavaScript异步特性的影响并不大,这是因为Node.js标准库里包含了许多同步的方法,使用这些同步方法便可以完成绝大部分工作了,例如:

"use strict";

var path = require("path"),
    fs = require("fs"),
    utils = require("../lib/utils"),
    Jscex = utils.Jscex,
    _ = Jscex._;

var devDir = path.join(__dirname, "../bin/dev");
var srcDir = path.join(__dirname, "../src");

if (path.existsSync(devDir)) {
    utils.rmdirSync(devDir);
}

fs.mkdirSync(devDir);

var coreName = "jscex-" + Jscex.coreVersion + ".js"
utils.copySync(path.join(srcDir, "jscex.js"), path.join(devDir, coreName));
console.log(coreName + " generated.");

var moduleList = ["parser", "jit", "builderbase", "async", "async-powerpack"];

_.each(moduleList, function (i, module) {
    var fullName = "jscex-" + module;
    var version = Jscex.modules[module].version;
    var outputName = fullName + "-" + version + ".js";
    utils.copySync(path.join(srcDir, fullName + ".js"), path.join(devDir, outputName));
    console.log(outputName + " generated.");
});

以上便是发布开发版Jscex的脚本,可见其中每一步IO操作,例如判断目录是否存在,删除/创建目录以及复制文件,都使用了同步的版本。不过,Node.js并不能够完全代替Shell脚本,因为Shell脚本有其重要的武器:管道。试想,你如何使用JavaScript来实现下面这行Shell脚本所做的事情?

cat /data/logs/run.log | grep 'node' | grep -v 'innode' | awk {'print $2'} | xargs sudo kill -9

因此,有时候我们不可避免要在Node.js中调用Shell脚本。同理,我们总会有需要调用外部程序的时候,例如,在发布生产环境的Jscex时,我需要使用Google Closure Compiler来压缩脚本体积,这就需要使用exec来执行外部命令。可惜的是,exec方法没有同步的版本,因此我们必须使用回调函数来处理结果,这又会再次陷入回调函数的泥潭。不过幸好我们有Jscex:

var fromCallback = Jscex.Async.Binding.fromCallback,
    exec = require('child_process').exec,
    execAsync = fromCallback(exec, "_ignored_", "stdout", "stderr");

var buildOne = eval(Jscex.compile("async", function (module) {
    var fullName, version;
    if (!module) {
        fullName = "jscex";
        version = Jscex.coreVersion;
    } else {
        fullName = "jscex-" + module;
        version = Jscex.modules[module].version;
    }

    var inputFile = fullName + ".js";
    var outputFile = fullName + "-" + version + ".min.js";

    var command = _.format(
        "java -jar {0} --js {1} --js_output_file {2} --compilation_level SIMPLE_OPTIMIZATIONS",
        gccPath,
        path.join(srcDir, inputFile),
        path.join(prodDir, outputFile));

    utils.stdout("Generating {0}...", outputFile);

    var r = $await(execAsync(command));
    if (r.stderr) {
        utils.stdout("failed.\n");
        utils.stderr(r.stderr + "\n");
    } else {
        utils.stdout("done.\n");
    }
}));

以上代码片段出自生产环境的Jscex发布脚本,可见有了Jscex之后,“阻塞”式地exec外部调用也完全不是问题。可以说,使用Jscex,可以基本解决Node.js代替编写Shell脚本的编程习惯或是风格问题,也欢迎大家多多尝试这种方法,并多多反馈,有问题及时发布在GitHub的Issues页面中。

至于Jscex的0.7.0版本,目前的计划是使用一个合适的JavaScript语法分析器来替换并统一UglifyJS和Narcissus的分析器。UglifyJS的分析器提供的信息太少,而Narcissus则实现地十分不靠谱,例如它连\r\n这种换行符都不支持,此外它还自做主张地将module作为关键字,这对来说Node.js是个很大的麻烦,因此其实目前Jscex的预编译器使用的是经过少许修改的Narcissus语法分析器。在0.7.0版本中,我希望能从语法分析器中得到更多信息,这样便可以引入Source Map,更进一步地支持调试。此外,在改写Jscex的过程中,详细的单元测试自然是必不可少的。

正像我一开始说的那样,Jscex的0.7.0版本将会在项目功能与质量,以及专业性上有重大突破。这么做也不枉已经有600人在GitHub上关注着Jscex:

在Node.js的模块列表上,流程控制分类有80余个项目。其中关注人数最多的是async(2444人),其次是Step(862人),第三便是Jscex

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

21 条回复

  1. metaboy
    122.85.56.*
    链接

    metaboy 2012-06-19 00:31:41

    先睹为快! 不小心从微薄就链过来了!呵呵

  2. Cary
    128.88.255.*
    链接

    Cary 2012-06-19 09:45:30

    .net 程序员大都不会powershell

    这个不知道哪来的结论,有没有调查过? 我经历了很多项目,多数。net程序都会。

  3. aysun168
    219.141.211.*
    链接

    aysun168 2012-06-19 14:09:45

    http://jscex.info/zh-cn/manuals/开发指南的链接大部分不可用

  4. 老赵
    admin
    链接

    老赵 2012-06-19 14:12:05

    @aysun168

    嗯,现在只是空链接,文档需要逐渐补充。

  5. 躺着读书
    183.62.221.*
    链接

    躺着读书 2012-06-19 14:31:21

    老赵博客快变成网易新闻了…… 向来不看内容 直接看评论……而且要点“点此显示”……

  6. JamesYing
    116.228.133.*
    链接

    JamesYing 2012-06-25 14:03:32

    老赵,你的Jscex能写个入门的教程不,看了几篇还是不太懂,必须跟node.js一起来使用的吗?

  7. 老赵
    admin
    链接

    老赵 2012-06-25 15:38:13

    @JamesYing

    你肯定没看文档,显然浏览器也可以的,其实文档写的足够入门了……

  8. skhse
    169.145.89.*
    链接

    skhse 2012-07-23 19:12:34

    你现在几个人在开发Jscex?

  9. 老赵
    admin
    链接

    老赵 2012-07-24 10:16:06

    @skhse

    就我一个,找不到别人……

  10. L.YK
    202.22.249.*
    链接

    L.YK 2012-07-24 23:09:27

  11. 老赵
    admin
    链接

    老赵 2012-07-26 00:46:49

    @L.YK

    你其实连Jscex是干什么都还没有搞清楚……Jscex又不是这个函数的替代品,是为了让你更方便地使用这个函数。就这么说吧,你就在循环里使用下带回调的exec函数吧。

  12. 4k
    205.156.160.*
    链接

    4k 2012-09-10 17:08:40

    点此显示 的内容,果然很费眼,雾蒙蒙的~

  13. sunnycute
    115.236.164.*
    链接

    sunnycute 2012-10-26 10:39:39

    大量使用Node.js来编写脚本并不如想象中的麻烦,它受JavaScript异步特性的影响并不大? 确定?

  14. 老赵
    admin
    链接

    老赵 2012-10-31 10:02:00

    @sunnycute

    因为大量接口都有同步的版本,少量异步接口可以用Wind.js完成……

已自动隐藏某些不合适的评论内容(主题无关,争吵谩骂,装疯卖傻等等),如需阅读,请准备好眼药水并点此登陆后查看(如登陆后仍无法浏览请留言告知)。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我