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.JS

5. 提供简单易用的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|Object
18     //     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为模块数组或对象,则同bind
22     //bind:文本模块绑定对象(文本模块只在此对象上生效),可以传入一个空对象以注册一个全局文本模块
23     register: function (type, module, bind),
24
25     //创建路径筛选正则表达式,将默认匹配路径的结束位置
26     //pattern: 匹配规则,点、斜杠等会被转义,**表示所有字符,*表示斜杠之外的字符 eg: demo/**.html
27     //isdir: 是否目录,若为true,将匹配路径的起始位置
28     getPathRegex: function (pattern, isdir),
29
30     //获取匹配的文件,默认基于config.root
31     //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.dir
35     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 默认为fullname
45     readFile: function (f, callback, read_key),
46     //保存文件(f.text => f.dest)
47     saveFile: function (f, callback),
48
49     //简单文本解析,支持属性或函数的连续调用,支持简单参数传递,若参数含小括号,需用@包裹 eg:%[email protected](f.stat.size,{join:‘()‘})@%
50     //不支持函数嵌套 eg:path.normalize(path.dirname(f.dest))
51     //eg:parse_text("%f.name.trim()[email protected]({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
52     //eg:parse_text("%path.dirname(f.dest)%", { dest: "aa/b.js"});  => aa
53     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.json
65     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.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. 文本处理模块格式

 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 源码+示例代码

写在最后

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

时间: 2024-08-07 04:33:07

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

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

做前端项目,如果没有一个自动化构建工具,手动处理那简直就是坑爹O(∩_∩)O.于是上网了解了下,grunt用的人不少,功能也挺强大.看了一下grunt的配置(包括gulp),感觉稍显复杂.当时项目结构非常简单,就是单文件夹下的html文件,再加上js.css.图片.需要的功能也就js的合并和压缩,html和css的简单格式化,功能简单,So easy……开搞,搞定第一版,一直用到今年.最近整理项目,感觉只支持单一文件夹,功能全内置,实在不够灵活,于是重写了第二版.功能实现没什么难的,麻烦的是打造

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

我在项目使用了AngularJS框架,用RequireJS做异步模块加载(AMD),在做文件合并压缩时,遇到了一些坑,有些只是解决了,但不明白原因. 那些坑 1. build.js里面的paths必须跟main.js里面的保持一致. 这个build.js就是r.js使用的配置文件,而main.js就是RequireJS的main文件.在合并压缩时候,build.js文件里面也需要写paths,而且还是跟main.js一样,我很奇怪为什么就不能识别main里面的require.config的pat

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

在ASP.NET MVC 中JS/CSS文件动态合并及压缩通过调用System.Web.Optimization定义的类ScriptBundle及StyleBundle来实现. 大致步骤如下: 1.App_Start添加: public class BundleConfig { public static void RegisterBundles(BundleCollection bundles) { bundles.Add(new StyleBundle("~/Themes/Homecss&q

AngularJS分别RequireJS做文件合并压缩的那些坑

小心!AngularJS合并RequireJS做文件离开压缩的那些坑 小心!AngularJS合并RequireJS做文件离开压缩的那些坑,各人正在做文件离开压缩的时辰一定要注意,感乐趣的网友可以参考一下正在项目运用了AngularJS框架,用RequireJS做异步模块加载(AMD),正在做文件离开压缩时,遇到了一些坑,有些只是搞定了,但不大白原因. 那些坑1. build.js内里的paths必须跟main.js内里的保持等同. 这个build.js便是r.js运用的设置文件,而main.j

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

现在会进行代码的合并和压缩已成为前端人员的必备知识,那么现在来介绍一个grunt的工具.grunt是个风靡世界的工具,它的首页是  http://www.gruntjs.net 这是个中文网站,有文档可以参考.但是只看文档是不是觉得很蛋疼呢?一个字:看不太懂啊! 好了,废话不多说,直接发安装步骤和注意事项,都是心酸研究出来的啊... 这里介绍mac的安装方法.windows方法相类似,等我稍微研究一下后再发上来 首先,要安装nodejs,grunt是依赖node滴.上 www.nodejs.or

grunt 合并压缩js和css文件(二)

具体node及文件配置请看: grunt 安装使用(一) 要压缩的文件 --src/ ajax.js assets.js touch.js zepto.js 目录结构: dist/ node_modules/ src/ Gruntfile.js package.json package.json是依赖库文件 Gruntfile.js是执行步骤程序 一.js合并压缩 第一次需要先安装grunt.执行 npm install -g grunt-cli 进行安装.如果已经安装,可以忽略. 1.pack

grunt配置详情

这个grunt配置 是我的一个程序员朋友从网上无意间看到的,然后他亲测了下,恩,是可以的.不过我到目前还未测试过是否可以. 一.安装node, 首先确保电脑已有node的环境.然后 运行  npm install -g grunt-cli   然后运行 grunt --version 查看当前grunt是否安装;二.在项目根目录下面创建一个js文件,叫gruntfile.js;    配置项如下:    module.exports = function (grunt) {    var con

设置XML文件的自动格式化配置

设置XML文件的自动格式化配置: 在Eclipse下编辑XML文件时可以像Java文件一样进行格式化.不过,默认的格式化设置效果不太理想,尤其是标签属性较多时.通过以下设置可以达到一个相对理想的效果:Window -> Preferences -> XML ->XML Files -> Editor : 1.1设置每行宽度Line width   设定为80到100个字符. 1.2标签的每个属性都单独一行显示:选中Split multiple attributes each on 

电脑配置太低?使用这几个网站,你的电脑软件要少装一半!

你还在为你的电脑配置太低导致软件过多而卡顿感到烦恼?还是电脑游戏太多,无法运行一些其他软件而不开心?其实不用难过,小编教你们使用一些在线网站,可以轻松的将你的电脑上的软件少安装一半!你们准备好了吗! 一.创客贴-在线图片设计 创客贴是一个在线图片设计网站,不需要太多的专业知识,也不需要安装一些比较好用的专业作图软件,只需要使用这个网站就可以轻松的去进行logo的设计以及通过现成的模板和资源制作不少高逼格的PPT.网页.微信图片,轻松提升讲演稿质量和朋友圈的逼格! 二.uzer.me--云端应用聚