转载

kmdjs集成uglifyjs2打造极致的编程体验

上篇 文章大概展示了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 (nodeinstanceof 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 (nodeinstanceof 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 (nodeinstanceof 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 (nodeA.expression === nodeB) {                 cloneNodes.splice(j, 1);                 j--;                 cLen--;             }             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 (nodeinstanceof  U2.AST_New) {             replacement = new U2.AST_New({                 expression: new U2.AST_SymbolRef({                     name:fullName                 }),                 args: node.args             });         } else if (nodeinstanceof  U2.AST_Dot) {             replacement = new U2.AST_Dot({                 expression: new U2.AST_SymbolRef({                     name: fullName                 }),                 property: node.property             });         }else if(nodeinstanceof 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上找到:

https://github.com/kmdjs/kmdjs
原文  http://www.alloyteam.com/2016/06/kmdjs-integrated-uglifyjs2-to-build-the-ultimate-programming-experience/
正文到此结束
Loading...