上次简单介绍了下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.JS

5. 提供简单易用的api,以简化插件编写。

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

文件合并

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

 module.exports = {
root: "../", concat: {
title: "文件合并", dir: "demo/js/src",
output: "release/js-concat", list: [
{
dir: "a",
src: ["t1.js", "t2.js", "t3.js"],
dest: "a.js",
prefix: "//----------- APPEND TEST (%f.filename%) -----------\n"
},
{
dir: "b",
src: ["t1.js", "t2.js", "t-error.js"],
dest: "b.js"
},
{
//不从父级继承,以/开头直接基于root定义的目录
dir: "/release/js-concat",
src: ["a.js", "b.js"],
dest: "ab.js"
}
]
}
};

js压缩

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

 module.exports = {
dir: "../demo",
output: "../release", cmd: {
title: "压缩js",
//cmd: "java -jar D:\\tools\\compiler.jar --js=%f.fullname% --js_output_file=%f.dest%",
cmd: "uglifyjs %f.fullname% -o %f.dest% -c -m", match: "js/*.js",
exclude: "js/error.js", before: "//build:%NOW%\n"
}
};

文件格式化

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

 module.exports = {
dir: "../demo",
output: "../release", format: [
{
title: "格式化html文件", match: "*.html",
exclude: "**.old.html", replace: [
//移除html注释
[/(<!--(?!\[if\s)([^~]|~)*?-->)/gi, ""],
//移除无效的空格或换行
[/(<div[^>]*>)[\s\r\n]+(<\/div>)/gi, "$1$2"],
//移除多余的换行
[/(\r?\n)(\r?\n)+/g, "$1"],
//移除首尾空格
[/^\s+|\s+$/, ""]
]
},
{
title: "格式化css文件", match: "css/*.css", replace: [
//移除css注释
[/\/\*([^~]|~)*?\*\//g, ""],
//移除多余的换行
[/(\r?\n)(\r?\n)+/g, "$1"],
//移除首尾空格
[/^\s+|\s+$/, ""]
]
}
]
};

文件同步(复制)

 module.exports = {
dir: "../demo",
output: "../release", copy: [
{
title: "同步js数据",
match: "js/data/**.js"
},
{
title: "同步图片",
match: "images/**"
}
]
};

插件(模块)编写

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

1> match模式

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

2> list模式

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

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

 global.Qbuild = {
ROOT, //配置文件所在目录,与config.root不同
ROOT_EXEC, //文件执行路径,即build.js所在路径 config, //配置对象 HOT, //红色输出,用于print和log,下同
GREEN, //绿色输出
YELLOW, //黄色输出
PINK, //粉红色输出 print:function (msg,color), //输出控制台信息,不换行,可指定输出颜色
log:function (msg,color), //输出控制台信息并换行,可指定输出颜色
error:function (msg), //输出错误信息,默认黄色 //注册模块
//type:String|Array|Object
// String:模块类型 eg: register("concat",fn|object)
// Array: 模块数组 eg: register([module,module],bind)
// Object:模块对象 eg: register({type:module},bind)
//module:模块方法或对象,当为function时相当于 { exec:fn } ,若type为模块数组或对象,则同bind
//bind:文本模块绑定对象(文本模块只在此对象上生效),可以传入一个空对象以注册一个全局文本模块
register: function (type, module, bind), //创建路径筛选正则表达式,将默认匹配路径的结束位置
//pattern: 匹配规则,点、斜杠等会被转义,**表示所有字符,*表示斜杠之外的字符 eg: demo/**.html
//isdir: 是否目录,若为true,将匹配路径的起始位置
getPathRegex: function (pattern, isdir), //获取匹配的文件,默认基于config.root
//pattern:匹配规则,支持数组 eg:["js/**.js","m/js/**.js"]
//ops:可指定扫描目录、输出目录、排除规则、扫描时是否跳过输出目录 eg:{ dir:"demo/",output:"release/",exclude:"**.old.js",skipOutput:true }
getFiles: function (pattern, ops) ,
//获取相对路径,默认相对于config.dir
getRelname: function (fullname, rel_dir),
//获取不带扩展名的名称
getNameWithoutExt: function (name),
//设置文件变更 => map_dest[f.destname.toLowerCase()]={src: f.fullname, dest: f.dest}
setChangedFile: function (f),
//获取输出路径映射,返回 { map: map_dest, last: map_last_dest }
getDestMap: function (),
//确保文件夹存在
mkdir: function (dir),
//读取文件内容(f[read_key] => f.text),read_key 默认为fullname
readFile: function (f, callback, read_key),
//保存文件(f.text => f.dest)
saveFile: function (f, callback), //简单文本解析,支持属性或函数的连续调用,支持简单参数传递,若参数含小括号,需用@包裹 eg:%Q.formatSize@(f.stat.size,{join:'()'})@%
//不支持函数嵌套 eg:path.normalize(path.dirname(f.dest))
//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 MB
//eg:parse_text("%path.dirname(f.dest)%", { dest: "aa/b.js"}); => aa
parseText: function (text, f), //执行命令行调用
shell: function (cmd, callback), //运行文本处理模块
runTextModules: function (f, task), //设置检测函数,检查文件是否需要更新
setCheck: function (task, check), //自定义存储操作,文件默认为build.store.json
store: {
init: function (callback), //读取json数据并解析
get: function (key),
set: function (key, value),
save: function (callback) //保存json数据到文件
}
};

3. 任务处理模块格式

 module.exports = {
//模块类型,即任务属性名称,可以为数组
type:"concat", //可选,任务初始化时触发
//task:任务对象 => config[module.type]
init: function (task), //可选,文件预处理函数
check: function (f, task), //可选,任务处理完毕触发(仅对exec有效)
after: function (task), //文件处理函数(针对单个文件)
exec: function (f, task, callback), //文件处理函数(针对所有文件),exec和process任选其一,process主要针对特殊情况
//task.files :文件对象列表,match模式
//task.list :文件对象列表,list模式
process:function (task, callback)
};

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

//转交给 module.process 处理
if (module.process) return fire(module.process, module, task, callback); //针对单一的文件或任务处理
//Q.Queue为自定义队列对象,详见 build/lib/Q.js
var 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. 文本处理模块格式

 module.exports = {
//模块类型,即任务属性名称,可以为数组
type:["before","after"], //可选,任务初始化时触发
//data: 在配置中指定的参数 => task[type]
//task: 任务对象
init: function (data, task), //文本处理函数,通过操作f.text实现内容更新 eg:f.text=f.text+"OK!";
//f: 文件对象
//type: 文本模块触发时的类型,比如在内容前后追加文本 f.text=type=="before"?data+f.text:f.text+data;
process:function (f, data, task, type)
};

5. 模块注册(程序会默认导入指定目录的模块,一般无需手动注册)

 module.exports = {
//注册任务处理模块,基于根目录,默认导入./module/*.js
register: "./module/*.js", //另一种注册方式
//注意:此种方式注册的type会覆盖模块默认定义的type (module.type)
/*register: {
concat: "./module/concat.js",
format: "./module/format.js",
cmd: "./module/cmd.js",
copy: "./module/copy.js", //若处理程序相同,可重用已注册的模块
formatCss:"format"
},*/ //要启动的任务,按顺序执行,不支持*,默认运行所有
//run: ["concat", "cmd", "formatCss", "format", "copy"], //注册文本处理模块,基于根目录,默认导入./module/text/*.js
//对所有任务均生效(如果模块调用了文本处理)
registerText: "./module/text/*.js", //另一种注册方式,同register
//registerText: {}, //要执行的文本处理模块(按顺序执行),*表示其它模块,默认运行所有
//runText: ["replace", "before", "after", "*"], //定义任务对象(相当于传给任务模块的参数),名称要和模块定义的type一致
//可以为数组 eg:[{},{}]
formatCss: {
//此处可以注册仅对本任务生效的文本处理模块
registerText: "./module/text/custom/test.js", //指定运行的文本处理模块及顺序
//runText:[], //传给文本处理模块的参数,和文本模块type对应
//是否需要参数,取决于文本处理模块
before: "",
after: "", match:""
}
};

模块示例

1. 任务处理模块

 /*
* copy.js 文件同步模块
* author:devin87@qq.com
* update:2015/07/10 16:23
*/
var log = Qbuild.log,
print = Qbuild.print,
mkdir = Qbuild.mkdir, formatSize = Q.formatSize; module.exports = {
type: ["copy", "copy0", "copy1"], init: function (task) {
//不预加载文件内容,不重命名文件
task.preload = task.rename = false;
}, exec: function (f, task, callback) {
if (f.skip) {
log("跳过:" + f.relname);
return Q.fire(callback);
} print("复制:" + f.relname, Qbuild.HOT);
print(" " + formatSize(f.stat.size)); //确保输出文件夹存在
mkdir(path.dirname(f.dest)); var rs = fs.createReadStream(f.fullname), //创建读取流
ws = fs.createWriteStream(f.dest); //创建写入流 //通过管道来传输流
rs.pipe(ws); rs.on("end", function () {
print(" √\n", Qbuild.GREEN);
callback();
}); rs.on("error", function () {
print(" ×\n", Qbuild.YELLOW);
});
}
};
 /*
* format.js 文件格式化模块
* author:devin87@qq.com
* update:2015/07/10 16:23
*/
var log = Qbuild.log,
print = Qbuild.print; module.exports = {
type: ["format", "format0", "format1"], exec: function (f, task, callback) {
if (f.skip) {
log("跳过:" + f.relname);
return Q.fire(callback);
} //log("处理:" + f.relname, Qbuild.HOT); print("处理:" + f.relname, Qbuild.HOT);
if (f.rename) print(" => " + f.rename);
print("\n"); Qbuild.readFile(f, function () {
Qbuild.runTextModules(f, task);
Qbuild.saveFile(f, callback);
});
}
};

2. 文本处理模块

 /*
* replace.js 文本模块:内容替换
* author:devin87@qq.com
* update:2015/07/10 16:23
*/
module.exports = {
type: "replace", process: function (f, data, task, type) {
if (!data) return; var text = f.text || ""; Q.makeArray(data).forEach(function (item) {
var pattern = item[0],
replacement = item[1],
flags = item[2]; if (!pattern || typeof replacement != "string") return; var regex = new RegExp(pattern, flags);
text = text.replace(regex, replacement);
}); f.text = text;
}
};

代码下载

Qbuild.js 源码+示例代码

写在最后

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

grunt配置太复杂?使用Qbuild进行文件合并、压缩、格式化等处理的更多相关文章

  1. grunt配置太复杂?发布一个前端构建工具,简单高效,自动跳过未更新的文件

    做前端项目,如果没有一个自动化构建工具,手动处理那简直就是坑爹O(∩_∩)O.于是上网了解了下,grunt用的人不少,功能也挺强大.看了一下grunt的配置(包括gulp),感觉稍显复杂.当时项目结构 ...

  2. AngularJS结合RequireJS做文件合并压缩的那些坑

    我在项目使用了AngularJS框架,用RequireJS做异步模块加载(AMD),在做文件合并压缩时,遇到了一些坑,有些只是解决了,但不明白原因. 那些坑 1. build.js里面的paths必须 ...

  3. RequireJS 文件合并压缩

    RequireJS的define 以及require 对于我们进行简化JavaScript 开发,进行模块化的处理具有很大的帮助 但是请求加载的js 文件会有一些影响,一般的处理是对于文件进行压缩,但 ...

  4. gulp 之一 安装及简单CSS,JS文件合并压缩

    最近研究了一下gulp构建工具,发现使用起来比grunt顺手一些.(个人感受),以下是grunt和gulp构建方式和原理: grunt 基于文件方式构建,会把文件先写到临时目录下,然后进行读文件,修改 ...

  5. 使用System.Web.Optimization对CSS和JS文件合并压缩

    在ASP.NET MVC 中JS/CSS文件动态合并及压缩通过调用System.Web.Optimization定义的类ScriptBundle及StyleBundle来实现. 大致步骤如下: 1.A ...

  6. 配置grunt进行css、js的检查、合并和压缩

    现在会进行代码的合并和压缩已成为前端人员的必备知识,那么现在来介绍一个grunt的工具.grunt是个风靡世界的工具,它的首页是  http://www.gruntjs.net 这是个中文网站,有文档 ...

  7. grunt配置任务

    这个指南解释了如何使用 Gruntfile 来为你的项目配置task.如果你还不知道 Gruntfile 是什么,请先阅读 快速入门 指南并看看这个Gruntfile 实例. Grunt配置 Grun ...

  8. 负载均衡下的资源文件配置/多站点下的资源文件夹共享(Windows IIS)

    前言: 负载均衡用的是NLB,微软的方案不太靠谱,举个例子吧,AB两台服务器负载出C,如果用户访问访问C之后分配的是A,那么如果A挂了,是不会自动切换到B的.据说后来还有一种NLB的方案可以实现,也不 ...

  9. 使用grunt完成requirejs的合并压缩和js文件的版本控制

    最近有一个项目使用了 requirejs 来解决前端的模块化,但是随着页面和模块的越来越多,我发现我快要hold不住这些可爱的js文件了,具体表现在每个页面都要设置一堆 requirejs 的配置( ...

随机推荐

  1. iOS开发阶段技能总结

    这是一篇自己平时纪录的笔记... 1.基本的数据结构常识:链表,队列,栈 2.基本的算法:排序,动态规划等常用算法 3.基本的概念,cocoa,各种自带的view的使用. 4.xcode自带的测试:O ...

  2. [转]CSS 表单元素对齐详解

    来源:http://blog.sina.com.cn/s/blog_655388ed0100lzyk.html 简单的设置input{vertical-align:middle}即可,完美的: lab ...

  3. vim中替换命令

    在VIM中进行文本替换:       1.  替换当前行中的内容:    :s/from/to/    (s即substitude)         :s/from/to/     :  将当前行中的 ...

  4. jenkins配置自动发送邮件

    1.开通QQ的SMTP服务,需要发一条短信,qq会给你一个密码(不是你的QQ邮箱密码哦) 2.安装 Email Extension Plugin 插件 3.进入系统管理--系统设置 3.1按照如下图设 ...

  5. awk实现join

    awk 有时确实比较省事,做些简单的文本处理,还是很方便的,在这介绍下两个文件的join的操作. 原始文本 bb.txt a 10 b 12 cc.txt a 11 b 13 c 15 awk joi ...

  6. day--6_python常用模块

    常用模块: time和datetime shutil模块 radom string shelve模块 xml处理 configparser处理 hashlib subprocess logging模块 ...

  7. ipv6过审函数

    int _ResolveIPv4Address(const char* ipv4_str, char* buffer, int bufferSize) { struct addrinfo hints, ...

  8. linux 驱动学习笔记03--Linux 内核的引导

    如图所示为 X86 PC 上从上电/复位到运行 Linux 用户空间初始进程的流程.在进入与 Linux相关代码之间,会经历如下阶段. ( 1 ) 当系统上电或复位时, CPU 会将 PC 指针赋值为 ...

  9. 路由器TL-WR941N V5.1:救砖、MOD

    完成效果图: --- 拆芯片时需要贴上胶带,防止误伤: --- 堆锡拆除旧的flash芯片: --- 购入物品: --- 编程器刷入Breed: --- Web刷机: --- 预处理材料: --- 7 ...

  10. socket-自我总结(2)

    这里总结下一个服务端与多个客户端之间的通信. 先看demo: #/usr/bin/env python #_*_coding:utf-8_*_ __author__ = 'ganzl' import ...