grunt配置太复杂?使用Qbuild进行文件合并、压缩、格式化等处理
上次简单介绍了下Qbuild的特点和配置,其实实现一个自动化工具并不复杂,往简单里说,无非就是筛选文件和处理文件。但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;
}
};
代码下载
写在最后
如果本文或本项目对您有帮助的话,请不吝点个赞。欢迎交流!
grunt配置太复杂?使用Qbuild进行文件合并、压缩、格式化等处理的更多相关文章
- grunt配置太复杂?发布一个前端构建工具,简单高效,自动跳过未更新的文件
做前端项目,如果没有一个自动化构建工具,手动处理那简直就是坑爹O(∩_∩)O.于是上网了解了下,grunt用的人不少,功能也挺强大.看了一下grunt的配置(包括gulp),感觉稍显复杂.当时项目结构 ...
- AngularJS结合RequireJS做文件合并压缩的那些坑
我在项目使用了AngularJS框架,用RequireJS做异步模块加载(AMD),在做文件合并压缩时,遇到了一些坑,有些只是解决了,但不明白原因. 那些坑 1. build.js里面的paths必须 ...
- RequireJS 文件合并压缩
RequireJS的define 以及require 对于我们进行简化JavaScript 开发,进行模块化的处理具有很大的帮助 但是请求加载的js 文件会有一些影响,一般的处理是对于文件进行压缩,但 ...
- gulp 之一 安装及简单CSS,JS文件合并压缩
最近研究了一下gulp构建工具,发现使用起来比grunt顺手一些.(个人感受),以下是grunt和gulp构建方式和原理: grunt 基于文件方式构建,会把文件先写到临时目录下,然后进行读文件,修改 ...
- 使用System.Web.Optimization对CSS和JS文件合并压缩
在ASP.NET MVC 中JS/CSS文件动态合并及压缩通过调用System.Web.Optimization定义的类ScriptBundle及StyleBundle来实现. 大致步骤如下: 1.A ...
- 配置grunt进行css、js的检查、合并和压缩
现在会进行代码的合并和压缩已成为前端人员的必备知识,那么现在来介绍一个grunt的工具.grunt是个风靡世界的工具,它的首页是 http://www.gruntjs.net 这是个中文网站,有文档 ...
- grunt配置任务
这个指南解释了如何使用 Gruntfile 来为你的项目配置task.如果你还不知道 Gruntfile 是什么,请先阅读 快速入门 指南并看看这个Gruntfile 实例. Grunt配置 Grun ...
- 负载均衡下的资源文件配置/多站点下的资源文件夹共享(Windows IIS)
前言: 负载均衡用的是NLB,微软的方案不太靠谱,举个例子吧,AB两台服务器负载出C,如果用户访问访问C之后分配的是A,那么如果A挂了,是不会自动切换到B的.据说后来还有一种NLB的方案可以实现,也不 ...
- 使用grunt完成requirejs的合并压缩和js文件的版本控制
最近有一个项目使用了 requirejs 来解决前端的模块化,但是随着页面和模块的越来越多,我发现我快要hold不住这些可爱的js文件了,具体表现在每个页面都要设置一堆 requirejs 的配置( ...
随机推荐
- 用SQL语句获得一个存储过程返回的表
1. 定义一个表变量 declare @table table(ReportType nvarchar(30),ReportPath nvarchar(200),ParaCnt int,DataAre ...
- 【转】MaxScript.Net接收本地端口的消息执行
MaxScript里开不了线程,但是可以用.Net的BackgroundWorker来做后台处理 BackgroundWorker Fn BackgroundTcpListenerDoWork the ...
- C#学习网站记录
C# 编程指南--Microfsoft官方C#编程指南 https://msdn.microsoft.com/zh-cn/library/67ef8sbd(v=vs.100).aspx
- android开发--okhttp
一.okhttp简单实用: 一般的get请求 一般的post请求 基于Http的文件上传 文件下载 加载图片 支持请求回调,直接返回对象.对象集合 支持session的保持 二.实用教程 1.添加an ...
- ES6 学习笔记(1)
恰逢换工作之际,新公司的是以 ES6 + webpack + vue 为技术栈, 正好ES6是我下个学习目标, 因此买了阮老师的 ES6标准入门,也当是支持阮老师了. 笔记将会照着这本书的阅读展开而做 ...
- Orchard分类Taxonomies图文教程
Orchard分类和标签都实现对内容的分类管理,两者区别是分类的子项之间是具有级别(同级.上下级)关系,而标签是很随意的,子项之间可以有关系也可以没有,今天给大家分享分类的使用方法. 一.环境说明 O ...
- 解决Bringing up interface eth0: Device eth0 does not seem to be present, delaying initialization.
一.问题描述 OS:centos 原因是拷贝虚拟机造成的. 使用vmworkstation打开虚拟机的时候,要选择copy而非move. 二.解决描述 网络上解决步骤各异,其实就一句话.只要保证vmw ...
- 最新的hosts
# Copyright (c) 2014-2016, racaljk.# https://github.com/racaljk/hosts# Last updated: 2016-07-03 # Th ...
- hive中拉链表
在有些情况下,为了保持历史的一些状态,需要用拉链表来做,这样做目的在可以保留所有状态的情况下可以节省空间. 拉链表适用于以下几种情况吧 数据量有点大,表中某些字段有变化,但是呢变化的频率也不是很高,业 ...
- 利用Aspose.Pdf将扫描的电子书修改为适合在kindle上查看
很多扫描版的电子书,留有很大的页边距,大屏的设备看起来没有啥影响,可是在kindle上看起来就麻烦了,放大操作简直就没法用,最好能把留白去掉. 将pdf文件转换为图片这个看看 例子里的 JpegDev ...