有点不太一样的 grunt 任务.
1. clean --- 清理临时文件. 作为入门了解 grunt 任务.
在 Gruntfile.js 中, 有加载 clean 任务的部分:
grunt.loadNpmTasks('grunt-contrib-clean');
找到位于 node_modules 目录下的 grunt-contrib-clean 子目录 (通过 npm 安装的), 找到对应的 package.json,
打开查看, 可发现该模块也放在 github 上面: https://github.com/gruntjs/grunt-contrib-clean .
现在简单看一眼 tasks 目录下的 clean.js 文件. 里面的重点有:
grunt.registerMultiTask('clean', ..., function() {
... clean(filepath, options); // 调用实际清理函数.
});
再看看 grunt 模块下的各个文件(没时间细看, 简单翻了一下), 大致是读取 Gruntfile.js, package,json 提取信息,
然后调用各种 task 执行任务.
2. 重点是一个 dependence 任务:
任务的 npm 模块为 grunt-module-dependence, 到该子目录的 package.json 有
homepage = https://github.com/HanCong03/
该任务与其它任务不同的是, 它通过组装多个 js 文件为一个, 动态改变了程序的结构. 必须要了解最后生成的文件
结构, 才能理解前面使用 seajs 要求的规格 define(xxx) . 以我们的 hello.js 和一个子模块 foobar.js 为例:
var _p = { // 这是 dependence 模块生成的一个对象. r: function() ... // 稍后细看, 估计类似于 require() 函数. }; // src/foobar.js 被编号为 0, 估计按照顺序. _p[0] = { // 这个函数 value 即是 foobar.js 中用 define() 定义的工厂函数. value: function(require, exports, module) { ... } }; // src/hello.js 编号为 1. _p[1] = { // 同样是工厂函数. value: function(...) { // 重点在这里, 原来写的 require('foobar') 被代换为调用 _p.r() 函数以索引 0. var foobar = _p.r(0); ... 其它部分 ... } }; // 后面其它部分稍后需要再看.
如上所示, 重点在于改变了程序的结构, 模块被变成 _p[n] = {...} 形式, 对其它模块的引用变成 _p.r(n) 的形式.
其中函数 _p.r(n) 现在可以细看看, 因已知 n 是模块编号, 且可通过 _p[n] 的方式访问到.
_p.r = function(index) { var mod = _p[index]; // 用一个 mod 名字表明这是一个模块, 原程序不是这样. // 如果此模块已经初始化过, 则直接返回其 value. if (mod.inited) return mod.value; // 调用工厂方法(如果是的话)进行初始化. if (mod.value is function) { mod.inited = true; // 先设置值以 防止递归? mod.value.call(......); // 调用该工厂方法. 可能导致递归调用 _p.r() // 处理返回的 exports 略 } else { // 可能只是定义了一些数据, 而不是工厂方法 mod.inited = true; return mod.value; } }
因此这里的 _p.r() 函数, 相当于取代了 seajs.require() 方法, 以将一组模块组装在一起成为单一的大的 js.
生成的大的 js 文件后面还有 use() 方法, 看似取代 seajs.use() 方法, 并初始加载所指定的主模块 'hello'.
这是在 options 中指定的 entrance 值.
所有这些变换无疑都是 dependence 模块运作的了, 一旦了解了原理, 当需要的时候我们就能够自己定义这些
过程, 进行自己所需的变换.
// dependence.js 部分代码摘读. module.exports = function(grunt) { // 注册为一种 grunt 任务. 这里有一个自吹自擂我们就略过吧... grunt.registerMultiTask('dependence', 'The best Grunt plugin ever.' function() { // 其它略... 这里遍历要合并的 src 文件. src_files.forEach(function() { source = read(src); // 读入源文件. // 对代码进行变换(transform) 然后放入模块列表中. all_source[module_index++] = transform(source); // 合并后的源码再处理处理. removeRequire(...); join() ... wrap() ... format()... -> 写入目标文件 }); }); // 看注释了解即可, 详细代码略. // 对 module 执行转换, 更改其定义方式, 使其可以脱离 define 方法. function transform(...) { // 用正则表达式找 define... , 修改|替换某些东西... } // 删除 require 依赖. function removeRequire(...) { // 用正则表达式替换 require(...) 为 _p.r() } // 其它 format(格式化源码), wrap(包裹最终的源码) 等辅助函数略.
大致是这样了. 这样最后合并出的 kity.js 就不需要 seajs 的支持了, 而且代码也很少, 这一点还是值得赞的.
不过程序的变换也许可以更好一些, 例如, define, require 函数也许不需要变换, 直接提供一个简单的?
3. 任务 concat
给步骤 2 生成的 js 外面再包一个闭包, 即提供一个 banner, 提供一个 footer. 比较简单就不用细说了.
4. 任务 uglify
压缩 js 为最小化. 理解即可, 实在需要再去看怎么实现, 但估计是用 flex 做代码分析, 压缩空格, 去掉注释, 命名改短
等, 现在还不想看, 了解作用即可.