magix-composer
Version:
compile html, style and javascript files into javascript
1,050 lines (1,045 loc) • 43.3 kB
JavaScript
/*
对模板增加根变量的分析,模板引擎中不需要用with语句
压缩模板引擎代码
*/
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 md5 = require('./util-md5');
let jsGeneric = require('./js-generic');
let {
htmlAttrParamPrefix,
revisableReg,
tmplMxViewParamKey,
tmplCondPrefix,
tmplVarTempKey,
tmplGlobalVars
} = require('./util-const');
let regexp = require('./util-rcache');
let tmplCmdReg = /<%([@=!:&*~]|\.{3})?([\s\S]*?)%>|$/g;
let tagReg = /<([^>\s\/\x07]+)([^>]*)>/g;
let bindReg = /([^>\s\/=]+)\s*=\s*(["'])(<%'\x17\d+\x11[^\x11]+\x11\x17'%>)?<%:([\s\S]+?)%>\s*\2/g;
let bindReg2 = /\s*(<%'\x17\d+\x11[^\x11]+\x11\x17'%>)?<%:([\s\S]+?)%>\s*/g;
let textaraReg = /<textarea([^>]*)>([\s\S]*?)<\/textarea>/g;
let mxViewAttrReg = /(?:\b|\s|^)mx-view\s*=\s*(['"])([\s\S]+?)\1/g;
let checkboxReg = /(?:\b|\s|^)type\s*=\s*(['"])checkbox\1/;
let indeterminateReg = /(?:\b|\s|^)indeterminate(?:\b|\s|=|$)/;
let atRefOrAnalysePathExprReg = /<%([@~])([\s\S]+?)%>/g;
let vphUse = String.fromCharCode(0x7528); //用
let vphDcd = String.fromCharCode(0x58f0); //声
let vphCst = String.fromCharCode(0x56fa); //固
let vphGlb = String.fromCharCode(0x5168); //全
let vphAsg = String.fromCharCode(0x8d4b);
let creg = /[\u7528\u58f0\u56fa\u5168\u8d4b]/g;
let hreg = /([\x01\x02\x10])\d+/g;
let stringReg = /^['"]/;
let numIndexReg = /^\[(\d+)\]$/;
let tailEmptyReg = /\+''$/;
let bindEventParamsReg = /^\s*"([^"]+)",/;
let artCtrlsReg = /<%'\x17\d+\x11([^\x11]+)\x11\x17'%>(<%[\s\S]+?%>)/g;
let stringHolderReg = /(['"])?(\x04\d+\x04)\1/g;
let refKeyReg = /,'\x1e[#a-zA-Z0-9]+'$/;
let cmap = {
[vphUse]: '\x01',
[vphDcd]: '\x02',
[vphGlb]: '\x03',
[vphCst]: '\x06',
[vphAsg]: '\x10'
};
let stripChar = str => str.replace(creg, m => cmap[m]);
let stripNum = str => str.replace(hreg, '$1');
let leftOuputReg = /\x18",/g;
let rightOutputReg = /,\s*"/g;
let numberReg1 = /^[+-]?\.\d+(?:E[+-]?\d+)?$/i;
let numberReg2 = /^[+-]?(?:0x|0b|0o)[0-9a-f]+$/i;
let numberReg3 = /^[+-]?\d+\.?\d*(?:E[+-]?\d+)?$/i;
let numberReg4 = /^[+-]?\d+n$/;
let numberReg5 = /^[+-]?BigInt\(\s*(['"`])?\s*(?:0x|0b|0o)?[0-9a-f]+n?\s*\1\s*\)$/i;
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 = regexp.get(`\\s${regexp.escape(htmlAttrParamPrefix)}([\\w\\-]+)=(["'])([\\s\\S]*?)\\2`, 'g');
//前导符后跟合法的id
let IdReg = /(?:[\x03\x06]\.|\x01\d+)[\x24\x30-\x39\x41-\x5a\x5f\x61-\x7a]+/g;
let ExtractIds = v => {
let keys = [];
if (v == '\x03') {
keys.push(v);
} else {
v.replace(IdReg, m => {
keys.push(m);
});
}
return keys;
};
let SpreadPattern = (fn, node) => {
//debugger;
if (node.type == 'ArrayPattern' ||
node.type == 'ArrayExpression') {
let a = [];
for (let p of node.elements) {
if (p) {
if (p.type == 'ObjectPattern' ||
p.type == 'ObjectExpression' ||
p.type == 'ArrayPattern' ||
p.type == 'ArrayExpression') {
a.push(SpreadPattern(fn, p));
} else {
a.push(fn.slice(p.start, p.end));
}
} else {
a.push('');
}
}
return `[${a.join(',')}]`;
} else {
let a = [];
for (let p of node.properties) {
if (p.shorthand) {
if (p.value.type == 'Identifier') {
a.push(`${p.key.name}:${p.value.name}`);
} else {
a.push(`${p.key.name}:${fn.slice(p.value.start, p.value.end)}`);
}
} else {
let c = '';
if (p.type == 'Property') {
if (p.key.type == 'Identifier') {
if (p.computed) {
c = `[${p.key.name}]:`;
} else {
c = `${p.key.name}:`;
}
} else if (p.key.type == 'Literal') {
c = `${p.key.raw}:`;
} else if (p.key.type == 'TemplateLiteral') {
c = `[${fn.slice(p.key.start, p.key.end)}]:`;
}
if (p.value.type == 'Identifier') {
c += p.value.name;
} else if (p.value.type == 'ObjectPattern' ||
p.value.type == 'ObjectExpression' ||
p.value.type == 'ArrayPattern' ||
p.value.type == 'ArrayExpression') {
c += SpreadPattern(fn, p.value);
} else {
c += fn.slice(p.value.start, p.value.end);
}
} else {
c += fn.slice(p.start, p.end);
}
a.push(c);
}
}
return `{${a.join(',')}}`;
}
};
let PeelTempKeyPrefix = '$peel_key_';
let PeelTempValuePrefix = '$peel_val_';
let PeelTempRootPrefix = '$peel_root_';
let PeelPatternVariable = (fn, node) => {
let a = [];
let left, right, prefix = 'let ', spliter = ';';
if (node.type == 'VariableDeclarator') {
left = node.id;
right = node.init;
prefix = '';
spliter = ',';
} else {
left = node.left;
right = node.right;
}
let main = fn.slice(right.start, right.end);
if (right.type != 'Identifier') {
let mainKey = utils.uId(PeelTempRootPrefix, fn, true);
a.push(`${prefix}${mainKey}=${main}`, spliter);
main = mainKey;
}
let peel = (n, host) => {
if (n.type == 'ObjectPattern') {
for (let p of n.properties) {
if (p.type == 'Property') {
if (p.value.type == 'ObjectPattern' ||
p.value.type == 'ArrayPattern') {
let key = p.key.name;
let next = key;
if (p.computed) {
key = `[${key}]`;
next = utils.uId(PeelTempValuePrefix, fn, true);
} else {
key = `.${key}`;
}
a.push(`${next}=${host}${key}`, spliter);
peel(p.value, next);
} else if (p.value.type == 'Identifier') {
let key;
if (p.computed) {
key = `[${p.key.name}]`;
} else if (p.key.type == 'Identifier') {
key = `.${p.key.name}`;
} else {
key = `[${p.key.raw}]`;
}
a.push(`${p.value.name}=${host}${key}`, spliter);
} else if (p.value.type == 'AssignmentPattern') {
let key;
if (p.computed) {
key = `[${p.key.name}]`;
} else if (p.key.type == 'Identifier') {
key = `.${p.key.name}`;
} else {
key = `[${p.key.raw}]`;
}
let v = p.value;
let left = v.left.name;
let right;
if (v.right.type == 'Literal') {
right = v.right.raw;
} else {
right = v.right.name;
}
let tv = utils.uId(PeelTempValuePrefix, fn, true);
a.push(`${prefix}${tv}=${host}${key}${spliter}${left}=void 0===${tv}?${right}:${tv}`, spliter);
}
} else {
//RestElement 简单实现
a.push(`${p.argument.name}=${host}`, spliter);
}
}
} else if (n.type == 'ArrayPattern') {
let idx = 0;
for (let p of n.elements) {
if (p) {
if (p.type == 'ObjectPattern') {
let k = utils.uId(PeelTempKeyPrefix, fn, true);
a.push(`${k}=${host}[${idx}]`, spliter);
peel(p, k);
} else if (p.type == 'ArrayPattern') {
let k = utils.uId(PeelTempKeyPrefix, fn, true);
a.push(`${prefix}${k}=${host}[${idx}]`, spliter);
peel(p, k);
} else if (p.type == 'AssignmentPattern') {
let tv = utils.uId(PeelTempValuePrefix, fn, true);
let key = p.left.name;
let v = p.right.type == 'Literal' ? p.right.raw : p.right.name;
a.push(`${prefix}${tv}=${host}[${idx}]${spliter}${key}=void 0===${tv}?${v}:${tv}`, spliter);
} else if (p.type == 'RestElement') {
a.push(`${p.argument.name}=${host}.slice(${idx})`, spliter);
} else {
a.push(`${p.name}=${host}[${idx}]`, spliter);
}
}
idx++;
}
}
};
peel(left, main);
if (!prefix) {
if (a.length) {
a.pop();
} else {
a.push(utils.uId('$pole_', fn, true));
}
}
return a.join('');
};
/*
\x00 `反撇
\x01 模板中局部变量 用
\x02 变量声明的地方 声
\x03 模板中全局变量 全
\x04 命令中的字符串
\x05 html中的字符串
\x06 constVars 固定不会变的变量
\x07 存储命令
\x11 精准识别rqeuire
\x12 精准识别@符
\x17 模板中的纯字符串
\x18 模板中的绑定参数对象
\x19 模板中的循环
\x10 赋值
第一遍用汉字
第二遍用不可见字符
*/
module.exports = {
process: (tmpl, e) => {
//console.log(tmpl);
let sourceFile = e.shortHTMLFile;
let fn = [];
let index = 0;
let htmlStore = Object.create(null);
let htmlIndex = 0;
//console.log(tmpl);
let htmlKey = utils.uId('\x05', tmpl);
let htmlHolderReg = new RegExp(htmlKey + '\\d+' + htmlKey, 'g');
let charReg = new RegExp('(?:;`' + htmlKey + '|' + htmlKey + '`;)', 'g');
let toSourceHTML = src => {
src = src.replace(charReg, htmlKey);
src = stripChar(src);
src = src.replace(htmlHolderReg, m => htmlStore[m]);
src = src.replace(tmplCmdReg, (match, operate, content) => {
if (operate) {
return '<%' + operate + content.slice(1, -1) + '%>';
}
return match;
});
return src;
};
tmpl.replace(tmplCmdReg, (match, operate, content, offset) => {
let start = 2;
if (operate) {
start += operate.length;
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);
try {
ast = acorn.parse(fn, null, sourceFile);
} catch (ex) {
let { column } = ex.loc;
let start = column,
end = column;
while (fn.charAt(start) != '`') {
start--;
}
while (fn.charAt(end) != '`') {
end++;
}
let msg = fn.substring(start + 2, end - 1);
if (msg.startsWith('[') && msg.endsWith(']')) {
msg = msg.substring(1, msg.length - 1);
}
slog.ever(chalk.red('[MXC Error(tmpl-vars)] Parse template js code ast error: ' + ex.message), 'at', chalk.magenta(e.shortHTMLFile), 'near', chalk.red(msg));
throw ex;
}
/*
变量和变量声明在ast里面遍历的顺序不一致,需要对位置信息保存后再修改fn
*/
let modifiers = [];
let stringStore = Object.create(null);
let stringIndex = 0;
let recoverString = tmpl => {
//还原代码中的字符串,代码中的字符串占位符使用\x04包裹
//模板中的绑定特殊字符串包含\x17,这里要区分这个字符串的来源
return tmpl.replace(stringHolderReg, (m, q, c) => {
q = q || '';
let str = stringStore[c];
if (q) {
str = str.slice(1, -1);//获取源字符串
}
let result;
if (str.charAt(0) == '\x17') { //如果是\x17,这个是绑定时的特殊字符串
result = q + str.substring(1) + q;
} else { //其它情况再使用\x17包裹,方便在后续如 <div class="{{='selector'}}"中进一步处理
result = q + '\x17' + str + '\x17' + q;
}
//console.log(JSON.stringify(m), result, JSON.stringify(result));
return result;
});
};
let constVars = Object.create(null);
let patternChecker = (node, instead) => {
let msg = '[MXC Error(tmpl-vars)] unpupport ' + node.type + ' near `' + toSourceHTML(fn.substring(node.start, node.end)) + '`';
slog.ever(chalk.red(msg), 'at', chalk.grey(sourceFile), (instead ? chalk.magenta(`use ${instead} instead`) : ''));
throw new Error(msg);
};
let pattersObject = Object.create(null);
let processExpressions = Object.create(null);
let objectExpr = node => {
let key = node.start + '~' + node.end;
let key1 = fn.slice(node.start, node.end);
let process = 0;
if (node.type == 'ObjectPattern' ||
node.type == 'ArrayPattern') {
processExpressions[key1] = 1;
process = 1;
} else {
process = processExpressions[key1];
}
if (!process) return;
pattersObject[key] = node;
if (node.type == 'ObjectPattern' ||
node.type == 'ObjectExpression') {
for (let p of node.properties) {
if (p.type == 'Property') {
if (p.value.type == 'ObjectPattern' ||
p.value.type == 'ObjectExpression' ||
p.value.type == 'ArrayPattern' ||
p.value.type == 'ArrayExpression') {
key = p.value.start + '~' + p.value.end;
if (pattersObject[key]) {
delete pattersObject[key];
}
}
}
}
} else {
for (let p of node.elements) {
if (p) {
if (p.type == 'ObjectPattern' ||
p.type == 'ObjectExpression' ||
p.type == 'ArrayPattern' ||
p.type == 'ArrayExpression') {
key = p.start + '~' + p.end;
if (pattersObject[key]) {
delete pattersObject[key];
}
}
}
}
}
};
acorn.walk(ast, {
ForOfStatement: patternChecker,
FunctionDeclaration: patternChecker,
FunctionExpression: patternChecker,
ArrowFunctionExpression: patternChecker,
ObjectPattern: objectExpr,
ObjectExpression: objectExpr,
ArrayPattern: objectExpr,
ArrayExpression: objectExpr,
VariableDeclaration(node) {
if (node.kind != 'let') {
node.type = `"${node.kind}" ${node.type}`;
patternChecker(node, 'let');
}
},
CallExpression(node) { //方法调用
let vname = '';
let callee = node.callee;
if (callee.name) { //只处理模板中 <%=fn(a,b)%> 这种,不处理<%=x.fn()%>,后者x对象上除了挂方法外,还有可能挂普通数据。对于方法我们不把它当做变量处理,因为给定同样的参数,方法需要返回同样的结果
vname = callee.name;
constVars[vname] = 1;
}
},
VariableDeclarator(node) {
if (node.init) {
switch (node.init.type) {
case 'ArrayExpression':
case 'ObjectExpression':
case 'FunctionExpression':
case 'ArrowFunctionExpression':
slog.ever(chalk.red('[MXC Tip(tmpl-vars)] avoid declare ' + fn.substring(node.start, node.end)), 'at', chalk.grey(sourceFile));
break;
}
}
}
});
for (let p in pattersObject) {
let v = pattersObject[p];
modifiers.push({
start: v.start,
end: v.end,
content: SpreadPattern(fn, v)
});
}
if (modifiers.length) {
modifiers.sort((a, b) => a.start - b.start);
for (let i = modifiers.length, m; i--;) {
m = modifiers[i];
fn = fn.substring(0, m.start) + m.content + fn.substring(m.end);
}
modifiers = [];
//console.log(fn);
ast = acorn.parse(fn, null, sourceFile);
}
acorn.walk(ast, {
VariableDeclarator(node) {
if (node.id.type == 'ObjectPattern' ||
node.id.type == 'ArrayPattern') {
modifiers.push({
start: node.start,
end: node.end,
content: PeelPatternVariable(fn, node)
});
}
},
ExpressionStatement(node) {
let expr = node.expression;
if (expr.type == 'AssignmentExpression') {
if (expr.left.type == 'ObjectPattern' ||
expr.left.type == 'ArrayPattern') {
modifiers.push({
start: node.start,
end: node.end,
content: PeelPatternVariable(fn, expr)
});
}
}
}
});
if (modifiers.length) {
modifiers.sort((a, b) => a.start - b.start);
for (let i = modifiers.length, m; i--;) {
m = modifiers[i];
fn = fn.substring(0, m.start) + m.content + fn.substring(m.end);
}
modifiers = [];
//console.log(fn);
ast = acorn.parse(fn, null, sourceFile);
}
let blockRanges = [];
acorn.walk(ast, {
BlockStatement(node) {
blockRanges.push({
start: node.start,
end: node.end,
key: node.start + '~' + node.end
});
}
});
if (blockRanges.length) {
blockRanges.sort((a, b) => b.start - a.start);
}
let outerKey = '0~' + fn.length;
blockRanges.push({
start: 0,
end: fn.length,
key: outerKey,
global: true
});
let rangeDeclares = Object.create(null);
rangeDeclares[outerKey] = Object.create(null);
rangeDeclares[outerKey][tmplVarTempKey] = 2;
let queryRangeByPos = pos => {
for (let i = 0; i < blockRanges.length; i++) {
let b = blockRanges[i];
if (b.start < pos && pos < b.end) {
return b;
}
}
return null;
};
let queryVarsByPos = pos => {
let vars = Object.create(null);
for (let i = 0; i < blockRanges.length; i++) {
let b = blockRanges[i];
if (b.start < pos && pos < b.end) {
let d = rangeDeclares[b.key];
if (d) {
Object.assign(vars, d);
}
}
}
return vars;
};
let processString = (node, tl) => { //存储字符串,减少分析干扰
if (tl ||
stringReg.test(node.raw)) {
let q = tl ? '' : node.raw.match(stringReg)[0];
let key = '\x04' + (stringIndex++) + '\x04';
if (revisableReg.test(node.raw) && !configs.debug) {
node.raw = node.raw.replace(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
});
}
};
let globalVars = Object.create(null);
acorn.walk(ast, {
Property(node) {
if (node.key.type == 'Literal') {
processString(node.key);
}
},
Literal: processString,
TemplateLiteral(node) {
for (let q of node.quasis) {
q.raw = q.value.raw;
if (!q.raw.startsWith(htmlKey)) {
processString(q, true);
}
}
},
Identifier(node) {
let tname = node.name;
let r = queryVarsByPos(node.start);
let isGlobal = 0;
if (!tmplGlobalVars[tname] && (
!r[tname])) {
isGlobal = 1;
}
if (isGlobal) { //模板中全局不存在这个变量
modifiers.push({ //如果是指定不会改变的变量,则加固定前缀,否则会全局前缀
key: (constVars[tname] ? vphCst : vphGlb) + '.',
start: node.start,
end: node.end,
name: tname
});
globalVars[tname] = 1;
} else {
modifiers.push({
key: vphUse + node.end,
start: node.start,
end: node.end,
name: tname
});
}
},
AssignmentExpression(node) { //赋值语句
if (node.left.type == 'Identifier') {
let lname = node.left.name;
let r = queryVarsByPos(node.start);
if (!r[lname]) {
//模板中使用如<%list=20%>这种,虽然可以,但是不建议使用,因为在模板中可以修改js中的数据,这是非常不推荐的
slog.ever(chalk.red('[MXC Tip(tmpl-vars)] undeclare variable:' + lname), 'at', chalk.grey(sourceFile));
globalVars[lname] = 1;
} else {
let left = node.left;
modifiers.push({
key: vphAsg + left.end,
start: left.start,
end: left.end,
name: left.name
});
}
} else if (node.left.type == 'MemberExpression') {
let start = node.left;
while (start.object) {
start = start.object;
} //模板中使用如<%list.x=20%>这种
let r = queryVarsByPos(node.start);
if (!r[start.name]) {
globalVars[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 (globalVars[tname] || globalVars[tname]) {
let msg = '[MXC Error(tmpl-vars)] avoid redeclare variable:' + tname;
slog.ever(chalk.red(msg), 'at', chalk.grey(sourceFile));
throw new Error(msg);
}
let r = queryRangeByPos(node.start);
if (r) {
if (!rangeDeclares[r.key]) {
rangeDeclares[r.key] = Object.create(null);
}
rangeDeclares[r.key][tname] = node.init ? 3 : 2;
}
modifiers.push({
key: vphDcd + node.start,
start: node.id.start,
end: node.id.end,
name: tname
});
},
ThisExpression(node) {
modifiers.push({
key: '',
start: node.start,
end: node.end,
name: vphGlb
});
}
});
if (modifiers.length) {
modifiers.sort((a, b) => a.start - b.start);
for (let i = modifiers.length, m; i--;) {
m = modifiers[i];
fn = fn.substring(0, m.start) + m.key + m.name + fn.substring(m.end);
}
//console.log(fn);
ast = acorn.parse(fn, null, sourceFile);
}
let globalTracker = Object.create(null);
acorn.walk(ast, {
VariableDeclarator(node) {
let key = stripChar(node.id.name); //把汉字前缀换成代码前缀
let m = key.match(/\x02(\d+)/);
if (m) {
let pos = m[1] | 0; //获取这个变量在代码中的位置
key = key.replace(/\x02\d+/, '\x01'); //转换变量标记,统一变成使用的标记
if (!globalTracker[key]) {
globalTracker[key] = [];
}
let hasValue = false;
let value = null;
let type = '';
if (node.init) { //如果有赋值
hasValue = true;
let { init } = node;
type = init.type;
value = stripChar(fn.substring(init.start, init.end));
}
let r = queryRangeByPos(pos);
globalTracker[key].push({
pos,
start: r.start,
end: r.end,
hasValue,
value,
type
});
}
},
AssignmentExpression(node) {
let key = stripChar(node.left.name);
let m = key.match(/\x10(\d+)/);
if (m) {
let pos = m[1] | 0; //获取这个变量在代码中的位置
let r = queryRangeByPos(pos);
let { right } = node;
let value = stripChar(fn.substring(right.start, right.end));
key = key.replace(/\x10\d+/, '\x01'); //转换变量标记,统一变成使用的标记
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;
i.type = right.type;
i.start = r.start;
i.end = r.end;
break;
}
}
if (!found) { //该变量存在重复赋值,记录这些重复赋值的地方,后续在变量分析追踪时有用,如<%var a=name%>...<%~a%> ....<%a=age%>...<%~a%> 两次<%~a%>输出的结果对应不同的根变量
list.push({
pos,
value: value,
type: right.type,
start: r.start,
end: r.end
});
}
}
}
}
});
fn = toSourceHTML(fn); //把合法的js代码转换成原来的模板代码
let cmdStore = Object.create(null);
let getParentRefKey = (key, pos) => {
let list = globalTracker[key];
if (!list) return null;
for (let i = list.length, item; i--;) {
item = list[i];
if (item.pos < pos &&
item.start < pos &&
pos < item.end) {
if (item.type == 'CallExpression') {
return null;
}
return item.value;
}
}
return null;
};
let toOriginalExpr = expr => stripNum(expr).replace(artCtrlsReg, '{{$1}}');
let best = head => {
let match = head.match(/\x01(\d+)/); //获取使用这个变量时的位置信息
if (!match) return null;
let pos = match[1];
pos = pos | 0;
let key = head.replace(/\x01\d+/, '\x01'); //获取这个变量对应的赋值信息
return getParentRefKey(key, pos);
};
let find = (expr, srcExpr, prefix) => {
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) {
if (!prefix) {
let tipExpr = toOriginalExpr(srcExpr.trim());
slog.ever(chalk.red('[MXC Error(tmpl-vars)] can not resolve bind expression: ' + tipExpr), 'at', chalk.grey(sourceFile), 'check variable reference or global variable declaration,read more: https://github.com/thx/magix/issues/37');
return ['<%throw new Error("can not resolve bind expression")'];
}
return [prefix()];
}
if (info != '\x03' || info != '\x06') { //递归查找,第3种情况
ps = find(info, srcExpr, prefix).concat(ps.slice(1));
}
return ps; //.join('.');
};
let analyseExpr = (expr, source, prefix) => {
let result = find(expr, source, prefix); //获取表达式信息
let vars = [];
if (prefix) {
let rebuild = [],
temp = [];
let takeParts = () => {
if (temp.length) {
let part = temp.join('.');
if (!configs.debug) {
part = md5(part, 'compressRefExpr', '', true);
}
rebuild.push(part);
temp.length = 0;
}
};
for (let one of result) {
if (one.charAt(0) == '[' &&
one.charAt(one.length - 1) == ']') {
takeParts();
one = `'+(${one.slice(1, -1)})+'`;
rebuild.push(one);
} else {
temp.push(one);
}
}
takeParts();
result = rebuild.join('.');
} else {
//["user","[name]","[key[value]"]=> user.<%=name%>.<%=key[value]%>
for (let i = 0, one; i < result.length; i++) {
one = result[i].replace(numIndexReg, '$1');
if (one.charAt(0) == '[' &&
one.charAt(one.length - 1) == ']') {
one = '<%=' + one.slice(1, -1) + '%>';
vars.push(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 == '@') {
v = v.replace(refKeyReg, '');
//console.log(v);
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('?');
//console.log(value,q);
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, (_, 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;
let tempVarsPrefixKey = 0;
let literalValues = Object.create(null);
let isLiteralValue = v => {
if (v === 'true' ||
v === 'false' ||
v === 'null' ||
v === 'undefined') {
return true;
}
if (numberReg1.test(v) ||
numberReg2.test(v) ||
numberReg3.test(v) ||
numberReg4.test(v) ||
numberReg5.test(v)) {
return true;
}
return false;
};
fn = fn.replace(tagReg, (_, tag, attrs) => {
let hasMagixView = mxViewAttrReg.test(attrs); //是否有mx-view属性
let hasIndeter = checkboxReg.test(attrs) && indeterminateReg.test(attrs);
attrs = tmplCmd.recover(attrs, cmdStore, recoverString); //还原
let findCount = 0;
let mxRefExprInfo = [];
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(htmlAttrParamPrefix)) {
let an = attrName.substring(htmlAttrParamPrefix.length);
an = utils.camelize(an);
e += `,a:'${an}'`;
}
e += '}';
mxRefExprInfo.push(e);
};
attrs = attrs.replace(bindReg, (m, name, q, art, expr) => {
expr = expr.trim();
let exprInfo = extractFunctions(expr);
art = art || '';
transformEvent(exprInfo, m, name, art);
findCount++;
let replacement = '<%=';
if (hasMagixView) {
if (name.startsWith(htmlAttrParamPrefix)) {
replacement = '<%@';
} else if (name.startsWith(tmplCondPrefix)) {
let key = name.replace(tmplCondPrefix, '');
let cd = e.tmplConditionAttrs[key];
if (cd && cd.attrName.startsWith(htmlAttrParamPrefix)) {
replacement = '<%@';
}
}
}
m = name + '=' + q + art + replacement + exprInfo.expr + '%>' + q;
return m;
}).replace(bindReg2, (m, art, expr) => {
expr = expr.trim();
let exprInfo = extractFunctions(expr);
art = art || '';
transformEvent(exprInfo, m, null, art);
findCount++;
return ' ';
}).replace(atRefOrAnalysePathExprReg, (m, pfx, cmd) => {
let key = `'\x1e#'`;
if (cmd != '\x03') {
let prefix = () => {
if (isLiteralValue(cmd)) {
if (!literalValues[cmd]) {
literalValues[cmd] = md5('\x00' + tempVarsPrefixKey++, 'compressRefExpr', '', true);
}
return literalValues[cmd];
}
return md5('\x00' + tempVarsPrefixKey++, 'compressRefExpr', '', true);
}
let expr = analyseExpr(cmd, m, prefix);
key = `'\x1e${expr.result}'`.replace(tailEmptyReg, '');
}
if (pfx == '~') {
hasIndeter = true;
return `<%=${key}%>`;
}
return `<%@${cmd},${key}%>`;
});
if (findCount > 0) {
attrs = ' mxc="[' + mxRefExprInfo.join(',') + ']" ' + attrs;
}
if (hasIndeter) {
let mxo = '\x1f';
attrs = ` mxo="${mxo}"` + attrs;
}
if (hasMagixView) {
let keys = extractMxViewRootKeys(attrs);
if (keys.length) {
attrs = ` ${tmplMxViewParamKey}="${keys}"${attrs}`;
}
}
let prefix = '';
return prefix + '<' + tag + attrs + '>';
});
fn = tmplCmd.recover(fn, cmdStore);
fn = recoverString(stripNum(fn));
e.globalVars = Object.keys(globalVars);
return fn;
}
};