上次简单介绍了下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压缩。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 //简单文本解析,支持属性或函数的连续调用,支持简单参数传递,若参数含小括号,需用@包裹 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 //文件处
新闻热点
疑难解答