magix-combine
Version:
合并Magix View的html,js,css成一个js文件,并检测html,js,css中可能存在的问题
1,196 lines (1,183 loc) • 52.1 kB
JavaScript
/*
对模板增加根变量的分析,模板引擎中不需要用with语句
压缩模板引擎代码
!!
不推荐在模板中声明变量,尽管目前是支持的
不推荐在模板中进行函数调用,尽管目前也是支持的
对于函数调用,如<%=fn(args1,args2)%>,一定是给定同样的args1及args2,返回值必须相同,不支持像Math.random()这样的随机返回的函数调用
*/
let acorn = require('./js-acorn');
let chalk = require('chalk');
let tmplCmd = require('./tmpl-cmd');
let configs = require('./util-config');
let utils = require('./util');
let slog = require('./util-log');
let regexp = require('./util-rcache');
let md5 = require('./util-md5');
let jsGeneric = require('./js-generic');
let consts = require('./util-const');
let tmplCmdReg = /<%([@=!:~\x1a-\x1d&*])?([\s\S]*?)%>|$/g;
let tagReg = /<([^>\s\/\u0007]+)([^>]*)>/g;
let bindReg = /([^>\s\/=]+)\s*=\s*(["'])(<%'\x17\d+\x11[^\x11]+\x11\x17'%>)?<%([:\x1b])([\s\S]+?)%>\s*\2/g;
let bindReg2 = /\s*(<%'\x17\d+\x11[^\x11]+\x11\x17'%>)?<%[:\x1b]([\s\S]+?)%>\s*/g;
let pathReg = /<%~([\s\S]+?)%>/g;
let textaraReg = /<textarea([^>]*)>([\s\S]*?)<\/textarea>/g;
let mxViewAttrReg = /(?:\b|\s|^)mx-view\s*=\s*(['"])([^'"]+?)\1/g;
let vphUse = String.fromCharCode(0x7528); //用
let vphDcd = String.fromCharCode(0x58f0); //声
let vphCst = String.fromCharCode(0x56fa); //固
let vphGlb = String.fromCharCode(0x5168); //全
let creg = /[\u7528\u58f0\u56fa\u5168]/g;
let hreg = /([\u0001\u0002])\d+/g;
let compressVarReg = /\u0001\d+[a-zA-Z\_$]+/g;
let scharReg = /(?:`;|;`)/g;
let stringReg = /^['"]/;
let bindEventParamsReg = /^\s*"([^"]+)",/;
let removeTempReg = /[\u0002\u0001\u0003\u0006]\.?/g;
let artCtrlsReg = /<%'\x17\d+\x11([^\x11]+)\x11\x17'%>(<%[\s\S]+?%>)/g;
let cmap = {
[vphUse]: '\u0001',
[vphDcd]: '\u0002',
[vphGlb]: '\u0003',
[vphCst]: '\u0006'
};
let stripChar = str => str.replace(creg, m => cmap[m]);
let stripNum = str => str.replace(hreg, '$1');
/*let loopNames = {
forEach: 1,
map: 1,
filter: 1,
some: 1,
every: 1,
reduce: 1,
reduceRight: 1,
find: 1,
each: 1
};*/
let leftOuputReg = /\u0018",/g;
let rightOutputReg = /,\s*"/g;
let efCache = Object.create(null);
let extractFunctions = expr => { //获取绑定的其它附加信息,如 <%:user.name<change,input>({refresh:true,required:true})%> => evts:change,input expr user.name fns {refresh:true,required:true}
let c = efCache[expr];
if (c) {
return c;
}
let oExpr = expr;
let fns = '';
let m = expr.match(bindEventParamsReg);
if (m) {
expr = expr.replace(bindEventParamsReg, '');
}
let firstComma = expr.indexOf(',');
if (firstComma > -1) {
fns = expr.substring(firstComma + 1).trim().slice(1, -1);
expr = expr.substring(0, firstComma);
fns = fns.replace(leftOuputReg, '\'<%@').replace(rightOutputReg, '%>\'');
}
return (efCache[oExpr] = {
expr,
fns
});
};
let viewAttrReg = /\s(?:view-|\*)([\w\-@]+)=(["'])([\s\S]*?)\2/g;
/*
let getMemberExpr = (node, fn) => {
let prop = getPropExpr(node.property, fn);
let host = getHostExpr(node.object, fn);
if (host === vphGlb || host === vphCst) {
return host + '.' + prop;
}
let direct = node.property.type == 'Identifier';
let exprs = jsGeneric.splitSafeguardExpr(host);
let last = '&&' + exprs.pop();//获取最右的保护表达式
return host + last + (direct ? '.' : '[') + prop + (direct ? '' : ']');
};
let getHostExpr = (node, fn) => {
if (node.type == 'MemberExpression') {
return getMemberExpr(node, fn);
} else if (node.type == 'Identifier') {
return node.name;
} else if (node.type == 'ArrayExpression') {
return fn.slice(node.start, node.end);
} else {
throw new Error('unsupport host expr,host type:' + node.type);
}
};
let getPropExpr = (node, fn) => {
if (node.type === 'MemberExpression') {
return getMemberExpr(node, fn);
} else if (node.type == 'Identifier') {
return node.name;
} else if (node.type == 'Literal') {
return node.raw;
} else {
throw new Error('unsupport prop expr,prop type:' + node.type);
}
};
let getSafeguardExpression = (i, fn) => {
let node = i.node;
if (i.ae) {//赋值表达式比较特殊
let host = getHostExpr(node.object, fn);
let prop = node.property;
if (prop.type == 'Identifier') {
return '(' + host + '||{}).' + prop.name;
} else if (prop.type == 'MemberExpression') {
prop = getPropExpr(prop, fn);
return '(' + host + '||{})[' + prop + ']';
} else {
throw new Error('unsupport safeguard expression,prop type:' + prop.type);
}
}
return getMemberExpr(node, fn);
};*/
let IdReg = /(?:[\x03\x06]\.|\x01\d+)[\x24\x30-\x39\x41-\x5a\x5f\x61-\x7a]+/g;
let ExtractIds = v => {
let keys = [];
v.replace(IdReg, m => {
keys.push(m);
});
return keys;
};
/*
\u0000 `反撇
\u0001 模板中局部变量 用
\u0002 变量声明的地方 声
\u0003 模板中全局变量 全
\u0004 命令中的字符串
\u0005 html中的字符串
\u0006 constVars 固定不会变的变量
\u0007 存储命令
\u0011 精准识别rqeuire
\u0012 精准识别@符
\u0017 模板中的纯字符串
\u0018 模板中的绑定参数对象
\u0019 模板中的循环
第一遍用汉字
第二遍用不可见字符
*/
module.exports = {
process: (tmpl, e, extInfo) => {
let sourceFile = e.shortHTMLFile;
let fn = [];
let index = 0;
let htmlStore = Object.create(null);
let htmlIndex = 0;
let htmlKey = utils.uId('\u0005', tmpl);
let htmlHolderReg = new RegExp(htmlKey + '\\d+' + htmlKey, 'g');
//console.log(tmpl);
tmpl.replace(tmplCmdReg, (match, operate, content, offset) => {
let start = 2;
if (operate) {
start = 3;
if (content.trim()) {
content = '[' + content + ']';
}
}
let source = tmpl.substring(index, offset + start);
let key = htmlKey + (htmlIndex++) + htmlKey;
htmlStore[key] = source;
index = offset + match.length - 2;
fn.push(';`' + key + '`;', content || '');
});
fn = fn.join(''); //移除<%%> 使用`变成标签模板分析
let ast;
//console.log(fn);
//return;
//console.log(fn);
try {
ast = acorn.parse(fn, null, sourceFile);
} catch (ex) {
slog.ever('[MXC Error(tmpl-vars)] parse html cmd ast error:', chalk.red(ex.message), 'at', chalk.magenta(e.shortHTMLFile));
slog.ever('[MXC Error(tmpl-vars)] current state:', fn);
slog.ever('[MXC Error(tmpl-vars)] prev state:' + fn.replace(htmlHolderReg, m => htmlStore[m]));
throw ex;
}
let globalExists = Object.assign(Object.create(null), extInfo.tmplScopedGlobalVars);
let globalTracker = Object.create(null);
let globalVars = Object.create(null);
/*
变量和变量声明在ast里面遍历的顺序不一致,需要对位置信息保存后再修改fn
*/
let modifiers = [];
let stringStore = Object.create(null);
let stringIndex = 0;
let compressVarsMap = Object.create(null);
let varCount = 0;
let recoverString = tmpl => {
//还原代码中的字符串,代码中的字符串占位符使用\x04包裹
//模板中的绑定特殊字符串包含\x17,这里要区分这个字符串的来源
return tmpl.replace(/(['"])(\x04\d+\x04)\1/g, (m, q, c) => {
let str = stringStore[c].slice(1, -1); //获取源字符串
let result;
if (str.charAt(0) == '\x17') { //如果是\x17,这个是绑定时的特殊字符串
result = q + str.substring(1) + q;
} else { //其它情况再使用\x17包裹
result = q + '\x17' + str + '\x17' + q;
}
//console.log(JSON.stringify(m), result, JSON.stringify(result));
return result;
});
};
let fnRange = [];
//let blockRange = [{ start: 0, end: fn.length }];
//let vds = [];
//let blockVariableMap = Object.create(null);
let compressVarToOriginal = Object.create(null);
let constVars = Object.assign(Object.create(null), extInfo.tmplScopedConstVars);
let patternChecker = node => {
let msg = '[MXC Error(tmpl-vars)] unpupport ' + fn.substring(node.start, node.end);
slog.ever(chalk.red(msg), 'at', chalk.grey(sourceFile));
throw new Error(msg);
};
acorn.walk(ast, {
ArrayPattern: patternChecker,
ObjectPattern: patternChecker,
ForOfStatement: patternChecker,
// BlockStatement(node) {
// blockRange.push(node);
// },
CallExpression(node) { //方法调用
let vname = '';
let callee = node.callee;
if (callee.name) { //只处理模板中 <%=fn(a,b)%> 这种,不处理<%=x.fn()%>,后者x对象上除了挂方法外,还有可能挂普通数据。对于方法我们不把它当做变量处理,因为给定同样的参数,方法需要返回同样的结果
vname = callee.name;
constVars[vname] = 1;
} else {
//以下是记录,如user.name.get('key');某个方法在深层对象中,需要把整个路径给还原出来。
vname = fn.substring(callee.start, callee.end);
//let vpath = [];
//let start = callee;
//console.log(callee);
//while (start) {
// if (start.property) {
// vpath.push(start.property.name);
// }
// if (start.object) {
// start = start.object;
// } else {
// break;
// }
//}
//if (!start.name) { //连调情况,如a.replace().replace();
// return;
//}
//vpath.push(start.name);
//vname = vpath.reverse().join('.'); //路径如user.name.get
}
let args = configs.tmplPadCallArguments(vname, sourceFile);
if (args && args.length) {
if (!Array.isArray(args)) {
args = [args];
}
for (let i = 0; i < args.length; i++) {
args[i] = vphGlb + '.' + args[i];
}
modifiers.push({
start: node.end - 1,
end: node.end - 1,
name: (node.arguments.length ? ',' : '') + args.join(',')
});
}
},
VariableDeclarator(node) {
if (node.init) {
switch (node.init.type) {
case 'ArrayExpression':
case 'ObjectExpression':
case 'FunctionExpression':
case 'ArrowFunctionExpression':
slog.ever(chalk.magenta('[MXC Tip(tmpl-vars)] avoid declare ' + fn.substring(node.start, node.end)), 'at', chalk.grey(sourceFile));
break;
}
}
}/*,
VariableDeclaration(node) {
if (node.kind == 'let' || node.kind == 'const') {
vds.push(node);
}
}*/
});
// blockRange.sort((a, b) => a.start - b.start);
// for (let vd of vds) {
// let range;
// for (let br of blockRange) {
// if (vd.start > br.start && vd.end < br.end) {
// range = br;
// }
// }
// if (range) {
// let key = range.start + '~' + range.end;
// if (!blockVariableMap[key]) {
// blockVariableMap[key] = Object.create(null);
// }
// for (let d of vd.declarations) {
// if (!d.id.name.startsWith('$art_')) {
// blockVariableMap[key][d.id.name] = utils.uId('$rewrite_scoped_' + d.id.name + '_', tmpl, true);
// }
// }
// }
// }
// let varsReplace = node => {
// let map;
// for (let br of blockRange) {
// if (node.start > br.start && node.end < br.end) {
// let key = br.start + '~' + br.end;
// let m = blockVariableMap[key];
// if (m && m[node.name]) {
// map = m;
// }
// }
// }
// if (map) {
// modifiers.push({
// start: node.start,
// end: node.end,
// name: map[node.name],
// });
// }
// };
// acorn.walk(ast, {
// VariableDeclarator(node) {
// varsReplace(node.id);
// },
// Identifier: varsReplace
// });
// modifiers.sort((a, b) => a.start - b.start);
// for (let i = modifiers.length - 1, m; i >= 0; i--) {
// m = modifiers[i];
// fn = fn.substring(0, m.start) + m.name + fn.substring(m.end);
// }
modifiers = [];
ast = acorn.parse(fn, null, sourceFile);
let processString = node => { //存储字符串,减少分析干扰
if (stringReg.test(node.raw)) {
let q = node.raw.match(stringReg)[0];
let key = '\x04' + (stringIndex++) + '\x04';
if (consts.revisableReg.test(node.raw) && !configs.debug) {
node.raw = node.raw.replace(consts.revisableReg, m => {
return md5(m, 'revisableString', configs.revisableStringPrefix);
});
}
stringStore[key] = node.raw;
modifiers.push({
key: '',
start: node.start,
end: node.end,
name: q + key + q
});
}
};
acorn.walk(ast, {
Property(node) {
if (node.key.type == 'Literal') {
processString(node.key);
}
},
Literal: processString,
Identifier(node) {
let tname = node.name; // compressVarsMap[node.name] || node.name;
//console.log(compressVarsMap,node.name,node.start,fnRange);
if (!globalExists[tname]) { //模板中全局不存在这个变量
modifiers.push({ //如果是指定不会改变的变量,则加固定前缀,否则会全局前缀
key: (constVars[tname] ? vphCst : vphGlb) + '.',
start: node.start,
end: node.end,
name: tname
});
globalVars[tname] = 1;
} else { //如果变量不在全局变量里,则增加使用前缀
//console.log(node.name, compressVarsMap);
modifiers.push({
key: vphUse + node.end,
start: node.start,
end: node.end,
name: tname,
type: 'ui'
});
}
},
AssignmentExpression(node) { //赋值语句
if (node.left.type == 'Identifier') {
let lname = node.left.name;
let tname = lname; // compressVarsMap[lname] || lname;
if (!configs.debug && configs.tmplCompressVariable) { //如果压缩,则压缩变量
modifiers.push({
key: '',
start: node.left.start,
end: node.left.end,
name: tname,
type: 'ae'
});
}
if (!globalExists[tname] || globalExists[tname] === 1) { //模板中使用如<%list=20%>这种,虽然可以,但是不建议使用,因为在模板中可以修改js中的数据,这是非常不推荐的
//console.log(fn);
slog.ever(chalk.red('[MXC Tip(tmpl-vars)] undeclare variable:' + lname), 'at', chalk.grey(sourceFile));
}
globalExists[tname] = (globalExists[tname] || 0) + 1; //记录某个变量被重复赋值了多少次,重复赋值时,在子模板拆分时会有问题
if (globalExists[tname] > 3) {
if (e.refGlobalLeak && !e.refGlobalLeak['_' + lname]) {
e.refGlobalLeak['_' + lname] = 1;
e.refGlobalLeak.reassigns.push(chalk.red('[MXC Tip(tmpl-vars)] avoid reassign variable:' + lname) + ' at ' + chalk.grey(sourceFile));
}
}
} else if (node.left.type == 'MemberExpression') {
let start = node.left;
while (start.object) {
start = start.object;
} //模板中使用如<%list.x=20%>这种,虽然可以,但是不建议使用,因为在模板中可以修改js中的数据,这是非常不推荐的
if (!globalExists[start.name] || globalExists[start.name] === 1) {
slog.ever(chalk.red('[MXC Tip(tmpl-vars)] avoid writeback: ' + fn.slice(node.start, node.end)), 'at', chalk.grey(sourceFile));
}
}
},
VariableDeclarator(node) { //变量声明
let tname = node.id.name;
if (!configs.debug && configs.tmplCompressVariable) {
let cname;
do {
cname = md5.byNum(varCount++);
}
while (globalExists[cname]);
if (!compressVarsMap[tname]) { //可能使用同一个key进行多次声明,我们要处理这种情况
compressVarsMap[tname] = [];
}
compressVarsMap[tname].push({
name: cname,
pos: node.start
});
}
globalExists[tname] = node.init ? 3 : 2; //每次到变量声明的地方都重新记录这个变量的赋值次数
modifiers.push({
key: vphDcd + node.start,
start: node.id.start,
end: node.id.end,
name: tname,
type: 'vd',
});
},
ThisExpression(node) {
modifiers.push({
key: '',
start: node.start,
end: node.end,
name: vphGlb
});
},
FunctionDeclaration: node => { //函数声明
globalExists[node.id.name] = 3;
fnRange.push(node);
},
FunctionExpression: node => fnRange.push(node),
ArrowFunctionExpression: node => fnRange.push(node)/*,
ForOfStatement: node => {
if (e.checker.tmplCmdFnOrForOf) {
slog.ever(chalk.red('translate ForOfStatement: ' + fn.slice(node.start, node.right.end + 1)), 'to', chalk.red('ForStatement'), 'at', chalk.grey(sourceFile), 'more info:', chalk.magenta('https://github.com/thx/magix/issues/37'));
}
}*/
});
fnRange.sort((a, b) => {
return a.start - b.start;
});
let fnProcessor = () => { //函数在遍历时要特殊处理
let processOne = (node, pInfo) => {
let fns = [node.type == 'ArrowFunctionExpression' ? '(' : 'function('];
if (node.params && node.params.length) { //还原成function(args1,args){},用于控制台的提示
node.params.forEach(p => {
fns.push(p.name, ',');
});
fns.pop();
}
if (node.type == 'ArrowFunctionExpression') {
fns.push(')=>{}');
} else {
fns.push('){}');
}
if (e.checker.tmplCmdFnOrForOf && configs.debug) {
slog.ever(chalk.magenta('[MXC Tip(tmpl-vars)] avoid use Function: ' + fns.join('')), 'at', chalk.grey(sourceFile), 'more info:', chalk.magenta('https://github.com/thx/magix/issues/37')); //尽量不要在模板中声明function,因为一个function就是一个独立的上下文,对于后续的绑定及其它变量的获取会很难搞定
}
let params = Object.create(null);
let pVarsMap = Object.create(null);
/*
1. 记录参数,函数体内的与参数同名的变量不做任何处理
2. 压缩参数
*/
for (let i = 0, p; i < node.params.length; i++) { //处理参数
p = node.params[i];
params[p.name] = 1; //记录有哪些参数
if (!configs.debug && configs.tmplCompressVariable) { //如果启用变量压缩
let cname;
do {
cname = md5.byNum(varCount++);
} while (globalExists[cname]);
modifiers.push({ //压缩参数
key: vphDcd + p.start,
start: p.start,
end: p.end,
name: pVarsMap[p.name] = cname //压缩参数
});
} else {
modifiers.push({
key: vphDcd + p.start,
start: p.start,
end: p.end,
name: p.name
});
}
}
//移除arguments;
for (let j = modifiers.length - 1; j >= 0; j--) {
let m = modifiers[j];
if (m.name == 'arguments' && node.start < m.start && node.end > m.end) {
modifiers.splice(j, 1);
}
}
let walk = expr => { //遍历函数体
if (expr) {
if (expr.type == 'Identifier') {
//该标识在参数里,且在函数体内没有声明过,即考虑这样的情况
/*
function(aaa){
//...
var aaa=20;
}
函数参数中有aaa,但函数体内又重新声明了aaa,则忽略参数的aaa压缩
*/
if (pInfo.params[expr.name] && !pInfo.vd[expr.name]) {
//如果在参数里,移除修改器里面的,该参数保持不变
let find = false;
//修改器中的标识优先于函数,该处把修改器中的与当前函数有关的移除
for (let j = modifiers.length - 1; j >= 0; j--) {
let m = modifiers[j];
if (expr.start == m.start) {
find = true;
//modifiers[j].key = vphUse + modifiers[j].end;
modifiers.splice(j, 1);
break;
}
}
//如果该参数从修改器中移除,则表示是当前方法的形参,如果启用压缩,则使用压缩后的变量
if (find) {
//if (!configs.debug && configs.tmplCompressVariable) {
let v = pVarsMap[expr.name] || expr.name;
modifiers.push({
key: vphUse + expr.end,
start: expr.start,
end: expr.end,
name: v
});
compressVarToOriginal['\u0001' + expr.end + v] = expr.name;
//}
}
}
} else if (Array.isArray(expr)) {
for (let i = 0; i < expr.length; i++) {
walk(expr[i]);
}
} else if (expr instanceof Object) {
for (let p in expr) {
walk(expr[p]);
}
}
}
};
walk(node.body.body);
};
let getParentParamsAndVD = node => { //获取所有父级的函数参数及当前函数内的声明
/*
_.each(function(a,b){
var a=20;
_.each(b,function(c,d){
var d=20;
//在处理该函数体的标识时,比如
<%=a%>
//该处的a来源于外层函数的形参,如果压缩,则需要与外层对应上
});
})
*/
let p = [node];
for (let i = 0, r; i < fnRange.length; i++) { //查找在哪些个函数体内,因为函数可以一直嵌套
r = fnRange[i];
if (r != node && r.start < node.start && r.end > node.end) {
p.push(r);
}
}
let params = Object.create(null),
vd = Object.create(null);
if (p.length) { //打平参数
for (let i = 0, n; i < p.length; i++) {
n = p[i];
for (let j = 0, a; j < n.params.length; j++) {
a = n.params[j];
params[a.name] = 1;
}
}
}
//获取当前函数体内的声明语句
for (let z = modifiers.length - 1, m; z >= 0; z--) {
m = modifiers[z];
if (m.type == 'vd' && node.start < m.start && node.end > m.end) {
vd[m.name] = 1;
}
}
return {
params,
vd
};
};
let start = 0;
let paramsAndVD = [];
while (start < fnRange.length) {
paramsAndVD[start] = getParentParamsAndVD(fnRange[start]);
start++;
}
while (--start > -1) {
processOne(fnRange[start], paramsAndVD[start]);
}
//console.log(paramsAndVD);
};
let getFnRangeByPos = pos => { //根据位置获取在哪个函数体内
for (let i = 0, r; i < fnRange.length; i++) {
r = fnRange[i];
if (r.start < pos && pos < r.end) {
return r;
}
}
};
let getCompressVar = (vname, pos) => { //获取压缩后的变量
let list = compressVarsMap[vname]; //获取同一个key对应的列表
if (!list) {
return vname;
}
if (list.length == 1) { //如果只有一个,则不查找直接返回
return list[0].name;
}
if (fnRange.length) {
let range = getFnRangeByPos(pos); //获取当前变量对应的函数体
if (range) {
let tlist = [];
//获取当前函数体内对应的都有哪些声明
for (let i = 0, r; i < list.length; i++) {
r = list[i];
if (r.pos > range.start && r.pos < range.end) {
tlist.push(r);
}
}
list = tlist;
} else { //如果位置不在函数体内,则需要把函数体内的声明清除,避免影响分析
/*
<%var aaa=20%>
<%_.each(function(a){%>
<%var aaa=30%>
<%})%>
<%=aaa%> //函数体内有aaa声明,但该处需要外部声明的aaa
*/
let tlist = list.slice();
for (let i = tlist.length - 1, r; i >= 0; i--) {
r = tlist[i];
for (let j = 0, rj; j < fnRange.length; j++) {
rj = fnRange[j];
if (r.pos > rj.start && r.pos < rj.end) {
tlist.splice(i, 1);
}
}
}
list = tlist;
}
}
/*
考虑这样的情况
<%var a=20%>
...
<%=a%>
<%var a=30%>
...
<%=a%>
输出a时,即变量压缩时,需要从列表中查找当前a对应的最佳的压缩对象
*/
for (let i = 0, v, n; i < list.length; i++) {
v = list[i];
n = list[i + 1];
if (v.pos <= pos && (!n || pos < n.pos)) {
return v.name;
}
}
return vname;
};
fnProcessor();
//console.log(compressVarsMap, fnRange);
//根据start大小排序,这样修改后的fn才是正确的
modifiers.sort((a, b) => a.start - b.start);
if (!configs.debug && configs.tmplCompressVariable) { //直接修改修改器中的值即可
for (let i = 0, m; i < modifiers.length; i++) {
m = modifiers[i];
if (m.type) {
let oname = m.name;
m.name = getCompressVar(m.name, m.start);
compressVarToOriginal['\u0001' + m.end + m.name] = oname;
}
}
}
for (let i = modifiers.length - 1, m; i >= 0; i--) {
m = modifiers[i];
fn = fn.substring(0, m.start) + m.key + m.name + fn.substring(m.end);
}
//modifiers = [];
//console.log(fn,compressVarToOriginal,modifiers);
//重新遍历变量带前缀的代码
ast = acorn.parse(fn, null, sourceFile);
//let recordLoop = node => {
// modifiers.push({
// key: '\u0019',
// start: node.start,
// end: node.start,
// name: ''
// });
//};
//let meMap = Object.create(null);
acorn.walk(ast, {
VariableDeclarator(node) {
let key = stripChar(node.id.name); //把汉字前缀换成代码前缀
var m = key.match(/\x02(\d+)/);
if (m) {
let pos = m[1]; //获取这个变量在代码中的位置
key = key.replace(/\x02\d+/, '\x01'); //转换变量标记,统一变成使用的标记
if (!globalTracker[key]) { //全局追踪
globalTracker[key] = [];
}
let hasValue = false;
let value = null;
if (node.init) { //如果有赋值
hasValue = true;
value = stripChar(fn.substring(node.init.start, node.init.end));
}
globalTracker[key].push({
pos: pos | 0,
hasValue,
value
});
}
},
AssignmentExpression(node) {
//let i = meMap[node.start];
//if (i) {
// i.ae = true;
//}
let key = '\x01' + node.left.name;
let value = stripChar(fn.substring(node.right.start, node.right.end));
if (!globalTracker[key]) {
globalTracker[key] = [];
}
let list = globalTracker[key];
if (list) {
let found = false;
for (let i of list) {
if (!i.hasValue) { //如果是首次赋值,则直接把原来的变成新值
i.value = value;
i.hasValue = found = true;
break;
}
}
if (!found) { //该变量存在重复赋值,记录这些重复赋值的地方,后续在变量分析追踪时有用,如<%var a=name%>...<%~a%> ....<%a=age%>...<%~a%> 两次<%~a%>输出的结果对应不同的根变量
list.push({
pos: node.left.end,
value: value
});
}
}
}/*,
VariableDeclaration(node) {
if (node.kind == 'let' || node.kind == 'const') {
for (let vd of node.declarations) {
let range;
for (let br of blockRange) {
if (br.start < vd.start && vd.end < br.end) {
range = br;
break;
}
}
if (range) {
fnRange.push(range);
}
}
}
}*/
/*
MemberExpression(node) {
//处理模板中的对象表达式
if (configs.tmplMESafeguard) {
let temp = meMap[node.start];
if (temp) {//同一个位置的对象表达式可能会被遍历多次,如user.name.first,我们只需要记录最长的即可。遍历根据计算规则来,所以同样的位置我们更新即可
temp.node = node;
temp.raw = fn.slice(node.start, node.end);
temp.end = node.end;
} else {//新的表达式
temp = {
start: node.start,
end: node.end,
node,
key: '',
raw: fn.slice(node.start, node.end)
};
meMap[node.start] = temp;
}
//对于 user.name[name.first] 我们要把name.first从整体表达式中删除
for (let p in meMap) {
if (p != node.start) {
let i = meMap[p];
if (i.start >= node.start && i.end <= node.end) {
i.name = '';
delete meMap[p];
}
}
}
}
},
ForStatement: recordLoop,
WhileStatement: recordLoop,
DoWhileStatement: recordLoop,
ForOfStatement: recordLoop,
ForInStatement: recordLoop,
CallExpression: node => {
let args = node.arguments;
if (args && args.length > 0) {
let a0 = args[0];
let a1 = args[1];
if (a0.type == 'FunctionExpression' ||
a0.type == 'ArrowFunctionExpression' ||
(a1 && (a1.type == 'FunctionExpression' ||
a1.type == 'ArrowFunctionExpression'))) {
let callee = node.callee;
if (callee.type == 'MemberExpression') {
let p = callee.property;
if (loopNames.hasOwnProperty(p.name)) {
modifiers.push({
key: '\u0019',
start: node.start,
end: node.start,
name: ''
});
}
}
}
}
}*/
});
//for (let me in meMap) {
// let i = meMap[me];
// i.name = getSafeguardExpression(i, fn);
// modifiers.push(i);
//}
//modifiers.sort((a, b) => a.start - b.start);
//for (let i = modifiers.length, m; i--;) {
// m = modifiers[i];
// fn = fn.slice(0, m.start) + m.key + m.name + fn.slice(m.end);
//}
//console.log(globalTracker);
//fn = stripChar(fn);
//console.log(fn);
fn = fn.replace(scharReg, '');
fn = stripChar(fn);
fn = fn.replace(htmlHolderReg, m => htmlStore[m]);
//console.log(JSON.stringify(fn));
fn = fn.replace(tmplCmdReg, (match, operate, content) => {
if (operate) {
return '<%' + operate + content.slice(1, -1) + '%>';
}
return match;
}); //把合法的js代码转换成原来的模板代码
//console.log(fn);
//console.log(globalTracker, fnRange);
let cmdStore = Object.create(null);
let getTrackerList = (key, pos) => {
let list = globalTracker[key];
if (!list) return null;
if (fnRange.length) { //处理带函数的情况
/*
<%var a=usr.name%>
<%_.each(function(){%>
<%var a=usr.age%>
<input <%:a%> />
<%x(function(){%>
<%var a=usr.sex%>
<input <%:a%> />
<%})%>
<%})%>
<input <%:a%> />
思路:接函数划分区间,找出当前变量落在哪个函数范围内,在该范围内再搜索对应的变量
*/
let range = getFnRangeByPos(pos);
if (range) {
let tlist = [];
for (let i = 0, r; i < list.length; i++) {
r = list[i];
if (r.pos > range.start && r.pos < range.end) {
tlist.push(r);
}
}
list = tlist;
} else {
let tlist = list.slice();
for (let i = tlist.length - 1, r; i >= 0; i--) {
r = tlist[i];
for (let j = 0, rj; j < fnRange.length; j++) {
rj = fnRange[j];
if (r.pos > rj.start && r.pos < rj.end) {
tlist.splice(i, 1);
}
}
}
list = tlist;
}
}
return list;
};
let toOriginalExpr = expr => {
if (!configs.debug && configs.tmplCompressVariable) {
expr = stripNum(expr.replace(compressVarReg, m => compressVarToOriginal[m] || m));
} else {
expr = stripNum(expr);
}
return expr;
};
let best = head => {
//console.log(head);
let match = head.match(/\u0001(\d+)/); //获取使用这个变量时的位置信息
if (!match) return null;
let pos = match[1];
pos = pos | 0;
let key = head.replace(/\u0001\d+/, '\u0001'); //获取这个变量对应的赋值信息
let list = getTrackerList(key, pos); //获取追踪列表
if (!list) return null;
for (let i = list.length - 1, item; i >= 0; i--) { //根据赋值时的位置查找最优的对应
item = list[i];
if (item.pos < pos) {
return item.value;
}
}
return null;
};
let find = (expr, srcExpr) => {
if (!srcExpr) {
srcExpr = expr;
}
//slog.ever('expr', expr);
let ps = jsGeneric.splitExpr(expr);//表达式拆分,如user[name][key[value]]=>["user","[name]","[key[value]"]
/*
1. <%:user.name%>
2. <%var a=user.name%>...<%:a%>
3. <%var a=user%> ...<%var b=a.name%> ....<%:b%>
*/
let head = ps[0]; //获取第一个
if (head == '\x03' || head == '\x06') { //如果是根变量,则直接返回 第1种情况
return ps.slice(1);
}
let info = best(head); //根据第一个变量查找最优的对应的根变量,第2种情况
if (!info) {
let tipExpr = toOriginalExpr(srcExpr.trim());
expr = toOriginalExpr(expr);
slog.ever(chalk.red('[MXC Error(tmpl-vars)] can not resolve expr: ' + tipExpr), 'at', chalk.grey(sourceFile), 'check variable reference or global variable declaration');
return ['<%throw new Error("can not resolve bind expr: ' + expr + ' read more: https://github.com/thx/magix/issues/37")%>'];
}
if (info != '\x03' || info != '\x06') { //递归查找,第3种情况
ps = find(info, srcExpr).concat(ps.slice(1));
}
return ps; //.join('.');
};
let analyseExpr = (expr, source) => {
//if (configs.tmplMESafeguard) {
//expr = jsGeneric.splitSafeguardExpr(expr).pop();
//}
let result = find(expr, source); //获取表达式信息
let vars = [];
//slog.ever('result', result);
//把形如 ["user","[name]","[key[value]"]=> user.<%=name%>.<%=key[value]%>
for (let i = 0, one; i < result.length; i++) {
one = result[i];
if (one.charAt(0) == '[' && one.charAt(one.length - 1) == ']') {
one = '<%=' + one.slice(1, -1) + '%>';
vars.push(one);
}
//one = stripNum(one);
result[i] = one;
}
result = result.join('.');
return {
vars,
result
};
};
let findRoot = expr => {
if (expr == '\x03') {
return '#';
}
let ps = jsGeneric.splitExpr(expr);
let head = ps[0];
if (head == '\x03') {
return ps[1];
} else if (head == '\x06') {
return null;
}
let info = best(head);
if (!info) {
return null;
}
return findRoot(info);
};
let extractMxViewRootKeys = attrs => {
let keys = [];
let takeKeys = (m, c, v) => {
if (c == '@') {
let ks = ExtractIds(v);
if (ks.length) {
for (let k of ks) {
m = findRoot(k);
if (m && keys.indexOf(m) === -1) {
keys.push(m);
}
}
}
}
};
attrs.replace(artCtrlsReg, '$2')
.replace(mxViewAttrReg, (m, q, value) => {
q = value.indexOf('?');
if (q > -1) {
value = value.substring(q + 1);
value.replace(tmplCmdReg, takeKeys);
}
}).replace(viewAttrReg, (m, key, q, value) => {
value.replace(tmplCmdReg, takeKeys);
});
return keys;
};
fn = tmplCmd.store(fn, cmdStore); //存储代码,只分析模板
//textarea情况:<textarea><%:taValue%></textarea>处理成=><textarea <%:taValue%>><%=taValue%></textarea>
fn = fn.replace(textaraReg, (match, attr, content) => {
attr = tmplCmd.recover(attr, cmdStore);
content = tmplCmd.recover(content, cmdStore, recoverString);
if (bindReg2.test(content)) {
bindReg2.lastIndex = 0;
let bind = '', artExpr = '';
content = content.replace(bindReg2, (m, art, expr) => {
bind = m;
artExpr = art;
let i = extractFunctions(expr);
return `<%=${i.expr}%>`;
}).replace(artExpr, '');
attr = attr + ' ' + bind;
}
content = tmplCmd.store(content, cmdStore);
attr = tmplCmd.store(attr, cmdStore);
return '<textarea' + attr + '>' + content + '</textarea>';
});
let mxeCount = 0;
fn = fn.replace(tagReg, (match, tag, attrs) => {
let hasMagixView = mxViewAttrReg.test(attrs); //是否有mx-view属性
//console.log(cmdStore, attrs);
attrs = tmplCmd.recover(attrs, cmdStore, recoverString); //还原
let findCount = 0;
let mxeInfo = [];
let syncPaths = [];
let transformEvent = (exprInfo, source, attrName, art) => { //转换事件
let expr = exprInfo.expr;
expr = analyseExpr(expr, source); //分析表达式
for (let v of expr.vars) {
if (syncPaths.indexOf(v) == -1) {
syncPaths.push(v);
}
}
let e = `${art}{p:'${expr.result}'`;
if (exprInfo.fns) {
e += `,f:` + exprInfo.fns;
}
/*
对于view的绑定,如
<mx-calendar.rangepicker start="{{:date.start}}" end="{{:date.end}}"/>
不像input,只能读取value这唯一输入源。
自定义的情况下,输入源可以有多个,那么当我们绑定时,需要知道当前绑定表达式对应的属性是什么,从而来确定如何从输入源中把数据取出来
*/
if (attrName && attrName.startsWith('view-')) {
let an = attrName.substring(5);
if (an.startsWith('@')) {
an = an.substring(1);
}
e += `,a:'${an}'`;
}
e += '}';
mxeInfo.push(e);
};
attrs = attrs.replace(bindReg, (m, name, q, art, op, expr) => {
expr = expr.trim();
let exprInfo = extractFunctions(expr);
art = art || '';
transformEvent(exprInfo, m, name, art);
findCount++;
let replacement = '<%=';
if (hasMagixView && name.indexOf('view-') === 0) {
replacement = '<%@';
}
m = name + '=' + q + art + replacement + exprInfo.expr + '%>' + q;
//console.log('enter?',m,name,q,art,expr);
return m;
}).replace(bindReg2, (m, art, expr) => {
expr = expr.trim();
let exprInfo = extractFunctions(expr);
art = art || '';
transformEvent(exprInfo, m, null, art);
findCount++;
return ' ';
});
if (findCount > 0) {
let bindExpr = ``;
if (configs.magixUpdaterBindExpression) {
let mxe = '\x1f_' + mxeCount.toString(16);
if (syncPaths.length) {
mxe += '_' + syncPaths.join('_');
}
bindExpr = ` mxo="\x1f" mxe="${mxe}"`;
}
attrs = bindExpr + ' mxc="[' + mxeInfo.join(',') + ']" ' + attrs;
mxeCount++;
}
if (configs.magixUpdaterIncrement) {
let keys = extractMxViewRootKeys(attrs);
if (keys.length) {
attrs = ' mxv="' + keys + '"' + attrs;
}
}
//console.log(attrs);
return '<' + tag + attrs + '>';
});
fn = tmplCmd.recover(fn, cmdStore);
fn = fn.replace(pathReg, (m, expr) => {
expr = expr.trim();
//console.log('expr', expr);
//debugger;
expr = analyseExpr(expr, m);
return expr.result;
});
fn = recoverString(stripNum(fn));
if (!configs.debug && configs.tmplCompressVariable) {
let refVarsMap = Object.create(null);
for (let p in compressVarToOriginal) {
refVarsMap[stripNum(p)] = compressVarToOriginal[p];
}
e.toTmplSrc = (expr, refCmds) => {
expr = tmplCmd.recover(expr, refCmds);
for (let map in refVarsMap) {
let reg = regexp.get(regexp.escape(map), 'g');
expr = expr.replace(reg, refVarsMap[map]);
}
return expr.replace(removeTempReg, '').replace(artCtrlsReg, '');
};
} else {
e.toTmplSrc = (expr, refCmds) => {
expr = tmplCmd.recover(expr, refCmds);
return expr.replace(removeTempReg, '').replace(artCtrlsReg, '{{$1}}');
};
}
e.globalVars = Object.keys(globalVars);
//slog.ever(JSON.stringify(fn));
return fn;
}
};