上篇 文章大概展示了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的方式来调用。
这种编程体验虽然已经足够好,但是可以更好。怎样才算更好?
如下所示:
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就可以实现上面的效果。
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