dexteryy
9/10/2012 - 8:39 AM

AMD -> module pattern

AMD -> module pattern

// 这个define的实现会让amd模块声明变成传统的module pattern
function define(fullname, deps, block){
    if (!block) {
        if (deps) {
            block = deps;
        } else {
            block = fullname;
            fullname = [];
        }
        if (typeof fullname !== 'string') {
            deps = fullname;
            fullname = "";
        } else {
            deps = [];
        }
    }
    var callee = define,
        len = deps.length,
        exports = {},
        opt = callee._global_exports[fullname] || {},
        current_ns = callee._current_ns = opt.ns 
            || callee._current_ns || callee._ns || 'window';
    deps = ((/^function.*?\(([\w'"\/\-\:,\n\r\s]*)\)/
                .exec(block.toString()) || [])[1] || '')
                .replace(/\s+/g, '').split(',');
    deps.length = len;
    var args = deps.map(function(name){
        return (window[current_ns] || {})[name];
    });
    args.push(function(reqs, callback){
        if (callback) {
            require(reqs, callback);
        } else {
            return (callee._global_exports[reqs] || {}).exports;
        }
    });
    args.push(exports);
    exports = opt.exports = block.apply(this, args) || exports;
    if (opt.names) {
        opt.names.forEach(function(name){
            name = name.split('.');
            var context = window, i = name[0];
            if (name[1]) {
                context = context[i] = context[i] || {};
                i = name[1];
            }
            context[i] = exports;
        });
    }
    callee._current_ns = null;
}

// require的实现跟define一样,不过是异步的
function require(reqs){
    if (typeof reqs === 'string') {
        reqs = [reqs];
    }
    var args = arguments;
    setTimeout(function(){
        define.apply(this, args);
    }, 0);
}

define._global_exports = {};

// 为模块设置命名空间,既可以避免产生全局变量,
// 也可以隔离不同体系的模块(比如'mod/event'和'jquery/event'),避免命名冲突
define.ns = function(mid, namespace){
    if (!namespace) {
        this._ns = mid;
    } else {
        var opt = this._global_exports;
        if (!opt[mid]) {
            opt[mid] = { 
                names: [] 
            };
        }
        opt[mid].ns = namespace;
    }
};

// 将模块的exports映射到全局命名空间下,让没有封装到amd模块里的代码也可以使用
define.config = function(mid, vars){
    if (typeof mid === 'object') {
        for (var i in mid) {
            this.config(i, mid[i]);
        }
        return;
    }
    var opt = this._global_exports;
    if (!opt[mid]) {
        opt[mid] = {
            ns: this._ns,
            names: []
        };
    }
    if (typeof vars === 'string') {
        vars = [vars];
    }
    [].push.apply(opt[mid].names, vars);
};


// 以下是DEMO

// 自定义默认命名空间,默认为window
define.ns('Ark');
// 给模块单独定义命名空间,这个模块依赖的参数都会从这个命名空间下读取
define.ns('B', 'Y');

// 手工配置模块的exports在全局命名空间下的名称(一对多)
define.config('mod/dialog', '$.dialog');
define.config('mod/dialog', ['Ark.dui_dialog', 'Ark.Dialog']);
define.config({ 
    'mod/lang': ['_', 'Ark._'],
    'A': ['Ark.A', 'Y.A'],
    'B': ['Ark.B', 'Y.B']
});

// 合并进来的 mod/lang.js,如果之前没有模块名,可在合并脚本执行的时候自动生成
// 要用现成工具的话,可以先用ozma.js把所有amd模块合并为一个发布文件,再把这个发布文件用 @import 的方式合并到传统网页的js中
define('mod/lang', function(require, exports){
    exports.isFunction = function(){};
});

// 合并进来的 mod/dialog.js
define('mod/dialog', ['mod/lang'], function(_, require, exports){

    console.info('mod/dialog running..');

    require('A', function(A){
        // 因为这里的require在模块内调用,所以这个函数会异步执行,参数A从mod/dialog的命名空间下读取(Ark.A)
        console.info('require A: ', A);
    });

    return function(str){
        // 只有一个参数的require会立刻执行,返回模块B的exports,不需要全局命名空间
        console.info(str, _, require('B'));
    };

});

define('A', function(){
    return { name: 'A' };
});

// 因为模块B的命名空间是Y,所以这里的参数A取的是Y.A
define('B', ['A'], function(A){
    return { name: 'B', A: A };
});

Ark.dui_dialog('Dialog: ');

console.info('lang: ', _);
console.info('Ark: ', Ark);
console.info('Y: ', Y);