kmdjs整合uglifyjs2打造極致的程式設計體驗

【當耐特】發表於2016-06-14

回顧

上篇文章大概展示了kmdjs0.1.x時期的程式設計正規化:
如下面所示,可以直接依賴注入到function裡,

kmdjs.define('main',['util.bom','app.Ball','util.dom.test'], function(bom,Ball,test) {
    var ball = new Ball(0, 0, 28, 1, -2, 'kmdjs');
    var vp = bom.getViewport();
});

也可以直接在程式碼裡把full namespace加上來呼叫,如:

kmdjs.define('main',['util.bom','app.Ball','util.dom.test'], function() {
    var ball = new app.Ball(0, 0, 28, 1, -2, 'kmdjs');
    var vp = util.bom.getViewport();
});

而且,在迴圈依賴的場景,因為執行順序的問題,會導致第一種方式注入undefined,所以迴圈依賴的情況下只能用full namespace的方式來呼叫。

這種程式設計體驗雖然已經足夠好,但是可以更好。怎樣才算更好?

  1. 不用依賴注入function
  2. 不用寫full namespace,自動匹配依賴

如下所示:

kmdjs.define('main',['util.bom','app.Ball','util.dom.test'], function() {
    var ball = new Ball(0, 0, 28, 1, -2, 'kmdjs');
    var vp = bom.getViewport();
});

這就要藉助uglifyjs能力,把function的字串替換成帶有namespace就可以實現上面的效果。

uglifyjs依賴分析和程式碼重構

function fixDeps(fn,deps) {
    var U2 = UglifyJS;
    //uglify2不支援匿名轉ast
    var code = fn.toString().replace('function','function ___kmdjs_temp');
    var ast = U2.parse(code);
    ast.figure_out_scope();
    var nodes = [];


    ast.walk(new U2.TreeWalker(function (node) {

        if (node instanceof U2.AST_New) {
            var ex = node.expression;
            var name = ex.name;
            isInWindow(name) || isInArray(nodes, node) || isInScopeChainVariables(ex.scope, name) || nodes.push({name:name,node:node});
        }

        if (node instanceof U2.AST_Dot) {
            var ex = node.expression;
            var name = ex.name;
            var scope = ex.scope;
            if (scope) {
                isInWindow(name) || isInArray(nodes, node) || isInScopeChainVariables(ex.scope, name) || nodes.push({name:name,node:node});
            }
        }

        if (node instanceof U2.AST_SymbolRef) {
            var name = node.name;
            isInWindow(name) || isInArray(nodes, node) || isInScopeChainVariables(node.scope, name) || nodes.push({name:name,node:node});
        }
    }));

    var cloneNodes = [].concat(nodes);
    //過濾new nodes 中的symbo nodes
    for (var i = 0, len = nodes.length; i < len; i++) {
        var nodeA = nodes[i].node;
        for (var j = 0, cLen = cloneNodes.length; j < cLen; j++) {
            var nodeB = cloneNodes[j].node;
            if (nodeB.expression === nodeA) {
                nodes.splice(i, 1);
                i--;
                len--;
            }
        }
    }

    for (var i = nodes.length; --i >= 0;) {
        var item = nodes[i],
            node=item.node,
            name=item.name;
        var fullName=getFullName(deps,name);
        var replacement;
        if (node instanceof  U2.AST_New) {
            replacement = new U2.AST_New({
                expression: new U2.AST_SymbolRef({
                    name:fullName
                }),
                args: node.args
            });
        } else if (node instanceof  U2.AST_Dot) {
            replacement = new U2.AST_Dot({
                expression: new U2.AST_SymbolRef({
                    name: fullName
                }),
                property: node.property
            });
        }else if(node instanceof U2.AST_SymbolRef){
            replacement = new U2.AST_SymbolRef({
                    name: fullName
            });
        }

        var start_pos = node.start.pos;
        var end_pos = node.end.endpos;

        code = splice_string(code, start_pos, end_pos, replacement.print_to_string({
            beautify: true
        }));
    }
    return code.replace('function ___kmdjs_temp','function');
}

function getFullName(deps,name){
    var i= 0,
        len=deps.length,
            matchCount= 0,
            result=[];

    for(;i<len;i++) {
        var fullName = deps[i];
        if (fullName.split('.').pop() === name) {
            matchCount++;
            if (!isInArray(result, fullName))  result.push(fullName);
        }
    }

    if(matchCount>1){
        throw "the same name conflict: "+result.join(" and ");
    } else if(matchCount===1){
        return result[0];
    }else{
        throw ' can not find module ['+name+']';
    }
}

function splice_string(str, begin, end, replacement) {
    return str.substr(0, begin) + replacement + str.substr(end);
}

function isInScopeChainVariables(scope, name) {
    var vars = scope.variables._values;
    if (Object.prototype.hasOwnProperty.call(vars, "$" + name)) {
        return true;
    }

    if (scope.parent_scope) {
        return isInScopeChainVariables(scope.parent_scope, name);
    }

    return false;
}

function isInArray(arr,name){
    var i= 0,len=arr.length;
    for(;i<len;i++){
        if(arr[i]===name){
            return true;
        }
    }
    return false;
}

function isInWindow(name){
    if(name==='this')return true;
    return name in window;
}

通過上面的fixDeps,可以對程式碼就行變換。如:

 console.log(fixDeps(function (A) {
        var eee = m;
        var b = new A();
        var b = new B();
        var c = new C();
        var d = G.a;
    },['c.B','AAA.G','SFSF.C','AAAA.m'] ))

輸出:

function (A) {
        var eee = AAAA.m;
        var b = new A();
        var b = new c.B();
        var c = new SFSF.C();
        var d = AAA.G.a;
}

這樣,kmdjs在執行模組function的時候,只需要fixDeps加上full namespace就行:

function buildBundler(){
    var topNsStr = "";
    each(kmdjs.factories, function (item) {
        nsToCode(item[0]);
    });
    topNsStr+=  kmdjs.nsList.join('\n') +"\n\n";
    each(kmdjs.factories, function (item) {
        topNsStr+=item[0]+' = ('+ fixDeps(item[2],item[1])+')();\n\n' ;
    });
    if(kmdjs.buildEnd) kmdjs.buildEnd(topNsStr);
    return topNsStr;
}

build出來的包,當然全都加上了namespace。再也不用區分迴圈依賴和非迴圈依賴了~~~

Github

上面的所有程式碼可以Github上找到:
https://github.com/kmdjs/kmdjs

相關文章