日期:2014-05-16  浏览次数:20307 次

使用Node.js打造前端自动化构建平台

1.前言

最近项目组打算从Apache+PHP环境迁移到Node上,正好刚看完入门资料,想借此练练手,也方便整合先前基于Grunt的压缩合并任务,于是,大幕拉开……

首先,说明下需要完成的任务,也即使用Node所能够带来的好处:

  1. 熟悉的JS操作,从函数的使用,到JSON的处理,以及事件、异步编程,乃是前端所擅长的,移植到Node后,可以减少对原有后端语言(PHP、Java等)的依赖,组内成员容易上手;

  2. 整合打包任务,也即项目构建,包括:Less编译CSS、合并、压缩、JSLint、打包等,后续还包括JSDoc、JSUnit等,借助Node的异步特性,可方便移植到Web平台上,实现自动化构建,从而无需由前端组专门构建;

  3. 结合WebSocket,除项目构建外,也可以打造消息平台,从IM客户端移植到Web上;

  4. ……

Node能实现的远超原先的想象,其所带来的性能也超乎想象,从易用性,到功能完整性,无不略胜一筹。在讲求敏捷开发的时代,不失为优秀的平台。

2.缘由

项目最早的构建是基于Ant,每次变更,都需要上传SVN后,登录SecureCRT手动执行命令更新到静态服务器上;然后后期有了压缩和合并,因为是基于SeaJS,整个合并的过程并没有那么简单,讨论的方案是使用Grunt进行模块的合并和压缩处理。于是,构建过程变成三步曲:执行合并压缩脚本、上传、部署脚本。烦不胜烦!而且在维护多个分支时,容易遗漏或忘记,在每次发测时已发生过不止一次版本不匹配情况。

3.开始

在确定移植到Node后,基于对Node的了解,突然想到Node的机制非常适合将构建过程移到Web上,并且可任务化、图形化、实时化,何乐而不为?做成了,对项目组乃至整个前端组都是大功一件!>_<

于是,立马动工……

入门的过程忽略,主要在于对各种Grunt模块的了解,以及Node的文件操作等。

3.1.导出SVN

因为有现成的工具和命令行可以直接执行SVN更新、导出等操作,所以首选命令行。首先采用spawn直接尝试运行输出,发现能运行ipconfig等命令,却不能执行cd等基本命令,各种搜索后,给出的答案是调用cmd,也因此而了解了调用子进程的四种方式之间的区别(spawn、exec、fork、execFile)。确认能实现后,搜索相关的Grunt模块,组员推荐的是grunt-shell,但有乱码问题,而且无法(至少目前没有找到)解决,然后找到grunt-shell-spawn,但出现无法输出的问题,对比grunt-shell源代码后,想起在grunt官网资料上看到的一段话:

写道
Why doesn't my asynchronous task complete?
Chances are this is happening because you have forgotten to call the this.async method to tell Grunt that your task is asynchronous. For simplicity's sake, Grunt uses a synchronous coding style, which can be switched to asynchronous by calling this.async() within the task body.
Note that passing false to the done() function tells Grunt that the task has failed.?

于是查看源代码,才恍然大悟,问题出在async配置上。

解决输出问题后,同样出现乱码问题,但现象和直接采用spawn类似,便借鉴后者的解决方法移植到模块配置上,并修改了模块源代码。因此顺带了解了iconv-lite模块。

看到屏幕上的输出,甚是欢喜,万事开头难,解决了第一步,已然胜利了大半。

导出SVN任务:

shell: {
    exportSvn: {
        command: 'svn export "<%= config.svn.repoUrl %>" <%= config.path.tmp.svn %> --username <%= config.svn.user %> --password <%= config.svn.password %>',
        options: {
            async: false,
            stdout: function(data) {
                process.stdout.write(iconv.decode(data, 'gb2312'));
            },
            stderr: function(data) {
                process.stderr.write(iconv.decode(data, 'gb2312'));
            }
        }
    }
}

3.2.文件替换

因为项目原先采用的是PHP和SHTML的语法,需要对文件包含等语句进行替换,以符合ejs的语法(也可不替换,但和注释语法混淆),同时,因为Node本身有中间件支持Less的解析,因此可以去除页面上解析Less的js包含语句,于是,采用replace模块实现如下:

replace: {
    shtml: {
        src: ['<%= config.path.dest.views %>/*.shtml'],
        overwrite: true,
        replacements: [{
            from: /<!--#include\s*file="([\w\-\.]+)"\s*-->/ig,
            to: '{{ include $1 }}'
        }]
    },
    less: {
        src: ['<%= config.path.dest.views %>/*.php'],
        overwrite: true,
        replacements: [{
            from: 'stylesheet/less',
            to: 'styl