UNPKG

magix-combine

Version:

合并Magix View的html,js,css成一个js文件,并检测html,js,css中可能存在的问题

841 lines (834 loc) 33 kB
/* 增加mx-tag自定义标签的处理,方便开发者提取公用的html片断 */ /* <mx-vframe src="app/views/default" pa="{{@a}}" pb="{{@b}}" /> <mx-vframe src="app/views/default" pa="{{@a}}" pb="{{@b}}"> loading... </mx-vframe> */ let fs = require('fs'); let path = require('path'); let configs = require('./util-config'); let tmplCmd = require('./tmpl-cmd'); let slog = require('./util-log'); let utils = require('./util'); let chalk = require('chalk'); let tmplParser = require('./tmpl-parser'); let attrMap = require('./tmpl-attr-map'); let customConfig = require('./tmpl-customtag-cfg'); let duAttrChecker = require('./checker-tmpl-duattr'); let atpath = require('./util-atpath'); let consts = require('./util-const'); let sep = path.sep; let cmdNumReg = /^\x07\d+$/; let selfClose = require('./html-selfclose-tags'); let uncheckTags = { 'mx-view': 1, 'mx-include': 1, 'mx-vframe': 1, 'mx-link': 1, 'mx-router': 1 }; let tagReg = /\btag\s*=\s*"([^"]+)"/; let attrNameValueReg = /(^|\s|\x07)([^=\/\s\x07]+)(?:\s*=\s*(["'])([\s\S]*?)\3)?/g; let inputTypeReg = /\btype\s*=\s*(['"])([\s\S]+?)\1/; let tmplAttrsReg = /\$\{attrs\.([a-zA-Z_]+)\}/g; let tmplContentReg = /\$\{content\}/g; let attrAtStartContentHolderReg = /\x03/g; let mxViewAttrHolderReg = /\x02/g; let atReg = /@/g; let tmplCommandAnchorReg = /\u0007\d+\u0007/; let mxViewAttrReg = /\bmx-view\s*=\s*(['"])([^'"]*?)\1/; let fileCache = Object.create(null); let processedGalleryInfo = Symbol('gallery.info.processed'); let valuableAttrReg = /\x07\d+\x07\s*\?\?\s*/; let booleanAttrReg = /\x07\d+\x07\s*\?\s*/; let isReservedAttr = key => { return key.startsWith('mx-') || key.startsWith('data-') || key.startsWith('native-') || key.startsWith('#') || key.startsWith('@native-') || key.startsWith('@#'); }; let toNativeKey = key => { if (key.startsWith('native-')) { key = key.substring(7); } else if (key.startsWith('#')) { key = key.substring(1); } else if (key.startsWith('@native-')) { key = '@' + key.substring(8); } else if (key.startsWith('@#')) { key = '@' + key.substring(2); } else if (key.startsWith('#@')) { key = '@' + key.substring(2); } return key; }; let toParamKey = (key, prefix) => { let c = prefix.length; if (key.startsWith(`@${prefix}-`)) { key = `${prefix}-@` + key.substring(c + 2); } else if (key.startsWith('@*')) { key = `${prefix}-@` + key.substring(2); } else if (key.startsWith('*@')) { key = `${prefix}-@` + key.substring(2); } else if (key.startsWith('*')) { key = `${prefix}-` + key.substring(1); } else if (!key.startsWith(prefix + '-')) { key = prefix + '-' + key; } return key; }; let relativeReg = /\.{1,2}\//g; let addAtIfNeed = tmpl => { return tmpl.replace(relativeReg, (m, offset, c) => { c = tmpl[offset - 1]; if (c == '@' || c == '/') { return m; } return '@' + m; }); }; let splitAttrs = (tag, type, attrs) => { let viewAttrs = ''; let viewAttrsMap = {}; attrs = attrs.replace(tagReg, (m, t) => { tag = t; return ''; }); if (tag == 'input') { let m = attrs.match(inputTypeReg); if (m) { type = m[2]; } } let attrsMap = attrMap.getAll(tag, type); let aMap = Object.create(null); attrs = attrs.replace(attrNameValueReg, (m, prefix, key, q, content) => { if (cmdNumReg.test(m)) { return m; } prefix = prefix || ''; if (!attrsMap[key] && !isReservedAttr(key)) { key = toParamKey(key, 'view'); if (q === undefined && !content) { q = '"'; content = 'true'; } viewAttrs += ' ' + key + '="' + content + '"'; viewAttrsMap[key.substring(5)] = content; return ''; } else { aMap[key] = content; } let tValue = (q === undefined && content === undefined) ? '' : `=${q}${content}${q}`; let nkey = toNativeKey(key); if (nkey != key) { return prefix + nkey + tValue; } return m; }).trim(); viewAttrs = viewAttrs.trim(); return { tag, unaryTag: selfClose.hasOwnProperty(tag), attrs, attrsMap: aMap, paramAttrs: viewAttrs, viewAttrs, paramAttrsMap: viewAttrsMap, viewAttrsMap }; }; let innerView = (result, info, gRoot, map, extInfo) => { if (info) { result.mxView = gRoot + info.path; result.seprateAttrs = (tag, type) => splitAttrs(info.tag || tag || 'div', type, result.attrs); } if (utils.isObject(info) && utils.isFunction(info.processor)) { return info.processor(result, map, extInfo) || ''; } let tag = 'div'; let hasTag = false; let attrs = result.attrs.replace(tagReg, (m, t) => { tag = t; hasTag = true; return ''; }); if (!hasTag && info && info.tag) { tag = info.tag; } let type = ''; if (tag == 'input') { let m = attrs.match(inputTypeReg); if (m) { type = m[2]; } else if (info && info.type) { type = info.type; } } let allAttrs = attrMap.getAll(tag, type); let hasPath = false; let processedAttrs = {}; attrs = attrs.replace(attrNameValueReg, (m, prefix, key, q, value) => { if (cmdNumReg.test(m)) { return m; } prefix = prefix || ''; if (!info) { if (key == 'path' || key == 'view' || key == 'src') { hasPath = true; return prefix + 'mx-view=' + q + value + q; } } let viewKey = false; let originalKey = key; if (!allAttrs.hasOwnProperty(key) && !isReservedAttr(key)) { key = toParamKey(key, 'view'); viewKey = true; } else { key = toNativeKey(key); } //处理其它属性 if (info) { let pKey = '_' + originalKey; if (info[originalKey]) {//如果配置中允许覆盖,则标记已经处理过 processedAttrs[originalKey] = 1; } else if (info[pKey]) {//如果配置中追加 processedAttrs[pKey] = 1;//标记处理过 if (q === undefined && value === undefined) {//对于unary的我们要特殊处理下 q = '"'; value = ''; } value += info[pKey]; } } if (q === undefined && viewKey) { q = '"'; value = 'true'; } return prefix + key + (q === undefined && !viewKey ? '' : '=' + q + value + q); }); if (info) { for (let p in info) { if (p != 'path' && p != 'tag' && !processedAttrs[p]) { let v = info[p]; if (p.startsWith('_')) { p = p.slice(1); } else if (!allAttrs.hasOwnProperty(p) && !isReservedAttr(p)) { p = toParamKey(p, 'view'); } else { p = toNativeKey(p); } attrs += ` ${p}="${v}"`; } } } if (!hasPath && info) { attrs += ' mx-view="' + result.mxView + '"'; } let html = `<${tag} ${attrs}`; let unary = selfClose.hasOwnProperty(tag); if (unary) { html += `/>`; } else { html += `>${result.content}`; html += `</${tag}>`; } return html; }; let innerLink = (result) => { let tag = 'a'; let href = '', paramKey = 0; let attrs = result.attrs; attrs = attrs.replace(attrNameValueReg, (m, prefix, key, q, value) => { if (cmdNumReg.test(m)) { return m; } if (key == 'to' || key == 'href') { href = value; return ''; } if (key == 'tag') { tag = value; return ''; } return m; }); let allAttrs = attrMap.getAll(tag); attrs = attrs.replace(attrNameValueReg, (m, prefix, key, q, value) => { if (cmdNumReg.test(m)) { return m; } prefix = prefix || ''; if (!allAttrs.hasOwnProperty(key) && !isReservedAttr(key)) { key = toParamKey(key, 'param'); paramKey = 1; } else { key = toNativeKey(key); } if (q === undefined && paramKey) { q = '"'; value = ''; } return prefix + key + '=' + q + value + q; }); let html = `<${tag} href="${href}" ${attrs}`; let unary = selfClose.hasOwnProperty(tag); if (unary) { html += `/>`; } else { html += `>${result.content}`; html += `</${tag}>`; } return html; }; let innerInclude = (result, info) => { let file = ''; let attrs = {}; let src = ''; result.attrs.replace(attrNameValueReg, (m, prefix, name, q, value) => { if (name == 'path' || name == 'src') { src = m; file = path.resolve(path.join(path.dirname(info.srcOwnerHTMLFile) + sep + value)); } else { attrs[name] = value; } }); if (!fs.existsSync(file)) { slog.ever(chalk.red('[MXC Error(tmpl-customtag)] can not find file:' + file), 'for tag', chalk.magenta('<' + result.tag + ' ' + src + '>'), 'at', chalk.magenta(info.shortOwnerHTMLFile)); return ''; } else { let content = ''; if (fileCache[file]) { content = fileCache[file]; } else { content = fs.readFileSync(file) + ''; fileCache[file] = content; } content = content.replace(tmplAttrsReg, (m, key) => { return attrs[key] || ''; }).replace(tmplContentReg, () => { return result.content; }); try { info.templateLang = path.extname(file).substring(1); info.srcHTMLFile = file; info.shortHTMLFile = file.replace(configs.moduleIdRemovedPath, '').substring(1); info.includeSnippet = true; content = configs.compileTmplStart(content, info); content = configs.compileTmplEnd(content, info); } catch (ex) { slog.ever(chalk.red('[MXC Error(tmpl-customtag)] compile template error ' + ex.message), 'at', chalk.magenta(info.shortHTMLFile)); } return content; } }; module.exports = { process(tmpl, extInfo, e) { let badTags = Object.create(null); let cmdCache = Object.create(null); let tempSkipTags = Object.create(null); let galleriesMap = configs.galleries; e.tmplComponents = []; let updateOffset = (pos, offset) => { let l = nodes => { //debugger; if (nodes) { for (let n of nodes) { l(n.children); if (n.start > pos) { n.start += offset; } if (n.end > pos) { n.end += offset; } if (n.hasAttrs) { if (n.attrsStart > pos) { n.attrsStart += offset; } if (n.attrsEnd > pos) { n.attrsEnd += offset; } } if (n.hasContent) { if (n.contentStart > pos) { n.contentStart += offset; } if (n.contentEnd > pos) { n.contentEnd += offset; } } } } }; l(tokens); }; let getTagInfo = (n, map) => { let content = '', attrs = '', children = []; //console.log(tmpl,n); if (n.hasAttrs) { attrs = tmpl.substring(n.attrsStart, n.attrsEnd); } if (n.hasContent) { content = tmpl.substring(n.contentStart, n.contentEnd); } if (n.children) { for (let r of n.children) { let i = Object.assign({}, r); i.html = tmpl.substring(i.start, i.end); children.push(i); } } let tag = n.tag; let oTag = tag; if (n.pfx) { tag = tag.substring(n.pfx.length + 1); } let tags = tag.split('.'); let mainTag = tags.shift(); //console.log(tags); let subTags = tags.length ? tags : ['index']; let result = { id: n.id, pId: n.pId, prefix: n.pfx, group: n.group, unary: !n.hasContent, tag: oTag, mainTag, subTags, attrs, nodesMap: map, firstElement: n.firstElement, lastElement: n.lastElement, attrsKV: n.attrsKV, attrsMap: n.attrsMap, content, children }; //console.log(result); return result; }; let processCustomTag = (n, map) => { let result = getTagInfo(n, map); if (configs.components[n.pfx + 'Root']) { tempSkipTags[result.tag] = 1; let jsFile = configs.components[n.pfx + 'Root'] + result.tag; e.tmplComponents.push(jsFile); } else { let content = result.content; let fn = galleriesMap[result.tag] || configs.customTagProcessor; let customContent = fn(result, map, extInfo, e); if (!customContent) { let tagName = result.tag; customContent = `<${tagName} ${result.attrs}>${content}</${tagName}>`; badTags[tagName] = 1; } if (content != customContent) { content = customContent; tmpl = tmpl.substring(0, n.start) + content + tmpl.substring(n.end); updateOffset(n.start, content.length - (n.end - n.start)); } } }; let processGalleryTag = (n, map) => { let result = getTagInfo(n, map); let content = result.content; let hasGallery = galleriesMap.hasOwnProperty(n.pfx + 'Root'); let gRoot = galleriesMap[n.pfx + 'Root'] || ''; let gMap = galleriesMap[n.pfx + 'Map'] || {}; if (!uncheckTags.hasOwnProperty(result.tag)) { let vpath = (n.group ? '' : n.pfx + '-') + result.mainTag; if (result.subTags.length) { vpath += '/' + result.subTags.join('/'); } if (hasGallery) { let i = gMap[result.tag]; if ((!i || !i[processedGalleryInfo]) && !utils.isFunction(i)) { let subs = result.subTags.slice(0, -1); if (subs.length) { subs = subs.join(sep); } else { subs = ''; } let main = (n.group ? '' : n.pfx + '-') + result.mainTag; let cpath = path.join(configs.moduleIdRemovedPath, gRoot, main, subs); if (fs.existsSync(cpath)) { let cfg = customConfig(cpath, main); if (cfg.hasOwnProperty(result.tag)) { gMap[result.tag] = cfg[result.tag]; } else if (!i) { gMap[result.tag] = { path: vpath }; } } else { //当文件不存在时,不检查,直接使用用户配置的路径 gMap[result.tag] = Object.assign({}, i, { path: vpath }); /*uncheckTags[result.tag] = { resolve: cpath + sep, msg: 'folder not found. try path' };*/ } } } else { uncheckTags[result.tag] = { resolve: `${n.pfx}Root or ${n.pfx}Map`, msg: 'missing config galleries' }; } if (gMap.hasOwnProperty(result.tag)) { let i = gMap[result.tag]; if (!i[processedGalleryInfo]) { if (utils.isFunction(i)) { i = { processor: i }; gMap[result.tag] = i; } if (!i.path) { i.path = vpath; } i[processedGalleryInfo] = 1; } } } let tip = uncheckTags[result.tag]; if (tip && tip !== 1) { slog.ever(chalk.red('[MXC Error(tmpl-custom)] can not process tag: ' + result.tag), 'at', chalk.magenta(e.shortHTMLFile), tip.msg, chalk.magenta(tip.resolve)); } let update = false; if (n.pfx == 'mx') { if (result.mainTag == 'view' || result.mainTag == 'vframe') { if (result.mainTag == 'view') { slog.ever(chalk.red('[MXC Tip(tmpl-custom)] deprecated tag: mx-view'), 'at', chalk.magenta(e.shortHTMLFile), 'use', chalk.magenta('mx-vframe'), 'instead'); } content = innerView(result); update = true; } else if (result.mainTag == 'include') { content = innerInclude(result, extInfo); update = true; } else if (result.mainTag == 'link' || result.mainTag == 'router') { content = innerLink(result, extInfo); update = true; } } if (!update && gMap.hasOwnProperty(result.tag)) { content = innerView(result, gMap[result.tag], gRoot, map, extInfo); update = true; } if (update) { tmpl = tmpl.substring(0, n.start) + content + tmpl.substring(n.end); updateOffset(n.start, content.length - (n.end - n.start)); } }; let processAtAttrs = n => { let result = getTagInfo(n); let update = false; let content = ''; let tag = result.tag; let attrs = result.attrs; let inputType = n.attrsMap.type; if (inputType) { inputType = inputType.value; } let bProps = attrMap.getBooleanProps(n.tag, inputType); attrs = attrs.replace(attrNameValueReg, (m, prefix, key, q, content) => { if (cmdNumReg.test(m)) { return m; } prefix = prefix || ''; if (key.startsWith('@')) { if (key.startsWith('@@')) { update = true; m = prefix + '\x03' + key.substring(2) + (q ? '=' + q + content + q : ''); } else if (tmplCommandAnchorReg.test(content)) { key = key.substring(1); let cmdContent = tmplCmd.extractCmdContent(content, cmdCache); //console.log(cmdContent); if (cmdContent.succeed) { update = true; m = m.trim().substring(1); let art = '', operate = cmdContent.operate || ''; let isBooleanProp = bProps[key] === 1; if (cmdContent.isArt) { art = `<%'${cmdContent.line}\x11${cmdContent.art}\x11'%>`; } if (configs.magixUpdaterQuick) { operate = tmplCmd.operatesMap[operate] || ''; let out = isBooleanProp ? `(${cmdContent.content})?true:null` : operate + cmdContent.content; return `${prefix}${key}="${art}<%${out}%>"`; } let out = isBooleanProp ? key : `${key}="<%${operate}$_temp%>"`; return `${prefix}${art}<%if(($_temp=${cmdContent.content})){%>${out}<%}%> `; } else { let ex0 = cmdContent.isArt ? `{{data.${key}}}` : `<%data.${key}%>`; let ex1 = cmdContent.isArt ? `{{=data.${key}}}` : `<%=data.${key}%>`; let ex2 = cmdContent.isArt ? `{{!data.${key}}}` : `<%!data.${key}%>`; let ex3 = cmdContent.isArt ? `{{@data.${key}}}` : `<%@data.${key}%>`; slog.ever(chalk.red('[MXC Tip(tmpl-custom)] check attribute ' + key + '=' + q + cmdContent.origin + q), 'at', chalk.magenta(e.shortHTMLFile), 'the attribute value only support expression like ' + key + '="' + ex0 + '" or ' + key + '="' + ex1 + '" or ' + key + '="' + ex2 + '" or ' + key + '="' + ex3 + '"'); } } else if (content === 'false' || content === '0' || content === '' || content === 'null') { update = true; m = prefix; } else { update = true; m = prefix + key.substring(1) + (q ? ('=' + q + content + q) : ''); } } return m; }); if (update) { let html = `<${tag} ${attrs}`; let unary = selfClose.hasOwnProperty(tag); if (unary) { html += `/`; } html += `>${result.content}`; if (!unary) { html += `</${tag}>`; } content = html; tmpl = tmpl.substring(0, n.start) + content + tmpl.substring(n.end); updateOffset(n.start, content.length - (n.end - n.start)); } }; let processAtAttrContents = n => { let result = getTagInfo(n); let content = ''; let tag = result.tag; let attrs = result.attrs; attrs = attrs.replace(attrNameValueReg, m => { return atpath.resolveContent(m, e.moduleId, '\x03') .replace(atReg, '\x03'); }); let html = `<${tag} ${attrs}`; let unary = selfClose.hasOwnProperty(tag); if (unary) { html += `/`; } html += `>${result.content}`; if (!unary) { html += `</${tag}>`; } content = html; tmpl = tmpl.substring(0, n.start) + content + tmpl.substring(n.end); updateOffset(n.start, content.length - (n.end - n.start)); }; let processEncodeAttr = n => { let result = getTagInfo(n); let content = ''; let tag = result.tag; let attrs = result.attrs; let entities = { '>': '&gt;', '<': '&lt;' }; let encodeEntities = m => m.replace(/[<>]/g, _ => entities[_]); attrs = attrs.replace(attrNameValueReg, encodeEntities); let html = `<${tag} ${attrs}`; let unary = selfClose.hasOwnProperty(tag); if (unary) { html += `/`; } html += `>${result.content}`; if (!unary) { html += `</${tag}>`; } content = html; tmpl = tmpl.substring(0, n.start) + content + tmpl.substring(n.end); updateOffset(n.start, content.length - (n.end - n.start)); }; let processMxView = n => { let result = getTagInfo(n); let content = ''; let tag = result.tag; let attrs = result.attrs; if (configs.useAtPathConverter) { //如果启用@路径转换规则 attrs = attrs.replace(mxViewAttrReg, (m, q, c) => { //let { pathname, query } = url.parse(c); let [pathname, searchParams] = c.split('?'); pathname = pathname || ''; pathname = addAtIfNeed(pathname); pathname = atpath.resolveContent(pathname, e.moduleId); let params = new URLSearchParams(searchParams || ''); let ps = []; //console.log(Object.fromEntries(params.entries())); for (let [k, v] of params) { //console.log(k,v); v = addAtIfNeed(v); ps.push(k + '=' + v); } pathname = configs.mxViewProcessor({ path: pathname, pkgName: e.pkgName }, e) || pathname; let view = pathname; if (ps.length) { view += `?${ps.join('&')}`; } return `\x02="${view}"`; }); } let html = `<${tag} ${attrs}`; let unary = selfClose.hasOwnProperty(tag); if (unary) { html += `/`; } html += `>${result.content}`; if (!unary) { html += `</${tag}>`; } content = html; tmpl = tmpl.substring(0, n.start) + content + tmpl.substring(n.end); updateOffset(n.start, content.length - (n.end - n.start)); }; let processCondAttrs = n => { let result = getTagInfo(n); let update = false; let content = ''; let tag = result.tag; let attrs = result.attrs; attrs = attrs.replace(attrNameValueReg, (m, prefix, key, q, content) => { prefix = prefix || ''; let valuable = valuableAttrReg.test(content); let boolean = !valuable && booleanAttrReg.test(content); if (valuable || boolean) { if (key == 'mx-view') { return m; } let cs = content.split(valuable ? '??' : '?'); let [cond, ext] = cs; //console.log(cond,ext,tmplCmd.recover(content,cmdCache)); update = true; cond = cond.trim(); ext = ext.trim(); let extract = tmplCmd.extractCmdContent(cond.trim(), cmdCache); if (extract.operate == '@' && !key.startsWith(htmlAttrParamPrefix)) { console.log(chalk.red('[MXC Tip(tmpl-custom)] ? or ?? only support "=" at attr ' + key), 'at', chalk.magenta(e.shortHTMLFile)); } if (!extract.succeed) { console.log(chalk.red('[MXC Tip(tmpl-custom)] check condition ' + tmplCmd.recover(cond, cmdCache)), 'at', chalk.magenta(e.shortHTMLFile)); } let trimedKey = key.trim(); if (trimedKey.startsWith('*') || trimedKey.startsWith('view-')) { if (trimedKey.startsWith('*')) { trimedKey = 'view-' + trimedKey.substring(1); } let ifCond = ''; if (ext) { ifCond = `<%if((${extract.content})${valuable ? '!=null' : ''}){%>${ext}<%}%>`; } else { ifCond = `<%if((${extract.content})${valuable ? '!=null' : ''}){%>${cond}<%}%>`; } return ` ${trimedKey}="${ifCond}"`; } else { if (ext) { return `<%if((${extract.content})${valuable ? '!=null' : ''}){%> ${key}="${ext}"<%}%>`; } else { return `<%if((${extract.content})${valuable ? '!=null' : ''}){%> ${key}<%}%>`; } } } return m; }); if (update) { let html = `<${tag} ${attrs}`; let unary = result.unary; if (unary) { html += `/`; } html += `>${result.content}`; if (!unary) { html += `</${tag}>`; } content = html; tmpl = tmpl.substring(0, n.start) + content + tmpl.substring(n.end); updateOffset(n.start, content.length - (n.end - n.start)); } }; let walk = (nodes, map) => { if (nodes) { if (!map) map = nodes.__map; for (let n of nodes) { if (configs.debug && e.checker.checkTmplDuplicateAttr && n.attrs && n.attrs.length) { duAttrChecker(n, e, cmdCache, tmpl.substring(n.attrsStart, n.attrsEnd)); } walk(n.children, map); if (n.needEncodeAttr) { processEncodeAttr(n, map); } else if (n.customTag) { if (configs.galleryPrefixes[n.pfx] === 1) { processGalleryTag(n, map); } else { processCustomTag(n, map); } } else if (n.atAttr) { processAtAttrs(n); } else if (n.condAttr) { processCondAttrs(n); } else if (n.atAttrContent) { processAtAttrContents(n); } else if (n.hasMxView) { processMxView(n); } } } }; let hasSpecialTags = nodes => { let map = nodes.__map; for (let n in map) { n = map[n]; if (!badTags[n.tag] && !tempSkipTags[n.tag]) { if (n.customTag || n.needEncodeAttr || n.atAttr || n.atAttrContent || n.condAttr || n.hasMxView) { return true; } } } return false; }; //debugger; tmpl = tmplCmd.store(tmpl, cmdCache); tmpl = tmplCmd.store(tmpl, cmdCache, consts.artCommandReg); let tokens = tmplParser(tmpl, e.shortHTMLFile); let checkTimes = 2 << 2; //console.log(JSON.stringify(tmpl)); while (hasSpecialTags(tokens) && --checkTimes) { //debugger; walk(tokens); tmpl = tmplCmd.store(tmpl, cmdCache); tmpl = tmplCmd.store(tmpl, cmdCache, consts.artCommandReg); //console.log(JSON.stringify(tmpl)); tokens = tmplParser(tmpl, e.shortHTMLFile); } tmpl = tmplCmd.recover(tmpl, cmdCache); tmpl = tmpl.replace(attrAtStartContentHolderReg, '@'); tmpl = tmpl.replace(mxViewAttrHolderReg, 'mx-view'); //console.log('out', tmpl); return tmpl; } };