回顾
上篇文章大概展示了 kmdjs0.1.x 时期的编程范式:
如下面所示,可以直接依赖注入到 function 里,
1 2 3 4 |
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 加上来调用,如:
1 2 3 4 |
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 的方式来调用。
这种编程体验虽然已经足够好,但是可以更好。怎样才算更好?
- 不用依赖注入 function
- 不用写 full namespace,自动匹配依赖
如下所示:
1 2 3 4 |
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 依赖分析和代码重构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
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,可以对代码就行变换。如:
1 2 3 4 5 6 7 |
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'] )) |
输出:
1 2 3 4 5 6 7 |
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 就行:
1 2 3 4 5 6 7 8 9 10 11 12 |
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
WEI 2016 年 7 月 17 日
最近发现很多 H5 把资源都压在一个文件里面了,能不能给点思路?