magix-combine
Version:
合并Magix View的html,js,css成一个js文件,并检测html,js,css中可能存在的问题
220 lines (211 loc) • 9.11 kB
JavaScript
/*
分析模板中不变的html片断,加速虚拟dom的diff
程序可自动识别哪些节点不会变化
开发者也可通过在节点上添加<div mx-static>强制指定该节点下所有的节点不会变化
mxs的使用场景:整个节点(包括属性)及子节点内不包含任何变量
mxa的使用场景:节点的属性不包含任何变量
mxv的使用场景:
<div>
<div mx-view="path/to/view?a={{@a}}"></div>
</div>
对于这段代码,因为a是使用`@a`的引用方式,即使a发生了改变,这段代码有可能不会变化
*/
let md5 = require('./util-md5');
let tmplParser = require('./tmpl-parser');
let configs = require('./util-config');
let tagReg = /<([^>\s\/]+)([^>]*?)(\/)?>/g;
let staticKeyReg = /\s*_mxs="[^"]+"/g;
let attrKeyReg = /\s*_mxa="[^"]+"/g;
let mxvKeyReg = /\s*_mxv="[^"]+"/g;
let mxViewOwnerReg = /\s*_mxo="[^"]+"/g;
let mxDiffReg = /\s*mx-diff(?:\s*=\s*(['"])[^'"]+\1)?/;
let tmplCommandAnchorRegTest = /\u0007\d+\u0007/;
let forceStaticKey = /\s+mx-static(?:-attr)?(?:\s*=\s*(['"])[^'"]+\1)?/;
let ifForReg = /\s*(?:if|for|for_declare)\s*=\s*"[^"]+"/g;
module.exports = (tmpl, file) => {
let g = 0;
let prefix = configs.projectName + md5(file, 'tmplFiles', '', true) + ':';
tmpl = tmpl.replace(tagReg, (match, tag, attrs, close, tKey) => {
tKey = ' _mxv="' + g++ + '"';
tKey += ' _mxs="' + g++ + '"';
tKey += ' _mxa="' + g++ + '"';
if (configs.magixVframeHost) {
tKey += ' _mxo="' + g++ + '"';
}
return '<' + tag + tKey + attrs + (close || '') + '>';
});
let tokens = tmplParser(tmpl, file);
let keysMap = Object.create(null),
userKeysMap = Object.create(null),
userAttrKeysMap = Object.create(null);
let removeChildrenStaticKeys = (children, keys) => {
for (let c of children) {
if (c.children) removeChildrenStaticKeys(c.children, keys);
let key = ' _mxs="' + c.mxsKey + '"';
if (keys.indexOf(key) == -1) {
keys.push(key);
}
key = ' _mxa="' + c.mxsAttrKey + '"';
if (keys.indexOf(key) == -1) {
keys.push(key);
}
}
};
let getRemovedStaticKeys = () => {
let keys = [];
let walk = nodes => {
for (let n of nodes) {
if (n.hasContent) {
if (n.children) {
walk(n.children);
}
}
if (!n.hasMxView && n.mxViewOwner) {
keys.push(' _mxo="' + n.mxViewOwner + '"');
}
let html = tmpl.substring(n.start, n.end)
.replace(staticKeyReg, '')
.replace(attrKeyReg, '')
.replace(mxvKeyReg, '')
.replace(mxViewOwnerReg, '');
if (configs.magixUpdaterQuick) {
html = html.replace(ifForReg, '');
}
let attr = tmpl.substring(n.attrsStart, n.attrsEnd);
attr = attr.replace(staticKeyReg, '')
.replace(attrKeyReg, '')
.replace(mxvKeyReg, '').trim();
if (configs.magixUpdaterQuick) {
attr = attr.replace(ifForReg, '').trim();
}
let removeStaticKey = false;
keysMap[' _mxs="' + n.mxsKey + '"'] = html;
keysMap[' _mxa="' + n.mxsAttrKey + '"'] = attr;
if (attr) {
if (tmplCommandAnchorRegTest.test(attr)) {
if (n.userStaticAttrKey) {
userAttrKeysMap[' _mxa="' + n.mxsAttrKey + '"'] = n.userStaticAttrKey;
} else {
keys.push(' _mxa="' + n.mxsAttrKey + '"');
}
}
} else {
keys.push(' _mxa="' + n.mxsAttrKey + '"');
}
//清理mxv
//先清理
//quick模板不用处理,在magix/quick.js中已自动处理了mxv
if (configs.magixUpdaterQuick) {
keys.push(' _mxv="' + n.mxvAutoKey + '"');
delete n.mxvAutoKey;
} else {
if (n.mxvKey) {
keys.push(' _mxv="' + n.mxvAutoKey + '"');
} else if (n.children) {
let hasSubView = 0;
for (let c of n.children) {
/*
对于input textarea等,我们也利用mxv属性深入diff
场景:<input value="{{=abc}}"/>
updater.digest({abc:'abc'});
然后用户删除了input中的abc修改成了123
此时依然updater.digest({abc:'abc'}),问input中的值该显示abc还是123?
该方案目的是显示abc
*/
if (c.mxvKey ||
c.mxvAutoKey ||
c.forceDiff ||
c.tag == 'input' ||
c.tag == 'direct' ||
c.tag == 'textarea' ||
c.tag == 'option') {
// if (c.mxvKey && c.namedSlot) {
// continue;
// }
hasSubView = 1;
break;
}
}
if (!hasSubView && !n.forceDiff) {
keys.push(' _mxv="' + n.mxvAutoKey + '"');
delete n.mxvAutoKey;
}
} else if (!n.forceDiff) {
keys.push(' _mxv="' + n.mxvAutoKey + '"');
delete n.mxvAutoKey;
}
}
if (tmplCommandAnchorRegTest.test(html)) {
if (n.userStaticKey) {
userKeysMap[' _mxs="' + n.mxsKey + '"'] = n.userStaticKey;
if (n.children && n.userStaticKey !== 'false') {
removeChildrenStaticKeys(n.children, keys);
}
} else {
removeStaticKey = true;
keys.push(' _mxs="' + n.mxsKey + '"');
}
} else if (n.children) {
let hasMxv = false;
for (let c of n.children) {
if (c.mxvKey ||
c.mxvAutoKey ||
c.forceDiff ||
c.tag == 'group' ||
c.tag == 'input' ||
c.tag == 'textarea' ||
c.tag == 'option') {
hasMxv = true;
removeStaticKey = true;
keys.push(' _mxs="' + n.mxsKey + '"');
break;
}
}
if (!hasMxv && !n.hasMxIs && !n.forceDiff) {
removeChildrenStaticKeys(n.children, keys);
}
}
if (!removeStaticKey) {
if (n.hasMxIs || n.forceDiff) {
keys.push(' _mxs="' + n.mxsKey + '"');
}
keys.push(' _mxa="' + n.mxsAttrKey + '"');
}
}
};
walk(tokens);
return keys;
};
let keys = getRemovedStaticKeys();
for (let key of keys) {
tmpl = tmpl.replace(key, '');
}
tmpl = tmpl.replace(tagReg, m => {
return m.replace(staticKeyReg, m => {
let r = userKeysMap[m];
if (r === 'false') return '';
//console.log(keysMap, m);
if (!r || r === true) {
r = keysMap[m];
r = md5(r, file + ':key', prefix, true);
} else {
r = md5(m, file + ':key', prefix, true) + ':' + r;
}
return ' mxs="' + r + '"';
}).replace(attrKeyReg, m => {
let r = userAttrKeysMap[m];
if (r === 'false') return '';
if (!r || r === true) {
r = keysMap[m];
r = md5(m, file + ':akey', prefix, true);
} else {
r = md5(m, file + ':akey', prefix, true) + ':' + r;
}
return ' mxa="' + r + '"';
}).replace(mxvKeyReg, ' mxv')
.replace(forceStaticKey, '')
.replace(mxDiffReg, '')
.replace(mxViewOwnerReg, ' mxo="\x1f"');
});
return tmpl;
};