你的位置:首页 > Java教程

[Java教程]grunt配置太复杂?使用Qbuild进行文件合并、压缩、格式化等处理

上次简单介绍了下Qbuild的特点和配置,其实实现一个自动化工具并不复杂,往简单里说,无非就是筛选文件和处理文件。但Qbuild的源码也并不少,还是做了不少工作的。

1. 引入了插件机制。在Qbuild中称作模块,分为任务处理模块(如合并、压缩等处理)和文本处理模块(如内容添加和替换等处理),一个任务处理模块可以有多个文本处理模块。任务和文本处理模块均可以按指定的顺序执行,可以指定要执行的模块。每个任务的配置可以继承或覆盖全局配置,既保证了简洁,也保证了灵活。2. 文件筛选支持通配符(*和**)和正则表达式,支持排除规则。支持基于文件夹定位。支持文件变动检测,跳过未更新的文件,大大提升处理效率。3. 模块路径和文件夹路径支持绝对路径,支持基于配置文件所在路径(以./开头),支持基于自定义的根目录(以/开头,全局root配置),支持基于程序所在路径( 以|开头)。4. 支持简单的参数引用和函数调用。eg:以下f为文件对象,仅列出部分属性  f: {dir,dest,fullname,filename:"test.js",name:"test",ext:".js",stat:{size:165346}}
    %Q.formatSize(f.stat.size)%  => Q.formatSize(165346) => 161.47KB
    %f.filename.toUpperCase().replace('.','$&parsed.')%  => TEST.parsed.JS5. 提供简单易用的api,以简化插件编写。

 

下面分别介绍每个功能的使用。

文件合并

配置文件位于 build-demo/test 目录,下同。t-error.js 实际并不存在,此为演示异常情况。

 1 module.exports = { 2   root: "../", 3  4   concat: { 5     title: "文件合并", 6  7     dir: "demo/js/src", 8     output: "release/js-concat", 9 10     list: [11       {12         dir: "a",13         src: ["t1.js", "t2.js", "t3.js"],14         dest: "a.js",15         prefix: "//----------- APPEND TEST (%f.filename%) -----------\n"16       },17       {18         dir: "b",19         src: ["t1.js", "t2.js", "t-error.js"],20         dest: "b.js"21       },22       {23         //不从父级继承,以/开头直接基于root定义的目录24         dir: "/release/js-concat",25         src: ["a.js", "b.js"],26         dest: "ab.js"27       }28     ]29   }30 };

js压缩

调用命令行来执行js压缩。error.js 演示js代码异常的情况。现在压缩工具一般都带语法检测,可以方便的定位错误信息。

 1 module.exports = { 2   dir: "../demo", 3   output: "../release", 4  5   cmd: { 6     title: "压缩js", 7     //cmd: "java -jar D:\\tools\\compiler.jar --js=%f.fullname% --js_output_file=%f.dest%", 8     cmd: "uglifyjs %f.fullname% -o %f.dest% -c -m", 9 10     match: "js/*.js",11     exclude: "js/error.js",12 13     before: "//build:%NOW%\n"14   }15 };

文件格式化

任务模块(format.js)并不直接执行html和css的格式化,而是调用文本处理模块(replace.js)来执行一些常规替换。

 1 module.exports = { 2   dir: "../demo", 3   output: "../release", 4  5   format: [ 6     { 7       title: "格式化html文件", 8  9       match: "*.html",10       exclude: "**.old.html",11 12       replace: [13         //移除html注释14         [/(<!--(?!\[if\s)([^~]|~)*?-->)/gi, ""],15         //移除无效的空格或换行16         [/(<div[^>]*>)[\s\r\n]+(<\/div>)/gi, "$1$2"],17         //移除多余的换行18         [/(\r?\n)(\r?\n)+/g, "$1"],19         //移除首尾空格20         [/^\s+|\s+$/, ""]21       ]22     },23     {24       title: "格式化css文件",25 26       match: "css/*.css",27 28       replace: [29         //移除css注释30         [/\/\*([^~]|~)*?\*\//g, ""],31         //移除多余的换行32         [/(\r?\n)(\r?\n)+/g, "$1"],33         //移除首尾空格34         [/^\s+|\s+$/, ""]35       ]36     }37   ]38 };

文件同步(复制)

 1 module.exports = { 2   dir: "../demo", 3   output: "../release", 4  5   copy: [ 6     { 7       title: "同步js数据", 8       match: "js/data/**.js" 9     },10     {11       title: "同步图片",12       match: "images/**"13     }14   ]15 };

插件(模块)编写

1. 了解文件对象。每个任务流程可以有多个任务对象(如上文的文件格式化和复制),除文件合并较特殊(姑且称之为list模式,传入的对象均有src属性,可以传入多个文件路径,但不支持通配符和正则表达式),其它都一样(暂称为match模式,支持通配符和正则表达式)。list模式下,每个对象是一个文件对象;match模式下每个文件是一个文件对象。下面是它们的属性。

   1> match模式

 1 { 2   dir,     //文件所在目录 3   destname,  //默认文件保存路径 4   dest,    //文件实际保存路径 5   fullname,  //文件完整路径 6   relname,   //相对于 config.dir 的路径 7   filename,  //文件名(带扩展名) 8   name,    //文件名(不带扩展名) 9   ext,     //文件扩展名10   stat,    //文件状态(最后访问时间、修改时间、文件大小等) {atime,mtime,size}11   12   skip,    //是否跳过文件13   14   //仅当启用重命名时15   rename,   //新文件名称(带扩展名)16   last_dest  //文件上次构建时的保存路径17 };

   2> list模式

 1 { 2   dir,     //文件所在目录(for src) 3   destname,  //文件保存路径 4   dest,    //同destname 5   fullname,  //同destname 6   filename,  //文件名(带扩展名) 7   name,    //文件名(不带扩展名) 8   ext,     //文件扩展名 9   src,     //文件路径列表10   11   skip,    //是否跳过文件12   13   //仅对concat.js生效14   join,    //文件连接字符串15   prefix    //要在合并文件头部添加的内容(concat.js内部支持,不同于文本模块append.js)16 };

2. 提供的api,已注册到全局变量,支持直接调用。

 1 global.Qbuild = { 2   ROOT,    //配置文件所在目录,与config.root不同 3   ROOT_EXEC,  //文件执行路径,即build.js所在路径 4  5   config,   //配置对象 6    7   HOT,     //红色输出,用于print和log,下同 8   GREEN,    //绿色输出 9   YELLOW,   //黄色输出10   PINK,    //粉红色输出11 12   print:function (msg,color), //输出控制台信息,不换行,可指定输出颜色13   log:function (msg,color),  //输出控制台信息并换行,可指定输出颜色14   error:function (msg),    //输出错误信息,默认黄色15 16   //注册模块17   //type:String|Array|Object18   //   String:模块类型 eg: register("concat",fn|object)19   //   Array: 模块数组 eg: register([module,module],bind)20   //   Object:模块对象 eg: register({type:module},bind)21   //module:模块方法或对象,当为function时相当于 { exec:fn } ,若type为模块数组或对象,则同bind22   //bind:文本模块绑定对象(文本模块只在此对象上生效),可以传入一个空对象以注册一个全局文本模块23   register: function (type, module, bind),24 25   //创建路径筛选正则表达式,将默认匹配路径的结束位置26   //pattern: 匹配规则,点、斜杠等会被转义,**表示所有字符,*表示斜杠之外的字符 eg: demo/**.html27   //isdir: 是否目录,若为true,将匹配路径的起始位置28   getPathRegex: function (pattern, isdir),29 30   //获取匹配的文件,默认基于config.root31   //pattern:匹配规则,支持数组 eg:["js/**.js","m/js/**.js"]32   //ops:可指定扫描目录、输出目录、排除规则、扫描时是否跳过输出目录 eg:{ dir:"demo/",output:"release/",exclude:"**.old.js",skipOutput:true }33   getFiles: function (pattern, ops) ,34   //获取相对路径,默认相对于config.dir35   getRelname: function (fullname, rel_dir),36   //获取不带扩展名的名称37   getNameWithoutExt: function (name),38   //设置文件变更 => map_dest[f.destname.toLowerCase()]={src: f.fullname, dest: f.dest}39   setChangedFile: function (f),40   //获取输出路径映射,返回 { map: map_dest, last: map_last_dest }41   getDestMap: function (),42   //确保文件夹存在43   mkdir: function (dir),44   //读取文件内容(f[read_key] => f.text),read_key 默认为fullname45   readFile: function (f, callback, read_key),46   //保存文件(f.text => f.dest)47   saveFile: function (f, callback),48 49   //简单文本解析,支持属性或函数的连续调用,支持简单参数传递,若参数含小括号,[email protected] eg:%Q.formatSize@(f.stat.size,{join:'()'})@%50   //不支持函数嵌套 eg:path.normalize(path.dirname(f.dest))51   //eg:parse_text("%f.name.trim().drop@({a:'1,2',b:'(1+2)'})@.toUpperCase()% | %Q.formatSize(f.stat.size).split('M').join(' M')%", { dest: "aa/b.js", name: "b.js", size: 666, stat: { size: 19366544 } }) => B.JS | 18.47 MB52   //eg:parse_text("%path.dirname(f.dest)%", { dest: "aa/b.js"}); => aa53   parseText: function (text, f),54 55   //执行命令行调用56   shell: function (cmd, callback),57 58   //运行文本处理模块59   runTextModules: function (f, task),60 61   //设置检测函数,检查文件是否需要更新62   setCheck: function (task, check),63 64   //自定义存储操作,文件默认为build.store.json65   store: {66     init: function (callback),  //读取json数据并解析67     get: function (key),68     set: function (key, value),69     save: function (callback)  //保存json数据到文件70   }71 };

3. 任务处理模块格式

 1 module.exports = { 2   //模块类型,即任务属性名称,可以为数组 3   type:"concat", 4    5   //可选,任务初始化时触发 6   //task:任务对象 => config[module.type] 7   init: function (task), 8    9   //可选,文件预处理函数10   check: function (f, task),11   12   //可选,任务处理完毕触发(仅对exec有效)13   after: function (task),14   15   //文件处理函数(针对单个文件)16   exec: function (f, task, callback),17   18   //文件处理函数(针对所有文件),exec和process任选其一,process主要针对特殊情况19   //task.files :文件对象列表,match模式20   //task.list :文件对象列表,list模式21   process:function (task, callback)22 };

关于 exec 和 process,可以参看部分源码实现

//转交给 module.process 处理if (module.process) return fire(module.process, module, task, callback);//针对单一的文件或任务处理//Q.Queue为自定义队列对象,详见 build/lib/Q.jsvar queue = new Q.Queue({  tasks: task.files || task.list,  //注入参数索引(exec回调函数所在位置)  injectIndex: 1,  exec: function (f, ok) {    //在检查文件是否需要更新后进行文件处理    after_check(f, function () {      fire(module.exec, module, f, task, ok);    });  },  complete: function () {    log();    log("处理完毕!", GREEN);    log();    fire(module.after, module, task);    fire(callback);  }});

4. 文本处理模块格式

 1 module.exports = { 2   //模块类型,即任务属性名称,可以为数组 3   type:["before","after"], 4    5   //文本处理函数,通过操作f.text实现内容更新 eg:f.text=f.text+"OK!"; 6   //f:  文件对象 7   //data: 在配置中指定的参数 => task[type] 8   //task: 任务对象 9   //type: 文本模块触发时的类型10   process:function (f, data, task, type)11 };

 

模块示例

1. 任务处理模块

 1 /* 2 * copy.js 文件同步模块 3 * author:[email protected] 4 * update:2015/07/10 16:23 5 */ 6 var log = Qbuild.log, 7   print = Qbuild.print, 8   mkdir = Qbuild.mkdir, 9 10   formatSize = Q.formatSize;11 12 module.exports = {13   type: ["copy", "copy0", "copy1"],14 15   init: function (task) {16     //不预加载文件内容,不重命名文件17     task.preload = task.rename = false;18   },19 20   exec: function (f, task, callback) {21     if (f.skip) {22       log("跳过:" + f.relname);23       return Q.fire(callback);24     }25 26     print("复制:" + f.relname, Qbuild.HOT);27     print(" " + formatSize(f.stat.size));28 29     //确保输出文件夹存在30     mkdir(path.dirname(f.dest));31 32     var rs = fs.createReadStream(f.fullname), //创建读取流33       ws = fs.createWriteStream(f.dest);   //创建写入流34 35     //通过管道来传输流36     rs.pipe(ws);37 38     rs.on("end", function () {39       print("  √\n", Qbuild.GREEN);40       callback();41     });42 43     rs.on("error", function () {44       print("  ×\n", Qbuild.YELLOW);45     });46   }47 };

 1 /* 2 * format.js 文件格式化模块 3 * author:[email protected] 4 * update:2015/07/10 16:23 5 */ 6 var log = Qbuild.log, 7   print = Qbuild.print; 8  9 module.exports = {10   type: ["format", "format0", "format1"],11 12   exec: function (f, task, callback) {13     if (f.skip) {14       log("跳过:" + f.relname);15       return Q.fire(callback);16     }17 18     //log("处理:" + f.relname, Qbuild.HOT);19 20     print("处理:" + f.relname, Qbuild.HOT);21     if (f.rename) print(" => " + f.rename);22     print("\n");23 24     Qbuild.readFile(f, function () {25       Qbuild.runTextModules(f, task);26       Qbuild.saveFile(f, callback);27     });28   }29 };

2. 文本处理模块

 1 /* 2 * replace.js 文本模块:内容替换 3 * author:[email protected] 4 * update:2015/07/10 16:23 5 */ 6 module.exports = { 7   type: "replace", 8  9   process: function (f, data, task, type) {10     if (!data) return;11 12     var text = f.text || "";13 14     Q.makeArray(data).forEach(function (item) {15       var pattern = item[0],16         replacement = item[1],17         flags = item[2];18 19       if (!pattern || typeof replacement != "string") return;20 21       var regex = new RegExp(pattern, flags);22       text = text.replace(regex, replacement);23     });24 25     f.text = text;26   }27 };

代码下载

Qbuild.js 源码+示例代码

写在最后

如果本文或本项目对您有帮助的话,请不吝点个赞。欢迎交流!


海南旅游团报价海南旅游团购优惠海南旅游线路报价海南旅游指南攻略大全海南旅游住哪里比较方便2015清明节华农紫荆花节门票多少钱?华农紫荆花清明节开的怎么样? 2015清明节华农紫荆花节开始了吗?华农紫荆花清明节好看吗? 2015清明节中山哪些景点免费?中山影视城清明节门票价格多少? 2015清明节中山哪里有活动?中山清明节去哪好玩? 土耳其航空公司航线版图再增3站 宝岛盛宴二度来袭广州丽思卡尔顿再推"台湾美食节"[三] 从化过年哪里有花展?2015春节从化花展时间地点? 二月邀你走徽州 品茶触雕享徽菜 普吉岛旅游去哪儿购物好? 武汉周边秋天烧烤地推荐之咸宁刘家桥 普吉岛在哪个国家?在哪里? 澳大利亚旅游注意事项有哪些? 去澳门旅游买什么带回来? 港澳通行证弄丢了怎么办?港澳通行证弄丢了能补办吗? 联通手机卡在香港澳门的漫游费是怎么收的? 香港哪里买电脑好?香港哪里买电脑便宜? AQ12EA5R6CAJME\500 Datasheet AQ12EA5R6CAJME\500 Datasheet 08055U100FAT2A Datasheet 08055U100FAT2A Datasheet SQCAEA0R3BATME Datasheet SQCAEA0R3BATME Datasheet 安顺出发去格鲁吉亚旅游 安顺出发去格鲁吉亚旅游 安顺出发去格鲁吉亚旅游 安顺出发去公主邮轮旅游 安顺出发去公主邮轮旅游 安顺出发去公主邮轮旅游 安顺出发去古巴旅游 安顺出发去古巴旅游 安顺出发去古巴旅游