UNPKG

magix-composer

Version:

compile html, style and javascript files into javascript

713 lines (710 loc) 26.8 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 url = require('url'); let qs = require('querystring'); let configs = require('./util-config'); let tmplCmd = require('./tmpl-cmd'); let slog = require('./util-log'); let util = require('util'); let chalk = require('chalk'); let utils = require('./util'); let tmplParser = require('./tmpl-parser'); let customConfig = require('./tmpl-customtag-cfg'); let atpath = require('./util-atpath'); let { quickDirectTagName, quickGroupTagName, htmlAttrParamPrefix, htmlAttrParamFlag, galleryProcessed, galleryDynamic, artCommandReg, galleryAttrAppendFlag, tmplCondPrefix, tmplGroupTag, tmplGroupKeyAttr, tmplGroupUseAttr } = require('./util-const'); let deps = require('./util-deps'); let sep = path.sep; let { selfCloseTags } = require('./html-tags'); let uncheckTags = { 'mx-vframe': 1, 'mx-link': 1, 'mx-group': 1 }; let skipTags = { [quickGroupTagName]: 1, [quickDirectTagName]: 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 attrAtStartContentHolderReg = /\x03/g; let mxViewAttrHolderReg = /\x02/g; let atReg = /@/g; let mxViewAttrReg = /\bmx-view\s*=\s*(['"])([^'"]*?)\1/; let valuableAttrReg = /\x07\d+\x07\s*\?\?/; let booleanAttrReg = /\x07\d+\x07\s*\?/; let wholeCmdReg = /^(?:\x07\d+\x07)+$/; let hasCmdReg = /\x07\d+\x07/; let httpProtocolReg = /^(?:https?:)?\/\//i; let entities = { '>': '&gt;', '<': '&lt;' }; let encodeEntities = m => m.replace(/[<>]/g, _ => entities[_]); let toParamKey = key => { key = htmlAttrParamPrefix + key.substring(1); return key; }; let relativeReg = /\.{1,2}\//g; let addAtIfNeed = tmpl => { return tmpl.replace(relativeReg, (m, offset, c) => { c = tmpl[offset - 1]; if (c == '@' || c == '/' || c == '\x03') { return m; } return '@' + m; }); }; let innerView = (result, info, gRoot, extInfo, actions, e) => { if (info) { result.mxView = path.relative(path.dirname(e.from), configs.moduleIdRemovedPath + path.sep + gRoot + info.path); } if (util.isObject(info) && util.isFunction(info.processor)) { let html = info.processor(result, actions, extInfo) || ''; return html; } 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; } if (tag == 'input') { let m = attrs.match(inputTypeReg); if (m) { type = m[2]; } else if (info && info.type) { type = info.type; } } let hasPath = false; let processedAttrs = {}; attrs = attrs.replace(attrNameValueReg, (m, prefix, key, q, value) => { prefix = prefix || ''; if (!info) { if (key == 'src') { hasPath = true; return prefix + 'mx-view=' + q + value + q; } } let viewKey = false; let originalKey = key; if (key.startsWith(htmlAttrParamFlag)) { key = toParamKey(key); viewKey = true; } //处理其它属性 if (info) { let pKey = galleryAttrAppendFlag + 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) { //from configs if (p != 'tag' && p != 'path' && !processedAttrs[p]) { let v = info[p]; if (p.startsWith(galleryAttrAppendFlag)) { p = p.slice(1); } else if (p.startsWith(htmlAttrParamFlag)) { p = toParamKey(p); } attrs += ` ${p}="${v}"`; } } } if (!hasPath && info) { attrs += ' mx-view="' + result.mxView + '"'; } let html = `<${tag} ${attrs}`; let unary = selfCloseTags.hasOwnProperty(tag); if (unary) { html += `/>`; } else { html += `>${result.content}`; html += `</${tag}${result.endAttrs}>`; } 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 (key == 'to') { href = value; return ''; } if (key == 'tag') { tag = value; return ''; } return m; }); attrs = attrs.replace(attrNameValueReg, (m, prefix, key, q, value) => { prefix = prefix || ''; if (key.startsWith(htmlAttrParamFlag)) { key = toParamKey(key); paramKey = 1; } if (q === undefined && paramKey) { q = '"'; value = ''; } return prefix + key + '=' + q + value + q; }); let html = `<${tag} href="${href}" ${attrs}`; let unary = selfCloseTags.hasOwnProperty(tag); if (unary) { html += `/>`; } else { html += `>${result.content}`; html += `</${tag}${result.endAttrs}>`; } return html; }; let innerGroup = (result) => { let tag = tmplGroupTag; let newAttrs = ''; result.attrs.replace(attrNameValueReg, (m, prefix, key, q, value) => { if (key == 'use') { //使用场景下,清空内容 result.content = ''; newAttrs += ` ${tmplGroupUseAttr}="${value}"`; } else if (key == 'name') { newAttrs += ` ${tmplGroupKeyAttr}="${value}"`; } else { newAttrs += ` ${key}${q ? `=${value}` : ''}`; } }); return `<${tag} ${newAttrs}>${result.content}</${tag}${result.endAttrs}>`; }; module.exports = { process(tmpl, extInfo, e) { let cmdCache = Object.create(null); let galleriesMap = configs.galleries; let tmplConditionAttrs = Object.create(null); let tmplConditionAttrsIndex = 0; let tempSkipTags = Object.create(null); e.tmplConditionAttrs = tmplConditionAttrs; e.tmplComponents = []; let actions = { getTokens(content) { return tmplParser(content, e.shortHTMLFile); }, isWholeCmd(cmd) { return wholeCmdReg.test(cmd); }, hasCmd(cmd) { return hasCmdReg.test(cmd); }, recoverCmd(cmd) { return tmplCmd.toArtCmd(cmd, cmdCache); }, readCmd(cmd) { return tmplCmd.extractCmdContent(cmd, cmdCache); }, buildCmd(line, operate, art, content) { return tmplCmd.buildCmd(line, operate, art, content); }, buildAttrs(attrs, cond) { let attrStr = ''; for (let p in attrs) { let v = attrs[p]; let resolve = cond ? cond(p, v) : v; if (resolve != null && resolve !== false) { if (resolve === true) { resolve = ''; } else { resolve = '="' + resolve + '"'; } attrStr += ' ' + p + resolve; } } return attrStr; } }; let updateOffset = (node, content) => { let pos = node.start, offset = content.length - (node.end - node.start); let l = nodes => { if (nodes) { for (let n of nodes) { l(n.children); if (n !== node) { 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 = ''; //console.log(tmpl,n); if (n.hasAttrs) { attrs = tmpl.substring(n.attrsStart, n.attrsEnd); } if (n.hasContent) { content = tmpl.substring(n.contentStart, n.contentEnd); } 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.unary, //first: n.first, //last: n.last, //firstElement: n.firstElement, //lastElement: n.lastElement, shared: n.shared,//共享数据 tag: oTag, mainTag, subTags, attrs, endAttrs: n.endAttrs || '', attrsKV: n.attrsKV, content, nodesMap: map }; /* shared设计 在处理自定义标签时,如 <mx-table> <mx-table.col/> <mx-table.rows/> </mx-table> 先处理mx-table.col,在处理mx-table.col时,可以通过节点间关系找到父节点mx-table,此时可以在mx-table节点上挂一些共享数据,供其它节点使用 后处理mx-table节点 */ return result; }; let processCustomTagOrAttr = (n, map, isCustomAttr) => { let result = getTagInfo(n, map); if (configs.components[n.pfx + 'Root']) { if (!tempSkipTags[result.tag]) { tempSkipTags[result.tag] = 1; let jsFile = configs.components[n.pfx + 'Root'] + result.tag; if (!httpProtocolReg.test(jsFile)) { jsFile = utils.extractModuleId(jsFile); } e.tmplComponents.push(jsFile); } } else if (!skipTags[result.tag]) { let content = result.content; let fn = galleriesMap[result.tag] || configs.customTagOrAttrProcessor; //console.log('xxx'); let customContent = fn(result, actions, extInfo, e); if (!customContent && !isCustomAttr) { skipTags[result.tag] = 1; customContent = content; } if (content != customContent) { content = customContent || ''; tmpl = tmpl.substring(0, n.start) + content + tmpl.substring(n.end); updateOffset(n, content); } } }; 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'] || (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[galleryProcessed]) && !util.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, file: configFile } = customConfig(cpath, main); if (cfg.hasOwnProperty(result.tag)) { let ci = cfg[result.tag]; if (util.isFunction(ci)) { ci = { processor: ci, file: configFile }; } ci[galleryDynamic] = configFile; configs.galleriesDynamicRequires[configFile] = ci; gMap[result.tag] = ci; } else if (!i) { gMap[result.tag] = { path: vpath }; } } else { //当文件不存在时,不检查,直接使用用户配置的路径 gMap[result.tag] = Object.assign({}, i, { path: vpath }); } } } 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[galleryProcessed]) { if (util.isFunction(i)) { i = { processor: i }; gMap[result.tag] = i; } if (!i.path) { i.path = vpath; } i[galleryProcessed] = 1; } if (i[galleryDynamic]) { deps.addConfigDepend(i[galleryDynamic], e.from, e.to); } } } 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 == 'vframe') { content = innerView(result); update = true; } else if (result.mainTag == 'link') { content = innerLink(result, extInfo); update = true; } else if (result.mainTag == 'group') { content = innerGroup(result, extInfo); update = true; } } if (!update && gMap.hasOwnProperty(result.tag)) { content = innerView(result, gMap[result.tag], gRoot, extInfo, actions, e); update = true; } if (update) { tmpl = tmpl.substring(0, n.start) + content + tmpl.substring(n.end); //console.log(tmpl); //throw new Error('abc'); updateOffset(n, content); } }; 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 = booleanAttrReg.test(content); if (valuable || boolean) { let cs = content.split(valuable ? '??' : '?'); let [cond, ext] = cs; update = true; let extract = tmplCmd.extractCmdContent(cond, cmdCache); let condKey = ''; if (!extract.succeed) { slog.ever(chalk.red('[MXC Tip(tmpl-custom)] check condition ' + tmplCmd.recover(cond, cmdCache)), 'at', chalk.magenta(e.shortHTMLFile)); } else { condKey = `\x1c${tmplConditionAttrsIndex++}\x1c`; tmplConditionAttrs[condKey] = { hasExt: ext, valuable, boolean, attrName: key }; } return ` ${tmplCondPrefix}${condKey}="${cond}" ${prefix}${condKey}${key}=${q}${ext}${q}`; } return m; }); if (update) { let html = `<${tag} ${attrs}`; let unary = result.unary; if (unary) { html += `/`; } html += `>${result.content}`; if (!unary) { html += `</${tag}${result.endAttrs}>`; } content = html; tmpl = tmpl.substring(0, n.start) + content + tmpl.substring(n.end); updateOffset(n, content); } }; let processEncodeAttr = n => { let result = getTagInfo(n); let content = ''; let tag = result.tag; let attrs = result.attrs; attrs = attrs.replace(attrNameValueReg, encodeEntities); let html = `<${tag} ${attrs}`; let unary = result.unary; if (unary) { html += `/`; } html += `>${result.content}`; if (!unary) { html += `</${tag}${result.endAttrs}>`; } content = html; tmpl = tmpl.substring(0, n.start) + content + tmpl.substring(n.end); updateOffset(n, content); }; let processParamsAttrs = 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 || ''; if (key.startsWith(htmlAttrParamFlag)) { update = true; m = prefix + toParamKey(key) + (q ? ('=' + q + content + q) : ''); } return m; }); if (update) { let html = `<${tag} ${attrs}`; let unary = result.unary; if (unary) { html += `/`; } html += `>${result.content}`; if (!unary) { html += `</${tag}${result.endAttrs}>`; } content = html; tmpl = tmpl.substring(0, n.start) + content + tmpl.substring(n.end); updateOffset(n, content); } }; 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 = result.unary; if (unary) { html += `/`; } html += `>${result.content}`; if (!unary) { html += `</${tag}${result.endAttrs}>`; } content = html; tmpl = tmpl.substring(0, n.start) + content + tmpl.substring(n.end); updateOffset(n, content); }; let processMxView = n => { let result = getTagInfo(n); let content = ''; let tag = result.tag; let attrs = result.attrs; attrs = attrs.replace(mxViewAttrReg, (m, q, c) => { let { pathname, query } = url.parse(c); pathname = pathname || ''; pathname = addAtIfNeed(pathname); pathname = atpath.resolveContent(pathname, e.moduleId); let params = []; query = qs.parse(query, '&', '=', { decodeURIComponent(v) { return v; } }); for (let p in query) { let v = query[p]; v = addAtIfNeed(v); params.push(`${p}=${v}`); } pathname = configs.mxViewProcessor({ path: pathname, pkgName: e.pkgName }, e) || pathname; let view = pathname; //params.push(`a={{@$_temp}}`); if (params.length) { view += `?${params.join('&')}`; } return `\x02="${view}"`; }); let html = `<${tag} ${attrs}`; let unary = result.unary; if (unary) { html += `/`; } html += `>${result.content}`; if (!unary) { html += `</${tag}${result.endAttrs}>`; } content = html; tmpl = tmpl.substring(0, n.start) + content + tmpl.substring(n.end); updateOffset(n, content); }; let walk = (nodes, map) => { if (nodes) { if (!map) map = nodes.__map; for (let n of nodes) { if (!n.isText) { walk(n.children, map); if (n.needEncode) { processEncodeAttr(n, map); } else if (n.hasCustAttr) { processCustomTagOrAttr(n, map, true); } else if (n.customTag) { if (configs.galleryPrefixes[n.pfx] === 1) { processGalleryTag(n, map); } else { processCustomTagOrAttr(n, map); } } else if (n.hasParamsAttr) { processParamsAttrs(n); } else if (n.condAttr) { processCondAttrs(n); } else if (n.atAttrContent) { processAtAttrContents(n); } else if (n.hasMxView) { processMxView(n); } } } } }; let hasSpceialAttrs = false; tmpl = tmplCmd.store(tmpl, cmdCache); tmpl = tmplCmd.store(tmpl, cmdCache, artCommandReg); let checkCallback = token => { if (!hasSpceialAttrs && !skipTags[token.tag] && !tempSkipTags[token.tag]) { if (token.customTag || token.condAttr || token.needEncode || token.hasCustAttr || token.hasParamsAttr || token.atAttrContent || token.hasMxView) { hasSpceialAttrs = true; } } }; let tokens = tmplParser(tmpl, e.shortHTMLFile, checkCallback); let checkTimes = 2 << 3; while (hasSpceialAttrs && --checkTimes) { walk(tokens); tmpl = tmplCmd.store(tmpl, cmdCache); tmpl = tmplCmd.store(tmpl, cmdCache, artCommandReg); hasSpceialAttrs = false; tokens = tmplParser(tmpl, e.shortHTMLFile, checkCallback); } tmpl = tmplCmd.recover(tmpl, cmdCache); tmpl = tmpl.replace(attrAtStartContentHolderReg, '@'); tmpl = tmpl.replace(mxViewAttrHolderReg, 'mx-view'); return tmpl; } };