上次简单介绍了下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. js数组的操作

    很早之前整理的一篇文章,感觉比较清晰. 一.数组元素的操作 1.数组的创建 var arrayObj = new Array(); var arrayObj = new Array(size); // ...

  2. [JSP]Maven+SSM框架(Spring+SpringMVC+MyBatis) - Hello World

    来源:http://blog.csdn.net/zhshulin/article/details/37956105?utm_source=tuicool&utm_medium=referral ...

  3. MongoDB快速学习笔记

    一,下载. XP系统,32位的下载地址: https://www.mongodb.org/dl/win32/i386 例:win32/mongodb-win32-i386-2.0.7.ziphttp: ...

  4. 修改tomcat应用日志默认编码格式

    前言 今天开发跟我说tomcat日志中的中文不能正常显示,根据以往的经验,我觉得可能跟服务器的编码有关,于是尝试各种方法,但还是没能解决问题. 后来我突然想到会不会跟tomcat的设置有关呢,于是在网 ...

  5. linux 获取线程号

    #include <sys/types.h> pid_t gettid(void); 如果系统库里没有,则可以这样做: #include <sys/syscall.h> pid ...

  6. Buffer Cache

    Buffer Cache Buffer Cache是SGA区中专门用于存放从数据文件中读取的的数据块拷贝的区域.Oracle进程如果发现需要访问的数据块已经在buffer cache中,就直接读写内存 ...

  7. mac 笔记

    ---恢复内容开始--- /* 因为外出mac 办公的时候越来越多,有时候4g wifi 流量告急,不得不寻找破解之道. */ 安装port& aircreak-ng sudo port in ...

  8. 机器学习PR:k近邻法分类

    k近邻法是一种基本分类与回归方法.本章只讨论k近邻分类,回归方法将在随后专题中进行. 它可以进行多类分类,分类时根据在样本集合中其k个最近邻点的类别,通过多数表决等方式进行预测,因此不具有显式的学习过 ...

  9. 利用Sql实现将指定表数据导入到另一个数据库示例

    因为工作中经常需要将数据从一个数据库导入到另一个数据库中,所以将这个功能写成一个存储过程,以方便调用.现在粘贴出来供大家参考: 注意:1,以下示例中用到了syscolumns,sysobjects等系 ...

  10. iOS 当请求到的数据是double类型,会失去精准度,并且去掉小数点后的0

    首先请求到的数据都会变成字符串,先将字符串转化为double类型 p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 17.0px Menlo; color: ...