Exemple #1
0
define('JsPackage', function (require, module, exports) {

    var $ = require('$');
    var File = require('File');
    var FileRefs = require('FileRefs');
    var Path = require('Path');
    var Patterns = require('Patterns');
    var Watcher = require('Watcher');
    var Defaults = require('Defaults');
    var MD5 = require('MD5');

    var Emitter = $.require('Emitter');
    


    var mapper = new Map();




    function JsPackage(dir, config) {


        config = Defaults.clone(module.id, config);

        var meta = {

            'dir': dir,
            'patterns': [],     //模式列表。
            'list': [],         //真实 js 文件列表及其它信息。
            'content': '',      //编译后的 js 内容。
            'emitter': new Emitter(this),
            'watcher': null,                //监控器,首次用到时再创建。



        };

        mapper.set(this, meta);

    }



    JsPackage.prototype = {
        constructor: JsPackage,

        /**
        * 重置为初始状态,即创建时的状态。
        */
        reset: function () {

            var meta = mapper.get(this);

            //删除之前的文件引用计数
            meta.list.forEach(function (item) {
                FileRefs.delete(item.file);         
            });


            $.Object.extend(meta, {
                'master': '',       //母版页的内容,在 parse() 中用到。
                'html': '',         //模式所生成的 html 块,即缓存 toHtml() 方法中的返回结果。
                'outer': '',        //包括开始标记和结束标记在内的原始的整一块 html。
                'patterns': [],     //模式列表。
                'list': [],         //真实 js 文件列表及其它信息。
                'content': '',      //编译后的 js 内容。
            });

        },

        /**
        * 根据当前模式获取对应真实的 js 文件列表。
        */
        get: function (patterns) {
            var meta = mapper.get(this);
            var dir = meta.dir;

            patterns = meta.patterns = Patterns.combine(dir, patterns);

            var list = Patterns.getFiles(patterns);

            list = list.map(function (file, index) {

                file = Path.format(file);
                FileRefs.add(file);

                return file;

            });

            meta.list = list;

        },


        /**
        * 合并对应的 js 文件列表。
        */
        concat: function (options) {

            var meta = mapper.get(this);
            var list = meta.list;
            if (list.length == 0) {
                meta.content = '';
                return;
            }

            //加上文件头部和尾部,形成闭包
            var header = options.header;
            if (header) {
                header = Path.format(header);
                FileRefs.add(header);
                list = [header].concat(list);
            }

            var footer = options.footer;
            if (footer) {
                footer = Path.format(footer);
                FileRefs.add(footer);
                list = list.concat(footer);
            }

            var JS = require('JS');
            var content = meta.content = JS.concat(list, options);
            var md5 = MD5.get(content);

            return md5;

        },

        /**
        * 压缩合并后的 js 文件。
        */
        minify: function (dest) {

            var meta = mapper.get(this);
            var content = meta.content;
            var JS = require('JS');

            //直接从内容压缩,不读取文件
            content = meta.content = JS.minify(content);

            if (typeof dest == 'object') {
                var name = dest.name;
                if (typeof name == 'number') {
                    name = MD5.get(content, name);
                    name += '.js';
                }

                dest = dest.dir + name;
                File.write(dest, content); //写入合并后的 js 文件
            }

            return dest;
        },



        /**
        * 监控当前模式下 js 文件的变化。
        */
        watch: function () {
            var meta = mapper.get(this);
            var patterns = meta.patterns;
            if (patterns.length == 0) { //列表为空,不需要监控
                return;
            }

            var watcher = meta.watcher;

            if (!watcher) { //首次创建
               
                watcher = meta.watcher = new Watcher();

                var self = this;
                var emitter = meta.emitter;

                function add(files) {

                    //增加到列表
                    var list = files.map(function (file, index) {
                        file = Path.format(file);
                        FileRefs.add(file);
                        return file;
                    });

                    meta.list = meta.list.concat(list);
                }



                watcher.on({
                    'added': function (files) {
                        add(files);
                        emitter.fire('change');
                    },

                    'deleted': function (files) {
                        //从列表中删除
                        var obj = {};
                        files.forEach(function (file) {
                            FileRefs.delete(file, true);
                            obj[file] = true;
                        });

                        meta.list = meta.list.filter(function (file) {
                            return !obj[file];
                        });

                        emitter.fire('change');
                    },

                    //重命名的,会先后触发 deleted 和 renamed
                    'renamed': function (files) {
                        add(files);
                        emitter.fire('change');
                    },

                    'changed': function (files) {
                        emitter.fire('change');
                    },

                });
                
            }


            watcher.set(patterns);

        },


        /**
        * 取消监控。
        */
        unwatch: function () {
            var meta = mapper.get(this);
            var watcher = meta.watcher;
            if (watcher) {
                watcher.close();
            }
        },


        /**
        * 删除模式列表中所对应的 js 物理文件。
        */
        delete: function () {
            var meta = mapper.get(this);

            meta.list.forEach(function (item) {
                FileRefs.delete(item.file);
            });
        },

        /**
        * 绑定事件。
        */
        on: function (name, fn) {
            var meta = mapper.get(this);
            var emitter = meta.emitter;

            var args = [].slice.call(arguments, 0);
            emitter.on.apply(emitter, args);
        },

       

    };


    return $.Object.extend(JsPackage, {

       
    });



});
Exemple #2
0
define('LessPackage', function (require, module, exports) {

    var $ = require('$');
    var File = require('File');
    var FileRefs = require('FileRefs');
    var Watcher = require('Watcher');
    var MD5 = require('MD5');
    var Path = require('Path');
    var Patterns = require('Patterns');
    var Defaults = require('Defaults');
    var MD5 = require('MD5');

    var Mapper = $.require('Mapper');
    var Emitter = $.require('Emitter');

   

    var mapper = new Mapper();



    function LessPackage(dir, config) {


        Mapper.setGuid(this);
        config = Defaults.clone(module.id, config);


        var meta = {
            'dir': dir,         //母版页所在的目录。
            'patterns': [],     //模式列表。
            'list': [],         //真实 less 文件列表。
            'less$item': {},    //less 文件所对应的信息
            'content': '',      //合并后或压缩后的内容。

            'emitter': new Emitter(this),
            'watcher': null,    //监控器,首次用到时再创建

        };

        mapper.set(this, meta);

    }



    LessPackage.prototype = {
        constructor: LessPackage,

        /**
        * 重置为初始状态,即创建时的状态。
        */
        reset: function () {
            var meta = mapper.get(this);
            var less$item = meta.less$item;

            meta.list.forEach(function (less) {
                var item = less$item[less];
                FileRefs.delete(less);
            });


            $.Object.extend(meta, {
                'patterns': [],     //模式列表。
                'list': [],         //真实 less 文件列表。
                'less$item': {},    //less 文件所对应的信息
            });
        },


        /**
        * 根据当前模式获取对应真实的 less 文件列表和将要产生 css 文件列表。
        */
        get: function (patterns) {
            var meta = mapper.get(this);
            var less$item = meta.less$item;

            patterns = meta.patterns = Patterns.combine(meta.dir, patterns);

            var list = Patterns.getFiles(patterns);

            list.forEach(function (less) {
                //如 less = '../htdocs/html/test/style/less/index.less';

                if (less$item[less]) { //已处理过该项,针对 watch() 中的频繁调用。
                    return;
                }

                less$item[less] = {
                    'content': '',  //编译后的 css 内容。
                    'md5': '',      //编译后的 css 内容对应的 md5 值,需要用到时再去计算。
                };

                FileRefs.add(less);

            });

            meta.list = list;
        },


        /**
        * 编译 less 文件列表(异步模式)。
        * 如果指定了要编译的列表,则无条件进行编译。
        * 否则,从原有的列表中过滤出尚未编译过的文件进行编译。
        * 已重载:
            compile(list, fn);
            compile(list, options);
            compile(list);
            compile(fn);
            compile(options);
            compile(options, fn);
        * @param {Array} [list] 经编译的 less 文件列表。 
            如果指定了具体的 less 文件列表,则必须为当前文件引用模式下的子集。 
            如果不指定,则使用原来已经解析出来的文件列表。
            提供了参数 list,主要是在 watch() 中用到。
        */
        compile: function (list, options) {
            var fn = null;
            if (list instanceof Array) {
                if (typeof options == 'function') { //重载 compile(list, fn);
                    fn = options;
                    options = null;
                }
                else if (typeof options == 'object') { //重载 compile(list, options);
                    fn = options.done;
                }
                else { //重载 compile(list);
                    options = null;
                }
            }
            else if (typeof list == 'function') { //重载 compile(fn);
                fn = list;
                list = null;
            }
            else if (typeof list == 'object') { //重载 compile(options); 或 compile(options, fn)
                fn = options;
                options = list;
                list = null;
                fn = fn || options.done;
            }


            options = options || {  //这个默认值不能删除,供开发时 watch 使用。
                'delete': false,    //删除 less,仅提供给上层业务 build 时使用。
            };


            var Less = require('Less');
            var meta = mapper.get(this);
            var less$item = meta.less$item;

            var force = !!list;         //是否强制编译
            list = list || meta.list;

            if (list.length == 0) { //没有 less 文件
                fn && fn();
                return;
            }



            //并行地发起异步的 less 编译
            var Tasks = require('Tasks');
            Tasks.parallel({
                data: list,
                each: function (less, index, done) {
                    var item = less$item[less];

                    //没有指定强制编译,并且该文件已经编译过了,则跳过。
                    if (!force && item.content) {
                        done();
                        return;
                    }

                    Less.compile({
                        'src': less,
                        'delete': options.delete,
                        'compress': false,
                        'done': function (css) {
                            item.content = css;
                            done();
                        },
                    });

                },
                all: function () {
                    //已全部完成
                    fn && fn();
                },
            });
        },

        /**
        * 合并对应的 css 文件列表。
        */
        concat: function (dest) {

            var meta = mapper.get(this);
            var list = meta.list;
            if (list.length == 0) { //没有 less 文件
                return;
            }

            var less$item = meta.less$item;
            var contents = [];

            list.forEach(function (less) {
                var item = less$item[less];
                contents.push(item.content);
            });

            var content = meta.content = contents.join('');


            if (dest) {
                File.write(dest, content); //写入合并后的 css 文件
            }

            var md5 = MD5.get(content);

            return md5;
        },

        /**
        * 压缩合并后的 css 文件。
        */
        minify: function (dest, done) {

            var meta = mapper.get(this);
            var content = meta.content;
            var Less = require('Less');

            
            
            Less.minify(content, function (css) {

                if (typeof dest == 'object') {
                    var name = dest.name;

                    if (typeof name == 'number') {
                        name = MD5.get(css, name);
                        name += '.css';
                    }

                    dest = dest.dir + name;
                }

                File.write(dest, css);

                done && done(dest, css);
            });

        },

        /**
        * 监控当前模式下的所有 less 文件。
        */
        watch: function () {
            var meta = mapper.get(this);
            var patterns = meta.patterns;

            if (patterns.length == 0) { //列表为空,不需要监控
                return;
            }

            var watcher = meta.watcher;
            if (!watcher) { //首次创建
                
                watcher = meta.watcher = new Watcher();

                var self = this;
                var less$item = meta.less$item;
                var emitter = meta.emitter;


                watcher.on({
                    'added': function (files) {
                        self.get();
                        self.compile(files, function () {
                            emitter.fire('change');
                        });
                        
                    },

                    'deleted': function (files) {

                        //删除对应的记录
                        files.forEach(function (less) {
                            var item = less$item[less];
                            delete less$item[less];

                            FileRefs.delete(less, true);
                        });

                        self.get();

                        emitter.fire('change');
                    },

                    //重命名的,会分别触发:deleted 和 renamed
                    'renamed': function (files) {
                        self.get();
                        self.compile(files, function () {
                            emitter.fire('change');
                        });
                        
                    },

                    'changed': function (files) {

                        //让对应的记录作废
                        files.forEach(function (less) {
                            var item = less$item[less];
                            item.md5 = '';
                            item.content = '';
                        });

                        self.compile(files, function () {
                            emitter.fire('change');
                        });
                    },

                });
            }

            watcher.set(patterns);
        },


        /**
        * 取消监控。
        */
        unwatch: function () {
            var meta = mapper.get(this);
            var watcher = meta.watcher;
            if (watcher) {
                watcher.close();
            }
        },

        /**
        * 绑定事件。
        */
        on: function (name, fn) {
            var meta = mapper.get(this);
            var emitter = meta.emitter;

            var args = [].slice.call(arguments, 0);
            emitter.on.apply(emitter, args);
        },

        

    };



    return LessPackage;



});
Exemple #3
0
define('LessList', function (require, module, exports) {

    var $ = require('$');
    var path = require('path');

    var File = require('File');
    var FileRefs = require('FileRefs');
    var Watcher = require('Watcher');
    var MD5 = require('MD5');
    var Path = require('Path');
    var Patterns = require('Patterns');
    var Defaults = require('Defaults');
    var Log = require('Log');

    var Emitter = $.require('Emitter');
    var Url = $.require('Url');
   

    var mapper = new Map();




    function LessList(dir, config) {


        config = Defaults.clone(module.id, config);


        var meta = {
            'dir': dir,         //母版页所在的目录。
            'master': '',       //母版页的内容,在 parse() 中用到。
            'html': '',         //模式所生成的 html 块。
            'outer': '',        //包括开始标记和结束标记在内的原始的整一块 html。
            'patterns': [],     //全部模式列表。
            'list': [],         //真实 less 文件列表。
            'less$item': {},    //less 文件所对应的信息

            'emitter': new Emitter(this),
            'watcher': null,    //监控器,首次用到时再创建

            'extraPatterns': config.extraPatterns,  //额外附加的模式。
            'md5': config.md5,                      //填充模板所使用的 md5 的长度
            'sample': config.sample,                //使用的模板
            'tags': config.tags,
            'htdocsDir': config.htdocsDir,
            'cssDir': config.cssDir,
            'concat': config.concat,
            'minify': config.minify,

            //记录 concat, minify 的输出结果
            'build': {
                file: '',       //完整物理路径
                href: '',       //用于 link 标签中的 href 属性
                content: '',    //合并和压缩后的内容
            },

        };

        mapper.set(this, meta);

    }



    LessList.prototype = {
        constructor: LessList,

        /**
        * 重置为初始状态,即创建时的状态。
        */
        reset: function () {
            var meta = mapper.get(this);
            var less$item = meta.less$item;

            meta.list.forEach(function (less) {
                var item = less$item[less];

                FileRefs.delete(less);
                FileRefs.delete(item.file);
            });


            $.Object.extend(meta, {
                'master': '',       //母版页的内容,在 parse() 中用到。
                'html': '',         //模式所生成的 html 块。
                'outer': '',        //包括开始标记和结束标记在内的原始的整一块 html。
                'patterns': [],     //模式列表。
                'list': [],         //真实 less 文件列表。
                'less$item': {},    //less 文件所对应的信息

                //记录 concat, minify 的输出结果
                'build': {
                    file: '',       //完整物理路径
                    href: '',       //用于 link 标签中的 href 属性
                    content: '',    //合并和压缩后的内容
                },
            });
        },

        /**
        * 从当前或指定的母版页 html 内容中提出 less 文件列表信息。
        * @param {string} master 要提取的母版页 html 内容字符串。
        */
        parse: function (master) {
            var meta = mapper.get(this);
            master = meta.master = master || meta.master;

            var tags = meta.tags;
            var html = $.String.between(master, tags.begin, tags.end);

            if (!html) {
                return;
            }

            var patterns = $.String.between(html, '<script>', '</script>');
            if (!patterns) {
                return;
            }

            var dir = meta.dir;

            //母版页中可能会用到的上下文。
            var context = {
                'dir': dir,
                'master': master,
                'tags': meta.tags,
                'htdocsDir': meta.htdocsDir,
                'cssDir': meta.cssDir,
            };

            var fn = new Function('require', 'context',
                //包装多一层匿名立即执行函数
                'return (function () { ' +
                    'var a = ' + patterns + '; \r\n' +
                    'return a;' +
                '})();'
            );

            //执行母版页的 js 代码,并注入变量。
            patterns = fn(require, context);

            if (!Array.isArray(patterns)) {
                throw new Error('引入文件的模式必须返回一个数组!');
            }

            patterns = patterns.concat(meta.extraPatterns); //跟配置中的模式合并
            patterns = Patterns.fill(dir, patterns);
            patterns = Patterns.combine(dir, patterns);

            console.log('匹配到'.bgGreen, patterns.length.toString().cyan, '个 less 模式:');
            Log.logArray(patterns);

            meta.patterns = patterns;
            meta.outer = tags.begin + html + tags.end;

        },

        /**
        * 根据当前模式获取对应真实的 less 文件列表和将要产生 css 文件列表。
        */
        get: function () {
            var meta = mapper.get(this);
            var patterns = meta.patterns;
            var htdocsDir = meta.htdocsDir;
            var cssDir = meta.cssDir;
            var less$item = meta.less$item;
          
            var list = Patterns.getFiles(patterns);

            list.forEach(function (less) {
                //如 less = '../htdocs/html/test/style/less/index.less';

                if (less$item[less]) { //已处理过该项,针对 watch() 中的频繁调用。
                    return;
                }

                var name = path.relative(htdocsDir, less);  //如 'html/test/style/less/index.less'
                var ext = path.extname(name);               //如 '.less' 
                var basename = path.basename(name, ext);    //如 'index'

                name = path.dirname(name);              //如 'html/test/style/less'
                name = name.split('\\').join('.');      //如 'html.test.style.less'
                name = name + '.' + basename + '.css';  //如 'html.test.style.less.index.css'

                var file = path.join(cssDir, name);
                file = Path.format(file);

                var href = path.relative(meta.dir, file);
                href = Path.format(href);

                less$item[less] = {
                    'file': file,   //完整的 css 物理路径。
                    'href': href,   //用于 link 标签中的 href 属性(css)
                    'content': '',  //编译后的 css 内容。
                    'md5': '',      //编译后的 css 内容对应的 md5 值,需要用到时再去计算。
                };

                FileRefs.add(less);
                FileRefs.add(file);

            });

            meta.list = list;
            
           
        },


        /**
        * 获取 less 文件列表所对应的 md5 值和引用计数信息。
        */
        md5: function () {
            var meta = mapper.get(this);
            var list = meta.list;
            var file$stat = {};

            list.forEach(function (file) {

                var stat = file$stat[file];
                if (stat) {
                    stat['count']++;
                    return;
                }

                var md5 = MD5.read(file);

                file$stat[file] = {
                    'count': 1,
                    'md5': md5,
                };

            });

            return file$stat;
        },

        /**
        * 编译 less 文件列表(异步模式)。
        * 如果指定了要编译的列表,则无条件进行编译。
        * 否则,从原有的列表中过滤出尚未编译过的文件进行编译。
        * 已重载:
            compile(list, fn);
            compile(list, options);
            compile(list);
            compile(fn);
            compile(options);
            compile(options, fn);
        * @param {Array} [list] 经编译的 less 文件列表。 
            如果指定了具体的 less 文件列表,则必须为当前文件引用模式下的子集。 
            如果不指定,则使用原来已经解析出来的文件列表。
            提供了参数 list,主要是在 watch() 中用到。
        */
        compile: function (list, options) {
            var fn = null;
            if (list instanceof Array) {
                if (typeof options == 'function') { //重载 compile(list, fn);
                    fn = options;
                    options = null;
                }
                else if (typeof options == 'object') { //重载 compile(list, options);
                    fn = options.done;
                }
                else { //重载 compile(list);
                    options = null;
                }
            }
            else if (typeof list == 'function') { //重载 compile(fn);
                fn = list;
                list = null;
            }
            else if (typeof list == 'object') { //重载 compile(options); 或 compile(options, fn)
                fn = options;
                options = list;
                list = null;
                fn = fn || options.done;
            }


            options = options || {  //这个默认值不能删除,供开发时 watch 使用。
                'write': true,      //写入 css
                'delete': false,    //删除 less,仅提供给上层业务 build 时使用。
            };


            var Less = require('Less');
            var meta = mapper.get(this);
            var less$item = meta.less$item;

            var force = !!list;         //是否强制编译
            list = list || meta.list;

            if (list.length == 0) { //没有 less 文件
                fn && fn();
                return;
            }



            //并行地发起异步的 less 编译
            var Tasks = require('Tasks');
            Tasks.parallel({
                data: list,
                each: function (less, index, done) {
                    var item = less$item[less];

                    //没有指定强制编译,并且该文件已经编译过了,则跳过。
                    if (!force && item.content) {
                        done();
                        return;
                    }

                    Less.compile({
                        'src': less,
                        'dest': options.write ? item.file : '',
                        'delete': options.delete,
                        'compress': false,
                        'done': function (css) {
                            item.content = css;
                            done();
                        },
                    });

                },
                all: function () {
                    //已全部完成
                    fn && fn();
                },
            });
        },


        
        /**
        * 把当前的动态 less 引用模式块转成真实的静态 css 引用所对应的 html。
        */
        toHtml: function () {
            var meta = mapper.get(this);
            var sample = meta.sample;
            var list = meta.list;
            if (list.length == 0) {
                meta.html = '';
                return;
            }

            var tags = meta.tags;
            var less$item = meta.less$item;

            //todo: 检查重复的文件
            list = $.Array.keep(list, function (less, index) {

                var item = less$item[less];
                var href = item.href;

                var len = meta.md5;
                if (len > 0) {

                    var md5 = item.md5;
                    if (!md5) { //动态去获取 md5 值。
                        md5 = item.md5 = MD5.get(item.content, len);
                    }
         
                    href = href + '?' + md5;
                }

                return $.String.format(sample, {
                    'href': href,
                });
            });

            meta.html =
                tags.begin + '\r\n    ' +
                list.join('\r\n    ') + '\r\n    ' +
                tags.end + '\r\n    ';


        },

        /**
        * 把整一块动态 less 引用模式替换成真实的静态 css 引用。
        * @param {string} [master] 要替换的母版 html。 如果不指定,则使用原来的。
        *   注意,如果使用新的模板,则该模板中的模式不能变。
        */
        mix: function (master) {
            var meta = mapper.get(this);
            var outer = meta.outer;

            master = master || meta.master;

            //实现安全替换
            var beginIndex = master.indexOf(outer);
            var endIndex = beginIndex + outer.length;

            master =
                master.slice(0, beginIndex) +
                meta.html +
                master.slice(endIndex);

            return master;

        },

        /**
        * 合并对应的 css 文件列表。
        */
        concat: function (options) {

            var meta = mapper.get(this);
            var list = meta.list;
            if (list.length == 0) { //没有 less 文件
                meta.html = '';
                return;
            }



            if (options === true) { //直接指定了为 true,则使用默认配置。
                options = meta.concat;
            }

            var build = meta.build;
            var cssDir = meta.cssDir;
            var less$item = meta.less$item;
           

            var contents = [];

            list.forEach(function (less) {
                var item = less$item[less];
                contents.push(item.content);

                if (options.delete) { //删除源分 css 文件
                    FileRefs.delete(item.file);
                }
                
            });

            var content = contents.join('');



            var name = options.name || 32;
            var isMd5Name = typeof name == 'number';  //为数字时,则表示使用 md5 作为名称。
            var md5 = MD5.get(content);

            if (isMd5Name) {
                name = md5.slice(0, name) + '.css';
            }

            var file = cssDir + name;

            var href = path.relative(meta.dir, file);
            href = Path.format(href);

            if (options.write) { //写入合并后的 css 文件
                File.write(file, content);
            }


            $.Object.extend(build, {
                'file': file,
                'href': href,
                'content': content,
            });

            //更新 html

            //当不是以 md5 作为名称时,即当成使用固定的名称,如 index.all.debug.css,
            //为了确保能刷新缓存,这里还是强行加进了 md5 值作为 query 部分。
            var len = meta.md5;
            if (len > 0 && !isMd5Name) {
                href = href + '?' + md5.slice(0, len);
            }

            meta.html = $.String.format(meta.sample, {
                'href': href,
            });

        },

        /**
        * 压缩合并后的 css 文件。
        */
        minify: function (options, fn) {

            if (!options) {
                fn && fn();
                return;
            }


            var meta = mapper.get(this);
            if (meta.list.length == 0) { //没有 less 文件
                meta.html = '';
                fn && fn();
                return;
            }


            if (options === true) { //直接指定了为 true,则使用默认配置。
                options = meta.minify;
            }

            var cssDir = meta.cssDir;
            var build = meta.build;
            var content = build.content;

            var Less = require('Less');

            Less.render(content, {
                compress: true,

            }, function (error, output) {
            
                var content = output.css;

                var file = MD5.get(content);
                file = cssDir + file + '.css';

                var href = path.relative(meta.dir, file);
                href = Path.format(href);

                //删除 concat() 产生的文件
                if (options.delete) {
                    File.delete(build.file);
                }

                File.write(file, content);


                $.Object.extend(build, {
                    'file': file,
                    'href': href,
                    'content': content,
                });

                //更新 html
                meta.html = $.String.format(meta.sample, {
                    'href': href,
                });

                fn && fn();

            });

        },

        /**
        * 删除模式列表中所对应的 less 物理文件。
        */
        delete: function () {
            var meta = mapper.get(this);
            var list = meta.list;

            list.forEach(function (less) {
                FileRefs.delete(less);
            });

        },


        /**
        * 监控当前模式下的所有 less 文件。
        */
        watch: function () {
            var meta = mapper.get(this);
            var patterns = meta.patterns;

            if (patterns.length == 0) { //列表为空,不需要监控
                return;
            }

            var watcher = meta.watcher;
            if (!watcher) { //首次创建
                
                watcher = meta.watcher = new Watcher();

                var self = this;
                var less$item = meta.less$item;
                var emitter = meta.emitter;


                watcher.on({
                    'added': function (files) {
                        self.get();
                        self.compile(files, function () {
                            self.toHtml();
                            emitter.fire('change');
                        });
                        
                    },

                    'deleted': function (files) {

                        //删除对应的记录
                        files.forEach(function (less) {
                            var item = less$item[less];
                            delete less$item[less];

                            FileRefs.delete(less, true);
                            FileRefs.delete(item.file, true); //实时删除对应的 css 文件。

                        });

                        self.get();
                        self.toHtml();

                        emitter.fire('change');
                    },

                    //重命名的,会分别触发:deleted 和 renamed
                    'renamed': function (files) {
                        self.get();
                        self.compile(files, function () {
                            self.toHtml();
                            emitter.fire('change');
                        });
                        
                    },

                    'changed': function (files) {

                        //让对应的记录作废
                        files.forEach(function (less) {
                            var item = less$item[less];
                            item.md5 = '';
                            item.content = '';
                        });


                        //有一种情况:less 虽然发生了变化,但生成的 css 文件内容却不变。
                        //比如在 less 里加了些无用的空格、空行等。
                        var html = meta.html;

                        self.compile(files, function () {
                            self.toHtml();

                            //生成后的内容确实发生了变化
                            if (meta.html != html) {
                                emitter.fire('change');
                            }
                        });
                        
                    },

                });

            }

            watcher.set(patterns);
        },


        copy: function () {

        },


        /**
        * 绑定事件。
        */
        on: function (name, fn) {
            var meta = mapper.get(this);
            var emitter = meta.emitter;

            var args = [].slice.call(arguments, 0);
            emitter.on.apply(emitter, args);

            return this;
        },

    };



    return LessList;



});
Exemple #4
0
define('JsList', function (require, module, exports) {

    var $ = require('$');
    var path = require('path');

    var File = require('File');
    var FileRefs = require('FileRefs');
    var Path = require('Path');
    var Patterns = require('Patterns');
    var MD5 = require('MD5');
    var Watcher = require('Watcher');
    var Defaults = require('Defaults');
    var Log = require('Log');
    var Attribute = require('Attribute');
    var Lines = require('Lines');
    var Url = require('Url');

    var Emitter = $.require('Emitter');
    


    var mapper = new Map();




    function JsList(dir, config) {


        config = Defaults.clone(module.id, config);

        var rid = $.String.random(4); //随机 id

        var meta = {

            'dir': dir,         //母版页所在的目录。
            'master': '',       //母版页的内容,在 parse() 中用到。
            'html': '',         //模式所生成的 html 块,即缓存 toHtml() 方法中的返回结果。
            'outer': '',        //包括开始标记和结束标记在内的原始的整一块 html。
            'patterns': [],     //模式列表。
            'list': [],         //真实 js 文件列表及其它信息。
            'file$stat': {},    //记录文件内容中的最大行数和最大列数信息。 
            'file$md5': {}, 


            'scriptType': $.String.random(64),      //用于 script 的 type 值。 在页面压缩 js 时防止重复压缩。
            'emitter': new Emitter(this),
            'watcher': null,                        //监控器,首次用到时再创建。

            'extraPatterns': config.extraPatterns,  //额外附加的模式。
            'regexp': config.regexp,
            'md5': config.md5,
            'sample': config.sample,
            'tags': config.tags,
            'concat': config.concat,
            'minify': config.minify,
            'inline': config.inline,
            'max': config.max,              //允许的最大行数和列数。
            'htdocsDir': config.htdocsDir,

            //记录 concat, minify 的输出结果
            'build': {
                file: '',       //完整物理路径
                content: '',    //合并和压缩后的内容
            },

        };

        mapper.set(this, meta);

    }



    JsList.prototype = {
        constructor: JsList,

        /**
        * 重置为初始状态,即创建时的状态。
        */
        reset: function () {

            var meta = mapper.get(this);

            //删除之前的文件引用计数
            meta.list.forEach(function (item) {
                FileRefs.delete(item.file);         
            });


            $.Object.extend(meta, {
                'master': '',       //母版页的内容,在 parse() 中用到。
                'html': '',         //模式所生成的 html 块,即缓存 toHtml() 方法中的返回结果。
                'outer': '',        //包括开始标记和结束标记在内的原始的整一块 html。
                'patterns': [],     //模式列表。
                'list': [],         //真实 js 文件列表及其它信息。
                'file$stat': {},    //文件所对应的最大行数和最大列数等统计信息。 
                'file$md5': {},     //
                'build': {
                    file: '',       //完整物理路径
                    content: '',    //合并和压缩后的内容
                },
            });

        },


        /**
        * 从当前或指定的母版页 html 内容中提出 js 文件列表信息。
        * @param {string} master 要提取的母版页 html 内容字符串。
        */
        parse: function (master) {
            var meta = mapper.get(this);
            master = meta.master = master || meta.master;
           

            var tags = meta.tags;
            var dir = meta.dir;
        
            var html = $.String.between(master, tags.begin, tags.end);
            if (!html) {
                return;
            }

            var patterns = $.String.between(html, '<script>', '</script>');

            if (!patterns) {

                var list = html.match(meta.regexp);
                if (!list) {
                    return;
                }
                
                var lines = Lines.get(html);
                var startIndex = 0;

                patterns = $.Array.map(list, function (item, index) {

                    var src = Attribute.get(item, 'src');
                    if (!src) {
                        console.log('JsList 块里的 script 标签必须含有 src 属性:'.bgRed, item);
                        throw new Error();
                    }

                    var index = Lines.getIndex(lines, item, startIndex);
                    var line = lines[index];    //整一行的 html。

                    //所在的行给注释掉了,忽略
                    if (Lines.commented(line, item)) {
                        return null;
                    }

                    startIndex = index + 1; //下次搜索的起始行号
                    
                    if (Url.checkFull(src)) { //是绝对(外部)地址
                        console.log('JsList 块里的 script 标签 src 属性不能引用外部地址:'.bgRed, item);
                        throw new Error();
                    }

                    src = Path.format(src);
                    return src;
                });

                patterns = JSON.stringify(patterns, null, 4);
            }


            if (!patterns) {
                return;
            }

            //母版页中可能会用到的上下文。
            var context = {
                'dir': dir,
                'master': master,
                'tags': meta.tags,
                'htdocsDir': meta.htdocsDir,
            };

            var fn = new Function('require', 'context',
                //包装多一层匿名立即执行函数
                'return (function () { ' +
                    'var a = ' + patterns + '; \r\n' +
                    'return a;' +
                '})();'
            );

            //执行母版页的 js 代码,并注入变量。
            patterns = fn(require, context);

            if (!Array.isArray(patterns)) {
                throw new Error('引入文件的模式必须返回一个数组!');
            }

            patterns = patterns.concat(meta.extraPatterns); //跟配置中的模式合并
            patterns = Patterns.fill(dir, patterns);
            patterns = Patterns.combine(dir, patterns);

            console.log('匹配到'.bgGreen, patterns.length.toString().cyan, '个 js 模式:');
            Log.logArray(patterns);

            meta.patterns = patterns;
            meta.outer = tags.begin + html + tags.end;

        },

        /**
        * 根据当前模式获取对应真实的 js 文件列表和其它信息。
        */
        get: function () {
            var meta = mapper.get(this);
           
            //删除之前的文件引用计数
            meta.list.forEach(function (item) {
                FileRefs.delete(item.file);
            });

            var patterns = meta.patterns;
            var list = Patterns.getFiles(patterns);

            list = $.Array.keep(list, function (file, index) {

                file = Path.format(file);

                var href = path.relative(meta.dir, file);
                href = Path.format(href);

                FileRefs.add(file);

                return {
                    'file': file,
                    'href': href,
                };

            });

            meta.list = list;

        },


        /**
        * 获取 js 文件列表所对应的 md5 值和引用计数信息。
        */
        md5: function () {
            var meta = mapper.get(this);
            var file$md5 = meta.file$md5;
            var list = meta.list;
   
            var file$stat = {};

            list.forEach(function (item) {

                var file = item.file;
                var stat = file$stat[file];
                if (stat) {
                    stat['count']++;
                    return;
                }


                var md5 = file$md5[file];
                if (!md5) {
                    md5 = file$md5[file] = MD5.read(file);
                }

                file$stat[file] = {
                    'count': 1,
                    'md5': md5,
                };

            });

            return file$stat;
        },

        /**
        * 把当前的动态 js 引用模式块转成真实的静态 js 引用所对应的 html。
        */
        toHtml: function () {
            var meta = mapper.get(this);
            var sample = meta.sample;
            var list = meta.list;
            if (list.length == 0) {
                meta.html = '';
                return;
            }

            var tags = meta.tags;
            var file$stat = meta.file$stat;
            var file$md5 = meta.file$md5;

            var max = meta.max;

            //需要排除的文件列表,即不作检查的文件列表。
            var excludes = max.excludes;
            if (excludes) {
                excludes = Patterns.combine(meta.htdocsDir, excludes);
            }


            //todo: 检查重复的文件
            list = $.Array.keep(list, function (item, index) {
                var href = item.href;
                var file = item.file;

                var stat = file$stat[file];
                if (!stat) {
                    var content = File.read(file);
                    stat = file$stat[file] = Lines.stat(content);
                }

                //在排除列表中的文件,不作检查。
                //具体为: 如果未指定排除列表,或者不在排除列表中。
                if (!excludes || !Patterns.matchedIn(excludes, file)) {

                    if (stat.y > max.y) {
                        console.log('超出所允许的最大行数'.bgRed, JSON.stringify({
                            '所在文件': file,
                            '当前原始行数': stat.y0,
                            '当前有效行数': stat.y,
                            '允许最大行数': max.y,
                            '超过行数': stat.y - max.y,
                        }, null, 4).yellow);
                        throw new Error();
                    }

                    if (stat.x > max.x) {
                        console.log('代码行超出所允许的最大长度'.bgRed, JSON.stringify({
                            '所在文件': file,
                            '所在行号': stat.no,
                            '当前行长度': stat.x,
                            '允许最大长度': max.x,
                            '超过长度': stat.x - max.x,
                        }, null, 4).yellow);
                        throw new Error();
                    }
                }



                var len = meta.md5;
                if (len > 0) {

                    var md5 = file$md5[file];

                    if (!md5) { //动态去获取 md5 值。
                        md5 = file$md5[file] = MD5.read(file);
                    }

                    md5 = md5.slice(0, len);

                    href = href + '?' + md5;
                }

                return $.String.format(sample, {
                    'href': href,
                });
            });

            meta.html =
                tags.begin + '\r\n    ' +
                list.join('\r\n    ') + '\r\n    ' +
                tags.end + '\r\n';

        },

        /**
        * 把整一块动态 js 引用模式替换成真实的静态 js 引用。
        * @param {string} [master] 要替换的母版 html。 如果不指定,则使用原来的。
        *   注意,如果使用新的模板,则该模板中的模式不能变。
        */
        mix: function (master) {
            var meta = mapper.get(this);
            var outer = meta.outer;

            master = master || meta.master;

            //实现安全替换
            var beginIndex = master.indexOf(outer);
            var endIndex = beginIndex + outer.length;

            master =
                master.slice(0, beginIndex) +
                meta.html + 
                master.slice(endIndex);

            return master;

        },

        /**
        * 监控当前模式下 js 文件的变化。
        */
        watch: function () {
            var meta = mapper.get(this);
            var patterns = meta.patterns;
            if (patterns.length == 0) { //列表为空,不需要监控
                return;
            }


            var watcher = meta.watcher;

            if (!watcher) { //首次创建
               
                watcher = meta.watcher = new Watcher();

                var self = this;
                var file$stat = meta.file$stat;
                var file$md5 = meta.file$md5;
                var emitter = meta.emitter;

                watcher.on({
                    'added': function (files) {
                        self.get();
                        self.toHtml();
                        emitter.fire('change');
                    },

                    'deleted': function (files) {

                        //删除对应的记录
                        files.forEach(function (file) {
                            delete file$stat[file];
                            delete file$md5[file];
                            FileRefs.delete(file, true);
                        });

                        self.get();
                        self.toHtml();
                        emitter.fire('change');
                    },

                    //重命名的,会先后触发:deleted 和 renamed
                    'renamed': function (files) {
                        self.get();
                        self.toHtml();
                        emitter.fire('change');
                    },

                    'changed': function (files) {

                        //让对应的记录作废
                        files.forEach(function (file) {
                            file$stat[file] = null;
                            file$md5[file] = null;
                        });

                        self.toHtml();
                        emitter.fire('change');
                    },

                });
                
            }


            watcher.set(patterns);

        },

        /**
        * 合并对应的 js 文件列表。
        */
        concat: function (options) {

            var meta = mapper.get(this);
            var list = meta.list;
            if (list.length == 0) {
                meta.html = '';
                return;
            }


            if (options === true) { //直接指定了为 true,则使用默认配置。
                options = meta.concat;
            }
           

            list = $.Array.keep(list, function (item) {
                return item.file;
            });

            //加上文件头部和尾部,形成闭包
            var header = options.header;
            if (header) {
                header = Path.format(header);
                FileRefs.add(header);
                list = [header].concat(list);
            }

            var footer = options.footer;
            if (footer) {
                footer = Path.format(footer);
                FileRefs.add(footer);
                list = list.concat(footer);
            }

            var JS = require('JS');
            var content = JS.concat(list, {
                'addPath': options.addPath,
                'delete': options.delete,
            });


            var name = options.name || 32;
            var isMd5Name = typeof name == 'number';  //为数字时,则表示使用 md5 作为名称。
            var md5 = MD5.get(content);

            if (isMd5Name) {
                name = md5.slice(0, name) + '.js';
            }

            var file = meta.dir + name;

            if (options.write) { //写入合并后的 js 文件
                File.write(file, content);
            }


            $.Object.extend(meta.build, {
                'file': file,
                'content': content,
            });


            //更新 html

            var href = name;

            //当不是以 md5 作为名称时,即当成使用固定的名称,如 index.all.debug.js,
            //为了确保能刷新缓存,这里还是强行加进了 md5 值作为 query 部分。
            var len = meta.md5;
            if (len > 0 && !isMd5Name) { 
                href = href + '?' + md5.slice(0, len);
            }
           
            meta.html = $.String.format(meta.sample, {
                'href': href,
            });

        },


        /**
        * 压缩合并后的 js 文件。
        */
        minify: function (options) {
           
            var meta = mapper.get(this);
            if (meta.list.length == 0) {
                meta.html = '';
                return;
            }


            if (options === true) { //直接指定了为 true,则使用默认配置。
                options = meta.minify;
            }


            var build = meta.build;
            var content = build.content;

            if (options.delete) { //删除 concat() 产生的文件
                File.delete(build.file);
            }

            var JS = require('JS');
            content = JS.minify(content);    //直接从内容压缩,不读取文件


            var name = options.name || 32;
            var isMd5Name = typeof name == 'number';  //为数字时,则表示使用 md5 作为名称。
            var md5 = MD5.get(content);

            if (isMd5Name) {
                name = md5.slice(0, name) + '.js';
            }

            var file = meta.dir + name;

            if (options.write) {
                File.write(file, content);
            }


            $.Object.extend(build, {
                'file': file,
                'content': content,
            });

            //更新 html
            var href = name;

            //当不是以 md5 作为名称时,即当成使用固定的名称,如 index.all.debug.js,
            //为了确保能刷新缓存,这里还是强行加进了 md5 值作为 query 部分。
            var len = meta.md5;
            if (len > 0 && !isMd5Name) {
                href = href + '?' + md5.slice(0, len);
            }

            meta.html = $.String.format(meta.sample, {
                'href': href,
            });


        },

        /**
        * 把 js 文件的内容内联到 html 中。
        */
        inline: function (options) {

            var meta = mapper.get(this);
            if (meta.list.length == 0) {
                meta.html = '';
                return;
            }

            if (options === true) {//直接指定了为 true,则使用默认配置。
                options = meta.inline;
            }

            var build = meta.build;
            var content = build.content;

            //删除 concat() 或 minify() 产生的文件
            if (options.delete) {
                File.delete(build.file);
            }
            
            //添加一个随机的 type 值,变成不可执行的 js 代码,
            //可以防止在压缩页面时重复压缩本 js 代码。
            var sample = '<script type="{type}">{content}</script>'
            meta.html = $.String.format(sample, {
                'type': meta.scriptType,
                'content': content,
            });

        },

        /**
        * 移除临时添加进去的 script type,恢复成可执行的 script 代码。
        */
        removeType: function (master) {
            var meta = mapper.get(this);

            var tag = $.String.format('<script type="{type}">', {
                'type': meta.scriptType,
            });

            master = master.split(tag).join('<script>'); //replaceAll
            return master;
        },

        /**
        * 删除模式列表中所对应的 js 物理文件。
        */
        delete: function () {
            var meta = mapper.get(this);

            meta.list.forEach(function (item) {
                FileRefs.delete(item.file);
            });
        },

        /**
        * 绑定事件。
        */
        on: function (name, fn) {
            var meta = mapper.get(this);
            var emitter = meta.emitter;

            var args = [].slice.call(arguments, 0);
            emitter.on.apply(emitter, args);

            return this;
        },

    };


    return $.Object.extend(JsList, {

        //子类,用于提供实例方法:
        //检查 JsList 块里是否包含指定的 script 标签。
        Checker: (function () {

            var tags = null;

            function Checker(master) {
                tags = tags || Defaults.get(module.id).tags;
                this.html = $.String.between(master, tags.begin, tags.end);
            }

            Checker.prototype = {
                constructor: Checker,

                /**
                * 检查 JsList 块里是否包含指定的 script 标签。
                * 该方法主要是给 JsScripts 模块使用。
                * @param {string} 要检查的 html 文本内容。
                * @param {string} script 要检查的 script 标签内容。
                * @return {boolean} 返回一个布尔值,该值指示指定的 script 标签是否出现在 JsList 块里。
                */
                has: function (script) {
                    return this.html.indexOf(script) >= 0;
                },
            };

            return Checker;

        })(),

    });



});
Exemple #5
0
define('/Favorites', function (require, module, exports) {

    var $ = require('$');
    var Directory = require('Directory');
    var File = require('File');
    var Config = require('Config');
    var API = require('API');
    var Image = require('Image');

    var Emitter = $.require('Emitter');
    var Parser = module.require('Parser');


    /**
    * 构造器。
    */
    function Favorites(config) {

        //重载 Favorites(userId)
        if (typeof config == 'string') {
            config = { 'userId': config };
        }

        config = Config.get(module.id, config);

        var dir = Directory.root();
        var userId = config.userId;

        var data = {
            'userId': userId,
            'dir': dir.slice(0, -1),
        };

        var url = $.String.format(config.url, data);
        var html = config.html;
        var json = config.json;

        var meta = {
            'userId': userId,
            'url': url,
            'host': url.split('/').slice(0, 3).join('/'),   
            'cache': config.cache,
            'html': {
                'file': $.String.format(html.file, data),
                'write': html.write,
            },
            'json': {
                'file': $.String.format(json.file, data),
                'write': json.write,
            },

            'emitter': new Emitter(this),
        };

        this.meta = meta;
    }


    //实例方法
    Favorites.prototype = { 
        constructor: Favorites,

        /**
        * 发起 GET 网络请求以获取信息。
        */
        get: function (fn) {

            fn && this.on('get', fn);

            var meta = this.meta;
            var emitter = meta.emitter;
            

            var api = new API({
                'cache': meta.cache,
                'file': meta.html.file,
                'url': meta.url,
                'parser': Parser,
            });

            api.on({
                'success': function (data) {
                    //增加些字段。
                    data = $.Object.extend(data, {
                        'userId': meta.userId,
                        'url': meta.url,
                        'host': meta.host,
                    });

                    if (meta.json.write) {
                        File.writeJSON(meta.json.file, data);
                    }

                    emitter.fire('get', [data]);
                },
            });

            api.get();
        },

        /**
        * 绑定事件。
        * 已重载 on({...},因此支持批量绑定。
        * @param {string} name 事件名称。
        * @param {function} fn 回调函数。
        */
        on: function (name, fn) {
            var meta = this.meta;
            var emitter = meta.emitter;
            var args = [].slice.call(arguments, 0);
            emitter.on.apply(emitter, args);
        },

    };

    return Favorites;



});
Exemple #6
0
define('SerialTasks', function (require, module, exports) {

    var $ = require('$');
    var Emitter = $.require('Emitter');

   
    function SerialTasks(list) {

        var meta = {
            'list': list,
            'emitter': new Emitter(this),
        };

        this.meta = meta;

    }



    SerialTasks.prototype = {
        constructor: SerialTasks,


        run: function () {
            var meta = this.meta;
            var list = meta.list;
            var emitter = meta.emitter;

            var index = 0;
            var len = list.length;
            var values = [];        

            function process() {
                var item = list[index];

                emitter.fire('each', [item, index, function (value) {
                    index++;
                    values.push(value); //需要收集的值,由调用者传入。

                    if (index < len) {
                        process();
                    }
                    else {
                        emitter.fire('all', [values]);
                    }
                }]);
            }

            process();
        },



        /**
        * 绑定事件。
        * 已重载 on({...},因此支持批量绑定。
        * @param {string} name 事件名称。
        * @param {function} fn 回调函数。
        */
        on: function (name, fn) {
            var meta = this.meta;
            var emitter = meta.emitter;
            var args = [].slice.call(arguments, 0);
            emitter.on.apply(emitter, args);
        },
    };

    return SerialTasks;




});
Exemple #7
0
define('LessLinks', function (require, module, exports) {

    var $ = require('$');
    var path = require('path');

    var Watcher = require('Watcher');
    var Defaults = require('Defaults');
    var MD5 = require('MD5');
    var File = require('File');
    var FileRefs = require('FileRefs');
    var Lines = require('Lines');
    var Path = require('Path');
    var Url = require('Url');
    var Attribute = require('Attribute');

    var Mapper = $.require('Mapper');
    var Emitter = $.require('Emitter');
    var $Url = $.require('Url');

    var mapper = new Mapper();



    function LessLinks(dir, config) {

        Mapper.setGuid(this);

        config = Defaults.clone(module.id, config);

        var meta = {
            'dir': dir,

            'master': '',
            'list': [],
            'lines': [],        //html 换行拆分的列表
            'less$item': {},    //less 文件所对应的信息

            'emitter': new Emitter(this),
            'watcher': null,    //监控器,首次用到时再创建

            'regexp': config.regexp,
            'md5': config.md5,          //填充模板所使用的 md5 的长度
            'sample': config.sample,    //使用的模板
            'tags': config.tags,
            'htdocsDir': config.htdocsDir,
            'cssDir': config.cssDir,
            'minify': config.minify,
          
        };





        mapper.set(this, meta);

    }



    LessLinks.prototype = {
        constructor: LessLinks,

        /**
        * 重置为初始状态,即创建时的状态。
        * @param {boolean} keep 是否保留之前编译过的信息。
        *   如果需要保留,请指定为 true;否则指定为 false 或不指定。
        */
        reset: function (keep) {
            var meta = mapper.get(this);
            var less$item = meta.less$item;

            meta.list.forEach(function (obj) {
                var less = obj.file;
                var item = less$item[less];

                FileRefs.delete(less);
                FileRefs.delete(item.file);
            });

            $.Object.extend(meta, {
                'master': '',
                'list': [],
                'lines': [],        //html 换行拆分的列表
                'less$item': keep ? less$item : {},    //less 文件所对应的信息
            });
        },

        /**
        * 从当前或指定的母版页 html 内容中提出 less 文件列表信息。
        * @param {string} master 要提取的母版页 html 内容字符串。
        */
        parse: function (master) {

            var meta = mapper.get(this);
            master = meta.master = master || meta.master;

            //这里必须要有,不管下面的 list 是否有数据。
            var lines = Lines.get(master);
            meta.lines = lines;

            //提取出 link 标签
            var list = master.match(meta.regexp);
            if (!list) {
                return;
            }

            var startIndex = 0;         //搜索的起始行号

            list = $.Array.map(list, function (item, index) {

                var href = Attribute.get(item, 'href');
                if (!href) {
                    return null;
                }

                var index = Lines.getIndex(lines, item, startIndex);
                startIndex = index + 1;     //下次搜索的起始行号。 这里要先加

                var line = lines[index];    //整一行的 html。
                lines[index] = null;        //先清空,后续会在 mix() 中重新计算而填进去。

                //所在的行给注释掉了,忽略
                if (Lines.commented(line, item)) {
                    return null;
                }

                var file = Path.join(meta.dir, href);

                return {
                    'file': file,
                    'index': index,     //行号,从 0 开始。
                    'html': item,       //标签的 html 内容。
                    'line': line,       //整一行的 html 内容。
                };
            });

            meta.list = list;



        },


        /**
        * 根据当前真实的 less 文件列表获取对应将要产生 css 文件列表。
        */
        get: function () {
            var meta = mapper.get(this);

            var htdocsDir = meta.htdocsDir;
            var cssDir = meta.cssDir;
            var less$item = meta.less$item;

            meta.list.forEach(function (item) {
                var less = item.file;

                //如 less = '../htdocs/html/test/style/less/index.less';

                if (less$item[less]) { //已处理过该项,针对 watch() 中的频繁调用。
                    return;
                }


                var name = path.relative(htdocsDir, less);  //如 'html/test/style/less/index.less'
                var ext = path.extname(name);               //如 '.less' 
                var basename = path.basename(name, ext);    //如 'index'

                name = path.dirname(name);              //如 'html/test/style/less'
                name = name.split('\\').join('.');      //如 'html.test.style.less'
                name = name + '.' + basename + '.css';  //如 'html.test.style.less.index.css'

                var file = path.join(cssDir, name);
                file = Path.format(file);

                var href = path.relative(meta.dir, file);
                href = Path.format(href);

                less$item[less] = {
                    'file': file,   //完整的 css 物理路径。
                    'href': href,   //用于 link 标签中的 href 属性(css)
                    'content': '',  //编译后的 css 内容。
                    'md5': '',      //编译后的 css 内容对应的 md5 值,需要用到时再去计算。
                };

                FileRefs.add(less);
                FileRefs.add(file);

            });



        },


        /**
        * 获取 less 文件列表所对应的 md5 值和引用计数信息。
        */
        md5: function () {
            var meta = mapper.get(this);
            var list = meta.list;
            var file$stat = {};

            list.forEach(function (item) {

                var file = item.file;
                var stat = file$stat[file];

                if (stat) {
                    stat['count']++;
                    return;
                }

                var md5 = MD5.read(file);

                file$stat[file] = {
                    'count': 1,
                    'md5': md5,
                };

            });

            return file$stat;
        },



        /**
        * 编译 less 文件列表(异步模式)。
        * 如果指定了要编译的列表,则无条件进行编译。
        * 否则,从原有的列表中过滤出尚未编译过的文件进行编译。
        * 已重载:
            compile(list, fn);
            compile(list, options);
            compile(list);
            compile(fn);
            compile(options);
            compile(options, fn);
        * @param {Array} [list] 经编译的 less 文件列表。 
            如果指定了具体的 less 文件列表,则必须为当前文件引用模式下的子集。 
            如果不指定,则使用原来已经解析出来的文件列表。
            提供了参数 list,主要是在 watch() 中用到。
        */
        compile: function (list, options) {
            var fn = null;
            if (list instanceof Array) {
                if (typeof options == 'function') { //重载 compile(list, fn);
                    fn = options;
                    options = null;
                }
                else if (typeof options == 'object') { //重载 compile(list, options);
                    fn = options.done;
                }
                else { //重载 compile(list);
                    options = null;
                }
            }
            else if (typeof list == 'function') { //重载 compile(fn);
                fn = list;
                list = null;
            }
            else if (typeof list == 'object') { //重载 compile(options); 或 compile(options, fn)
                fn = options;
                options = list;
                list = null;
                fn = fn || options.done;
            }


            options = options || {  //这个默认值不能删除,供开发时 watch 使用。
                'write': true,      //写入 css
                'minify': false,    //使用压缩版。
                'delete': false,    //删除 less,仅提供给上层业务 build 时使用。
            };


            var Less = require('Less');
            var meta = mapper.get(this);
            var less$item = meta.less$item;
            var force = !!list;         //是否强制编译

            list = list || meta.list.map(function (item) {
                return item.file;
            });


            if (list.length == 0) { //没有 less 文件
                fn && fn();
                return;
            }



            //并行地发起异步的 less 编译
            var Tasks = require('Tasks');
            Tasks.parallel({
                data: list,
                each: function (less, index, done) {
                    var item = less$item[less];

                    //没有指定强制编译,并且该文件已经编译过了,则跳过。
                    if (!force && item.content) {
                        done();
                        return;
                    }

                    Less.compile({
                        'src': less,
                        'dest': options.write ? item.file : '',
                        'delete': options.delete,
                        'compress': options.minify,
                        'done': function (css) {
                            item.content = css;
                            done();
                        },
                    });

                },
                all: function () {  //已全部完成
                    fn && fn();
                },
            });
        },


        /**
        * 监控 css 文件的变化。
        */
        watch: function () {
            var meta = mapper.get(this);

            //这里不要缓存起来,因为可能在 parse() 中给重设为新的对象。
            //var list = meta.list; 
            var watcher = meta.watcher;

            if (!watcher) { //首次创建。
                watcher = meta.watcher = new Watcher();

                var self = this;
                var emitter = meta.emitter;
                var less$item = meta.less$item;

                watcher.on({

                    'deleted': function (files) {
                        console.log('文件已给删除'.yellow, files);
                    },

                    'changed': function (files) {

                        //让对应的记录作废
                        files.forEach(function (less) {
                            var item = less$item[less];
                            item.md5 = '';
                            item.content = '';

                            //根据当前文件名,找到具有相同文件名的节点集合。
                            //让对应的 html 作废。
                            meta.list.forEach(function (item) {
                                if (item.file != less) {
                                    return;
                                }

                                meta.lines[item.index] = null;
                            });
                        });

                        self.compile(files, function () {
                            emitter.fire('change');
                        });

                    },

                });
            }


            var files = meta.list.map(function (item) {
                return item.file;
            });

            watcher.set(files);

        },


        /**
        * 
        */
        mix: function () {
            var meta = mapper.get(this);
            var list = meta.list;
            var lines = meta.lines;
            var replace = $.String.replaceAll;
            var len = meta.md5;
            var less$item = meta.less$item;
            var sample = meta.sample;


            list.forEach(function (obj) {

                var index = obj.index;
                if (lines[index]) { //之前已经生成过了
                    return;
                }

                var less = obj.file;
                var item = less$item[less];
                var href = item.href;

                if (len > 0) {
                    var md5 = item.md5;
                    if (!md5) { //动态去获取 md5 值。
                        md5 = item.md5 = MD5.get(item.content, len);
                    }

                    href = href + '?' + md5;
                }

                var html = $.String.format(sample, {
                    'href': href,
                });

                var line = replace(obj.line, obj.html, html);

                lines[index] = line;

            });


            return Lines.join(lines);

        },





        /**
        * 绑定事件。
        */
        on: function (name, fn) {
            var meta = mapper.get(this);
            var emitter = meta.emitter;

            var args = [].slice.call(arguments, 0);
            emitter.on.apply(emitter, args);

            return this;
        },



    };



    return LessLinks;



});
Exemple #8
0
define('/Page', function (require, module, exports) {

    var $ = require('$');
    var Directory = require('Directory');
    var File = require('File');
    var Config = require('Config');
    var API = require('API');

    var Emitter = $.require('Emitter');
    var Parser = module.require('Parser');

    /**
    * 构造器。
    */
    function Page(config) {
        config = Config.get(module.id, config);

        var dir = Directory.root();
        var userId = config.userId; 
        var no = config.no;         //当前页码,从 1 开始。
        var total = config.total;   //总页数。
        var sn = no - total;        //编程页码,最后一页为 0,倒数第二页为 -1,倒数第三页为 -2,依次类推。

        var data = {
            'dir': dir.slice(0, -1),
            'userId': userId,
            'no': no,
            'sn': sn,
        };

        var url = $.String.format(config.url, data);

        var html = config.html;
        var json = config.json;

        var meta = {
            'userId': userId,
            'url': url,
            'no': no,
            'total': total,
            'cache': config.cache,
            'html': {
                'file': $.String.format(html.file, data),
                'write': html.write,
            },
            'json': {
                'file': $.String.format(json.file, data),
                'write': json.write,
            },

            'emitter': new Emitter(this),
        };

        this.meta = meta;
    }






    Page.prototype = { //实例方法
        constructor: Page,

        /**
        * 发起 GET 网络请求以获取信息。
        */
        get: function (fn) {

            fn && this.on('get', fn);

            var meta = this.meta;
            var emitter = meta.emitter;

            var api = new API({
                'cache': meta.cache,
                'file': meta.html.file,
                'url': meta.url,
                'parser': Parser,
            });

            api.on({
                'success': function (data) {

                    $.Object.extend(data, {
                        'userId': meta.userId,
                        'url': meta.url,
                        'no': meta.no,
                        'total': meta.total,
                    });

                    if (meta.json.write) {
                        File.writeJSON(meta.json.file, data);
                    }

                    emitter.fire('get', [data]);
                },
                'fail': function () {
                    emitter.fire('get', [null]);
                },
                'error': function () {
                    emitter.fire('get', []);
                },
            });

            api.get();
        },

        /**
        * 绑定事件。
        * 已重载 on({...},因此支持批量绑定。
        * @param {string} name 事件名称。
        * @param {function} fn 回调函数。
        */
        on: function (name, fn) {
            var meta = this.meta;
            var emitter = meta.emitter;
            var args = [].slice.call(arguments, 0);
            emitter.on.apply(emitter, args);
        },

    };




    return Page;



});
Exemple #9
0
define('Package', function (require, module, exports) {

    var $ = require('$');
    var path = require('path');

    var File = require('File');
    var FileRefs = require('FileRefs');
    var Path = require('Path');
    var Watcher = require('Watcher');
    var Defaults = require('Defaults');
    var Lines = require('Lines');
    var Url = require('Url');
    var Patterns = require('Patterns');
    var Log = require('Log');

    var Emitter = $.require('Emitter');
    var HtmlPackage = require('HtmlPackage');
    var JsPackage = require('JsPackage');
    var LessPackage = require('LessPackage');

    var mapper = new Map();
    var name$file = {};         //记录包的名称与文件名的对应关系,防止出现重名的包。



    function Package(file, config) {

        config = Defaults.clone(module.id, config);

        var htdocsDir = config.htdocsDir;
        file = Path.join(htdocsDir, file);

        var dir = Path.dirname(file); //分包 package.json 文件所在的目录

        var meta = {
            'dir': dir, 
            'file': file,

            'htdocsDir': htdocsDir,
            'packageDir': config.packageDir,
            'cssDir': config.cssDir,
            'compile': config.compile,
            'minify': config.minify,
            'md5': config.md5,


            'emitter': new Emitter(this),
            'watcher': null, //监控器,首次用到时再创建

            'old': {},      //用来存放旧的 HtmlPackage、JsPackage 和 LessPackage。

            'HtmlPackage': null,
            'JsPackage': null,
            'LessPackage': null,

            'css': '',
            'html': '',
            'js': '',
        };

        mapper.set(this, meta);
    }



    Package.prototype = {
        constructor: Package,

        /**
        * 重置上一次可能存在的结果。
        */
        reset: function () {
            var meta = mapper.get(this);
            var old = meta.old;

            //先备份。 old 中的一旦有值,将再也不会变为 null。
            old.HtmlPackage = meta.HtmlPackage;
            old.JsPackage = meta.JsPackage;
            old.LessPackage = meta.LessPackage;

            //再清空。
            $.Object.extend(meta, {
                'HtmlPackage': null,
                'JsPackage': null,
                'LessPackage': null,

                'css': '',
                'html': '',
                'js': '',
            });
        },

        /**
        * 
        */
        parse: function () {
            var meta = mapper.get(this);
            var file = meta.file;
            var dir = meta.dir;
            var htdocsDir = meta.htdocsDir;
 
            var json = File.readJSON(file);
            var name = json.name;

            //如果未指定 name,则以包文件所在的目录的第一个 js 文件名作为 name。
            if (!name) {
                var files = Patterns.getFiles(dir, '*.js');
                name = files[0];
                if (!name) {
                    console.log('包文件'.bgRed, file.yellow, '中未指定 name 字段,且未在其的所在目录找到任何 js 文件。'.bgRed);
                    throw new Error();
                }
                name = Path.relative(dir, name);
                name = name.slice(0, -3); //去掉 `.js` 后缀。
            }
            else if (name == '*') {
                name = Path.relative(htdocsDir, dir);
                name = name.split('/').join('.');
            }

            var oldFile = name$file[name];
            if (oldFile && oldFile != file) {
                console.log('存在同名'.bgRed, name.green, '的包文件:'.bgRed);
                Log.logArray([oldFile, file], 'yellow');
                throw new Error();
            }

            name$file[name] = file;
            meta.name = name;

            var old = meta.old;
            var packageDir = htdocsDir + meta.packageDir;

            if (json.html) {
                meta.HtmlPackage = old.HtmlPackage || new HtmlPackage(dir);
                meta.html = {
                    'src': json.html,
                    'dir': packageDir,
                    'dest': packageDir + name + '.html',
                    'md5': '',
                };
            }

            if (json.js) {
                meta.JsPackage = old.JsPackage || new JsPackage(dir);
                meta.js = {
                    'src': json.js,
                    'dir': packageDir,
                    'dest': packageDir + name + '.js',
                    'md5': '',
                };
            }

            if (json.css) {
                var cssDir = htdocsDir + meta.cssDir;
                meta.LessPackage = old.LessPackage || new LessPackage(dir);
                meta.css = {
                    'src': json.css,
                    'dir': cssDir,
                    'dest': cssDir + name + '.css',
                    'md5': '',
                };
            }

        },
        
        /**
        * 编译当前包文件。
        */
        compile: function (options, done) {

            //重载 compile(done)
            if (typeof options == 'function') {
                done = options;
                options = null;
            }

            var meta = mapper.get(this);
            var HtmlPackage = meta.HtmlPackage;
            var JsPackage = meta.JsPackage;
            var LessPackage = meta.LessPackage;


            options = options || meta.compile;

            if (HtmlPackage) {
                var file = options.html.write ? meta.html.dest : '';

                HtmlPackage.reset();
                HtmlPackage.get(meta.html.src);

                meta.html.md5 = HtmlPackage.compile(file);

                if (options.html.delete) {
                    HtmlPackage.delete();
                }
            }
           

            if (JsPackage) {
                var js = options.js;
                js.dest = js.write ? meta.js.dest : '';

                JsPackage.reset();
                JsPackage.get(meta.js.src);
                meta.js.md5 = JsPackage.concat(js);
            }
            

            if (LessPackage) {
                var less = options.less;
                var opt = { delete: less.delete };

                LessPackage.reset();
                LessPackage.get(meta.css.src);

                LessPackage.compile(opt, function () {

                    var css = less.write ? meta.css.dest : '';
                    meta.css.md5 = LessPackage.concat(css);

                    done && done();
                });
            }
            else {
                done && done();
            }
           
        },

        /**
        * 压缩。
        */
        minify: function (options, done) {
            //重载 minify(done)
            if (typeof options == 'function') {
                done = options;
                options = null;
            }

            var meta = mapper.get(this);
            var dest = meta.dest;
            var HtmlPackage = meta.HtmlPackage;
            var JsPackage = meta.JsPackage;
            var LessPackage = meta.LessPackage;


            options = options || meta.minify;

            if (HtmlPackage) {
                var opt = options.html;
                if (opt) {
                    if (opt === true) { //当指定为 true 时,则使用默认的压缩选项。
                        opt = meta.minify.html;
                    }

                    var html = meta.html;
                    html.dest = HtmlPackage.minify(opt, {
                        'dir': html.dir,
                        'name': 32,         //md5 的长度。
                    });

                    html.md5 = '';
                }
            }

            if (JsPackage) {
                var opt = options.js;
                if (opt && opt.write) {
                    var js = meta.js;
                    js.dest = JsPackage.minify({
                        'dir': js.dir,
                        'name': 32,
                    });

                    js.md5 = '';
                }
            }
            
            if (LessPackage) {
                var opt = options.less;
                if (opt && opt.write) {
                    var css = meta.css;
                    var dest = {
                        'dir': css.dir,
                        'name': 32,         //md5 的长度。
                    };

                    LessPackage.minify(dest, function (dest, content) {
                        css.dest = dest;
                        css.md5 = '';

                        done && done();
                    });
                }
                else {
                    done && done();
                }
            }
            else {
                done && done();
            }
           
           
        },

        /**
        * 监控当前包文件及各个资源引用模块。
        */
        watch: function () {
            var meta = mapper.get(this);
            var HtmlPackage = meta.HtmlPackage;
            var JsPackage = meta.JsPackage;
            var LessPackage = meta.LessPackage;
            var emitter = meta.emitter;
            var old = meta.old;

            if (HtmlPackage) {
                HtmlPackage.watch();
                if (!old.HtmlPackage) {
                    HtmlPackage.on('change', function () {
                        var html = meta.html;
                        html.md5 = HtmlPackage.compile(html.dest);
                        emitter.fire('change');
                    });
                }
            }
            else if(old.HtmlPackage) {
                old.HtmlPackage.unwatch();
            }


            if (JsPackage) {
                JsPackage.watch();
                if (!old.JsPackage) {
                    JsPackage.on('change', function () {
                        var js = meta.js;
                        js.md5 = JsPackage.concat({ 'dest': js.dest, });
                        emitter.fire('change');
                    });
                }
            }
            else if (old.JsPackage) {
                old.JsPackage.unwatch();
            }

            if (LessPackage) {
                LessPackage.watch();
                if (!old.LessPackage) {
                    LessPackage.on('change', function () {
                        var css = meta.css;
                        css.md5 = LessPackage.concat(css.dest);
                        emitter.fire('change');
                    });
                }
            }
            else if (old.LessPackage) {
                old.LessPackage.unwatch();
            }

       
            var watcher = meta.watcher;
            if (!watcher) {

                var self = this;

                watcher = meta.watcher = new Watcher();
                watcher.set(meta.file);      //这里只需要添加一次

                watcher.on('changed', function () {
                    self.reset();
                    self.parse();       //json 文件发生变化,重新解析。
                    self.compile();     //根节点发生变化,需要重新编译。
                    self.watch();

                    emitter.fire('change');

                });
            }


        },

        /**
        * 构建。
        */
        build: function (options, done) {

            var pkg = this;
            pkg.parse();

            pkg.compile(options.compile, function () {

                pkg.minify(options.minify, function () {
           
                    done && done();

                });
            });

        },

        /**
        * 绑定事件。
        */
        on: function (name, fn) {
            var meta = mapper.get(this);
            var emitter = meta.emitter;

            var args = [].slice.call(arguments, 0);
            emitter.on.apply(emitter, args);

            return this;
        },

        clean: function () {
            var meta = mapper.get(this);
            FileRefs.delete(meta.file);
        },

        /**
        * 获取输出目标包的信息。
        * 该方法由静态方法 write 调用。
        */
        get: function () {
            var meta = mapper.get(this);
            var name = meta.name;
            var htdocsDir = meta.htdocsDir;

            var data = {};

            ['js', 'html', 'css'].forEach(function (type) {

                var item = meta[type];
                if (!item) {
                    return;
                }

                var href = Path.relative(htdocsDir, item.dest);
                var md5 = item.md5.slice(0, meta.md5);

                if (md5) {
                    href = href + '?' + md5;
                }

                data[type] = href;

            });

            var obj = {};
            obj[name] = data;

            return obj;
        },


    };


    //静态方法。
    return $.Object.extend(Package, {

        /**
        * 写入到指定的总包。
        */
        write: function (dest, pkgs, minify) {

            var json = File.readJSON(dest) || {};

            pkgs.forEach(function (pkg) {

                var obj = pkg.get();

                $.Object.extend(json, obj);
            });
      
            File.writeJSON(dest, json, minify);
        },
    });



});
Exemple #10
0
define('/Avatar', function (require, module, exports) {

    var $ = require('$');
    var Directory = require('Directory');
   
    var Config = require('Config');
    var Image = require('Image');

    var Emitter = $.require('Emitter');

    /**
    * 构造器。
    */
    function Avatar(config) {

        config = Config.get(module.id, config);

        var dir = Directory.root();
        var userId = config.userId;

        //用于 config 中的模板填充。
        var data = {
            'userId': userId,
            'dir': dir.slice(0, -1),
        };

        var url = config.url;
        if (!url.startsWith('http')) { // 有些以 `//` 开头
            url = config.host.split('//')[0] + url;
        }

        var meta = {
            'userId': userId,
            'url': url,
            'cache': config.cache,
            'file': $.String.format(config.file, data),
            'write': config.write,

            'emitter': new Emitter(this),
        };

        this.meta = meta;
    }






    Avatar.prototype = { //实例方法
        constructor: Avatar,

        /**
        * 发起 GET 网络请求以获取信息。
        */
        get: function (fn) {

            fn && this.on('get', fn);

            var meta = this.meta;
            var emitter = meta.emitter;
  
            Image.get({
                'cache': meta.cache,
                'url': meta.url,
                'file': meta.file,
                'done': function (error) {
                    emitter.fire('get', []);
                },
            });
        },

        /**
        * 绑定事件。
        * 已重载 on({...},因此支持批量绑定。
        * @param {string} name 事件名称。
        * @param {function} fn 回调函数。
        */
        on: function (name, fn) {
            var meta = this.meta;
            var emitter = meta.emitter;
            var args = [].slice.call(arguments, 0);
            emitter.on.apply(emitter, args);
        },

    };

    return Avatar;



});
Exemple #11
0
define('/FavUsers', function (require, module, exports) {

    var $ = require('$');

    var Url = $.require('Url');

    var Directory = require('Directory');
    var File = require('File');
    var Config = require('Config');
    var API = require('API');

    var Emitter = $.require('Emitter');
    var Parser = module.require('Parser');

    /**
    * 构造器。
    */
    function FavUsers(config) {

        config = Config.get(module.id, config);

        var dir = Directory.root();
        var userId = config.userId;

        var data = {
            'userId': userId,
            'dir': dir.slice(0, -1),
        };

   

        var html = config.html;
        var json = config.json;
        var host = config.host;

        var url = host + config.url;
        url = Url.addQueryString(url, { 'size': 100000, }); //增大每页的记录数,以便一次性全取回来。

        var meta = {
            'userId': userId,
            'url': url,
            'host': host,
            'cache': config.cache,
            'html': {
                'file': $.String.format(html.file, data),
                'write': html.write,
            },
            'json': {
                'file': $.String.format(json.file, data),
                'write': json.write,
            },

            'emitter': new Emitter(this),
        };

        this.meta = meta;
    }






    FavUsers.prototype = { //实例方法
        constructor: FavUsers,

        /**
        * 发起 GET 网络请求以获取信息。
        */
        get: function (fn) {

            fn && this.on('get', fn);

            var meta = this.meta;
            var emitter = meta.emitter;
     
            var api = new API({
                'cache': meta.cache,
                'file': meta.html.file,
                'url': meta.url,
                'parser': Parser,
            });

            api.on({
                'success': function (data) {
                    data = $.Object.extend(data, {
                        'userId': meta.userId,
                        'url': meta.url,
                        'host': meta.host,
                    });


                    if (meta.json.write) {
                        File.writeJSON(meta.json.file, data);
                    }

                    emitter.fire('get', [data]);
                },
                'fail': function () {
                    emitter.fire('get', [null]);
                },
                'error': function () {
                    emitter.fire('get', []);
                },
            });

            api.get();
        },

        /**
        * 绑定事件。
        * 已重载 on({...},因此支持批量绑定。
        * @param {string} name 事件名称。
        * @param {function} fn 回调函数。
        */
        on: function (name, fn) {
            var meta = this.meta;
            var emitter = meta.emitter;
            var args = [].slice.call(arguments, 0);
            emitter.on.apply(emitter, args);
        },

    };

    return FavUsers;



});
Exemple #12
0
define('JsScripts', function (require, module, exports) {

    var $ = require('$');
    var path = require('path');

    var Watcher = require('Watcher');
    var Defaults = require('Defaults');
    var MD5 = require('MD5');
    var File = require('File');
    var FileRefs = require('FileRefs');
    var Lines = require('Lines');
    var Path = require('Path');
    var Url = require('Url');
    var Attribute = require('Attribute');

    var Mapper = $.require('Mapper');
    var Emitter = $.require('Emitter');
    var $Url = $.require('Url');

    var mapper = new Mapper();



    function JsScripts(dir, config) {

 
        Mapper.setGuid(this);

        config = Defaults.clone(module.id, config);

        var meta = {
            'dir': dir,
            'master': '',
            'list': [],     //js 文件列表及其它信息。
            'lines': [],    //html 换行拆分的列表
            'file$md5': {},
         
            'emitter': new Emitter(this),
            'watcher': null,    //监控器,首次用到时再创建。

            'regexp': config.regexp,
            'md5': config.md5,
            'exts': config.exts,
            'minify': config.minify,
        };

        mapper.set(this, meta);


    }



    JsScripts.prototype = {
        constructor: JsScripts,

        /**
        * 重置为初始状态,即创建时的状态。
        */
        reset: function () {

            var meta = mapper.get(this);


            meta.list.forEach(function (item) {
                FileRefs.delete(item.file); //删除之前的文件引用计数
                FileRefs.delete(item.build.file); //删除之前的文件引用计数
            });


            $.Object.extend(meta, {
                'master': '',
                'list': [],
                'lines': [],
                'file$md5': {},
            });

        },

        /**
        * 从当前或指定的母版页 html 内容中提出 js 文件列表信息。
        * @param {string} master 要提取的母版页 html 内容字符串。
        */
        parse: function (master) {

            var meta = mapper.get(this);
            master = meta.master = master || meta.master;

            //这个不能少,不管下面的 list 是否为空。 在 mix() 中用到。
            var lines = Lines.get(master);
            meta.lines = lines;             


            //<script src="f/jquery/jquery-2.1.1.debug.js"></script>
            //提取出含有 src 属性的 script 标签
            //var reg = /<script\s+.*src\s*=\s*["\'][\s\S]*?["\'].*>[\s\S]*?<\/script>/ig;
            //var reg = /<script[^>]*?>[\s\S]*?<\/script>/gi;
            //var reg = /<script\s+.*src\s*=\s*[^>]*?>[\s\S]*?<\/script>/gi;
            var list = master.match(meta.regexp);

            if (!list) {
                return;
            }

            var debug = meta.exts.debug;
            var min = meta.exts.min;
            
            var Checker = require('JsList').Checker;
            var JsList = new Checker(master);

            var startIndex = 0;

            list = $.Array.map(list, function (item, index) {

                //不含有 src 属性,忽略掉。
                var src = Attribute.get(item, 'src');
                if (!src) {
                    return null;
                }

                //该 script 标签出现在 JsList 块里,忽略掉。
                if (JsList.has(item)) {
                    return null;
                }

                var index = Lines.getIndex(lines, item, startIndex);
                var line = lines[index];    //整一行的 html。
                lines[index] = null;        //先清空,后续会在 mix() 中重新计算而填进去。

                //所在的行给注释掉了,忽略掉。
                if (Lines.commented(line, item)) {
                    return null;
                }


                startIndex = index + 1; //下次搜索的起始行号
            
                var suffix = Url.suffix(src);
                var prefix = suffix ? src.slice(0, -suffix.length) : src;
                var ext = $.String.endsWith(prefix, debug) ? debug :
                        $.String.endsWith(prefix, min) ? min :
                        path.extname(prefix);

                var name = ext ? prefix.slice(0, -ext.length) : prefix;

                var file = '';

                if (!Url.checkFull(src)) { //不是绝对(外部)地址
                    file = Path.format(src);
                    file = Path.join(meta.dir, file);
             
                    FileRefs.add(file);
                }
                


                return {
                    'file': file,       //完整的物理路径。 如果是外部地址,则为空字符串。
                    'src': src,         //原始地址,带 query 和 hash 部分。
                    'suffix': suffix,   //扩展名之后的部分,包括 '?' 在内的 query 和 hash 一整体。
                    'name': name,       //扩展名之前的部分。
                    'ext': ext,         //路径中的后缀名,如 '.debug.js'|'.min.js'|'.js'。
                    'index': index,     //行号,从 0 开始。
                    'html': item,       //标签的 html 内容。
                    'line': line,       //整一行的 html 内容。
                    'build': {},        //记录 build() 的输出结果。
                };

            });

            meta.list = list;

        },

        /**
        * 获取 js 文件列表所对应的 md5 值和引用计数信息。
        */
        md5: function () {
            var meta = mapper.get(this);
            var file$md5 = meta.file$md5;
            var list = meta.list;

            var file$stat = {};

            list.forEach(function (item) {

                var file = item.file;
                if (!file) {
                    return;
                }

                var stat = file$stat[file];
                if (stat) {
                    stat['count']++;
                    return;
                }


                var md5 = file$md5[file];
                if (!md5) {
                    md5 = file$md5[file] = MD5.read(file);
                }

                file$stat[file] = {
                    'count': 1,
                    'md5': md5,
                };
                
            });

            return file$stat;
        },

        /**
        * 监控 js 文件的变化。
        */
        watch: function () {
            var meta = mapper.get(this);

            //这里不要缓存起来,因为可能在 parse() 中给重设为新的对象。
            //var list = meta.list; 
            //var file$md5 = meta.file$md5; 

            var watcher = meta.watcher;

            if (!watcher) { //首次创建。

                watcher = meta.watcher = new Watcher();

                var self = this;
                var emitter = meta.emitter;

                watcher.on({

                    'added': function (files) {
                        
                    },

                    'deleted': function (files) {
                        
                        console.log('文件已给删除'.yellow, files);
                    },

                    //重命名的,会先后触发:deleted 和 renamed
                    'renamed': function (files) {
                       
                        //emitter.fire('change');
                    },


                    'changed': function (files) {

                        files.forEach(function (file) {

                            //让对应的 md5 记录作废。
                            meta.file$md5[file] = '';

                            //根据当前文件名,找到具有相同文件名的节点集合。
                            var items = $.Array.grep(meta.list, function (item, index) {
                                return item.file == file;
                            });

                            //对应的 html 作废。
                            items.forEach(function (item) {
                                meta.lines[item.index] = null;
                            });

                        });

                        emitter.fire('change');
                    },

                });
            }


            var files = $.Array.map(meta.list, function (item) {
                return item.file || null;
            });



            //watcher.set(files);


        },

        /**
        * 
        */
        mix: function () {
            var meta = mapper.get(this);
            var list = meta.list;
            var lines = meta.lines;
            var file$md5 = meta.file$md5;
            var replace = $.String.replaceAll;

            var len = meta.md5;
            var rid = $.String.random(32);

            list.forEach(function (item) {

                var index = item.index;
                if (lines[index]) { //之前已经生成过了
                    return;
                }

                var build = item.build;
                var ext = build.ext || item.ext;
                var dest = item.name + ext + item.suffix;
                var file = build.file || item.file;
               
                if (file) {//引用的是本地文件
                    var md5 = file$md5[file];

                    if (!md5) { //动态去获取 md5 值。
                        md5 = file$md5[file] = MD5.read(file);
                    }

                    md5 = md5.slice(0, len);
                    dest = $Url.addQueryString(dest, md5, rid);
                    dest = replace(dest, md5 + '=' + rid, md5); //为了把类似 'MD5=XXX' 换成 'MD5'。
                }

   
                var html = replace(item.html, item.src, dest);
                var line = replace(item.line, item.html, html);

                lines[index] = line;
                
            });

            return Lines.join(lines);

        },


        /**
        * 压缩对应的 js 文件。
        */
        minify: function (options) {

            var meta = mapper.get(this);

            if (options === true) { //直接指定了为 true,则使用默认配置。
                options = meta.minify;
            }

            //https://github.com/mishoo/UglifyJS2
            var UglifyJS = require('uglify-js');
            var list = meta.list;

            list.forEach(function (item) {

                var ext = item.ext;
                var opts = options[ext];
                if (!opts) {
                    return;
                }


                var file = item.file;
                if (!file) { //外部地址
                    if (opts.outer) { //指定了替换外部地址为压缩版
                        item.build.ext = opts.ext;
                    }
                    return;
                }

               
                var result = UglifyJS.minify(file);
                var content = result.code;

                if (opts.delete) { //删除源 js文件
                    FileRefs.delete(file);
                }

                var dest = item.name + opts.ext;
                dest = Path.join(meta.dir, dest);

                if (opts.write) {
                    if (File.exists(dest)) {
                        if (opts.overwrite) {
                            File.write(dest, content);
                        }
                    }
                    else {
                        File.write(dest, content);
                    }
                }

                $.Object.extend(item.build, {
                    'file': dest,
                    'ext': opts.ext,
                    'content': content,
                });

            });

        },


        /**
        * 把 js 文件的内容内联到 html 中。
        */
        inline: function (items) {

            var meta = mapper.get(this);
            var list = meta.list;
            var lines = meta.lines;

            //重载 inline();
            if (!items) {
                items = $.Array.map(list, function (item) {
                    var file = item.file;
                    if (!file) {
                        return null;
                    }

                    return {
                        'file': item.file,
                        'delete': false, //是否删除源 js 文件。
                    };
                });
            }


            items.forEach(function (item) {
                var file = Path.format(item.file);
                var content = File.read(file);

                var items = $.Array.grep(list, function (item) {
                    return item.file == file;
                });

                items.forEach(function (item) {
                    var index = item.index;
                    lines[index] = '    <script>' + content + '</script>';
                });

                //删除
                if (item.delete) {
                    FileRefs.delete(file);
                }
            });

            return Lines.join(lines);


        },


        /**
        * 删除列表中所对应的 js 物理文件。
        */
        'delete': function () {
            var meta = mapper.get(this);
            var list = meta.list;

            list.forEach(function (item) {
                FileRefs.delete(item.file);
            });


        },


        /**
        * 绑定事件。
        */
        on: function (name, fn) {
            var meta = mapper.get(this);
            var emitter = meta.emitter;

            var args = [].slice.call(arguments, 0);
            emitter.on.apply(emitter, args);

            return this;
        },



    };



    return JsScripts;



});
Exemple #13
0
define('HtmlLinks', function (require, module, exports) {

    var $ = require('$');
    var path = require('path');

    var File = require('File');
    var FileRefs = require('FileRefs');
    var Path = require('Path');
    var Watcher = require('Watcher');
    var Defaults = require('Defaults');
    var Lines = require('Lines');
    var Attribute = require('Attribute');

    var Emitter = $.require('Emitter');
    var Url = $.require('Url');

    var LogicFile = module.require('LogicFile');

    var mapper = new Map();




    function HtmlLinks(dir, config) {


        config = Defaults.clone(module.id, config);


        //base 为下级页面的基目录。
        //假如 base='Detail',而引入下级页面的 href='/panel.html',
        //则 href='Detai/panel.html',即提供了一种短名称引入下级页面的方式。

        var meta = {
            'dir': dir,     //当前分母版页所在的目录。
            'master': '',   //当前分母版页的内容。
            'lines': [],    //html 换行拆分的列表
            'list': [],     //html 片段文件列表及其它信息。
            'emitter': new Emitter(this),
            'watcher': null,            //监控器,首次用到时再创建
            'regexp': config.regexp,    //
            'base': config.base,        //下级页面的基目录。
        };

        mapper.set(this, meta);


    }



    HtmlLinks.prototype = {
        constructor: HtmlLinks,

        /**
        * 重置为初始状态,即创建时的状态。
        */
        reset: function () {
            var meta = mapper.get(this);


            meta.list.forEach(function (item) {
                item.links.destroy();               //移除之前的子节点实例
                FileRefs.delete(item.file);         //删除之前的文件引用计数
            });


            $.Object.extend(meta, {
                'master': '',   //当前分母版页的内容。
                'lines': [],    //html 换行拆分的列表
                'list': [],     //html 片段文件列表及其它信息。
            });

        },

        /**
        * 从当前或指定的母版页 html 内容中提出 html 标签列表信息
        * @param {string} master 要提取的母版页 html 内容字符串。
        */
        parse: function (master) {
            var meta = mapper.get(this);
            master = meta.master = master || meta.master;

            //提取出如引用了 html 分文件的 link 标签
            var list = master.match(meta.regexp);
            if (!list) {
                return;
            }


            var lines = Lines.get(master);
            meta.lines = lines;

            var startIndex = 0;

            list = $.Array.map(list, function (item, index) {

                var index = Lines.getIndex(lines, item, startIndex);
                var line = lines[index]; //整一行的 html
                startIndex = index + 1; //下次搜索的起始行号

                //所在的行给注释掉了,忽略
                if (Lines.commented(line, item)) {
                    return null;
                }

                var href = Attribute.get(item, 'href');
                if (!href) {
                    return null;
                }

                var file = '';
                var prefix = Attribute.get(item, 'prefix');

                if (prefix) {
                    var selector = ' ' + prefix + '="' + href + '"';  //如 ` data-panel="/User/List" `
                    var matches = LogicFile.get(selector);

                    file = matches[0];

                    if (!file) {
                        console.log('无法找到内容中含有 '.bgRed, selector.bgYellow, ' 的 html 文件'.bgRed);
                        throw new Error();
                    }

                    if (matches.length > 1) {
                        console.log('找到多个内容中含有 '.bgRed, selector.bgYellow, ' 的 html 文件'.bgRed);
                        Log.logArray(matches, 'yellow');
                        throw new Error();
                    }

                    href = Path.relative(meta.dir, file);
                }
                else {
                    //以 '/' 开头,如 '/panel.html',则补充完名称。
                    if (href.slice(0, 1) == '/') {
                        href = meta.base + href;
                    }

                    file = path.join(meta.dir, href);
                }
                

                href = Path.format(href);
                file = Path.format(file);

                FileRefs.add(file); //添加文件引用计数。
               

                var pad = line.indexOf(item);       //前导空格数
                pad = new Array(pad + 1).join(' '); //产生指定数目的空格

                var dir = Path.dirname(file);


                //递归下级页面

                //下级节点的基目录,根据当前页面的文件名得到
                var ext = path.extname(file);
                var base = path.basename(file, ext);


                var master = File.read(file);
                var links = new HtmlLinks(dir, { 'base': base });
                var list = links.parse(master);

                if (list && list.length > 0) {
                    console.log('复合片段'.bgMagenta, file.bgMagenta);
                }
            
                return {
                    'href': href,   //原始地址
                    'file': file,   //完整的物理路径。 
                    'index': index, //行号,从 0 开始
                    'html': item,   //标签的 html 内容
                    'line': line,   //整一行的 html 内容
                    'pad': pad,     //前导空格
                    'dir': dir,     //所在的目录
                    'name': base,   //基本名称,如 'CardList'
                    'links': links,  //下级页面
                };

            });

            meta.list = list;

            return list;
           

        },

        /**
        * 混入(递归)。
        * 即把对 html 分文件的引用用所对应的内容替换掉。
        */
        mix: function (options) {

            options = options || {
                'delete': false,    //是否删除源 master 文件,仅提供给上层业务 build 时使用。
            };

            var meta = mapper.get(this);
            var list = meta.list;

            if (list.length == 0) { //没有下级页面
                return meta.master;   //原样返回
            }

            var lines = meta.lines;


            list.forEach(function (item, index) {
              
                var html = item.links.mix(options); //递归
                console.log('混入'.yellow, item.file.green);


                //在所有行的前面加上空格串,以保持原有的缩进
                var pad = item.pad;
                html = pad + Lines.get(html).join(Lines.seperator + pad);

                lines[item.index] = html;

                if (options.delete) {
                    FileRefs.delete(item.file);
                }

            });

            var html = Lines.join(lines);

            return html;
        },

        /**
        * 监控当前 html 文件列表的变化。
        */
        watch: function () {
            var meta = mapper.get(this);
            var emitter = meta.emitter;
            var watcher = meta.watcher;

            if (!watcher) { //首次创建
                
                watcher = meta.watcher = new Watcher();

                //因为是静态文件列表,所以只监控文件内容是否发生变化即可。
                watcher.on('changed', function (files) {

                    //{ 文件名: [节点, 节点, ..., 节点] },一对多的关系。
                    files.forEach(function (file) {

                        //根据当前文件名,找到具有相同文件名的节点集合。
                        //闭包的关系,这里必须用 meta.list,且不能缓存起来。
                        var items = $.Array.grep(meta.list, function (item, index) {
                            return item.file == file;
                        });

                        items.forEach(function (item) {

                            var file = item.file;
                            var html = File.read(file);
                            var links = item.links;

                            links.reset();      //
                            links.parse(html);  //可能添加或移除了下级子节点
                            links.watch();      //更新监控列表

                        });
                    });

                    emitter.fire('change');

                });
            }


            //监控下级节点所对应的文件列表。

            var files = [];

            meta.list.forEach(function (item) {

                files.push(item.file);


                var links = item.links;

                links.on('change', function () {
                    emitter.fire('change');
                });

                links.watch();

            });

            watcher.set(files);

        },


        /**
        * 删除引用列表中所对应的 html 物理文件。
        */
        delete: function () {
            var meta = mapper.get(this);
            var list = meta.list;

            list.forEach(function (item) {

                FileRefs.delete(item.file);

                item.links.delete(); //递归删除下级的
            });

        },



        /**
        * 绑定事件。
        */
        on: function (name, fn) {
            var meta = mapper.get(this);
            var emitter = meta.emitter;

            var args = [].slice.call(arguments, 0);
            emitter.on.apply(emitter, args);

            return this;
        },

        /**
        * 销毁当前对象。
        */
        destroy: function () {

            var meta = mapper.get(this);

            meta.emitter.destroy();

            var watcher = meta.watcher;
            watcher && watcher.destroy();

            meta.list.forEach(function (item) {
                item.links.destroy();
            });

            mapper.delete(this);



        },

    };



    return HtmlLinks;



});
Exemple #14
0
define('HtmlList', function (require, module, exports) {

    var $ = require('$');
    var path = require('path');

    var Watcher = require('Watcher');
    var Patterns = require('Patterns');
    var Path = require('Path');
    var Defaults = require('Defaults');
    var Log = require('Log');
    var Mapper = $.require('Mapper');
    var Emitter = $.require('Emitter');
    var Url = $.require('Url');
   

    var mapper = new Mapper();


    //该模块不需要进行资源文件引用计数,交给 HtmlLinks 计数即可。

    function HtmlList(dir, config) {

 
        Mapper.setGuid(this);
        config = Defaults.clone(module.id, config);

        var rid = $.String.random(4); //随机 id

        var meta = {
            
            'dir': dir,         //母版页所在的目录。
            'master': '',       //母版页的内容,在 parse() 中用到。
            'html': '',         //模式所生成的 html 块,即缓存 toHtml() 方法中的返回结果。
            'outer': '',        //包括开始标记和结束标记在内的原始的整一块的 html。
            'patterns': [],     //模式列表。
            'list': [],         //真实 html 文件列表及其它信息。

            'emitter': new Emitter(this),
            'watcher': null,    //监控器,首次用到时再创建

            'extraPatterns': config.extraPatterns,  //额外附加的模式。
            'sample': config.sample, //使用的模板
            'tags': config.tags,

        };

        mapper.set(this, meta);

    }



    HtmlList.prototype = {
        constructor: HtmlList,

        /**
        * 重置为初始状态,即创建时的状态。
        */
        reset: function () {

            var meta = mapper.get(this);

            $.Object.extend(meta, {
                'master': '',       //母版页的内容,在 parse() 中用到。
                'html': '',         //模式所生成的 html 块,即缓存 toHtml() 方法中的返回结果。
                'outer': '',        //包括开始标记和结束标记在内的原始的整一块的 html。
                'patterns': [],     //模式列表。
                'list': [],         //真实 html 文件列表及其它信息。
            });

        },


        /**
        * 从当前或指定的母版页 html 内容中提出 html 文件列表信息。
        * @param {string} master 要提取的母版页 html 内容字符串。
        * @return {Array} 返回一个模式数组。
        */
        parse: function (master) {
            var meta = mapper.get(this);
            master = meta.master = master || meta.master;

            var tags = meta.tags;
            var dir = meta.dir;

            var html = $.String.between(master, tags.begin, tags.end);
            if (!html) {
                return;
            }

            var patterns = $.String.between(html, '<script>', '</script>');
            if (!patterns) {
                return;
            }

            //母版页中可能会用到的上下文。
            var context = {
                'dir': dir,
                'master': master,
                'tags': tags,
            };

            var fn = new Function('require', 'context',
                //包装多一层匿名立即执行函数
                'return (function () { ' +
                    'var a = ' + patterns + '; \r\n' +
                    'return a;' +
                '})();'
            );

            //执行母版页的 js 代码,并注入变量。
            patterns = fn(require, context);

            if (!Array.isArray(patterns)) {
                throw new Error('引入文件的模式必须返回一个数组!');
            }

            patterns = patterns.concat(meta.extraPatterns); //跟配置中的模式合并
            patterns = Patterns.fill(dir, patterns);
            patterns = Patterns.combine(dir, patterns);

            console.log('匹配到'.bgGreen, patterns.length.toString().cyan, '个 html 模式:');
            Log.logArray(patterns);

            meta.patterns = patterns;
            meta.outer = tags.begin + html + tags.end;

        },

        /**
        * 根据当前模式获取对应真实的 html 文件列表和其它信息。
        */
        get: function () {

            var meta = mapper.get(this);


            var patterns = meta.patterns;
            var list = Patterns.getFiles(patterns);

            meta.list = list = list.map(function (file, index) {

                file = Path.format(file);

                var href = path.relative(meta.dir, file);
                href = Path.format(href);

                return {
                    'file': file,
                    'href': href,
                };

            });

            return list;

        },

        /**
        * 把当前的动态 html 引用模式块转成真实的静态 html 引用所对应的 html。
        */
        toHtml: function () {
            var meta = mapper.get(this);

            var list = meta.list;
            if (list.length == 0) {
                meta.html = '';
                return;
            }


            var tags = meta.tags;
            var sample = meta.sample;

            //todo: 检查重复的文件
            list = list.map(function (item, index) {

                return $.String.format(sample, {
                    'href': item.href,
                });
            });

            var Lines = require('Lines');
            var seperator = Lines.seperator + '    ';
            meta.html = list.join(seperator) + seperator;

        },

        /**
        * 把整一块动态 html 引用模式替换成真实的静态 html 引用。
        * @param {string} [master] 要替换的母版 html。 如果不指定,则使用原来的。
        *   注意,如果使用新的模板,则该模板中的模式不能变。
        */
        mix: function (master) {
            var meta = mapper.get(this);
            var outer = meta.outer;

            master = master || meta.master;

            //实现安全替换
            var beginIndex = master.indexOf(outer);
            var endIndex = beginIndex + outer.length;

            master =
                master.slice(0, beginIndex) +
                meta.html +
                master.slice(endIndex);

            return master;

        },

        /**
        * 监控当前模式下 html 文件的变化。
        */
        watch: function () {
            var meta = mapper.get(this);
            var patterns = meta.patterns;
            if (patterns.length == 0) { //列表为空,不需要监控
                return;
            }

            var watcher = meta.watcher;
            if (!watcher) { //首次创建
                watcher = meta.watcher = new Watcher();

                var emitter = meta.emitter;
                var self = this;

                watcher.on({
                    'added': function (files) {
                        self.get();
                        self.toHtml();
                        emitter.fire('change');
                    },

                    'deleted': function (files) {
                        self.get();
                        self.toHtml();
                        emitter.fire('change');
                    },

                    //重命名的,会先后触发:deleted 和 renamed
                    'renamed': function (files) {
                        self.get();
                        self.toHtml();
                        emitter.fire('change');
                    },

                });

            }

            watcher.set(patterns);
           

        },
        

        /**
        * 绑定事件。
        */
        on: function (name, fn) {
            var meta = mapper.get(this);
            var emitter = meta.emitter;

            var args = [].slice.call(arguments, 0);
            emitter.on.apply(emitter, args);

            return this;
        },



    };



    return HtmlList;



});