@hap-toolkit/compiler
Advanced tools
Comparing version 1.9.3-beta.1 to 1.9.3-beta.2
198
lib/index.js
@@ -1,2 +0,198 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.parseFragmentsWithCache=parseFragmentsWithCache,exports.parseTemplate=parseTemplate,exports.parseStyle=parseStyle,exports.parseScript=parseScript;var _parse=_interopRequireDefault(require("parse5")),_template=_interopRequireDefault(require("./template")),_style=_interopRequireDefault(require("./style")),_script=_interopRequireDefault(require("./script")),_utils=require("./utils");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function _formatFragment(e,t){let r,a,s,n;const o={};if(t.__location){const e=t.__location;e.startTag&&e.endTag?(r=e.startTag.endOffset||0,a=e.endTag.startOffset||0):(r=e.startOffset||0,a=e.endOffset||0),s=e.line,n=e.col}else r=a=s=n=0;return t.attrs&&t.attrs.length&&t.attrs.forEach((function(e){o[e.name]=e.value})),{type:t.nodeName,attrs:o,content:e.substring(r,a),location:{start:r,end:a,line:s,column:n}}}function parseFragments(e,t){const r={import:[],template:[],style:[],script:[]};return _parse.default.parseFragment(e,{treeAdapter:_parse.default.treeAdapters.default,locationInfo:!0}).childNodes.forEach(a=>{const s=_formatFragment(e,a);if(r[a.nodeName]&&r[a.nodeName].push(s),"import"===a.nodeName&&a.__location&&!a.__location.endTag)throw new Error(`${t} : <import> 组件缺少闭合标签,请检查 @${s.location.line} : ${s.location.column}`);if("plaintext"===a.nodeName.toLowerCase())throw new Error(`${t} : 禁止使用plaintext @${s.location.line} : ${s.location.column}`)}),r}const fragsCache=new Map;function parseFragmentsWithCache(e,t){return fragsCache.has(t)&&fragsCache.get(t).source===e||fragsCache.set(t,{source:e,frags:parseFragments(e,t)}),fragsCache.get(t).frags}function parseTemplate(e,t){const r=_template.default.parse(e,t),{jsonTemplate:a,log:s,depFiles:n}=r;return{parsed:(0,_utils.serialize)(a,2),log:s,depFiles:n}}function parseStyle(e){const t=_style.default.parse(e),{jsonStyle:r,depList:a,log:s,depFiles:n}=t;return{parsed:JSON.stringify(r,null,2),depList:a,log:s,depFiles:n,jsonStyle:r}}function parseScript(e){return{parsed:_script.default.parse(e)}} | ||
/* | ||
* Copyright (C) 2017, hapjs.org. All rights reserved. | ||
*/ | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.parseFragmentsWithCache = parseFragmentsWithCache; | ||
exports.parseTemplate = parseTemplate; | ||
exports.parseStyle = parseStyle; | ||
exports.parseScript = parseScript; | ||
var _parse = _interopRequireDefault(require("parse5")); | ||
var _template = _interopRequireDefault(require("./template")); | ||
var _style = _interopRequireDefault(require("./style")); | ||
var _script = _interopRequireDefault(require("./script")); | ||
var _utils = require("./utils"); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/** | ||
* 格式化节点 | ||
* @param source | ||
* @param node | ||
*/ | ||
function _formatFragment(source, node) { | ||
let start, end, line, column; | ||
const attrs = {}; // 获取当前节点在文档中的位置信息 | ||
if (node.__location) { | ||
const __location = node.__location; | ||
if (__location.startTag && __location.endTag) { | ||
start = __location.startTag.endOffset || 0; | ||
end = __location.endTag.startOffset || 0; | ||
} else { | ||
start = __location.startOffset || 0; | ||
end = __location.endOffset || 0; | ||
} | ||
line = __location.line; | ||
column = __location.col; | ||
/* istanbul ignore else */ | ||
} else { | ||
start = end = line = column = 0; | ||
} | ||
if (node.attrs && node.attrs.length) { | ||
node.attrs.forEach(function (item) { | ||
attrs[item.name] = item.value; | ||
}); | ||
} | ||
return { | ||
type: node.nodeName, | ||
attrs: attrs, | ||
// 节点属性值 | ||
content: source.substring(start, end), | ||
// 节点的文本内容 | ||
location: { | ||
start: start, | ||
end: end, | ||
line: line, | ||
column: column | ||
} | ||
}; | ||
} | ||
/** | ||
* 解析片段 | ||
* @param {string} source - 源码 | ||
* @param {string} filePath - 文件绝对路径 | ||
*/ | ||
function parseFragments(source, filePath) { | ||
const frags = { | ||
import: [], | ||
// 导入组件 | ||
template: [], | ||
// 模板 | ||
style: [], | ||
// 样式 | ||
script: [] // 脚本 | ||
}; | ||
const fragment = _parse.default.parseFragment(source, { | ||
treeAdapter: _parse.default.treeAdapters.default, | ||
locationInfo: true | ||
}); // 存储片段解析结果 | ||
fragment.childNodes.forEach(node => { | ||
const fragmentInfo = _formatFragment(source, node); | ||
frags[node.nodeName] && frags[node.nodeName].push(fragmentInfo); // 判断<import>组件标签是否闭合 | ||
if (node.nodeName === 'import' && node.__location && !node.__location.endTag) { | ||
throw new Error(`${filePath} : <import> 组件缺少闭合标签,请检查 @${fragmentInfo.location.line} : ${fragmentInfo.location.column}`); | ||
} | ||
if (node.nodeName.toLowerCase() === 'plaintext') { | ||
throw new Error(`${filePath} : 禁止使用${`plaintext`} @${fragmentInfo.location.line} : ${fragmentInfo.location.column}`); | ||
} | ||
}); | ||
return frags; | ||
} // 片段缓存 | ||
const fragsCache = new Map(); | ||
/** | ||
* 解析片段,优先从缓存中获取 | ||
* @param {string} source - 源码 | ||
* @param {string} filePath - 文件绝对路径 | ||
* @returns {Object} | ||
*/ | ||
function parseFragmentsWithCache(source, filePath) { | ||
if (!fragsCache.has(filePath) || fragsCache.get(filePath).source !== source) { | ||
// 解析并缓存片段 | ||
fragsCache.set(filePath, { | ||
source: source, | ||
frags: parseFragments(source, filePath) | ||
}); | ||
} | ||
return fragsCache.get(filePath).frags; | ||
} | ||
/** | ||
* 解析模板 | ||
* @param {String} source - 源码 | ||
* @param {Object} options | ||
* @param {String} options.uxType - 文件类型 | ||
* @param {String} options.filePath - 当前执行文件的绝对路径 | ||
* @returns {Object} | ||
*/ | ||
function parseTemplate(source, options) { | ||
const templateObj = _template.default.parse(source, options); | ||
const { | ||
jsonTemplate, | ||
log, | ||
depFiles | ||
} = templateObj; | ||
const parsed = (0, _utils.serialize)(jsonTemplate, 2); | ||
return { | ||
parsed, | ||
log, | ||
depFiles | ||
}; | ||
} | ||
/** | ||
* 解析CSS | ||
* @param {String} source | ||
* @returns {Object} | ||
*/ | ||
function parseStyle(source) { | ||
const styleObj = _style.default.parse(source); | ||
const { | ||
jsonStyle, | ||
depList, | ||
log, | ||
depFiles | ||
} = styleObj; | ||
const parsed = JSON.stringify(jsonStyle, null, 2); | ||
return { | ||
parsed, | ||
depList, | ||
log, | ||
depFiles, | ||
jsonStyle | ||
}; | ||
} | ||
/** | ||
* 解析脚本 | ||
* @param {String} source - 源码 | ||
* @returns {Object} | ||
*/ | ||
function parseScript(source) { | ||
const parsed = _script.default.parse(source); | ||
return { | ||
parsed: parsed | ||
}; | ||
} | ||
//# sourceMappingURL=index.js.map |
@@ -1,2 +0,50 @@ | ||
"use strict";function parse(e){return e}function replaceModuleImport(e){let r=e.replace(/require\s*\(\s*(['"])@([\w$_][\w$-.]*?)\1\)/gm,(e,r,p)=>`$app_require$(${r}@app-module/${p}${r})`);return r=r.replace(/import\s+([\w${}]+?)\s+from\s+(['"])@([\w$_][\w$-.]*?)\2/gm,(e,r,p,t)=>`var ${r} = $app_require$(${p}@app-module/${t}${p})`),r}Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=void 0;var _default={parse:parse,replaceModuleImport:replaceModuleImport};exports.default=_default; | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = void 0; | ||
/* | ||
* Copyright (C) 2017, hapjs.org. All rights reserved. | ||
*/ | ||
/** | ||
* 解析源码 | ||
* @param source | ||
*/ | ||
function parse(source) { | ||
return source; | ||
} | ||
/** | ||
* 替换脚本中对模块的引用 | ||
* | ||
* NOTE: 正则表达式 ([\w$_][\w$-.]) 部分对模块的名称给予了大于快应用实际的范围 | ||
* @param {String} source - 代码 | ||
* @return {String} | ||
*/ | ||
function replaceModuleImport(source) { | ||
// require('@mod') => $app_require$('@app-module/mod') | ||
let result = source.replace(/require\s*\(\s*(['"])@([\w$_][\w$-.]*?)\1\)/gm, (_, quote, mod) => { | ||
return `$app_require$(${quote}@app-module/${mod}${quote})`; | ||
}); | ||
/* | ||
* import mod from '@mod' => var mod = require('@app-module/mod') | ||
* import {prop} from '@mod' ?? => var {prop} = require('@app-module/mod') | ||
* TODO source 已被转换成 require,下面的代码其实不再需要 | ||
*/ | ||
result = result.replace(/import\s+([\w${}]+?)\s+from\s+(['"])@([\w$_][\w$-.]*?)\2/gm, (_, ref, quote, mod) => { | ||
return `var ${ref} = $app_require$(${quote}@app-module/${mod}${quote})`; | ||
}); | ||
return result; | ||
} | ||
var _default = { | ||
parse, | ||
replaceModuleImport | ||
}; | ||
exports.default = _default; | ||
//# sourceMappingURL=index.js.map |
@@ -1,2 +0,173 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.compressDescSelector=compressDescSelector,exports.compressCssAttr=compressCssAttr;const cssDescSelectorList=[{newN:"t",oldN:"type",newV:["d","a","t","u","p","pe"],oldV:["descendant","attribute","tag","universal","pseudo","pseudo-element"]},{newN:"n",oldN:"name"},{newN:"i",oldN:"ignoreCase"},{newN:"a",oldN:"action"},{newN:"v",oldN:"value"}],cssAttrMap={width:"w",height:"h",paddingLeft:"pL",paddingRight:"pR",paddingTop:"pT",paddingBottom:"pB",marginLeft:"mL",marginRight:"mR",marginTop:"mT",marginBottom:"mB",borderLeftWidth:"bLW",borderTopWidth:"bTW",borderRightWidth:"bRW",borderBottomWidth:"bBW",borderLeftColor:"bLC",borderTopColor:"bTC",borderRightColor:"bRC",borderBottomColor:"bBC",borderStyle:"bS",borderRadius:"bR",borderBottomLeftRadius:"bBLR",borderBottomRightRadius:"bBRR",borderTopLeftRadius:"bTLR",borderTopRightRadius:"bTRR",indicatorSize:"iS",flex:"f",flexGrow:"fG",flexShrink:"fS",flexBasis:"fB",flexDirection:"fD",flexWrap:"fW",justifyContent:"jC",alignItems:"aI",alignContent:"aC",alignSelf:"aS",position:"p",top:"t",bottom:"b",left:"l",right:"r",zIndex:"zI",opacity:"o",background:"bg",backgroundColor:"bgC",backgroundImage:"bgI",backgroundSize:"bgS",backgroundRepeat:"bgR",backgroundPosition:"bgP",display:"d",visibility:"v",lines:"ls",color:"c",fontSize:"foS",fontStyle:"fSt",fontWeight:"foW",textDecoration:"tD",textAlign:"tA",lineHeight:"lH",textOverflow:"tO",transform:"ts",transformOrigin:"tsO",animationName:"aN",animationDuration:"aD",animationTimingFunction:"aTF",animationDelay:"aDe",animationIterationCount:"aIC",animationFillMode:"aFM",placeholderColor:"pC",selectedColor:"sC",textColor:"tC",timeColor:"tiC",textHighlightColor:"tHC",strokeWidth:"sW",progressColor:"prC",indicatorColor:"iC",indicatorSelectedColor:"iSC",slideWidth:"slW",slideMargin:"sM",resizeMode:"rM",columns:"col",columnSpan:"cS",maskColor:"mC",starBackground:"sB",starForeground:"sF",starSecondary:"sS"};function compressDescSelector(o){o=o[0]||[];for(let t=0;t<o.length;t++){const e=o[t]||{};cssDescSelectorList.forEach((function(o){e.hasOwnProperty(o.oldN)&&(e[o.newN]=e[o.oldN],delete e[o.oldN],o.oldV&&o.oldV.indexOf(e[o.newN])>-1&&(e[o.newN]=o.newV[o.oldV.indexOf(e[o.newN])]))}))}return o}function compressCssAttr(o){for(const t in o){const e=o[t];if("@KEYFRAMES"!==t&&"object"==typeof e)for(const o in e)if(cssAttrMap[o]){e[cssAttrMap[o]]=e[o],delete e[o]}}} | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.compressDescSelector = compressDescSelector; | ||
exports.compressCssAttr = compressCssAttr; | ||
/* | ||
* Copyright (C) 2017, hapjs.org. All rights reserved. | ||
*/ | ||
// CSS后代选择器全写-缩写对照表 | ||
const cssDescSelectorList = [{ | ||
newN: 't', | ||
oldN: 'type', | ||
newV: ['d', 'a', 't', 'u', 'p', 'pe'], | ||
oldV: ['descendant', 'attribute', 'tag', 'universal', 'pseudo', 'pseudo-element'] | ||
}, { | ||
newN: 'n', | ||
oldN: 'name' | ||
}, { | ||
newN: 'i', | ||
oldN: 'ignoreCase' | ||
}, { | ||
newN: 'a', | ||
oldN: 'action' | ||
}, { | ||
newN: 'v', | ||
oldN: 'value' | ||
}]; // CSS属性名全写-缩写对照表 | ||
const cssAttrMap = { | ||
// boxModel | ||
width: 'w', | ||
height: 'h', | ||
paddingLeft: 'pL', | ||
paddingRight: 'pR', | ||
paddingTop: 'pT', | ||
paddingBottom: 'pB', | ||
marginLeft: 'mL', | ||
marginRight: 'mR', | ||
marginTop: 'mT', | ||
marginBottom: 'mB', | ||
borderLeftWidth: 'bLW', | ||
borderTopWidth: 'bTW', | ||
borderRightWidth: 'bRW', | ||
borderBottomWidth: 'bBW', | ||
borderLeftColor: 'bLC', | ||
borderTopColor: 'bTC', | ||
borderRightColor: 'bRC', | ||
borderBottomColor: 'bBC', | ||
borderStyle: 'bS', | ||
borderRadius: 'bR', | ||
borderBottomLeftRadius: 'bBLR', | ||
borderBottomRightRadius: 'bBRR', | ||
borderTopLeftRadius: 'bTLR', | ||
borderTopRightRadius: 'bTRR', | ||
indicatorSize: 'iS', | ||
// flexbox | ||
flex: 'f', | ||
flexGrow: 'fG', | ||
flexShrink: 'fS', | ||
flexBasis: 'fB', | ||
flexDirection: 'fD', | ||
flexWrap: 'fW', | ||
justifyContent: 'jC', | ||
alignItems: 'aI', | ||
alignContent: 'aC', | ||
alignSelf: 'aS', | ||
// position | ||
position: 'p', | ||
top: 't', | ||
bottom: 'b', | ||
left: 'l', | ||
right: 'r', | ||
zIndex: 'zI', | ||
// common | ||
opacity: 'o', | ||
background: 'bg', | ||
backgroundColor: 'bgC', | ||
backgroundImage: 'bgI', | ||
backgroundSize: 'bgS', | ||
backgroundRepeat: 'bgR', | ||
backgroundPosition: 'bgP', | ||
display: 'd', | ||
visibility: 'v', | ||
// text | ||
lines: 'ls', | ||
color: 'c', | ||
fontSize: 'foS', | ||
fontStyle: 'fSt', | ||
fontWeight: 'foW', | ||
textDecoration: 'tD', | ||
textAlign: 'tA', | ||
lineHeight: 'lH', | ||
textOverflow: 'tO', | ||
// animation | ||
transform: 'ts', | ||
transformOrigin: 'tsO', | ||
animationName: 'aN', | ||
animationDuration: 'aD', | ||
animationTimingFunction: 'aTF', | ||
animationDelay: 'aDe', | ||
animationIterationCount: 'aIC', | ||
animationFillMode: 'aFM', | ||
// custom | ||
placeholderColor: 'pC', | ||
selectedColor: 'sC', | ||
textColor: 'tC', | ||
timeColor: 'tiC', | ||
textHighlightColor: 'tHC', | ||
strokeWidth: 'sW', | ||
progressColor: 'prC', | ||
indicatorColor: 'iC', | ||
indicatorSelectedColor: 'iSC', | ||
slideWidth: 'slW', | ||
slideMargin: 'sM', | ||
resizeMode: 'rM', | ||
columns: 'col', | ||
columnSpan: 'cS', | ||
maskColor: 'mC', | ||
// custom style | ||
starBackground: 'sB', | ||
starForeground: 'sF', | ||
starSecondary: 'sS' | ||
}; | ||
/** | ||
* 压缩后代选择器 | ||
* @param itemList | ||
* @returns {*|Array} | ||
*/ | ||
function compressDescSelector(itemList) { | ||
itemList = itemList[0] || []; | ||
for (let i = 0; i < itemList.length; i++) { | ||
const item = itemList[i] || {}; | ||
cssDescSelectorList.forEach(function (ccl) { | ||
if (item.hasOwnProperty(ccl.oldN)) { | ||
item[ccl.newN] = item[ccl.oldN]; | ||
delete item[ccl.oldN]; | ||
if (ccl.oldV && ccl.oldV.indexOf(item[ccl.newN]) > -1) { | ||
item[ccl.newN] = ccl.newV[ccl.oldV.indexOf(item[ccl.newN])]; | ||
} | ||
} | ||
}); | ||
} | ||
return itemList; | ||
} | ||
/** | ||
* 压缩CSS属性名 | ||
* @param jsonStyle | ||
*/ | ||
function compressCssAttr(jsonStyle) { | ||
for (const selector in jsonStyle) { | ||
const cssAttrObj = jsonStyle[selector]; | ||
if (selector !== '@KEYFRAMES' && typeof cssAttrObj === 'object') { | ||
for (const cssAttrName in cssAttrObj) { | ||
if (cssAttrMap[cssAttrName]) { | ||
const cssAttrAbbrName = cssAttrMap[cssAttrName]; | ||
cssAttrObj[cssAttrAbbrName] = cssAttrObj[cssAttrName]; | ||
delete cssAttrObj[cssAttrName]; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
//# sourceMappingURL=compress.js.map |
@@ -1,2 +0,236 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=void 0;var _path=_interopRequireDefault(require("path")),_css=_interopRequireDefault(require("css")),_compilationConfig=require("@hap-toolkit/shared-utils/compilation-config"),_validator=require("./validator"),_compress=require("./compress"),_process=require("./process"),_utils=require("../utils");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function parse(e){let t;const s={},o=[];let r=[],a=e.code||"";const l=e.filePath,i=_path.default.dirname(l),n=[];a=(0,_process.processImport)(a,i,o,n);const c=_css.default.parse(a,{silent:!0});return c.stylesheet.parsingErrors&&c.stylesheet.parsingErrors.length&&(t=c.stylesheet.parsingErrors,t.forEach((function(e){o.push({line:e.line,column:e.column,reason:e.toString()})}))),c&&"stylesheet"===c.type&&c.stylesheet&&c.stylesheet.rules&&c.stylesheet.rules.length&&c.stylesheet.rules.forEach((function(e){const t=e.type,a={};if("rule"===t&&e.declarations&&e.declarations.length&&(0,_process.processSingleClass)(e,s,a,o,l,r),"media"===t)s["@MEDIA"]||(s["@MEDIA"]=[]),(0,_process.processMediaQueryCss)(e,s["@MEDIA"],o,l,"",r);else if("font-face"===t){if(e.declarations&&e.declarations.length){const t={};e.declarations.forEach((function(e){if("declaration"!==e.type)return;const s=(0,_utils.hyphenedToCamelCase)(e.property),a=e.value;if("fontFamily"===s)t.fontFamily=a.replace(/['"]+/g,"");else if("src"===s){const s=(0,_validator.validate)("fontSrc",a,{filePath:l});s.log&&o.push({line:e.position.start.line,column:e.position.start.column,reason:s.log.reason}),t.src=s.value;const i=s.value||[];r=r.concat(i)}})),s["@FONT-FACE"]||(s["@FONT-FACE"]={}),t.fontName=t.fontFamily,t.fontSrc=t.src,s["@FONT-FACE"][t.fontFamily]=t}}else if("keyframes"===t&&e.keyframes&&e.keyframes.length){const t=e.name,a=[];e.keyframes.forEach((function(s){let i;if("keyframe"===s.type&&s.declarations&&s.declarations.length)if(i={},s.declarations.forEach((function(e){if("declaration"!==e.type)return;const t=e.property,s=e.value,a=(0,_utils.hyphenedToCamelCase)(t),n=(0,_validator.validate)(a,s,{filePath:l});n.value.forEach(e=>{(0,_utils.isValidValue)(e.v)&&(i[e.n]=e.v,(0,_process.shouldAddToDependency)(e.n,e.v)&&r.push(e.v))}),n.log&&o.push({line:e.position.start.line,column:e.position.start.column,reason:n.log.reason})})),(0,_utils.isEmptyObject)(i))o.push({line:e.position.start.line,column:e.position.start.column,reason:"ERROR: 动画 `"+t+"` 的关键帧 `"+JSON.stringify(s.values)+"` 没有有效的属性"});else{let e;s.values.forEach(t=>{e="from"===t?0:"to"===t?100:parseFloat(t.replace("%","")),i.time=e,a.push(i)})}})),a.sort((function(e,t){return e.time-t.time})),s["@KEYFRAMES"]||(s["@KEYFRAMES"]={}),s["@KEYFRAMES"][t]=a}})),_compilationConfig.compileOptionsObject.optimizeCssAttr&&(0,_compress.compressCssAttr)(s),{jsonStyle:s,depList:n,log:o,depFiles:r}}var _default={parse:parse,validateDelaration:_validator.validate,mightReferlocalResource:_validator.mightReferlocalResource,shouldAddToDependency:_process.shouldAddToDependency};exports.default=_default; | ||
/* | ||
* Copyright (C) 2017, hapjs.org. All rights reserved. | ||
*/ | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = void 0; | ||
var _path = _interopRequireDefault(require("path")); | ||
var _css = _interopRequireDefault(require("css")); | ||
var _compilationConfig = require("@hap-toolkit/shared-utils/compilation-config"); | ||
var _validator = require("./validator"); | ||
var _compress = require("./compress"); | ||
var _process = require("./process"); | ||
var _utils = require("../utils"); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/** | ||
* 解析<style>为JSON对象 | ||
* @param {String} source - 源码 | ||
* @returns {Object} | ||
*/ | ||
function parse(source) { | ||
let err; | ||
const jsonStyle = {}; | ||
const log = []; | ||
let depFiles = []; | ||
let code = source.code || ''; | ||
const filePath = source.filePath; | ||
const curDir = _path.default.dirname(filePath); // 引入的CSS文件列表 | ||
const depList = []; // 合并css | ||
code = (0, _process.processImport)(code, curDir, log, depList); // css解析 | ||
const ast = _css.default.parse(code, { | ||
silent: true | ||
}); // 异常处理,打印错误 | ||
if (ast.stylesheet.parsingErrors && ast.stylesheet.parsingErrors.length) { | ||
err = ast.stylesheet.parsingErrors; | ||
err.forEach(function (err) { | ||
log.push({ | ||
line: err.line, | ||
column: err.column, | ||
reason: err.toString() | ||
}); | ||
}); | ||
} // 遍历 | ||
if (ast && ast.type === 'stylesheet' && ast.stylesheet && ast.stylesheet.rules && ast.stylesheet.rules.length) { | ||
// 遍历样式规则 | ||
ast.stylesheet.rules.forEach(function (rule) { | ||
const type = rule.type; | ||
const ruleResult = {}; // 只考虑rule和fontface,其余暂时不支持 | ||
if (type === 'rule') { | ||
if (rule.declarations && rule.declarations.length) { | ||
(0, _process.processSingleClass)(rule, jsonStyle, ruleResult, log, filePath, depFiles); | ||
} | ||
} | ||
if (type === 'media') { | ||
if (!jsonStyle['@MEDIA']) { | ||
jsonStyle['@MEDIA'] = []; | ||
} | ||
(0, _process.processMediaQueryCss)(rule, jsonStyle['@MEDIA'], log, filePath, '', depFiles); | ||
} else if (type === 'font-face') { | ||
if (rule.declarations && rule.declarations.length) { | ||
const fontFaceObj = {}; | ||
rule.declarations.forEach(function (declaration) { | ||
/* istanbul ignore if */ | ||
if (declaration.type !== 'declaration') { | ||
return; | ||
} | ||
const name = (0, _utils.hyphenedToCamelCase)(declaration.property); | ||
const value = declaration.value; | ||
if (name === 'fontFamily') { | ||
// 记录字体名.剔除字体名的外层引号 | ||
fontFaceObj.fontFamily = value.replace(/['"]+/g, ''); | ||
} else if (name === 'src') { | ||
// 校验属性值 | ||
const subResult = (0, _validator.validate)('fontSrc', value, { | ||
filePath | ||
}); | ||
if (subResult.log) { | ||
log.push({ | ||
line: declaration.position.start.line, | ||
column: declaration.position.start.column, | ||
reason: subResult.log.reason | ||
}); | ||
} | ||
fontFaceObj.src = subResult.value; | ||
const srcFiles = subResult.value || []; | ||
depFiles = depFiles.concat(srcFiles); | ||
} | ||
}); // 所有的fontface放入一个对象中 | ||
if (!jsonStyle['@FONT-FACE']) { | ||
jsonStyle['@FONT-FACE'] = {}; | ||
} // 兼容之前平台版本 | ||
fontFaceObj.fontName = fontFaceObj.fontFamily; | ||
fontFaceObj.fontSrc = fontFaceObj.src; | ||
jsonStyle['@FONT-FACE'][fontFaceObj.fontFamily] = fontFaceObj; | ||
} | ||
} else if (type === 'keyframes') { | ||
if (rule.keyframes && rule.keyframes.length) { | ||
const name = rule.name; | ||
const frameResult = []; | ||
rule.keyframes.forEach(function (keyframe) { | ||
let keyResult; | ||
/* istanbul ignore if */ | ||
if (keyframe.type !== 'keyframe') { | ||
return; | ||
} // 处理关键帧内部样式 | ||
if (keyframe.declarations && keyframe.declarations.length) { | ||
keyResult = {}; | ||
keyframe.declarations.forEach(function (declaration) { | ||
const subType = declaration.type; | ||
/* istanbul ignore if */ | ||
if (subType !== 'declaration') { | ||
return; | ||
} // 样式的属性和值 | ||
const subname = declaration.property; | ||
const subvalue = declaration.value; // 校验属性值 | ||
const subcamelCasedName = (0, _utils.hyphenedToCamelCase)(subname); | ||
const subResult = (0, _validator.validate)(subcamelCasedName, subvalue, { | ||
filePath | ||
}); | ||
subResult.value.forEach(item => { | ||
// 如果校验成功,则保存转换后的属性值 | ||
if ((0, _utils.isValidValue)(item.v)) { | ||
keyResult[item.n] = item.v; | ||
if ((0, _process.shouldAddToDependency)(item.n, item.v)) { | ||
depFiles.push(item.v); | ||
} | ||
} | ||
}); | ||
if (subResult.log) { | ||
log.push({ | ||
line: declaration.position.start.line, | ||
column: declaration.position.start.column, | ||
reason: subResult.log.reason | ||
}); | ||
} | ||
}); // 检查对象是否为空 | ||
if ((0, _utils.isEmptyObject)(keyResult)) { | ||
log.push({ | ||
line: rule.position.start.line, | ||
column: rule.position.start.column, | ||
reason: 'ERROR: 动画 `' + name + '` 的关键帧 `' + JSON.stringify(keyframe.values) + '` 没有有效的属性' | ||
}); | ||
} else { | ||
// 可能包含多个 | ||
let percentValue; | ||
keyframe.values.forEach(v => { | ||
if (v === 'from') { | ||
percentValue = 0; | ||
} else if (v === 'to') { | ||
percentValue = 100; | ||
} else { | ||
percentValue = parseFloat(v.replace('%', '')); | ||
} | ||
keyResult['time'] = percentValue; | ||
frameResult.push(keyResult); | ||
}); | ||
} | ||
} | ||
}); // 排序 | ||
frameResult.sort(function (a, b) { | ||
return a.time - b.time; | ||
}); // 所有的keyframes放入一个数组中 | ||
if (!jsonStyle['@KEYFRAMES']) { | ||
jsonStyle['@KEYFRAMES'] = {}; | ||
} | ||
jsonStyle['@KEYFRAMES'][name] = frameResult; | ||
} | ||
} | ||
}); | ||
} // 是否压缩CSS属性名 | ||
if (_compilationConfig.compileOptionsObject.optimizeCssAttr) { | ||
(0, _compress.compressCssAttr)(jsonStyle); | ||
} | ||
return { | ||
jsonStyle, | ||
depList, | ||
log, | ||
depFiles | ||
}; | ||
} | ||
var _default = { | ||
parse, | ||
validateDelaration: _validator.validate, | ||
mightReferlocalResource: _validator.mightReferlocalResource, | ||
shouldAddToDependency: _process.shouldAddToDependency | ||
}; | ||
exports.default = _default; | ||
//# sourceMappingURL=index.js.map |
@@ -1,2 +0,424 @@ | ||
"use strict";const RE_MEDIA_QUERY=/(?:(only|not)?\s*)?(\s*[^\s()]+)?(?:(?:\s*(and)\s*)?(.+))?/i,RE_MEDIA_FEATURE=/^(\(\s*[^()]+\)\s+[a-zA-Z]+\s+)+\(\s*[^()]+\)\s*$|^\(\s*[^()]+\)$/,RE_MQ_DISCRETE_EXPRESSION=/\(\s*([^\s:)]+)\s*(?::\s*([^\s)]+))?\s*\)/,RE_MQ_RANGE_EXPRESSION=/^\((?:([-+]?\d*\.?(?:\d+[a-zA-Z]*|\d+\s*\/\s*\d+))\s*(<|>|<=|>=)?\s*)?(aspect-ratio|resolution|width|height|device-width|device-height)(?:\s*(<|>|<=|>=)?\s*([-+]?\d*\.?(?:\d+[a-zA-Z]*|\d+\s*\/\s*\d+)))?\)$/,mediaQueryTypes=["screen"],featureValidatorMap={height:"number","min-height":"number","max-height":"number",width:"number","min-width":"number","max-width":"number",resolution:"resolution","min-resolution":"resolution","max-resolution":"resolution",orientation:"orientation","aspect-ratio":"ratio","min-aspect-ratio":"ratio","max-aspect-ratio":"ratio","device-height":"number","min-device-height":"number","max-device-height":"number","device-width":"number","min-device-width":"number","max-device-width":"number","prefers-color-scheme":"preferColorScheme"},featureValidator={number:e=>/^(\d+)(px|dp)?$/.test(e)?{value:e}:{reason:function(r){return"ERROR: 媒体特征 `"+r+"` 的值 `"+e+"` 不正确, 必须为 `数值`"}},resolution(e){if(/^\d+(dpi|dppx)$/.test(e))return{value:e};if(/^\d+$/.test(e)){return{value:e+"dpi",reason:function(e){return"WARN: 媒体特征 `"+e+"` 的单位为 `dpi | dppx` 自动补齐为 `dpi`"}}}return{reason:function(r){return"ERROR: 媒体特征 `"+r+"` 的值 `"+e+"` 不正确, 必须为 `数值 + dpi | dppx`"}}},orientation:e=>/^(portrait|landscape)$/.test(e)?{value:e}:{reason:function(r){return"WARN: 媒体特征 `"+r+"` 的值 `"+e+"` 不正确, 必须为 `portrait | landscape`"}},ratio:e=>/^(\d+\s*\/\s*\d+|\d+\.\d+|\d+)$/.test(e)?{value:e}:{reason:function(r){return"WARN: 媒体特征 `"+r+"` 的值 `"+e+"` 不正确, 必须为 `数值 | 数值/数值`"}},preferColorScheme:e=>/^(light|dark|no-preference)$/.test(e)?{value:e}:{reason:function(r){return"WARN: 媒体特征 `"+r+"` 的值 `"+e+"` 不正确, 必须为 `light | dark | no-preference`"}}};function parseQuery(e){const r=[];return{result:e.split(",").map((function(e){const t=(e=e.trim()).match(RE_MEDIA_QUERY),n=t[1],i=t[2],a=t[3],o=t[4]||"";if(o&&!o.match(RE_MEDIA_FEATURE))return void r.push("WARN: 媒体特征格式错误");const s={};s.modifier=n,s.type=i?i.toLowerCase():"screen";let u=o.match(/\([^)]+\)/g)||[];return s.operator=a,s.expressions=u.map((function(t){const n=new RegExp("\\)\\s+([a-zA-Z]+)?\\s+"+t.replace(/\(/,"\\(").replace(/\)/,"\\)")),i=e.match(n);let a="";if(i&&(a=i[1],"and"!==a&&"or"!==a))return void r.push("WARN: 媒体特征连接符必须为 and 或者 or");let o=t.match(RE_MQ_DISCRETE_EXPRESSION);return o?{type:"discrete",combineSymbol:a,feature:o[1],value:o[2]}:(o=t.match(RE_MQ_RANGE_EXPRESSION),o?{type:"range",combineSymbol:a,beforeValue:o[1],beforeSymbol:o[2],feature:o[3],afterSymbol:o[4],afterValue:o[5]}:void r.push('WARN: 无效的媒体特征表达式: "'+t+'"'))})),s})),error:r}}function validateDiscreteValue(e,r,t,n){const i=featureValidator[t](e.value),{value:a,reason:o}=i;let s="";return o&&o&&n.push(o(r)),a&&(s+=`${r}: ${a}`),s}function validateRangeValue(e,r,t,n){function i(e){let i=featureValidator[t](e);const{reason:a,value:o}=i;return a&&n.push(a(r)),o}let{beforeValue:a,beforeSymbol:o,afterSymbol:s,afterValue:u}=e,d=r;if(a){if(a=i(a),!a)return"";d=`${a} ${o} ${d}`}if(u){if(u=i(u),!u)return"";d=`${d} ${s} ${u}`}return d}function validateMediaCondition(e){if(e){const r=[],t=parseQuery(e);if(t.error.length>0)return t.error.forEach(t=>{t+=", 表达式: `"+e+"` 有错误,请检查",r.push(t)}),{reason:r};const n=[];return t.result.forEach(e=>{const{modifier:t,type:i,operator:a,expressions:o}=e;if(-1===mediaQueryTypes.indexOf(i))return void r.push("WARN: 媒体类型 `"+i+"` 不支持");let s=a||"";o.length>0&&!a&&(s="and");let u=t?`${t} ${i} ${s}`:`${i} ${s}`;o.forEach(e=>{const{feature:t,combineSymbol:n,type:i}=e,a=featureValidatorMap[t];if(!a)return u="",void r.push("WARN: 媒体特征 `"+t+"` 不支持");let o="";"discrete"===i?o=validateDiscreteValue(e,t,a,r):"range"===i&&(o=validateRangeValue(e,t,a,r)),o?u+=n?` ${n} (${o})`:` (${o})`:u=""}),n.push(u)}),{value:n.join(" or "),reason:r}}return{value:""}}function findMediaClassByCondition(e,r){if(r){return e.find(e=>e.condition===r)}return null}function wrapMediaCode(e,r){return`@media ${r} {\n${e}\n}`}module.exports={validateMediaCondition:validateMediaCondition,findMediaClassByCondition:findMediaClassByCondition,wrapMediaCode:wrapMediaCode}; | ||
/* | ||
* Copyright (C) 2017, hapjs.org. All rights reserved. | ||
*/ | ||
'use strict'; // 完整的媒体查询 | ||
const RE_MEDIA_QUERY = /(?:(only|not)?\s*)?(\s*[^\s()]+)?(?:(?:\s*(and)\s*)?(.+))?/i; // feture集合表达式 | ||
// (foo) and (bar) | (baz) | ||
const RE_MEDIA_FEATURE = /^(\(\s*[^()]+\)\s+[a-zA-Z]+\s+)+\(\s*[^()]+\)\s*$|^\(\s*[^()]+\)$/; // (min-width: 100) 此类表达式 | ||
const RE_MQ_DISCRETE_EXPRESSION = /\(\s*([^\s:)]+)\s*(?::\s*([^\s)]+))?\s*\)/; // (width > 100) 此类表达式 | ||
// ([-+]?\d*\.?(?:\d+[a-zA-Z]*|\d+\/\d+)) => 如,100、100px、1/1 | ||
// (<|>|<=|>=)?\s*) => 如,< 、< 、>= 、<= 、 | ||
const RE_MQ_RANGE_EXPRESSION = /^\((?:([-+]?\d*\.?(?:\d+[a-zA-Z]*|\d+\s*\/\s*\d+))\s*(<|>|<=|>=)?\s*)?(aspect-ratio|resolution|width|height|device-width|device-height)(?:\s*(<|>|<=|>=)?\s*([-+]?\d*\.?(?:\d+[a-zA-Z]*|\d+\s*\/\s*\d+)))?\)$/; // 媒体查询类型 | ||
const mediaQueryTypes = ['screen']; | ||
const featureValidatorMap = { | ||
height: 'number', | ||
'min-height': 'number', | ||
'max-height': 'number', | ||
width: 'number', | ||
'min-width': 'number', | ||
'max-width': 'number', | ||
resolution: 'resolution', | ||
'min-resolution': 'resolution', | ||
'max-resolution': 'resolution', | ||
orientation: 'orientation', | ||
'aspect-ratio': 'ratio', | ||
'min-aspect-ratio': 'ratio', | ||
'max-aspect-ratio': 'ratio', | ||
'device-height': 'number', | ||
'min-device-height': 'number', | ||
'max-device-height': 'number', | ||
'device-width': 'number', | ||
'min-device-width': 'number', | ||
'max-device-width': 'number', | ||
'prefers-color-scheme': 'preferColorScheme' | ||
}; | ||
/** | ||
* 媒体特征各类型的值校验 | ||
*/ | ||
const featureValidator = { | ||
number(value) { | ||
const reg = /^(\d+)(px|dp)?$/; | ||
if (reg.test(value)) { | ||
return { | ||
value | ||
}; | ||
} | ||
return { | ||
reason: function (feature) { | ||
return 'ERROR: 媒体特征 `' + feature + '` 的值 `' + value + '` 不正确, 必须为 `数值`'; | ||
} | ||
}; | ||
}, | ||
resolution(value) { | ||
const reg = /^\d+(dpi|dppx)$/; | ||
if (reg.test(value)) { | ||
return { | ||
value | ||
}; | ||
} | ||
if (/^\d+$/.test(value)) { | ||
const newVal = value + 'dpi'; | ||
return { | ||
value: newVal, | ||
reason: function (feature) { | ||
return 'WARN: 媒体特征 `' + feature + '` 的单位为 `dpi | dppx` ' + '自动补齐为 `' + 'dpi`'; | ||
} | ||
}; | ||
} | ||
return { | ||
reason: function (feature) { | ||
return 'ERROR: 媒体特征 `' + feature + '` 的值 `' + value + '` 不正确, 必须为 `数值 + dpi | dppx`'; | ||
} | ||
}; | ||
}, | ||
orientation(value) { | ||
const reg = /^(portrait|landscape)$/; | ||
if (reg.test(value)) { | ||
return { | ||
value | ||
}; | ||
} | ||
return { | ||
reason: function (feature) { | ||
return 'WARN: 媒体特征 `' + feature + '` 的值 `' + value + '` 不正确, 必须为 `portrait | landscape`'; | ||
} | ||
}; | ||
}, | ||
ratio(value) { | ||
const reg = /^(\d+\s*\/\s*\d+|\d+\.\d+|\d+)$/; | ||
if (reg.test(value)) { | ||
return { | ||
value | ||
}; | ||
} | ||
return { | ||
reason: function (feature) { | ||
return 'WARN: 媒体特征 `' + feature + '` 的值 `' + value + '` 不正确, 必须为 `数值 | 数值/数值`'; | ||
} | ||
}; | ||
}, | ||
preferColorScheme(value) { | ||
const reg = /^(light|dark|no-preference)$/; | ||
if (reg.test(value)) { | ||
return { | ||
value | ||
}; | ||
} | ||
return { | ||
reason: function (feature) { | ||
return 'WARN: 媒体特征 `' + feature + '` 的值 `' + value + '` 不正确, 必须为 `light | dark | no-preference`'; | ||
} | ||
}; | ||
} | ||
}; | ||
/** | ||
* 解析返回媒体查询表达式 | ||
* @param {String} mediaQuery - 媒体查询表达式 | ||
* @returns {Object} object 解析后的结果 | ||
* https://github.com/ericf/css-mediaquery | ||
*/ | ||
function parseQuery(mediaQuery) { | ||
const error = []; | ||
const result = mediaQuery.split(',').map(function (query) { | ||
query = query.trim(); | ||
const captures = query.match(RE_MEDIA_QUERY); | ||
const modifier = captures[1]; | ||
const type = captures[2]; | ||
const operator = captures[3]; // 分割第一个操作符 | ||
const features = captures[4] || ''; // @media screen {} 无匹配条件 features 为undefined | ||
if (features && !features.match(RE_MEDIA_FEATURE)) { | ||
error.push('WARN: 媒体特征格式错误'); | ||
return; | ||
} | ||
const parsed = {}; // 媒体特征前面的标识符 | ||
parsed.modifier = modifier; // 媒体类型 | ||
parsed.type = type ? type.toLowerCase() : 'screen'; // Split expressions into a list. | ||
let expressions = features.match(/\([^)]+\)/g) || []; | ||
parsed.operator = operator; | ||
parsed.expressions = expressions.map(function (expression) { | ||
const combineReg = new RegExp(`\\)\\s+([a-zA-Z]+)?\\s+${expression.replace(/\(/, '\\(').replace(/\)/, '\\)')}`); | ||
const combineMatch = query.match(combineReg); // 单条媒体特征前面跟着的连接符:"or"、"and" | ||
let combineSymbol = ''; | ||
if (combineMatch) { | ||
combineSymbol = combineMatch[1]; | ||
if (combineSymbol !== 'and' && combineSymbol !== 'or') { | ||
error.push('WARN: 媒体特征连接符必须为 and 或者 or'); | ||
return; | ||
} | ||
} // 是否为: 形式 | ||
let captures = expression.match(RE_MQ_DISCRETE_EXPRESSION); | ||
if (captures) { | ||
return { | ||
type: 'discrete', | ||
combineSymbol, | ||
feature: captures[1], | ||
value: captures[2] | ||
}; | ||
} // 是否为 < > <= >= 形式 | ||
captures = expression.match(RE_MQ_RANGE_EXPRESSION); | ||
if (captures) { | ||
return { | ||
type: 'range', | ||
combineSymbol, | ||
beforeValue: captures[1], | ||
// 媒体特征左边的值 | ||
beforeSymbol: captures[2], | ||
// 媒体特征左边的运算符 | ||
feature: captures[3], | ||
// 媒体特征 | ||
afterSymbol: captures[4], | ||
// 媒体特征右边的值 | ||
afterValue: captures[5] // 媒体特征右边的运算符 | ||
}; | ||
} | ||
error.push('WARN: 无效的媒体特征表达式: "' + expression + '"'); | ||
}); | ||
return parsed; | ||
}); | ||
return { | ||
result, | ||
error | ||
}; | ||
} | ||
/** | ||
* 检验(min-width: 100px)此类表达式的值 | ||
* @param {String} exp - 媒体特征表达式 | ||
* @param {String} feature - 媒体特征 | ||
* @param {String} featureType - 媒体特征的类型 | ||
* @param {Array} logReason - log | ||
* @returns {String} 返回解析后的表达式 | ||
*/ | ||
function validateDiscreteValue(exp, feature, featureType, logReason) { | ||
const valResult = featureValidator[featureType](exp.value); | ||
const { | ||
value, | ||
reason | ||
} = valResult; | ||
let expStr = ''; | ||
if (reason) { | ||
reason && logReason.push(reason(feature)); | ||
} | ||
if (value) { | ||
expStr += `${feature}: ${value}`; | ||
} | ||
return expStr; | ||
} | ||
/** | ||
* 检验(100 < width < 200)此类表达式的值 | ||
* @param {String} exp - 媒体特征表达式 | ||
* @param {String} feature - 媒体特征 | ||
* @param {String} featureType - 媒体特征的类型 | ||
* @param {Array} logReason - log | ||
* @returns {String} 返回解析后的表达式 | ||
*/ | ||
function validateRangeValue(exp, feature, featureType, logReason) { | ||
function getValue(v) { | ||
let valResult = featureValidator[featureType](v); | ||
const { | ||
reason, | ||
value | ||
} = valResult; | ||
reason && logReason.push(reason(feature)); | ||
return value; | ||
} // 左边值,左边运算符,右边运算符,右边值 | ||
let { | ||
beforeValue, | ||
beforeSymbol, | ||
afterSymbol, | ||
afterValue | ||
} = exp; | ||
let expStr = feature; | ||
if (beforeValue) { | ||
beforeValue = getValue(beforeValue); | ||
if (beforeValue) { | ||
expStr = `${beforeValue} ${beforeSymbol} ${expStr}`; | ||
} else { | ||
return ''; | ||
} | ||
} | ||
if (afterValue) { | ||
afterValue = getValue(afterValue); | ||
if (afterValue) { | ||
expStr = `${expStr} ${afterSymbol} ${afterValue}`; | ||
} else { | ||
return ''; | ||
} | ||
} | ||
return expStr; | ||
} | ||
/** | ||
* 检验及解析media query的触发条件, 表达式一处错误,整条失效 | ||
* @param {String} condition - 查询条件 | ||
* @returns {Object} 返回解析后的查询条件 | ||
*/ | ||
function validateMediaCondition(condition) { | ||
if (condition) { | ||
const logReason = []; | ||
const ast = parseQuery(condition); | ||
if (ast.error.length > 0) { | ||
ast.error.forEach(err => { | ||
err += ', 表达式: `' + condition + '`' + ' 有错误,请检查'; | ||
logReason.push(err); | ||
}); | ||
return { | ||
reason: logReason | ||
}; | ||
} | ||
const conditionArr = []; // 当用","表示或的时候解析为数组 | ||
ast.result.forEach(_ast => { | ||
const { | ||
modifier, | ||
type, | ||
operator, | ||
expressions | ||
} = _ast; // type 需符合指定类型 | ||
if (mediaQueryTypes.indexOf(type) === -1) { | ||
logReason.push('WARN: 媒体类型 `' + type + '` 不支持'); | ||
return; | ||
} | ||
let connector = operator || ''; | ||
if (expressions.length > 0 && !operator) { | ||
connector = 'and'; | ||
} | ||
let conditionStr = modifier ? `${modifier} ${type} ${connector}` : `${type} ${connector}`; | ||
expressions.forEach(exp => { | ||
const { | ||
feature, | ||
combineSymbol, | ||
type: expType | ||
} = exp; // feature 需符合指定类型. 同时寻找对应的校验模式 | ||
const featureType = featureValidatorMap[feature]; | ||
if (!featureType) { | ||
conditionStr = ''; | ||
logReason.push('WARN: 媒体特征 `' + feature + '` 不支持'); | ||
return; | ||
} | ||
let expStr = ''; | ||
if (expType === 'discrete') { | ||
expStr = validateDiscreteValue(exp, feature, featureType, logReason); | ||
} else if (expType === 'range') { | ||
expStr = validateRangeValue(exp, feature, featureType, logReason); | ||
} | ||
if (expStr) { | ||
conditionStr += combineSymbol ? ` ${combineSymbol} (${expStr})` : ` (${expStr})`; | ||
} else { | ||
conditionStr = ''; | ||
} | ||
}); // conditionStr形如: "all and (min-height: 400px)" | ||
conditionArr.push(conditionStr); | ||
}); | ||
return { | ||
value: conditionArr.join(' or '), | ||
reason: logReason | ||
}; | ||
} | ||
return { | ||
value: '' | ||
}; | ||
} | ||
/** | ||
* 通过传入的条件查找数组里匹配的类 | ||
* @param {Object[]} mediaClasses - 媒体查询的类集合数组 | ||
* @param {String} condition - 查询条件 | ||
* @returns {Object|null} 返回匹配的类或者null | ||
*/ | ||
function findMediaClassByCondition(mediaClasses, condition) { | ||
if (condition) { | ||
let mediaCls = mediaClasses.find(cls => { | ||
return cls.condition === condition; | ||
}); | ||
return mediaCls; | ||
} | ||
return null; | ||
} | ||
/** | ||
* 生成带有媒体查询的代码 | ||
* @param {String} code - 代码 | ||
* @param {String} mediaquery - 媒体查询条件 | ||
* @returns {String} 返回已加上媒体查询的代码 | ||
*/ | ||
function wrapMediaCode(code, mediaquery) { | ||
return `@media ${mediaquery} {\n${code}\n}`; | ||
} | ||
module.exports = { | ||
validateMediaCondition, | ||
findMediaClassByCondition, | ||
wrapMediaCode | ||
}; | ||
//# sourceMappingURL=mediaquery.js.map |
@@ -1,2 +0,329 @@ | ||
"use strict";var _fs=_interopRequireDefault(require("fs")),_path=_interopRequireDefault(require("path")),_css=_interopRequireDefault(require("css")),_cssWhat=_interopRequireDefault(require("css-what")),_compilationConfig=require("@hap-toolkit/shared-utils/compilation-config"),_validator=require("./validator"),_compress=require("./compress"),_mediaquery=require("./mediaquery"),_utils=require("../utils");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}const VALID_IMPORT_FLAG="__VALID_IMPORT__",IMPORT_REG=new RegExp("@import\\s+__VALID_IMPORT__((?:['\"]([^()]+?)['\"])|(?:(?:url\\(([^()]+?)\\))))((?:\\s*)|(?:\\s+[^;]+))__VALID_IMPORT__;","g"),IMPORT_URL_REG=new RegExp("__VALID_IMPORT__(?:(?:['\"]([^()]+?)['\"])|(?:(?:url\\(([^()]+?)\\))))(\\s+[^;]+)?__VALID_IMPORT__;");function shouldAddToDependency(e,s){return(0,_validator.mightReferlocalResource)(e)&&!/^(data:|http)/.test(s)&&"string"==typeof s}function signValidCssImport(e){let s=!0;const t=_css.default.parse(e);if(t&&"stylesheet"===t.type&&t.stylesheet&&t.stylesheet.rules&&t.stylesheet.rules.length){t.stylesheet.rules.forEach(e=>{const t=e.type;"import"!==t&&"comment"!==t&&(s=!1),"import"===t&&s&&(e.import=VALID_IMPORT_FLAG+e.import+VALID_IMPORT_FLAG)}),e=_css.default.stringify(t)}return e}function processImport(e,s,t,o){let i=signValidCssImport(e);const r=i.match(IMPORT_REG);return r&&r.length>0&&(s?r.forEach(e=>{const r=e.match(IMPORT_URL_REG);if(r.length>1){const n=r[3],a=_path.default.resolve(s,r[1]||r[2]),l=_fs.default.readFileSync(a);if(l){const s=_path.default.dirname(a);let r=processImport(l.toString(),s,t,o);n&&(r=(0,_mediaquery.wrapMediaCode)(r,n)),i=i.replace(e,"\n"+r+"\n"),o.push(a)}else t.push({line:1,column:1,reason:"ERROR: 找不到文件 `"+e+"` , 导入失败"})}}):t.push({line:1,column:1,reason:"ERROR: 找不到资源路径, 无法处理@import"})),i}function processSingleClass(e,s,t,o,i,r){e.declarations.forEach((function(e){if("declaration"!==e.type)return;const s=e.property,n=e.value,a=(0,_utils.hyphenedToCamelCase)(s),l=(0,_validator.validate)(a,n,{filePath:i});l.value.forEach(e=>{(0,_utils.isValidValue)(e.v)&&(t[e.n]=e.v,shouldAddToDependency(e.n,e.v)&&r.push(e.v))}),l.log&&o.push({line:e.position.start.line,column:e.position.start.column,reason:l.log.reason})}));const n=/^[.#]?[A-Za-z0-9_\-:]+$/,a=/^([.#]?[A-Za-z0-9_-]+(\s+|\s*>\s*))+([.#]?[A-Za-z0-9_\-:]+)$/;e.selectors.forEach((function(i){const r={key:i,val:t};if(i.match(n)||i.match(a)){if(!processPseudoClass(r,o,e))return;if(!_compilationConfig.compileOptionsObject.optimizeDescMeta&&i.match(a))try{r.val=Object.assign({},r.val),r.val._meta={},r.val._meta.ruleDef=(0,_compress.compressDescSelector)((0,_cssWhat.default)(r.key))}catch(s){return void o.push({line:e.position.start.line,column:e.position.start.column,reason:"ERROR: 选择器 `"+r.key+"` 不支持"})}s[r.key]=(0,_utils.extend)({},s[r.key]||{},r.val)}else o.push({line:e.position.start.line,column:e.position.start.column,reason:"ERROR: 选择器 `"+i+"` 非法"})}))}function processMediaQueryCss(e,s,t,o,i,r){const n=(0,_mediaquery.validateMediaCondition)(e.media),a=n.value,l=n.reason;if(l&&l.length>0&&n.reason.forEach(s=>{t.push({line:e.position.start.line,column:e.position.start.column,reason:s})}),!a)return;const c=i?`${i} and ${a}`:a;e.rules.forEach(e=>{if("rule"===e.type){if(e.declarations&&e.declarations.length){let i=(0,_mediaquery.findMediaClassByCondition)(s,c);const n=!i;n&&(i={condition:c}),processSingleClass(e,i,{},t,o,r),n&&s.push(i)}}else"media"===e.type&&processMediaQueryCss(e,s,t,o,c,r)})}function processPseudoClass(e,s,t){const o=e.key.indexOf(":");if(o>-1){const i=e.key.slice(o);if(!(0,_validator.validatePseudoClass)(i))return s.push({line:t.position.start.line,column:t.position.start.column,reason:"ERROR: 不支持伪类选择器`"+i+"`"}),!1;e.key=e.key.slice(0,o);const r={};Object.keys(e.val).forEach((function(s){r[s+i]=e.val[s]})),e.val=r}return!0}module.exports={processImport:processImport,processSingleClass:processSingleClass,processMediaQueryCss:processMediaQueryCss,shouldAddToDependency:shouldAddToDependency}; | ||
/* | ||
* Copyright (C) 2017, hapjs.org. All rights reserved. | ||
*/ | ||
'use strict'; | ||
var _fs = _interopRequireDefault(require("fs")); | ||
var _path = _interopRequireDefault(require("path")); | ||
var _css = _interopRequireDefault(require("css")); | ||
var _cssWhat = _interopRequireDefault(require("css-what")); | ||
var _compilationConfig = require("@hap-toolkit/shared-utils/compilation-config"); | ||
var _validator = require("./validator"); | ||
var _compress = require("./compress"); | ||
var _mediaquery = require("./mediaquery"); | ||
var _utils = require("../utils"); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
// 有效@import的标识前后缀 | ||
const VALID_IMPORT_FLAG = '__VALID_IMPORT__'; // 匹配@import的正则 | ||
// (?:['"]([^()]+?)['"]) => "foo.css" | ||
// (?:(?:url\\(([^()]+?)\\))) => url(bar.css) | ||
// ((?:\\s*)|(?:\\s+[^;]+)) => 空格 或 除`;`之外字符 | ||
const IMPORT_REG = new RegExp(`@import\\s+${VALID_IMPORT_FLAG}((?:['"]([^()]+?)['"])|(?:(?:url\\(([^()]+?)\\))))((?:\\s*)|(?:\\s+[^;]+))${VALID_IMPORT_FLAG};`, 'g'); // 匹配@import url的正则 | ||
const IMPORT_URL_REG = new RegExp(`${VALID_IMPORT_FLAG}(?:(?:['"]([^()]+?)['"])|(?:(?:url\\(([^()]+?)\\))))(\\s+[^;]+)?${VALID_IMPORT_FLAG};`); | ||
/** | ||
* 是否将资源添加到依赖 | ||
* @param {string} name - 属性名 | ||
* @param {string} value - 属性值 | ||
* @returns {boolean} | ||
*/ | ||
function shouldAddToDependency(name, value) { | ||
return (0, _validator.mightReferlocalResource)(name) && !/^(data:|http)/.test(value) && typeof value === 'string'; | ||
} | ||
/** | ||
* 解析css中的有效的@import(只在最顶级最上面有效) | ||
* 此情况存在于未使用任何css预处理,使用less,sass等会依据自己语法解析css | ||
* @desc 给有效的@import打上标识前后缀,便于正则查找,返回新代码 | ||
* @param {String} csscode - css代码 | ||
* @returns {String} 新的css代码 | ||
*/ | ||
function signValidCssImport(csscode) { | ||
let isOnTop = true; | ||
const ast = _css.default.parse(csscode); | ||
if (ast && ast.type === 'stylesheet' && ast.stylesheet && ast.stylesheet.rules && ast.stylesheet.rules.length) { | ||
// 只需做最顶层的获取 | ||
const rules = ast.stylesheet.rules; | ||
rules.forEach(rule => { | ||
const type = rule.type; // 在import前面的只能是注释或者import | ||
if (type !== 'import' && type !== 'comment') { | ||
isOnTop = false; | ||
} | ||
if (type === 'import' && isOnTop) { | ||
// 打上标识符 | ||
rule.import = VALID_IMPORT_FLAG + rule.import + VALID_IMPORT_FLAG; | ||
} | ||
}); | ||
csscode = _css.default.stringify(ast); | ||
} | ||
return csscode; | ||
} | ||
/** | ||
* 处理@import | ||
* @desc 支持 @import '.file.css';或者 @import url(./file.css); | ||
* @param {String} csscode - css代码 | ||
* @param {String} dir - css文件所在的目录 | ||
* @param {String} log - log对象 | ||
* @param {Array} depList - 引入的CSS文件列表 | ||
* @returns {*} | ||
*/ | ||
function processImport(csscode, dir, log, depList) { | ||
// 处理@import | ||
let mergeCode = signValidCssImport(csscode); | ||
const importList = mergeCode.match(IMPORT_REG); | ||
if (importList && importList.length > 0) { | ||
if (dir) { | ||
// 读取css | ||
importList.forEach(res => { | ||
const inMatch = res.match(IMPORT_URL_REG); | ||
if (inMatch.length > 1) { | ||
// 媒体查询条件,这里不做检验 | ||
const importMediaQuery = inMatch[3]; | ||
const importPath = _path.default.resolve(dir, inMatch[1] || inMatch[2]); | ||
const importCode = _fs.default.readFileSync(importPath); | ||
if (importCode) { | ||
const importDir = _path.default.dirname(importPath); // 获取import文件里面的内容 | ||
let importContent = processImport(importCode.toString(), importDir, log, depList); | ||
if (importMediaQuery) { | ||
importContent = (0, _mediaquery.wrapMediaCode)(importContent, importMediaQuery); | ||
} | ||
mergeCode = mergeCode.replace(res, '\n' + importContent + '\n'); | ||
depList.push(importPath); | ||
} else { | ||
log.push({ | ||
line: 1, | ||
column: 1, | ||
reason: 'ERROR: 找不到文件 `' + res + '` , 导入失败' | ||
}); | ||
} | ||
} | ||
}); | ||
} else { | ||
log.push({ | ||
line: 1, | ||
column: 1, | ||
reason: 'ERROR: 找不到资源路径, 无法处理@import' | ||
}); | ||
} | ||
} | ||
return mergeCode; | ||
} | ||
/** | ||
* 解析单个class为JSON对象 | ||
* @param {Object} rule - ast的rule字段 | ||
* @param {Object} jsonStyle - 当前页面所有css解析结果 | ||
* @param {Object} ruleResult - 当前类的解析结果 | ||
* @param {Array} log - log对象 | ||
* @param {String} filePath - 文件路径 | ||
* @param {Array} depFiles - 使用到的资源集合 | ||
*/ | ||
function processSingleClass(rule, jsonStyle, ruleResult, log, filePath, depFiles) { | ||
rule.declarations.forEach(function (declaration) { | ||
const subType = declaration.type; // 只考虑声明类型 | ||
if (subType !== 'declaration') { | ||
return; | ||
} // 样式的属性和值 | ||
const name = declaration.property; | ||
const value = declaration.value; // 校验属性值 | ||
const camelCasedName = (0, _utils.hyphenedToCamelCase)(name); | ||
const subResult = (0, _validator.validate)(camelCasedName, value, { | ||
filePath | ||
}); | ||
subResult.value.forEach(item => { | ||
// 如果校验成功,则保存转换后的属性值 | ||
if ((0, _utils.isValidValue)(item.v)) { | ||
ruleResult[item.n] = item.v; | ||
if (shouldAddToDependency(item.n, item.v)) { | ||
depFiles.push(item.v); | ||
} | ||
} | ||
}); | ||
if (subResult.log) { | ||
log.push({ | ||
line: declaration.position.start.line, | ||
column: declaration.position.start.column, | ||
reason: subResult.log.reason | ||
}); | ||
} | ||
}); // 单个选择器:tag, class, id | ||
const REGEXP_SEL = /^[.#]?[A-Za-z0-9_\-:]+$/; // 复合选择器:tag, class, id后代选择 | ||
const REGEXP_SEL_COMPLEX = /^([.#]?[A-Za-z0-9_-]+(\s+|\s*>\s*))+([.#]?[A-Za-z0-9_\-:]+)$/; | ||
rule.selectors.forEach(function (selector) { | ||
// 定义 | ||
const hash = { | ||
key: selector, | ||
val: ruleResult | ||
}; | ||
if (selector.match(REGEXP_SEL) || selector.match(REGEXP_SEL_COMPLEX)) { | ||
// 处理伪类 | ||
const isValid = processPseudoClass(hash, log, rule); | ||
if (!isValid) { | ||
return; | ||
} // 是否编译复合选择器,生成_meta信息 | ||
if (!_compilationConfig.compileOptionsObject.optimizeDescMeta && selector.match(REGEXP_SEL_COMPLEX)) { | ||
try { | ||
hash.val = Object.assign({}, hash.val); | ||
hash.val._meta = {}; | ||
hash.val._meta.ruleDef = (0, _compress.compressDescSelector)((0, _cssWhat.default)(hash.key)); | ||
} catch (err) { | ||
log.push({ | ||
line: rule.position.start.line, | ||
column: rule.position.start.column, | ||
reason: 'ERROR: 选择器 `' + hash.key + '` 不支持' | ||
}); | ||
return; | ||
} | ||
} // 如果样式已经存在,则叠加,覆盖同名属性 | ||
jsonStyle[hash.key] = (0, _utils.extend)({}, jsonStyle[hash.key] || {}, hash.val); | ||
} else { | ||
log.push({ | ||
line: rule.position.start.line, | ||
column: rule.position.start.column, | ||
reason: 'ERROR: 选择器 `' + selector + '` 非法' | ||
}); | ||
} | ||
}); | ||
} | ||
/** | ||
* 解析media query为JSON对象 | ||
* @param {Object} rule - ast的rule字段 | ||
* @param {Object} jsonStyleMedia - media部分所有css解析结果 | ||
* @param {Array} log - log对象 | ||
* @param {String} filePath - 文件路径 | ||
* @param {String} upperCondition - 上级的查询条件,若本身为顶级则无此值 | ||
* @param {Array} depFiles - 使用到的资源集合 | ||
*/ | ||
function processMediaQueryCss(rule, jsonStyleMedia, log, filePath, upperCondition, depFiles) { | ||
const validateResult = (0, _mediaquery.validateMediaCondition)(rule.media); | ||
const currentCondition = validateResult.value; | ||
const errorReason = validateResult.reason; | ||
if (errorReason && errorReason.length > 0) { | ||
validateResult.reason.forEach(reason => { | ||
log.push({ | ||
line: rule.position.start.line, | ||
column: rule.position.start.column, | ||
reason | ||
}); | ||
}); | ||
} // 若无查询条件,判断为不合法,数据无效 | ||
if (!currentCondition) { | ||
return; | ||
} // 嵌套层级的条件用and连接起来 | ||
const condition = upperCondition ? `${upperCondition} and ${currentCondition}` : currentCondition; | ||
rule.rules.forEach(_rule => { | ||
if (_rule.type === 'rule') { | ||
if (_rule.declarations && _rule.declarations.length) { | ||
let jsonClassMedia = (0, _mediaquery.findMediaClassByCondition)(jsonStyleMedia, condition); | ||
const isNewCondition = !jsonClassMedia; | ||
const ruleResult = {}; | ||
if (isNewCondition) { | ||
jsonClassMedia = { | ||
condition | ||
}; | ||
} | ||
processSingleClass(_rule, jsonClassMedia, ruleResult, log, filePath, depFiles); // 新的查询条件class需要push | ||
isNewCondition && jsonStyleMedia.push(jsonClassMedia); | ||
} | ||
} else if (_rule.type === 'media') { | ||
processMediaQueryCss(_rule, jsonStyleMedia, log, filePath, condition, depFiles); | ||
} | ||
}); | ||
} | ||
/** | ||
* 处理伪类,将伪类写到Style的每个值上 | ||
* @param hash | ||
* @return {boolean} | ||
*/ | ||
function processPseudoClass(hash, log, rule) { | ||
// 处理伪选择器 | ||
const pseudoIndex = hash.key.indexOf(':'); | ||
if (pseudoIndex > -1) { | ||
const pseudoCls = hash.key.slice(pseudoIndex); | ||
if (!(0, _validator.validatePseudoClass)(pseudoCls)) { | ||
log.push({ | ||
line: rule.position.start.line, | ||
column: rule.position.start.column, | ||
reason: 'ERROR: 不支持伪类选择器`' + pseudoCls + '`' | ||
}); | ||
return false; | ||
} | ||
hash.key = hash.key.slice(0, pseudoIndex); | ||
const pseudoRuleResult = {}; // 将伪选择器text:active中的样式color属性名转换为color:active | ||
Object.keys(hash.val).forEach(function (prop) { | ||
pseudoRuleResult[prop + pseudoCls] = hash.val[prop]; | ||
}); | ||
hash.val = pseudoRuleResult; | ||
} | ||
return true; | ||
} | ||
module.exports = { | ||
processImport, | ||
processSingleClass, | ||
processMediaQueryCss, | ||
shouldAddToDependency | ||
}; | ||
//# sourceMappingURL=process.js.map |
@@ -1,2 +0,45 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.compressTemplateAttr=compressTemplateAttr;const templateAttrMap={type:"t",attr:"a",classList:"cL",style:"s",events:"e",children:"c"};function compressTemplateAttr(t){if(t){if(t.children)for(let e=0,r=t.children.length;e<r;e++){compressTemplateAttr(t.children[e])}for(const e in t)if(templateAttrMap[e]){t[templateAttrMap[e]]=t[e],delete t[e]}}} | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.compressTemplateAttr = compressTemplateAttr; | ||
/* | ||
* Copyright (C) 2017, hapjs.org. All rights reserved. | ||
*/ | ||
// 模板属性名全写-缩写对照表 | ||
const templateAttrMap = { | ||
type: 't', | ||
attr: 'a', | ||
classList: 'cL', | ||
style: 's', | ||
events: 'e', | ||
children: 'c' | ||
}; | ||
/** | ||
* 压缩模板属性名 | ||
* @param jsonObject | ||
*/ | ||
function compressTemplateAttr(jsonObject) { | ||
if (!jsonObject) { | ||
return; | ||
} | ||
if (jsonObject.children) { | ||
for (let i = 0, len = jsonObject.children.length; i < len; i++) { | ||
const child = jsonObject.children[i]; | ||
compressTemplateAttr(child); | ||
} | ||
} | ||
for (const k in jsonObject) { | ||
if (templateAttrMap[k]) { | ||
const kAbbr = templateAttrMap[k]; | ||
jsonObject[kAbbr] = jsonObject[k]; | ||
delete jsonObject[k]; | ||
} | ||
} | ||
} | ||
//# sourceMappingURL=compress.js.map |
@@ -1,2 +0,94 @@ | ||
"use strict";var _expression=_interopRequireDefault(require("./lib/expression")),_text=_interopRequireDefault(require("./lib/text")),_filterParser=_interopRequireDefault(require("./lib/filter-parser"));function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function trimhtml(e){if((e=e.replace(/^\s\s+/," ")).length<=1)return e;const t=" "===e.charAt(0)?1:0;return e.length-(e=e.trim()).length-t>=1&&(e+=" "),(t?" ":"")+e}function transExpr(expContent,toFunc){let ret;const trimExpContent=expContent.trim();if(_text.default.isExpr(trimExpContent)){ret=[];const tokens=_text.default.parseText(trimExpContent),isSingle=1===tokens.length;if(tokens.forEach((function(e){if(e.tag){let t=_expression.default.parseExpression((0,_filterParser.default)(e.value));isSingle||(t="("+t+")"),ret.push(t)}else ret.push("'"+e.value+"'")})),isSingle||ret.unshift("''"),ret=ret.join(" + "),!1!==toFunc)try{ret=eval("(function () {return "+ret+"})")}catch(e){throw e.isExpressionError=!0,e.expression=trimExpContent,e}}else ret=trimhtml(expContent);return ret}transExpr.isExpr=_text.default.isExpr,transExpr.singleExpr=_text.default.singleExpr,transExpr.removeExprffix=_text.default.removeExprffix,transExpr.addExprffix=_text.default.addExprffix,module.exports=transExpr; | ||
"use strict"; | ||
var _expression = _interopRequireDefault(require("./lib/expression")); | ||
var _text = _interopRequireDefault(require("./lib/text")); | ||
var _filterParser = _interopRequireDefault(require("./lib/filter-parser")); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/* | ||
* Copyright (C) 2017, hapjs.org. All rights reserved. | ||
*/ | ||
// 去除字符串头部空格或指定字符 | ||
function trimhtml(str) { | ||
// 2个空格以上, 仅保留一个空格 | ||
str = str.replace(/^\s\s+/, ' '); | ||
if (str.length <= 1) { | ||
return str; | ||
} | ||
const startSpace = str.charAt(0) === ' ' ? 1 : 0; | ||
const oldLength = str.length; | ||
str = str.trim(); // 尾部多余1个空格 | ||
if (oldLength - str.length - startSpace >= 1) { | ||
str = str + ' '; | ||
} | ||
return (startSpace ? ' ' : '') + str; | ||
} | ||
/** | ||
* 表达式转换 | ||
* @param expContent | ||
* @param toFunc | ||
* @returns {*} | ||
*/ | ||
function transExpr(expContent, toFunc) { | ||
let ret; | ||
const trimExpContent = expContent.trim(); | ||
if (!_text.default.isExpr(trimExpContent)) { | ||
ret = trimhtml(expContent); | ||
} else { | ||
ret = []; | ||
const tokens = _text.default.parseText(trimExpContent); | ||
const isSingle = tokens.length === 1; | ||
tokens.forEach(function (token) { | ||
if (token.tag) { | ||
let res = _expression.default.parseExpression((0, _filterParser.default)(token.value)); | ||
if (!isSingle) { | ||
res = '(' + res + ')'; | ||
} | ||
ret.push(res); | ||
} else { | ||
ret.push("'" + token.value + "'"); | ||
} | ||
}); // 确保多个插值表达式相邻时,是字符串拼接,而不是数值相加,例如:{{number1}}{{number2}} | ||
if (!isSingle) { | ||
ret.unshift("''"); | ||
} | ||
ret = ret.join(' + '); | ||
if (toFunc !== false) { | ||
try { | ||
/* eslint-disable no-eval */ | ||
ret = eval('(function () {return ' + ret + '})'); | ||
/* eslint-enable no-eval */ | ||
} catch (err) { | ||
err.isExpressionError = true; | ||
err.expression = trimExpContent; | ||
throw err; | ||
} | ||
} | ||
} | ||
return ret; | ||
} | ||
transExpr.isExpr = _text.default.isExpr; | ||
transExpr.singleExpr = _text.default.singleExpr; | ||
transExpr.removeExprffix = _text.default.removeExprffix; | ||
transExpr.addExprffix = _text.default.addExprffix; | ||
module.exports = transExpr; | ||
//# sourceMappingURL=exp.js.map |
@@ -1,2 +0,491 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=void 0;var _parse=_interopRequireDefault(require("parse5")),_parser=_interopRequireDefault(require("parse5/lib/parser")),_tokenizer=_interopRequireDefault(require("parse5/lib/tokenizer")),_sharedUtils=require("@hap-toolkit/shared-utils"),_compilationConfig=require("@hap-toolkit/shared-utils/compilation-config"),_validator=_interopRequireDefault(require("./validator")),_compress=require("./compress");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function calcSubTextNodesNum(e,t){let a=0;if(_validator.default.isSupportSpan(e)){const l=_validator.default.getTagChildren(e);t.forEach((function(e){("#text"===e.nodeName&&e.value.trim()||l.indexOf(e.nodeName)>-1)&&++a}))}return a}function traverse(e,t,a,l,o){_validator.default.checkTagName(e,t,o),(e.attrs||[]).forEach((function(r){let i=r.name;const n=i.match(/^:+/);n&&(i=i.slice(n.length));const s=r.value;let c={line:1,column:1};switch(e.__location&&(c={line:e.__location.line,column:e.__location.col}),i){case"id":_validator.default.checkId(s,t),_validator.default.checkAttr(i,s,t,e.tagName,c);break;case"class":_validator.default.checkClass(s,t);break;case"style":_validator.default.checkStyle(s,t,c,o);break;case"if":e._isroot||_validator.default.checkIf(s,t,!1,c,l);break;case"is":_validator.default.checkIs(s,t,c);break;case"else":e._isroot||a&&a.__cond__&&_validator.default.checkElse(a.__cond__,t,c,l);break;case"elif":e._isroot||a&&a.__cond__&&(e.__cond__=_validator.default.checkElif(s,a.__cond__,t,c,l));break;case"for":e._isroot||_validator.default.checkFor(s,t,c);break;case"tree":_validator.default.checkBuild("tree",t);break;case"model":_validator.default.checkModel(s,t,e,o);break;default:i.match(/^(on|@)/)?_validator.default.checkEvent(i,s,t):_validator.default.checkAttr(i,s,t,e.tagName,c,o)}}));const r=t.result,i=e.childNodes;if(i&&i.length){let a,l;const n=[],s=calcSubTextNodesNum(r.type,i);i.forEach((function(c,u){u>0&&(l=i[u-1],l.nodeName.match(/^#/)||(a=l,a.__cond__||a.attrs&&a.attrs.forEach((function(e){"if"!==e.name&&"elif"!==e.name||(a.__cond__=e.value)}))));const _={};if(c.nodeName.match(/^#/)){if("#text"===c.nodeName&&c.value.trim()){l&&_validator.default.isSupportedSelfClosing(l.nodeName)||_validator.default.isNotTextContentAtomic(e.tagName)&&t.log.push({line:e.__location.line,column:e.__location.col,reason:`Warn: 组件 ${e.tagName} 不支持文本内容作为字节点`});const a=_validator.default.isSupportSpan(e.tagName)&&s>=2,i=o.importNames&&o.importNames.indexOf(e.tagName)>-1;if((a||i)&&(_.type="span",t.result=_,r.children=r.children||[],r.children.push(_),t.log.push({line:e.__location.line,column:e.__location.col,reason:`WARNING: 文本和span标签并行存在,编译时将文本节点:"${c.value}" 用span包裹(关于span嵌套的使用,请参考官方文档"span嵌套")`}),_validator.default.checkAttr("value",c.value,t)),"option"===e.tagName){const e=t.result;return t.result=r,r.attr.hasOwnProperty("value")||_validator.default.checkAttr("value",c.value,t),_validator.default.checkAttr("content",c.value,t),void(t.result=e)}if(_validator.default.isSupportSpan(e.tagName)&&1===s||_validator.default.isTextContentAtomic(e.tagName)){const e=t.result;t.result=r,_validator.default.checkAttr("value",c.value,t),t.result=e}}}else t.result=_,r.children=r.children||[],r.children.push(_),traverse(c,t,a,n,o)})),r.children&&0===r.children.length&&(r.children=void 0)}t.result=r}function initParser(e,t,a){const l=new _parser.default(t),o=l._appendElement,r=l._insertElement;function i(e){if(!e.tagName)return;const t=e.tagName.toLowerCase(),o=e.selfClosing,r=_validator.default.isSupportedSelfClosing(t),i=_validator.default.isEmptyElement(t);if(l.__m.tagName&&!l.__m.selfClosing&&!i){const t=String(e.location.line)+":"+String(e.location.col);(!r||t!==l.__m.pos&&e.type===_tokenizer.default.START_TAG_TOKEN)&&(_sharedUtils.colorconsole.warn(`${l.__m.tagName}标签要闭合,请遵循XML规范 ${a}@${l.__m.pos}`),l.__m={})}r&&(e.type!==_tokenizer.default.START_TAG_TOKEN||o||(l.__m.tagName=t,l.__m.selfClosing=!1,l.__m.pos=String(e.location.line)+":"+String(e.location.col)),e.type===_tokenizer.default.END_TAG_TOKEN&&t===l.__m.tagName&&(l.__m.selfClosing=!0))}function n(e){e.tagName&&"plaintext"===e.tagName.toLowerCase()&&_sharedUtils.colorconsole.error(`${a} : 禁止使用 plaintext 标签@${e.location.line}:${e.location.col}`)}return l._insertElement=function(e){const t=(e.tagName||"").toLowerCase(),l=e.selfClosing,i=_validator.default.isSupportedSelfClosing(t);l&&!i&&_sharedUtils.colorconsole.error(`${t}标签,禁止使用自闭合 ${a}@${e.location.line}:${e.location.col}`),i||l&&t?o.apply(this,arguments):r.apply(this,arguments)},l.__m={},l._runParsingLoop=function(e){for(;!this.stopped;){this._setupTokenizerCDATAMode();const t=this.tokenizer.getNextToken();if(i(t),n(t),t.type===_tokenizer.default.HIBERNATION_TOKEN)break;if(this.skipNextNewLine&&(this.skipNextNewLine=!1,t.type===_tokenizer.default.WHITESPACE_CHARACTER_TOKEN&&"\n"===t.chars[0])){if(1===t.chars.length)continue;t.chars=t.chars.substr(1)}if(this._processInputToken(t),e&&this.pendingScript)break}},l.parseFragment(e)}function parse(e,t){const a=initParser(e,{treeAdapter:_parse.default.treeAdapters.default,locationInfo:!0},t.filePath),l={result:{},log:[],depFiles:[]};if(!a||!a.childNodes)return l.log.push({reason:"ERROR: <template>解析失败",line:1,column:1}),{jsonTemplate:l.result,log:l.log,depFiles:l.depFiles};const o=a.childNodes.filter((function(e){return"#"!==e.nodeName.charAt(0)}));if(0===o.length)return l.log.push({reason:"ERROR: 没有合法的根节点",line:1,column:1}),{jsonTemplate:l.result,log:l.log,depFiles:l.depFiles};if(o.length>1)return l.log.push({reason:"ERROR: <template>节点里只能有一个根节点",line:1,column:1}),{jsonTemplate:l.result,log:l.log,depFiles:l.depFiles};const r=o[0];r._isroot=!0;try{traverse(r,l,null,null,t)}catch(e){if(!e.isExpressionError)throw e;l.log.push({reason:`ERROR: 表达式解析失败 ${e.message}\n\n> ${e.expression}\n\nat ${t.filePath}`})}return _compilationConfig.compileOptionsObject.optimizeTemplateAttr&&(0,_compress.compressTemplateAttr)(l.result),{jsonTemplate:l.result,log:l.log,depFiles:l.depFiles}}var _default={parse:parse};exports.default=_default; | ||
/* | ||
* Copyright (C) 2017, hapjs.org. All rights reserved. | ||
*/ | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = void 0; | ||
var _parse = _interopRequireDefault(require("parse5")); | ||
var _parser = _interopRequireDefault(require("parse5/lib/parser")); | ||
var _tokenizer = _interopRequireDefault(require("parse5/lib/tokenizer")); | ||
var _sharedUtils = require("@hap-toolkit/shared-utils"); | ||
var _compilationConfig = require("@hap-toolkit/shared-utils/compilation-config"); | ||
var _validator = _interopRequireDefault(require("./validator")); | ||
var _compress = require("./compress"); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/** | ||
* 计算支持span的节点的有效子节点数 | ||
* @param tagName - 标签名 | ||
* @param childNodes - 标签的子节点 | ||
* @returns {number} | ||
*/ | ||
function calcSubTextNodesNum(tagName, childNodes) { | ||
let subTextNodesNum = 0; | ||
if (_validator.default.isSupportSpan(tagName)) { | ||
const tagChildren = _validator.default.getTagChildren(tagName); | ||
childNodes.forEach(function (child) { | ||
if (child.nodeName === '#text' && child.value.trim() || tagChildren.indexOf(child.nodeName) > -1) { | ||
++subTextNodesNum; | ||
} | ||
}); | ||
} | ||
return subTextNodesNum; | ||
} | ||
/** | ||
* 遍历模板,检查标签、属性和子节点是否合法 | ||
* @param {Object} node - 页面template模块的编译后的树对象 | ||
* @param {Object} output | ||
* @param {Object} output.result - 结果收集 | ||
* @param {Array} output.log - 日志收集 | ||
* @param {Object} previousNode - 前一个节点 | ||
* @param {Object} conditionList - 条件列表 | ||
* @param {Object} options | ||
* @param {String} options.uxType - 文件类型 | ||
* @param {String} options.filePath - 当前执行文件的绝对路径 | ||
* @param {Array} options.importNames - 页面引入的自定义组件的name | ||
*/ | ||
function traverse(node, output, previousNode, conditionList, options) { | ||
// 检查标签名 | ||
_validator.default.checkTagName(node, output, options); // 处理标签属性 | ||
// attrs: id/class/style/if/for/event/attr/show/model | ||
const attrs = node.attrs || []; | ||
attrs.forEach(function switchAttr(attr) { | ||
let name = attr.name; | ||
const inMatch = name.match(/^:+/); | ||
if (inMatch) { | ||
name = name.slice(inMatch.length); | ||
} | ||
const value = attr.value; // 获取位置信息 | ||
let locationInfo = { | ||
line: 1, | ||
column: 1 | ||
}; | ||
if (node.__location) { | ||
locationInfo = { | ||
line: node.__location.line, | ||
column: node.__location.col | ||
}; | ||
} | ||
switch (name) { | ||
case 'id': | ||
// 保留checkId为兼容原有:新打的RPK包兼容原来的APK平台 | ||
_validator.default.checkId(value, output); | ||
_validator.default.checkAttr(name, value, output, node.tagName, locationInfo); | ||
break; | ||
case 'class': | ||
_validator.default.checkClass(value, output); | ||
break; | ||
case 'style': | ||
_validator.default.checkStyle(value, output, locationInfo, options); | ||
break; | ||
case 'if': | ||
if (!node._isroot) { | ||
_validator.default.checkIf(value, output, false, locationInfo, conditionList); | ||
} | ||
break; | ||
case 'is': | ||
_validator.default.checkIs(value, output, locationInfo); | ||
break; | ||
case 'else': | ||
if (!node._isroot) { | ||
if (previousNode && previousNode.__cond__) { | ||
_validator.default.checkElse(previousNode.__cond__, output, locationInfo, conditionList); | ||
} | ||
} | ||
break; | ||
case 'elif': | ||
if (!node._isroot) { | ||
if (previousNode && previousNode.__cond__) { | ||
node.__cond__ = _validator.default.checkElif(value, previousNode.__cond__, output, locationInfo, conditionList); | ||
} | ||
} | ||
break; | ||
case 'for': | ||
if (!node._isroot) { | ||
_validator.default.checkFor(value, output, locationInfo); | ||
} | ||
break; | ||
case 'tree': | ||
_validator.default.checkBuild('tree', output); | ||
break; | ||
case 'model': | ||
_validator.default.checkModel(value, output, node, options); | ||
break; | ||
default: | ||
if (name.match(/^(on|@)/)) { | ||
// 事件以on或@开头 | ||
_validator.default.checkEvent(name, value, output); | ||
} else { | ||
// 其余为普通属性 | ||
_validator.default.checkAttr(name, value, output, node.tagName, locationInfo, options); | ||
} | ||
} | ||
}); // 处理子节点 | ||
const originResult = output.result; | ||
const childNodes = node.childNodes; | ||
if (childNodes && childNodes.length) { | ||
let previous; | ||
let preNode; | ||
const curNodeCondList = []; // 支持span的节点的有效子节点数 | ||
const subTextNodesNum = calcSubTextNodesNum(originResult.type, childNodes); | ||
childNodes.forEach(function (child, i) { | ||
if (i > 0) { | ||
preNode = childNodes[i - 1]; | ||
if (!preNode.nodeName.match(/^#/)) { | ||
previous = preNode; | ||
if (!previous.__cond__) { | ||
previous.attrs && previous.attrs.forEach(function (attr) { | ||
if (attr.name === 'if' || attr.name === 'elif') { | ||
previous.__cond__ = attr.value; | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
const childResult = {}; | ||
if (child.nodeName.match(/^#/)) { | ||
// 处理#text节点内容 | ||
if (child.nodeName === '#text' && child.value.trim()) { | ||
// 兄弟节点不为自闭合标签且非文本标签非原子组件 | ||
if (!preNode || !_validator.default.isSupportedSelfClosing(preNode.nodeName)) { | ||
if (_validator.default.isNotTextContentAtomic(node.tagName)) { | ||
output.log.push({ | ||
line: node.__location.line, | ||
column: node.__location.col, | ||
reason: `Warn: 组件 ${node.tagName} 不支持文本内容作为字节点` | ||
}); | ||
} | ||
} // 文本节点使用span包裹: | ||
// 1. 父节点支持span, 且有效子节点数不少于2 | ||
const useSpanForSupportNode = _validator.default.isSupportSpan(node.tagName) && subTextNodesNum >= 2; // 2. 自定义组件中嵌入文本,替换内部slot节点 | ||
const useSpanForCustomSlot = options.importNames && options.importNames.indexOf(node.tagName) > -1; | ||
if (useSpanForSupportNode || useSpanForCustomSlot) { | ||
childResult.type = 'span'; | ||
output.result = childResult; | ||
originResult.children = originResult.children || []; | ||
originResult.children.push(childResult); | ||
output.log.push({ | ||
line: node.__location.line, | ||
column: node.__location.col, | ||
reason: `WARNING: 文本和span标签并行存在,编译时将文本节点:"${child.value}" 用span包裹(关于span嵌套的使用,请参考官方文档"span嵌套")` | ||
}); | ||
_validator.default.checkAttr('value', child.value, output); | ||
} // 如果父节点是option, 处理value和content属性 | ||
if (node.tagName === 'option') { | ||
const tempResult = output.result; | ||
output.result = originResult; | ||
if (!originResult.attr.hasOwnProperty('value')) { | ||
_validator.default.checkAttr('value', child.value, output); | ||
} | ||
_validator.default.checkAttr('content', child.value, output); | ||
output.result = tempResult; | ||
return; | ||
} // 父节点支持span,且有且仅有一个有效子节点,或父节点为允许文本内容的原子节点,直接设置value值 | ||
if (_validator.default.isSupportSpan(node.tagName) && subTextNodesNum === 1 || _validator.default.isTextContentAtomic(node.tagName)) { | ||
const tempResult = output.result; // 备份当前result | ||
output.result = originResult; | ||
_validator.default.checkAttr('value', child.value, output); | ||
output.result = tempResult; | ||
} | ||
} | ||
return; | ||
} | ||
output.result = childResult; | ||
originResult.children = originResult.children || []; | ||
originResult.children.push(childResult); | ||
traverse(child, output, previous, curNodeCondList, options); | ||
}); // 无孩子 | ||
if (originResult.children && originResult.children.length === 0) { | ||
originResult.children = undefined; | ||
} | ||
} | ||
output.result = originResult; | ||
} | ||
/** | ||
* 对模板解析器的配置初始化 | ||
* @param {String} code - 代码内容 | ||
* @param {Object} options - 解析选项 | ||
* @param {String} filePath - code 文件路径 | ||
*/ | ||
function initParser(code, options, filePath) { | ||
const parser = new _parser.default(options); | ||
const oldAppendElement = parser._appendElement; | ||
const oldInsertElement = parser._insertElement; // 支持自闭合标签 | ||
parser._insertElement = function (token) { | ||
const tagName = (token.tagName || '').toLowerCase(); | ||
const selfClosing = token.selfClosing; // Fixed 对不允许自闭合的标签进行提示 | ||
const selfClosable = _validator.default.isSupportedSelfClosing(tagName); | ||
if (selfClosing && !selfClosable) { | ||
_sharedUtils.colorconsole.error(`${tagName}标签,禁止使用自闭合 ${filePath}@${token.location.line}:${token.location.col}`); | ||
} | ||
if (selfClosable || selfClosing && tagName) { | ||
oldAppendElement.apply(this, arguments); | ||
} else { | ||
oldInsertElement.apply(this, arguments); | ||
} | ||
}; | ||
parser.__m = {}; // 对标签进行xml规范检查 | ||
function checkToken(token) { | ||
if (!token.tagName) { | ||
return; | ||
} | ||
const tagName = token.tagName.toLowerCase(); | ||
const selfClosing = token.selfClosing; | ||
const selfClosable = _validator.default.isSupportedSelfClosing(tagName); | ||
const empty = _validator.default.isEmptyElement(tagName); | ||
if (parser.__m['tagName'] && !parser.__m['selfClosing'] && !empty) { | ||
const pos = String(token.location.line) + ':' + String(token.location.col); | ||
if (!selfClosable || pos !== parser.__m['pos'] && token.type === _tokenizer.default.START_TAG_TOKEN) { | ||
_sharedUtils.colorconsole.warn(`${parser.__m['tagName']}标签要闭合,请遵循XML规范 ${filePath}@${parser.__m['pos']}`); | ||
parser.__m = {}; | ||
} | ||
} | ||
if (selfClosable) { | ||
if (token.type === _tokenizer.default.START_TAG_TOKEN && !selfClosing) { | ||
parser.__m['tagName'] = tagName; | ||
parser.__m['selfClosing'] = false; | ||
parser.__m['pos'] = String(token.location.line) + ':' + String(token.location.col); | ||
} | ||
if (token.type === _tokenizer.default.END_TAG_TOKEN && tagName === parser.__m['tagName']) { | ||
parser.__m['selfClosing'] = true; | ||
} | ||
} | ||
} | ||
function checkPlainText(token) { | ||
if (!token.tagName) { | ||
return; | ||
} | ||
if (token.tagName.toLowerCase() === 'plaintext') { | ||
_sharedUtils.colorconsole.error(`${filePath} : 禁止使用 plaintext 标签@${token.location.line}:${token.location.col}`); | ||
} | ||
} | ||
parser._runParsingLoop = function (scriptHandler) { | ||
while (!this.stopped) { | ||
this._setupTokenizerCDATAMode(); | ||
const token = this.tokenizer.getNextToken(); | ||
checkToken(token); | ||
checkPlainText(token); | ||
if (token.type === _tokenizer.default.HIBERNATION_TOKEN) { | ||
break; | ||
} | ||
if (this.skipNextNewLine) { | ||
this.skipNextNewLine = false; | ||
if (token.type === _tokenizer.default.WHITESPACE_CHARACTER_TOKEN && token.chars[0] === '\n') { | ||
if (token.chars.length === 1) { | ||
continue; | ||
} | ||
token.chars = token.chars.substr(1); | ||
} | ||
} | ||
this._processInputToken(token); | ||
if (scriptHandler && this.pendingScript) { | ||
break; | ||
} | ||
} | ||
}; | ||
return parser.parseFragment(code); | ||
} | ||
/** | ||
* 解析<template> | ||
* @param {String} source - 源码 | ||
* @param {Object} options | ||
* @param {String} options.filePath - 文件绝对路径 | ||
* @returns {{jsonTemplate: ({}|result), log: Array}} | ||
*/ | ||
function parse(source, options) { | ||
const doc = initParser(source, { | ||
treeAdapter: _parse.default.treeAdapters.default, | ||
locationInfo: true | ||
}, options.filePath); | ||
const output = { | ||
result: {}, | ||
log: [], | ||
depFiles: [] | ||
}; // 模板为空或解析失败 | ||
/* istanbul ignore if */ | ||
if (!doc || !doc.childNodes) { | ||
output.log.push({ | ||
reason: 'ERROR: <template>解析失败', | ||
line: 1, | ||
column: 1 | ||
}); | ||
return { | ||
jsonTemplate: output.result, | ||
log: output.log, | ||
depFiles: output.depFiles | ||
}; | ||
} // 过滤合法标签名,如果标签名以#开头,则代表节点被注释或者#text, #comment | ||
const rootElements = doc.childNodes.filter(function (child) { | ||
return child.nodeName.charAt(0) !== '#'; | ||
}); // 合法节点数目只能等于1,0表示没有根容器 | ||
/* istanbul ignore if */ | ||
if (rootElements.length === 0) { | ||
output.log.push({ | ||
reason: 'ERROR: 没有合法的根节点', | ||
line: 1, | ||
column: 1 | ||
}); | ||
return { | ||
jsonTemplate: output.result, | ||
log: output.log, | ||
depFiles: output.depFiles | ||
}; | ||
} // 合法节点数目只能等于1,否则模板存在多个根容器 | ||
if (rootElements.length > 1) { | ||
output.log.push({ | ||
reason: 'ERROR: <template>节点里只能有一个根节点', | ||
line: 1, | ||
column: 1 | ||
}); | ||
return { | ||
jsonTemplate: output.result, | ||
log: output.log, | ||
depFiles: output.depFiles | ||
}; | ||
} // 从根目录开始, 遍历树 | ||
const current = rootElements[0]; | ||
current._isroot = true; | ||
try { | ||
traverse(current, output, null, null, options); | ||
} catch (err) { | ||
if (err.isExpressionError) { | ||
output.log.push({ | ||
reason: `ERROR: 表达式解析失败 ${err.message}\n\n> ${err.expression}\n\nat ${options.filePath}` | ||
}); | ||
} else { | ||
throw err; | ||
} | ||
} // 检查是否包含ERROR记录 | ||
// if (output.log.length > 0) { | ||
// } | ||
// 是否压缩模板属性名 | ||
if (_compilationConfig.compileOptionsObject.optimizeTemplateAttr) { | ||
(0, _compress.compressTemplateAttr)(output.result); | ||
} // 返回结果 | ||
return { | ||
jsonTemplate: output.result, | ||
log: output.log, | ||
depFiles: output.depFiles | ||
}; | ||
} | ||
var _default = { | ||
parse | ||
}; | ||
exports.default = _default; | ||
//# sourceMappingURL=index.js.map |
@@ -1,2 +0,149 @@ | ||
"use strict";const allowedKeywords="Infinity,undefined,NaN,null,isFinite,isNaN,true,false,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,this,require",allowedKeywordsRE=new RegExp("^("+allowedKeywords.replace(/,/g,"\\b|")+"\\b)"),improperKeywords="break,case,class,catch,const,continue,debugger,default,delete,do,else,export,extends,finally,for,function,if,import,in,instanceof,let,return,super,switch,throw,try,var,while,with,yield,enum,await,implements,package,protected,static,interface,private,public",improperKeywordsRE=new RegExp("^("+improperKeywords.replace(/,/g,"\\b|")+"\\b)"),wsRE=/\s/g,newlineRE=/\n/g,saveRE=/[{,]\s*[\w$_]+\s*:|('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`)|new |typeof |void /g,restoreRE=/"(\d+)"/g,pathTestRE=/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['.*?'\]|\[".*?"\]|\[\d+\]|\[[A-Za-z_$][\w$]*\])*$/,identRE=/[^\w$.](?:[A-Za-z_$][\w$]*)/g,literalValueRE=/^(?:true|false|null|undefined|Infinity|NaN)$/,saveRegRe=/(\/.+\/[gimy]*)/g,restoreRegRe=/"&&&(\d+)"/g,saved=[],savedReg=[];function save(e,t){const r=saved.length;return saved[r]=t?e.replace(newlineRE,"\\n"):e,'"'+r+'"'}function saveReg(e){const t=savedReg.length;return savedReg[t]=e,'"&&&'+t+'"'}function rewrite(e){const t=e.charAt(0);let r=e.slice(1);return allowedKeywordsRE.test(r)?e:(r=r.indexOf('"')>-1?r.replace(restoreRE,restore):r,t+"this."+r)}function restore(e,t){return saved[t]}function restoreReg(e,t){return savedReg[t]}function compileGetter(e){improperKeywordsRE.test(e)&&console.warn("### App Toolkit ### 不要在表达式中使用保留关键字: "+e),saved.length=0;let t=e.replace(saveRE,save).replace(saveRegRe,saveReg).replace(wsRE,"");return t=(" "+t).replace(identRE,rewrite).replace(restoreRegRe,restoreReg).replace(restoreRE,restore),t.trim()}function parseExpression(e){e=e.trim();return/^\/.+\/[gimy]*$/.test(e)?e:isSimplePath(e)&&e.indexOf("[")<0?"this."+e:compileGetter(e)}function isSimplePath(e){return pathTestRE.test(e)&&!literalValueRE.test(e)&&"Math."!==e.slice(0,5)}module.exports={parseExpression:parseExpression}; | ||
/* | ||
* Copyright (C) 2017, hapjs.org. All rights reserved. | ||
*/ | ||
'use strict'; // 表达式中允许的关键字 | ||
const allowedKeywords = 'Infinity,undefined,NaN,null,isFinite,isNaN,true,false,' + 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,' + 'this,' + // this | ||
'require'; // 适配 Webpack/Browserify | ||
const allowedKeywordsRE = new RegExp('^(' + allowedKeywords.replace(/,/g, '\\b|') + '\\b)'); // 表达式中不能包含的关键字 | ||
const improperKeywords = 'break,case,class,catch,const,continue,debugger,default,' + 'delete,do,else,export,extends,finally,for,function,if,' + 'import,in,instanceof,let,return,super,switch,throw,try,' + 'var,while,with,yield,enum,await,implements,package,' + 'protected,static,interface,private,public'; | ||
const improperKeywordsRE = new RegExp('^(' + improperKeywords.replace(/,/g, '\\b|') + '\\b)'); | ||
const wsRE = /\s/g; | ||
const newlineRE = /\n/g; | ||
const saveRE = /[{,]\s*[\w$_]+\s*:|('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`)|new |typeof |void /g; | ||
const restoreRE = /"(\d+)"/g; | ||
const pathTestRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['.*?'\]|\[".*?"\]|\[\d+\]|\[[A-Za-z_$][\w$]*\])*$/; | ||
const identRE = /[^\w$.](?:[A-Za-z_$][\w$]*)/g; | ||
const literalValueRE = /^(?:true|false|null|undefined|Infinity|NaN)$/; | ||
const saveRegRe = /(\/.+\/[gimy]*)/g; | ||
const restoreRegRe = /"&&&(\d+)"/g; | ||
const saved = []; | ||
const savedReg = []; | ||
/** | ||
* 将目标字符串替换为索引 | ||
* @param str | ||
* @param isString | ||
* @returns {string} | ||
*/ | ||
function save(str, isString) { | ||
const i = saved.length; | ||
saved[i] = isString ? str.replace(newlineRE, '\\n') // 回车转变为'\\n' | ||
: str; | ||
return '"' + i + '"'; | ||
} | ||
/** | ||
* 将正则表达式替换为索引 &&& + i | ||
* @param str | ||
* @returns {string} | ||
*/ | ||
function saveReg(str) { | ||
const i = savedReg.length; | ||
savedReg[i] = str; // 使用&&&防止被\w匹配到 | ||
return '"' + '&&&' + i + '"'; | ||
} | ||
/** | ||
* 将之前save的数字转换为字符串 | ||
* @param {String} raw | ||
* @return {String} | ||
*/ | ||
function rewrite(raw) { | ||
const c = raw.charAt(0); | ||
let path = raw.slice(1); | ||
if (allowedKeywordsRE.test(path)) { | ||
return raw; | ||
} else { | ||
path = path.indexOf('"') > -1 ? path.replace(restoreRE, restore) : path; | ||
return c + 'this.' + path; | ||
} | ||
} | ||
/** | ||
* 获取save字符串 | ||
* @param str | ||
* @param i | ||
* @returns {*} | ||
*/ | ||
function restore(str, i) { | ||
return saved[i]; | ||
} | ||
/** | ||
* 获取saveReg正则表达式字符串 | ||
* @param str | ||
* @param i | ||
* @returns {*} | ||
*/ | ||
function restoreReg(str, i) { | ||
return savedReg[i]; | ||
} | ||
/** | ||
* 编译表达式, 添加this前缀 | ||
* @param {String} exp | ||
* @return {Function} | ||
*/ | ||
function compileGetter(exp) { | ||
/* istanbul ignore if */ | ||
if (improperKeywordsRE.test(exp)) { | ||
console.warn('### App Toolkit ### 不要在表达式中使用保留关键字: ' + exp); | ||
} // 重置状态 | ||
saved.length = 0; // 处理表达式 | ||
let body = exp.replace(saveRE, save).replace(saveRegRe, saveReg).replace(wsRE, ''); // 剔除空格/分隔符 | ||
// 生成新表达式 | ||
body = (' ' + body).replace(identRE, rewrite).replace(restoreRegRe, restoreReg).replace(restoreRE, restore); | ||
return body.trim(); | ||
} | ||
/** | ||
* 解析表达式 | ||
* @param {String} exp | ||
* @return {String} | ||
*/ | ||
function parseExpression(exp) { | ||
exp = exp.trim(); // 处理正则表达式字面量 | ||
const regReg = /^\/.+\/[gimy]*$/; | ||
if (regReg.test(exp)) { | ||
return exp; | ||
} | ||
const res = isSimplePath(exp) && exp.indexOf('[') < 0 ? 'this.' + exp // 简单表达式, 直接添加this即可 | ||
: compileGetter(exp); // 复杂表达式, 需要遍历处理 | ||
return res; | ||
} | ||
/** | ||
* 检查表达式简单路径 | ||
* @param {String} exp | ||
* @return {Boolean} | ||
*/ | ||
function isSimplePath(exp) { | ||
return pathTestRE.test(exp) && // true/false/null/undefined/Infinity/NaN | ||
!literalValueRE.test(exp) && // Math常量 | ||
exp.slice(0, 5) !== 'Math.'; | ||
} | ||
module.exports = { | ||
parseExpression: parseExpression | ||
}; | ||
//# sourceMappingURL=expression.js.map |
@@ -1,2 +0,134 @@ | ||
"use strict";const validDivisionCharRE=/[\w).+\-_$\]]/;function parseFilters(e){let i,r,s,t,a,c,l=!1,o=!1,n=!1,f=!1,h=0,d=0,u=0,b=0;for(s=0;s<e.length;s++)if(r=i,i=e.charCodeAt(s),l)39===i&&92!==r&&(l=!1);else if(o)34===i&&92!==r&&(o=!1);else if(n)96===i&&92!==r&&(n=!1);else if(f)47===i&&92!==r&&(f=!1);else if(124!==i||124===e.charCodeAt(s+1)||124===e.charCodeAt(s-1)||h||d||u){switch(i){case 34:o=!0;break;case 39:l=!0;break;case 96:n=!0;break;case 40:u++;break;case 41:u--;break;case 91:d++;break;case 93:d--;break;case 123:h++;break;case 125:h--}if(47===i){let i=s-1;for(;i>=0&&(c=e.charAt(i)," "===c);i--);c&&validDivisionCharRE.test(c)||(f=!0)}}else void 0===t?(b=s+1,t=e.slice(0,s).trim()):k();function k(){(a||(a=[])).push(e.slice(b,s).trim()),b=s+1}if(void 0===t?t=e.slice(0,s).trim():0!==b&&k(),a)for(s=0;s<a.length;s++)t=wrapFilter(t,a[s]);return t}function wrapFilter(e,i){const r=i.indexOf("(");if(r<0)return`this.${i}(${e})`;{const s=i.slice(0,r),t=i.slice(r+1);return`this.${s}(${e}${")"!==t?","+t:t}`}}module.exports=parseFilters; | ||
"use strict"; | ||
const validDivisionCharRE = /[\w).+\-_$\]]/; | ||
function parseFilters(exp) { | ||
let inSingle = false; | ||
let inDouble = false; | ||
let inTemplateString = false; | ||
let inRegex = false; | ||
let curly = 0; | ||
let square = 0; | ||
let paren = 0; | ||
let lastFilterIndex = 0; | ||
let c, prev, i, expression, filters, p; | ||
for (i = 0; i < exp.length; i++) { | ||
prev = c; | ||
c = exp.charCodeAt(i); | ||
if (inSingle) { | ||
if (c === 0x27 && prev !== 0x5c) inSingle = false; | ||
} else if (inDouble) { | ||
if (c === 0x22 && prev !== 0x5c) inDouble = false; | ||
} else if (inTemplateString) { | ||
if (c === 0x60 && prev !== 0x5c) inTemplateString = false; | ||
} else if (inRegex) { | ||
if (c === 0x2f && prev !== 0x5c) inRegex = false; | ||
} else if (c === 0x7c && // pipe | ||
exp.charCodeAt(i + 1) !== 0x7c && exp.charCodeAt(i - 1) !== 0x7c && !curly && !square && !paren) { | ||
if (expression === undefined) { | ||
lastFilterIndex = i + 1; | ||
expression = exp.slice(0, i).trim(); | ||
} else { | ||
pushFilter(); | ||
} | ||
} else { | ||
switch (c) { | ||
case 0x22: | ||
inDouble = true; | ||
break; | ||
// " | ||
case 0x27: | ||
inSingle = true; | ||
break; | ||
// ' | ||
case 0x60: | ||
inTemplateString = true; | ||
break; | ||
// ` | ||
case 0x28: | ||
paren++; | ||
break; | ||
// ( | ||
case 0x29: | ||
paren--; | ||
break; | ||
// ) | ||
case 0x5b: | ||
square++; | ||
break; | ||
// [ | ||
case 0x5d: | ||
square--; | ||
break; | ||
// ] | ||
case 0x7b: | ||
curly++; | ||
break; | ||
// { | ||
case 0x7d: | ||
curly--; | ||
break; | ||
// } | ||
} | ||
if (c === 0x2f) { | ||
// / | ||
let j = i - 1; | ||
for (; j >= 0; j--) { | ||
p = exp.charAt(j); | ||
if (p !== ' ') break; | ||
} | ||
if (!p || !validDivisionCharRE.test(p)) { | ||
inRegex = true; | ||
} | ||
} | ||
} | ||
} | ||
if (expression === undefined) { | ||
expression = exp.slice(0, i).trim(); | ||
} else if (lastFilterIndex !== 0) { | ||
pushFilter(); | ||
} | ||
function pushFilter() { | ||
; | ||
(filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim()); | ||
lastFilterIndex = i + 1; | ||
} | ||
if (filters) { | ||
for (i = 0; i < filters.length; i++) { | ||
expression = wrapFilter(expression, filters[i]); | ||
} | ||
} | ||
return expression; | ||
} | ||
function wrapFilter(exp, filter) { | ||
const i = filter.indexOf('('); | ||
if (i < 0) { | ||
return `this.${filter}(${exp})`; | ||
} else { | ||
const name = filter.slice(0, i); | ||
const args = filter.slice(i + 1); | ||
return `this.${name}(${exp}${args !== ')' ? ',' + args : args}`; | ||
} | ||
} | ||
module.exports = parseFilters; | ||
//# sourceMappingURL=filter-parser.js.map |
@@ -1,2 +0,123 @@ | ||
"use strict";const tagSource="{{{([\\s\\S]+?)}}}|{{([\\s\\S]+?)}}",tagRE=new RegExp(tagSource,"g"),expRE=new RegExp(tagSource),htmlRE=new RegExp("^{{{[\\s\\S]*}}}$"),sexpRE=new RegExp("^{{{([\\s\\S]+?)}}}$|^{{([\\s\\S]+?)}}$");function parseText(e){if(e=e.replace(/\n/g,""),!tagRE.test(e))return null;const t=[];let r,s,x,n,p,i,E=tagRE.lastIndex=0;for(;r=tagRE.exec(e);)s=r.index,s>E&&t.push({value:e.slice(E,s)}),x=htmlRE.test(r[0]),n=x?r[1]:r[2],p=n.charCodeAt(0),i=42===p,n=i?n.slice(1):n,t.push({tag:!0,value:n.trim(),html:x,oneTime:i}),E=s+r[0].length;return E<e.length&&t.push({value:e.slice(E)}),t}function isExpr(e){return expRE.test(e)}function singleExpr(e){return sexpRE.test(e.trim())}function removeExprffix(e){return singleExpr(e)?e.replace(/^\s*{{/,"").replace(/}}\s*$/,""):e}function addExprffix(e){return singleExpr(e)||(e="{{"+e+"}}"),e}module.exports={parseText:parseText,isExpr:isExpr,singleExpr:singleExpr,addExprffix:addExprffix,removeExprffix:removeExprffix}; | ||
/* | ||
* Copyright (C) 2017, hapjs.org. All rights reserved. | ||
*/ | ||
'use strict'; | ||
const tagSource = '{{{([\\s\\S]+?)}}}|{{([\\s\\S]+?)}}'; // {{ }} 格式 | ||
const tagRE = new RegExp(tagSource, 'g'); | ||
const expRE = new RegExp(tagSource); | ||
const htmlRE = new RegExp('^{{{[\\s\\S]*}}}$'); | ||
const sexpRE = new RegExp('^{{{([\\s\\S]+?)}}}$|^{{([\\s\\S]+?)}}$'); | ||
/** | ||
* 将模板字符串解析为token数组 | ||
* | ||
* @param {String} text | ||
* @return {Array<Object> | null} | ||
* - {String} type | ||
* - {String} value | ||
* - {Boolean} [html] | ||
* - {Boolean} [oneTime] | ||
*/ | ||
function parseText(text) { | ||
// 剔除回车 | ||
text = text.replace(/\n/g, ''); | ||
/* istanbul ignore if */ | ||
if (!tagRE.test(text)) { | ||
return null; | ||
} | ||
const tokens = []; | ||
let lastIndex = tagRE.lastIndex = 0; | ||
let match, index, html, value, first, oneTime; | ||
/* eslint-disable no-cond-assign */ | ||
while (match = tagRE.exec(text)) { | ||
/* eslint-enable no-cond-assign */ | ||
index = match.index; // push text token | ||
if (index > lastIndex) { | ||
tokens.push({ | ||
value: text.slice(lastIndex, index) | ||
}); | ||
} // tag token | ||
html = htmlRE.test(match[0]); | ||
value = html ? match[1] : match[2]; | ||
first = value.charCodeAt(0); | ||
oneTime = first === 42; // * | ||
value = oneTime ? value.slice(1) : value; | ||
tokens.push({ | ||
tag: true, | ||
value: value.trim(), | ||
html: html, | ||
oneTime: oneTime | ||
}); | ||
lastIndex = index + match[0].length; | ||
} | ||
if (lastIndex < text.length) { | ||
tokens.push({ | ||
value: text.slice(lastIndex) | ||
}); | ||
} | ||
return tokens; | ||
} | ||
/** | ||
* 检测是否包含表达式 | ||
* @param {String} text | ||
* @return {Boolean} | ||
*/ | ||
function isExpr(text) { | ||
return expRE.test(text); | ||
} | ||
/** | ||
* 检测是否为单个表达式 | ||
* @param text | ||
* @returns {boolean} | ||
*/ | ||
function singleExpr(text) { | ||
return sexpRE.test(text.trim()); | ||
} | ||
/** | ||
* 移除头尾'{{}}' | ||
*/ | ||
function removeExprffix(text) { | ||
if (!singleExpr(text)) { | ||
return text; | ||
} | ||
return text.replace(/^\s*{{/, '').replace(/}}\s*$/, ''); | ||
} | ||
/** | ||
* 添加头尾'{{}}' | ||
*/ | ||
function addExprffix(text) { | ||
if (!singleExpr(text)) { | ||
text = '{{' + text + '}}'; | ||
} | ||
return text; | ||
} | ||
module.exports = { | ||
parseText: parseText, | ||
isExpr: isExpr, | ||
singleExpr: singleExpr, | ||
addExprffix: addExprffix, | ||
removeExprffix: removeExprffix | ||
}; | ||
//# sourceMappingURL=text.js.map |
@@ -1,2 +0,261 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=model;var _exp=_interopRequireDefault(require("./exp")),_utils=require("../utils"),_validator=_interopRequireDefault(require("./validator.js"));function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function model(e,t,n,u){if(!(0,_utils.isValidValue)(e))return!1;e=_exp.default.addExprffix(e);const a=t.result.type,d=getBindingAttr(n,"type")||"";let l=!1;"input"===a&&d.match(/^this\.+/)&&(l=!0),l?genDynamicModel(n,e,t,d):u.importNames&&u.importNames.indexOf(a)>-1?genComponentModel(e,t):"select"===a?genSelectModel(e,t):"input"===a&&"checkbox"===d?genCheckboxModel(n,e,t):"input"===a&&"radio"===d?genRadioModel(n,e,t):"input"===a||"textarea"===a?genDefaultModel(e,t):_validator.default.isReservedTag(a)||genComponentModel(e,t)}function genCheckboxModel(node,value,output){const expValue=(0,_exp.default)(value,!1),valueBinding=getBindingAttr(node,"value",!0)||"null",trueValueBinding=getBindingAttr(node,"true-value",!0)||"true",falseValueBinding=getBindingAttr(node,"false-value",!0)||"false",attrCheckedCode=`\n if (Array.isArray(${expValue})) {\n return ${expValue}.indexOf(${valueBinding}) > -1\n } else {\n return ${expValue}${"true"===trueValueBinding?"":"=== "+trueValueBinding}\n }`,eventChangeCode=`\n const checked = evt.checked;\n if (Array.isArray(${expValue})) {\n const index = ${expValue}.indexOf(${valueBinding})\n if (checked) {\n index < 0 && (${expValue} = ${expValue}.concat([${valueBinding}]))\n } else {\n index > -1 && (${expValue} = ${expValue}.slice(0, index).concat(${expValue}.slice(index + 1)))\n }\n } else {\n ${expValue} = checked ? ${trueValueBinding} : ${falseValueBinding}\n }`;return addAttr(output.result,"checked",eval(`(function() {${attrCheckedCode}})`)),addHandler(output.result,"change",eval(`(function(evt) {${eventChangeCode}})`)),{attr:{checked:attrCheckedCode},events:{change:eventChangeCode}}}function genRadioModel(node,value,output){const valueBinding=getBindingAttr(node,"value",!0)||"null",attrCheckedCode=`return ${(0,_exp.default)(value,!1)} === ${valueBinding}`,eventChangeCode=`${(0,_exp.default)(value,!1)} = ${valueBinding}`;return addAttr(output.result,"checked",eval(`(function() {${attrCheckedCode}})`)),addHandler(output.result,"change",eval(`(function(evt) {${eventChangeCode}})`)),{attr:{checked:attrCheckedCode},events:{change:eventChangeCode}}}function genSelectModel(value,output){addHandler(output.result,"change",eval(`(function(evt) { ${(0,_exp.default)(value,!1)} = evt.newValue})`))}function genDefaultModel(value,output){const eventChangeCode=(0,_exp.default)(value,!1)+" = evt.target.value";return addAttr(output.result,"value",(0,_exp.default)(value)),addHandler(output.result,"change",eval(`(function(evt) {${eventChangeCode}})`)),{events:{change:eventChangeCode}}}function genComponentModel(value,output){const expValue=(0,_exp.default)(value,!1);output.result.model={value:(0,_exp.default)(value),callback:eval(`(function (evt) { ${expValue} = evt.detail})`)}}function genDynamicModel(node,value,output,expType){const checkboxCode=genCheckboxModel(node,value,output),radioCode=genRadioModel(node,value,output),textCode=genDefaultModel(value,output);addAttr(output.result,"checked",eval(`\n (function() { \n if (${expType} === 'checkbox') {\n ${checkboxCode.attr.checked}\n } else if (${expType} === 'radio') {\n ${radioCode.attr.checked}\n }\n })\n `)),addHandler(output.result,"change",eval(`\n (function(evt) {\n if (${expType} === 'checkbox') {\n ${checkboxCode.events.change}\n } else if (${expType} === 'radio') {\n ${radioCode.events.change}\n } else {\n ${textCode.events.change}\n }\n })\n `))}function getBindingAttr(e,t,n=!1){const u=e.attrs||[];for(let e=0;e<u.length;e++){let a=u[e].name;const d=a.match(/^:+/);if(d&&(a=a.slice(d.length)),a===t){const t=(0,_exp.default)(u[e].value,!1);return n&&!t.match(/^this\.+/)?`"${t}"`:t}}}function addAttr(e,t,n){(e.attr||(e.attr={}))[t]=n}function addHandler(e,t,n){(e.events||(e.events={}))[t]=n} | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = model; | ||
var _exp = _interopRequireDefault(require("./exp")); | ||
var _utils = require("../utils"); | ||
var _validator = _interopRequireDefault(require("./validator.js")); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/** | ||
* 构建model属性 | ||
* @param {string} value model 绑定的数据名 | ||
* @param {object} output 构建的输出结果 | ||
* @param {object} node parse5 生成的 node 节点 | ||
* @param {object} options 配置信息 | ||
*/ | ||
function model(value, output, node, options) { | ||
if (!(0, _utils.isValidValue)(value)) { | ||
return false; | ||
} | ||
value = _exp.default.addExprffix(value); | ||
const tag = output.result.type; // 标签名 | ||
const type = getBindingAttr(node, 'type') || ''; // type 属性的值 | ||
let isDynamicType = false; // input type 为动态时绑定的属性和事件需要额外处理 | ||
if (tag === 'input' && type.match(/^this\.+/)) { | ||
isDynamicType = true; | ||
} | ||
/* eslint-disable no-eval */ | ||
if (!isDynamicType) { | ||
if (options.importNames && options.importNames.indexOf(tag) > -1) { | ||
genComponentModel(value, output); | ||
} else if (tag === 'select') { | ||
genSelectModel(value, output); | ||
} else if (tag === 'input' && type === 'checkbox') { | ||
genCheckboxModel(node, value, output); | ||
} else if (tag === 'input' && type === 'radio') { | ||
genRadioModel(node, value, output); | ||
} else if (tag === 'input' || tag === 'textarea') { | ||
genDefaultModel(value, output); | ||
} else if (!_validator.default.isReservedTag(tag)) { | ||
genComponentModel(value, output); | ||
} | ||
} else { | ||
genDynamicModel(node, value, output, type); | ||
} | ||
} | ||
/** | ||
* 构建 type 为 checkbox 的 input 组件的 model 属性 | ||
* @param {object} node parse5 生成的 node 节点 | ||
* @param {string} value model 绑定的值 | ||
* @param {object} output 构建的输出结果 | ||
* @returns {object} 构建过程中生成的代码字符串组成的对象,供 genDynamicModel 使用 | ||
*/ | ||
function genCheckboxModel(node, value, output) { | ||
const expValue = (0, _exp.default)(value, false); | ||
const valueBinding = getBindingAttr(node, 'value', true) || 'null'; | ||
const trueValueBinding = getBindingAttr(node, 'true-value', true) || 'true'; | ||
const falseValueBinding = getBindingAttr(node, 'false-value', true) || 'false'; | ||
const attrCheckedCode = ` | ||
if (Array.isArray(${expValue})) { | ||
return ${expValue}.indexOf(${valueBinding}) > -1 | ||
} else { | ||
return ${expValue}${trueValueBinding === 'true' ? '' : `=== ${trueValueBinding}`} | ||
}`; | ||
const eventChangeCode = ` | ||
const checked = evt.target.checked; | ||
if (Array.isArray(${expValue})) { | ||
const index = ${expValue}.indexOf(${valueBinding}) | ||
if (checked) { | ||
index < 0 && (${expValue} = ${expValue}.concat([${valueBinding}])) | ||
} else { | ||
index > -1 && (${expValue} = ${expValue}.slice(0, index).concat(${expValue}.slice(index + 1))) | ||
} | ||
} else { | ||
${expValue} = checked ? ${trueValueBinding} : ${falseValueBinding} | ||
}`; | ||
addAttr(output.result, 'checked', eval(`(function() {${attrCheckedCode}})`)); | ||
addHandler(output.result, 'change', eval(`(function(evt) {${eventChangeCode}})`)); | ||
return { | ||
attr: { | ||
checked: attrCheckedCode | ||
}, | ||
events: { | ||
change: eventChangeCode | ||
} | ||
}; | ||
} | ||
/** | ||
* 构建 type 为 radio 时的 input 组件的 model 属性 | ||
* @param {object} node parse5 生成的 node 节点 | ||
* @param {string} value model 绑定的值 | ||
* @param {object} output 构建的输出结果 | ||
* @returns {object} 构建过程中生成的代码字符串组成的对象,供 genDynamicModel 使用 | ||
*/ | ||
function genRadioModel(node, value, output) { | ||
const valueBinding = getBindingAttr(node, 'value', true) || 'null'; | ||
const attrCheckedCode = `return ${(0, _exp.default)(value, false)} === ${valueBinding}`; | ||
const eventChangeCode = `${(0, _exp.default)(value, false)} = ${valueBinding}`; | ||
addAttr(output.result, 'checked', eval(`(function() {${attrCheckedCode}})`)); | ||
addHandler(output.result, 'change', eval(`(function(evt) {${eventChangeCode}})`)); | ||
return { | ||
attr: { | ||
checked: attrCheckedCode | ||
}, | ||
events: { | ||
change: eventChangeCode | ||
} | ||
}; | ||
} | ||
/** | ||
* 构建 select 组件的 model | ||
* @param {string} value model 绑定的值 | ||
* @param {object} output 构建的输出结果 | ||
*/ | ||
function genSelectModel(value, output) { | ||
addHandler(output.result, 'change', eval(`(function(evt) { ${(0, _exp.default)(value, false)} = evt.newValue})`)); | ||
} | ||
/** | ||
* 构建 type 为 text 的 input 组件和 textarea 组件的 model 属性 | ||
* @param {string} value model 绑定的值 | ||
* @param {object} output 构建的输出结果 | ||
* @returns {object} 构建过程中生成的代码字符串组成的对象,供 genDynamicModel 使用 | ||
*/ | ||
function genDefaultModel(value, output) { | ||
const eventChangeCode = `console.log('888'); ${(0, _exp.default)(value, false)} = evt.target.value`; | ||
addAttr(output.result, 'value', (0, _exp.default)(value)); | ||
addHandler(output.result, 'change', eval(`(function(evt) {${eventChangeCode}})`)); | ||
return { | ||
events: { | ||
change: eventChangeCode | ||
} | ||
}; | ||
} | ||
/** | ||
* 构建自定义组件的 model 属性 | ||
* @param {string} value model 绑定的值 | ||
* @param {object} output 构建的输出结果 | ||
*/ | ||
function genComponentModel(value, output) { | ||
const expValue = (0, _exp.default)(value, false); // 使用一个属性保存配置信息。运行时读取用户的model配置再动态绑定属性和事件 | ||
output.result.model = { | ||
value: (0, _exp.default)(value), | ||
callback: eval(`(function (evt) { ${expValue} = evt.detail})`) | ||
}; | ||
} | ||
/** | ||
* 构建 type 为动态值时的 input 组件的 model 属性 | ||
* @param {object} node parse5 生成的 node 节点 | ||
* @param {string} value model 绑定的值 | ||
* @param {object} output 构建的输出结果 | ||
* @param {string} expType type 属性绑定的值 | ||
*/ | ||
function genDynamicModel(node, value, output, expType) { | ||
const checkboxCode = genCheckboxModel(node, value, output); | ||
const radioCode = genRadioModel(node, value, output); | ||
const textCode = genDefaultModel(value, output); | ||
addAttr(output.result, 'value', (0, _exp.default)(value)); | ||
addAttr(output.result, 'checked', eval(` | ||
(function() { | ||
if (${expType} === 'checkbox') { | ||
${checkboxCode.attr.checked} | ||
} else if (${expType} === 'radio') { | ||
${radioCode.attr.checked} | ||
} else { | ||
return false | ||
} | ||
}) | ||
`)); | ||
addHandler(output.result, 'change', eval(` | ||
(function(evt) { | ||
if (${expType} === 'checkbox') { | ||
${checkboxCode.events.change} | ||
} else if (${expType} === 'radio') { | ||
${radioCode.events.change} | ||
} else { | ||
${textCode.events.change} | ||
} | ||
}) | ||
`)); | ||
} | ||
/** | ||
* 获取节点上绑定的属性的值 | ||
* @param {object} node parse5 生成的 node 节点 | ||
* @param {string} attrName 想要获取值的属性名 | ||
* @param {boolean} addQuotation 是否对不是插值的结果(非this.xxx)添加双引号包裹。如name -> "name" | ||
* @returns {string} 属性对应的值,当为插值时返回 this.xxx | ||
*/ | ||
function getBindingAttr(node, attrName, addQuotation = false) { | ||
const attrs = node.attrs || []; | ||
for (let i = 0; i < attrs.length; i++) { | ||
let name = attrs[i].name; | ||
const inMatch = name.match(/^:+/); | ||
if (inMatch) { | ||
name = name.slice(inMatch.length); | ||
} | ||
if (name === attrName) { | ||
const expValue = (0, _exp.default)(attrs[i].value, false); | ||
if (addQuotation && !expValue.match(/^this\.+/)) { | ||
return `"${expValue}"`; | ||
} | ||
return expValue; | ||
} | ||
} | ||
} | ||
/** | ||
* 往结果上添加属性 | ||
* @param {object} result 输出的结果 | ||
* @param {string} name 需要添加的属性名 | ||
* @param {function} value 获取值时需要执行的回调 | ||
*/ | ||
function addAttr(result, name, value) { | ||
; | ||
(result.attr || (result.attr = {}))[name] = value; | ||
} | ||
/** | ||
* 往结果上添加事件 | ||
* @param {object} result 输出的结果 | ||
* @param {string} name 需要添加的事件名 | ||
* @param {function} value 事件触发时需要执行的回调 | ||
*/ | ||
function addHandler(result, name, value) { | ||
; | ||
(result.events || (result.events = {}))[name] = value; | ||
} | ||
//# sourceMappingURL=model.js.map |
@@ -1,2 +0,1899 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=void 0;var _path=_interopRequireDefault(require("path")),_sharedUtils=require("@hap-toolkit/shared-utils"),_exp=_interopRequireDefault(require("./exp")),_style=_interopRequireDefault(require("../style")),_model=_interopRequireDefault(require("./model")),_utils=require("../utils");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}const extList=[".mix",".ux",".vue"],richtextType=["mix","ux"],REG_TAG_DATA_ATTR=/^data-\w+/,REG_CLASS_VALUE=/[^{}\s]+((?=\s)|$)|[^{}\s]*\{\{[^]*?\}\}[^\s]*/g,RESERVED_TAGS=Object.keys(_utils.FRAG_TYPE).map(e=>_utils.FRAG_TYPE[e]),tagCommon={events:["click","focus","blur","key","longpress","appear","disappear","swipe","touchstart","touchmove","touchend","touchcancel","resize","animationstart","animationiteration","animationend"],attrs:{id:{},style:{},class:{},disabled:{enum:["false","true"]},if:{def:"true"},elif:{def:"true"},else:{},for:{},tid:{},show:{def:"true"},"aria-label":{},"aria-unfocusable":{enum:["false","true"]},forcedark:{enum:["true","false"]},focusable:{enum:["false","true"]},vtags:{},vindex:{},autofocus:{enum:["false","true"]},descendantfocusability:{enum:["before","after","block"]}},children:["block","slot","component"],parents:["block"]},tagNatives={div:{supportCard:!0,attrs:{enablevideofullscreencontainer:{enum:["false","true"]}}},a:{supportCard:!0,textContent:!0,children:["span"],attrs:{visited:{enum:["false","true"]},href:{}}},text:{supportCard:!0,textContent:!0,children:["a","span"],attrs:{type:{enum:["text","html"]}}},span:{supportCard:!0,textContent:!0,excludeRoot:!0,children:["span"],parents:["text","a","span"],attrs:{extendCommon:!1,id:{},style:{},class:{},for:{},tid:{},if:{def:"true"},elif:{def:"true"},else:{}}},label:{supportCard:!0,textContent:!0,atomic:!0,attrs:{target:{}}},image:{events:["complete","error"],empty:!0,supportCard:!0,selfClosing:!0,alias:["img"],atomic:!0,attrs:{src:{},alt:{},enablenightmode:{enum:["true","false"]},autoplay:{enum:["true","false"]}}},slider:{selfClosing:!0,atomic:!0,attrs:{enabled:{enum:["true","false"]},min:{def:0},max:{def:100},step:{def:1},value:{def:0}},events:["change"]},web:{atomic:!0,events:["pagestart","pagefinish","titlereceive","error","message","progress"],attrs:{src:{},trustedurl:{},allowthirdpartycookies:{enum:["false","true"]},enablenightmode:{enum:["true","false"]},showloadingdialog:{enum:["false","true"]},supportzoom:{enum:["true","false"]}}},list:{children:["list-item"],attrs:{scrollpage:{enum:["false","true"]},focusbehavior:{enum:["aligned","edged"]}},events:["scroll","scrollbottom","scrolltop","scrollend","scrolltouchup","selected"]},"list-item":{excludeRoot:!0,parents:["list"],attrs:{type:{required:!0}}},block:{excludeRoot:!0,supportCard:!0,attrs:{extendCommon:!1,for:{},tid:{},if:{def:"true"},elif:{def:"true"},else:{}}},component:{excludeRoot:!0,attrs:{extendCommon:!1,is:{required:!0},for:{},tid:{},if:{def:"true"},elif:{def:"true"},else:{}}},slot:{excludeRoot:!0,attrs:{name:{def:"default"},extendCommon:!1,content:{}}},input:{supportCard:!0,selfClosing:!0,atomic:!0,empty:!0,attrs:{type:{enum:["text","button","checkbox","radio","email","date","time","number","password","tel","eventbutton"]},autocomplete:{enum:["on","off"]},enterkeytype:{enum:["default","next","go","done","send","search"]},eventtype:{enum:["shortcut"]},model:{},maxlength:{},checked:{enum:["false","true"]},name:{},value:{},placeholder:{}},events:["change","enterkeyclick","selectionchange"]},button:{supportCard:!0,textContent:!0,atomic:!0},refresh:{attrs:{offset:{def:"132px"},refreshing:{enum:["false","true"]},type:{enum:["auto","pulldown"]},"enable-refresh":{enum:["true","false"]}},events:["refresh"]},refresh2:{attrs:{pulldownrefreshing:{enum:["false","true"]},pulluprefreshing:{enum:["false","true"]},animationduration:{def:300},enablepulldown:{enum:["true","false"]},enablepullup:{enum:["false","true"]},reboundable:{enum:["false","true"]},gesture:{enum:["true","false"]},offset:{def:"132px"},refreshing:{enum:["false","true"]},type:{enum:["auto","pulldown"]}},events:["pulldownrefresh","pulluprefresh","refresh"],onceChildren:["refresh-header","refresh-footer"]},"refresh-header":{attrs:{dragrate:{def:300},triggerratio:{def:300},tiggersize:{def:300},maxdragratio:{def:300},maxdragsize:{def:300},refreshdisplayratio:{def:300},refreshdisplaysize:{def:300},spinnerstyle:{enum:["translation","front","behind"]},autorefresh:{enum:["false","true"]},translationwithcontent:{enum:["false","true"]}},events:["move"],parents:["refresh2"]},"refresh-footer":{attrs:{dragrate:{def:300},triggerratio:{def:300},tiggersize:{def:300},maxdragratio:{def:300},maxdragdize:{def:300},refreshdisplayratio:{def:300},refreshdisplaysize:{def:300},spinnerstyle:{enum:["translation","front","behind"]},autorefresh:{enum:["false","true"]},translationwithcontent:{enum:["true","false"]}},events:["move"],parents:["refresh2"]},ad:{attrs:{unitid:{def:""},type:{def:"native"}},events:["load","error"]},swiper:{attrs:{index:{def:0},autoplay:{enum:["false","true"]},interval:{def:3e3},indicator:{enum:["true","false"]},loop:{enum:["true","false"]},duration:{},vertical:{enum:["false","true"]},previousmargin:{def:"0px"},nextmargin:{def:"0px"},enableswipe:{enum:["true","false"]}},events:["change"]},progress:{supportCard:!0,selfClosing:!0,atomic:!0,attrs:{percent:{def:0},type:{enum:["horizontal","circular"]}}},picker:{supportCard:!0,selfClosing:!0,atomic:!0,attrs:{type:{required:!0,enum:["text","date","time","multi-text"]},start:{def:"1970-1-1"},end:{def:"2100-12-31"},range:{},selected:{},value:{}},events:["change","columnchange","cancel"]},switch:{supportCard:!0,selfClosing:!0,atomic:!0,attrs:{checked:{enum:["false","true"]}},events:["change"]},textarea:{supportCard:!0,atomic:!0,textContent:!0,attrs:{placeholder:{},maxlength:{},model:{}},events:["change","selectionchange","linechange"]},video:{empty:!0,attrs:{src:{},muted:{enum:["false","true"]},autoplay:{enum:["false","true"]},controls:{enum:["true","false"]},poster:{},orientation:{enum:["landscape","portrait"]},titlebar:{enum:["true","false"]},title:{},playcount:{}},events:["prepared","start","pause","finish","error","seeking","seeked","timeupdate","fullscreenchange"]},camera:{atomic:!0,selfClosing:!0,attrs:{deviceposition:{enum:["back","front"]},flash:{enum:["auto","on","off","torch"]},framesize:{enum:["low","normal","high"]},autoexposurelock:{enum:["false","true"]},autowhitebalancelock:{enum:["false","true"]}},events:["error","camerainitdone","cameraframe"]},map:{children:["custommarker"],attrs:{latitude:{},longitude:{},coordtype:{},scale:{def:0},rotate:{def:0},markers:{},showmylocation:{enum:["true","false"]},polylines:{},polygons:{},circles:{},controls:{},groundoverlays:{},includepoints:{},heatmaplayer:{},showcompass:{enum:["true","false"]},enableoverlooking:{enum:["false","true"]},enablezoom:{enum:["true","false"]},enablescroll:{enum:["true","false"]},enablerotate:{enum:["true","false"]},showscale:{enum:["false","true"]},showzoom:{enum:["false","true"]}},events:["loaded","regionchange","tap","markertap","callouttap","controltap","poitap"]},custommarker:{parents:["map"],attrs:{custommarkerattr:{}}},canvas:{atomic:!0},stack:{supportCard:!0,events:["fullscreenchange"]},richtext:{textContent:!0,atomic:!0,attrs:{type:{required:!0,enum:["html"].concat(richtextType)}},events:["start","complete"]},tabs:{children:["tab-bar","tab-content"],attrs:{index:{def:0}},events:["change"]},"tab-content":{parents:["tabs"],attrs:{scrollable:{enum:["true","false"]}}},"tab-bar":{parents:["tabs"],attrs:{mode:{enum:["fixed","scrollable"]}}},popup:{supportCard:!0,children:["text"],attrs:{target:{required:!0},placement:{enum:["left","top","right","bottom","topLeft","topRight","bottomLeft","bottomRight"],def:"bottom"}},events:["visibilitychange"]},rating:{supportCard:!0,atomic:!0,attrs:{numstars:{def:"5"},rating:{def:"0"},stepsize:{def:"0.5"},indicator:{enum:["false","true"]}},events:["change"]},select:{supportCard:!0,children:["option"],events:["change"],excludeRoot:!0,attrs:{model:{}}},option:{supportCard:!0,parents:["select"],atomic:!0,textContent:!0,attrs:{selected:{def:!1},value:{}},excludeRoot:!0},marquee:{supportCard:!0,textContent:!0,atomic:!0,attrs:{scrollamount:{def:6},loop:{def:-1},direction:{enum:["left","right"]},value:{def:"6px"}},events:["bounce","finish","start"]},scrollview:{attrs:{"scroll-direction":{enum:["vertical","horizontal","vertical_horizontal"]},"show-scrollbar":{enum:["true","false"]}},events:["scroll","scrollbegindrag","scrollenddrag","scrollstart","scrollstop"]},drawer:{attrs:{enableswipe:{enum:["true","false"]}},events:["change","scroll"]},lottie:{empty:!0,attrs:{source:{required:!0},progress:{},speed:{},loop:{enum:[!0,!1]},autoplay:{enum:[!1,!0]},rendermode:{enum:["AUTOMATIC","HARDWARE","SOFTWARE"]}},events:["complete","error","change"]},"drawer-navigation":{parents:["drawer"],attrs:{direction:{enum:["start","end"]}}},"slide-view":{attrs:{edge:{enum:["right","left"]},enableslide:{enum:["true","false"]},isopen:{enum:["false","true"]},layer:{enum:["above","same"]},buttons:{}},events:["open","close","slide","buttonclick"]},"section-list":{children:["section-group","section-item"],events:["scroll","scrollend","scrolltouchup","scrolltop","scrollbottom"]},"section-group":{children:["section-group","section-item","section-header"],attrs:{expand:{enum:["false","true"]}},events:["change"]},"section-header":{parents:["section-group"]},"section-item":{}},tagReserved={};let tagComponents=[];const tagNativeKeys=Object.keys(tagNatives),tagAliasMap={},tagAttrMap={},tagEnumAttrMap={},tagDefaultAttrMap={},tagRequireAttrMap={},tagAtomics={},tagEmpty={},tagTextContent={},tagChildrenMap={},tagOnceChildrenMap={},tagParentsMap={},tagEventsMap={},tagNotRoot=[];function checkTagName(e,t,a={}){const n=t.result,s=t.log,r=_path.default.extname(a.filePath);let l=e.tagName;const o=e.childNodes||[],i=e.__location||{};tagComponents=a.importNames||[],tagAliasMap[l]&&("img"!==l&&s.push({line:i.line||1,column:i.col||1,reason:"NOTE: 组件名 `"+l+"` 自动转换为 `"+tagAliasMap[l]+"`"}),l=tagAliasMap[l]),n.type=l,RESERVED_TAGS.indexOf(l)>=0?s.push({line:i.line||1,column:i.col||1,reason:"ERROR: 组件名 `"+l+"` 是系统保留的组件名, 请修改"}):hasTagDefined(l)||s.push({line:i.line||1,column:i.col||1,reason:`WARN: 未识别的标签名 '${l}',如果是自定义组件,请确认已经引入`}),a.uxType===_utils.ENTRY_TYPE.CARD&&tagNatives[l]&&!tagNatives[l].supportCard&&s.push({line:i.line||1,column:i.col||1,reason:"ERROR: 卡片不支持组件名 `"+l+"`, 请修改"}),e._isroot&&tagNotRoot.hasOwnProperty(l)&&s.push({line:i.line||1,column:i.col||1,reason:"ERROR: 组件 `"+l+"` 不能作为根组件"}),tagEmpty.hasOwnProperty(l)&&o.length>0&&s.push({line:i.line||1,column:i.col||1,reason:"ERROR: 组件 `"+l+"` 内不能有子节点与文字,请修改"}),tagAtomics.hasOwnProperty(l)&&(tagTextContent.hasOwnProperty(l)?o.length>0&&o.every(e=>"#text"===e.nodeName||(s.push({line:i.line||1,column:i.col||1,reason:"ERROR: 组件 `"+l+"` 是原子类型,不应该有子节点"}),!1)):(o.length>1||o[0]&&"#text"!==o[0].nodeName)&&s.push({line:i.line||1,column:i.col||1,reason:"ERROR: 组件 `"+l+"` 只能有一个文字子节点"})),n.attr=n.attr||{};const u=e.attrs||[],c=[];if(u.forEach((function(e){c.push(e.name.toLowerCase())})),tagDefaultAttrMap[l]&&Object.keys(tagDefaultAttrMap[l]).forEach(t=>{const n=c.indexOf(t);n>=0&&""===u[n].value?(u[n].value=tagDefaultAttrMap[l][t],s.push({line:i.line||1,column:i.col||1,reason:"ERROR: 组件 `"+l+"` 属性 `"+t+"` 值为空, 默认设置为缺省值 `"+tagDefaultAttrMap[l][t]+"`"})):"slot"===l&&n<0&&(e.attrs.push({name:t,value:tagDefaultAttrMap[l][t]}),_sharedUtils.colorconsole.warn(`标签${l}的属性 \`${t}\` 值为空, 默认设置为缺省值 \`${tagDefaultAttrMap[l][t]}\` ${a.filePath}@${i.line||1},${i.col||1}`))}),e._isroot){const e=["for","if","elif","else","show"];c.forEach(t=>{e.indexOf(t)>=0&&s.push({line:i.line||1,column:i.col||1,reason:"ERROR: 根节点 `"+l+"` 不能使用属性 `"+t+"`"})})}if(tagRequireAttrMap[l]&&tagRequireAttrMap[l].forEach(e=>{c.indexOf(e)<0&&s.push({line:i.line||1,column:i.col||1,reason:"ERROR: 组件 `"+l+"` 没有定义属性 `"+e+"`"})}),tagEnumAttrMap[l]&&Object.keys(tagEnumAttrMap[l]).forEach(e=>{const t=c.indexOf(e);if(t>=0){const a=u[t].value;if(!_exp.default.isExpr(a)){const n=tagEnumAttrMap[l][e];n.indexOf(a)<0&&(u[t].value=n[0],s.push({line:i.line||1,column:i.col||1,reason:"ERROR: 组件 `"+l+"` 属性 `"+e+"` 的值 `"+a+"`非法, 默认设置为缺省值 `"+n[0]+"`"}))}}}),tagAttrMap[l]&&"component"!==l&&c.forEach(e=>{if(!e.match(/^(on|@)/)){if(r.indexOf(extList[2])>=-1&&/^(:|v-|key|ref|is|slot|slot-scope)/.test(e))return;const t=checkDataAttr(e),a=Object.prototype.hasOwnProperty.call(tagAttrMap[l],e);t||a||s.push({line:i.line||1,column:i.col||1,reason:"ERROR: 组件 `"+l+"` 不支持属性 `"+e+"`,支持的属性有 ["+Object.keys(tagAttrMap[l]).join(", ")+"]"})}}),tagEventsMap[l]&&"component"!==l){const e=tagEventsMap[l];c.forEach(t=>{if(t.match(/^(on|@)/)){const n=t.replace(/^(on|@)/,""),r=["appear","disappear","swipe"];a.uxType===_utils.ENTRY_TYPE.CARD&&r.indexOf(n.toLowerCase())>-1&&s.push({line:i.line||1,column:i.col||1,reason:"ERROR: 卡片组件 `"+l+"` 不支持事件 `"+n+"`"}),e.indexOf(n.toLowerCase())<0&&s.push({line:i.line||1,column:i.col||1,reason:"ERROR: 组件 `"+l+"` 不支持事件 `"+n+"`"})}})}if(o.length>0){const t=new Set,n={};o.forEach((e,r)=>{if(isReservedTag(l)&&isReservedTag(e.nodeName)){const o=tagParentsMap[e.nodeName],u=tagChildrenMap[l],c=tagOnceChildrenMap[l];if("slot"===e.nodeName){let n=e.parentNode;for(;n;){if("slot"===n.tagName){_sharedUtils.colorconsole.warn(`slot标签内不应该嵌套slot ${a.filePath}@${i.line||1},${i.col||1}`);break}n=n.parentNode}const s={};e.attrs.map(e=>{s[e.name]=e.value}),s.hasOwnProperty("name")?/\{\{\s*[\w$]+\s*\}\}/.test(s.name)&&_sharedUtils.colorconsole.warn(`标签${e.nodeName}的name属性暂时不支持动态绑定 ${a.filePath}@${i.line||1},${i.col||1}`):s.name="default",t.has(s.name)?_sharedUtils.colorconsole.warn(`标签${l}内存在name为 \`${s.name}\` 重复slot ${a.filePath}@${i.line||1},${i.col||1}`):t.add(s.name)}if(o&&o.indexOf(l)<0||u&&u.indexOf(e.nodeName)<0)s.push({line:i.line||1,column:i.col||1,reason:"ERROR: 组件 `"+l+"` 不支持子组件 `"+e.nodeName+"`"});else if(c&&c.indexOf(e.nodeName)>-1){let t;(n[e.nodeName]||0===n[e.nodeName])&&(t=!0),n[e.nodeName]={index:r,duplicated:t}}}});const r=Object.keys(n);r.length&&(r.filter(e=>n[e].duplicated).forEach(e=>{s.push({line:i.line||1,column:i.col||1,reason:`'WARNING: 组件 \`${e}\` 只允许在 \`${l}\` 组件中出现一次`})}),e.childNodes=o.filter((e,t)=>!(n[e.nodeName]&&n[e.nodeName].duplicated&&t!==n[e.nodeName].index)))}isSupportedSelfClosing(l)||!i.startTag||i.endTag||s.push({line:i.line||1,column:i.col||1,reason:"ERROR: 组件 `"+l+"` 缺少闭合标签,请检查"}),"list-item"===l&&hasIfOrFor(o)&&s.push({line:i.line||1,column:i.col||1,reason:"WARN: `list-item` 内部需谨慎使用指令 if 或 for,相同 type 属性的 list-item, DOM 结构必须完全相同`"})}function checkId(e,t){e&&(t.result.id=_exp.default.isExpr(e)?(0,_exp.default)(e):e)}function checkDataAttr(e){return REG_TAG_DATA_ATTR.test(e)}function checkBuild(e,t){e&&(t.result.append="tree"===e?"tree":"single")}function checkClass(className,output){let hasBinding,classList=[];if(className=className.trim(),className){let start=0,end=0;const segs=[],matched=className.match(REG_CLASS_VALUE);if(matched){let e;matched.forEach(t=>{end=className.indexOf(t,start),e=className.slice(start,end),e.length&&segs.push(e),segs.push(t),start+=e.length+t.length})}if(segs.push(className.slice(start)),classList=segs.reduce((e,t)=>_exp.default.isExpr(t)?(hasBinding=!0,e.push((0,_exp.default)(t,!1)),e):e.concat(t.split(/\s+/g).filter(e=>e).map(e=>`'${e}'`)),[]),classList=classList.filter(e=>e.trim()),hasBinding){const code="(function () {return ["+classList.join(", ")+"]})";try{output.result.classList=eval(code)}catch(e){throw e.isExpressionError=!0,e.expression=className,e}}else output.result.classList=classList.map(e=>e.slice(1,-1))}}function checkStyle(e,t,a,n){let s={};const r=t.log;if(e){if(_exp.default.singleExpr(e)){const n=_exp.default.removeExprffix(e);return _exp.default.isExpr(n)?r.push({line:a.line||1,column:a.col||1,reason:"ERROR: style 属性不能嵌套多层{{}}"}):s=(0,_exp.default)(e),void(t.result.style=s)}if(e.split(";").forEach((function(e){let t,l,o,i=e.trim().split(":");i.length>2&&(i[1]=i.slice(1).join(":"),i=i.slice(0,2)),2===i.length&&(t=i[0].trim(),t=(0,_utils.hyphenedToCamelCase)(t),l=i[1].trim(),l=(0,_exp.default)(l),o=_style.default.validateDelaration(t,l,n),l=o.value,l.forEach(e=>{((0,_utils.isValidValue)(e.v)||"function"==typeof e.v)&&(s[e.n]=e.v)}),o.log&&r.push({line:a.line||1,column:a.col||1,reason:o.log.reason}))})),"object"==typeof s)for(let e in s)_style.default.shouldAddToDependency(e,s[e])&&t.depFiles.push(s[e]);t.result.style=s}}function checkIs(e,t,a){const n=t.log;e?(e=_exp.default.addExprffix(e),t.result.is=(0,_exp.default)(e)):n.push({line:a.line||1,column:a.col||1,reason:"WARNING: is 属性为空"})}function checkIf(e,t,a,n,s){const r=t.log;e?(e=_exp.default.addExprffix(e),a?e="{{"+buildConditionExp(s)+"}}":(s.length>0&&(s.length=0),s.push(""+e.substr(2,e.length-4))),t.result.shown=(0,_exp.default)(e)):a||r.push({line:n.line||1,column:n.col||1,reason:"WARNING: if 属性为空"})}function checkElse(e,t,a,n){checkIf(e,t,!0,a,n),n.length=0}function checkElif(e,t,a,n,s){const r=a.log;let l=t;return e?(e=_exp.default.addExprffix(e),t=_exp.default.addExprffix(t),l="{{("+e.substr(2,e.length-4)+") && "+buildConditionExp(s)+"}}",a.result.shown=(0,_exp.default)(l),s.push(""+e.substr(2,e.length-4))):r.push({line:n.line||1,column:n.col||1,reason:"WARNING: Elif 属性为空"}),l}function checkFor(e,t,a){const n=t.log;if(e){let a,n;const s=(e=_exp.default.removeExprffix(e)).match(/(.*) (?:in) (.*)/);if(s){const t=s[1].match(/\((.*),(.*)\)/);t?(a=t[1].trim(),n=t[2].trim()):n=s[1].trim(),e=s[2]}let r;e="{{"+e+"}}",a||n?(r={exp:(0,_exp.default)(e)},a&&(r.key=a),n&&(r.value=n)):r=(0,_exp.default)(e),t.result.repeat=r}else n.push({line:a.line||1,column:a.col||1,reason:"WARNING: for 属性为空"})}function checkEvent(name,value,output){const originValue=value,eventName=name.replace(/^(on|@)/,"");if(eventName&&value){value=_exp.default.removeExprffix(value);const paramsMatch=value.match(/(.*)\((.*)\)/);if(paramsMatch){const funcName=paramsMatch[1];let params=paramsMatch[2];params?(params=params.split(/\s*,\s*/),-1===params.indexOf("evt")&&(params[params.length]="evt")):params=["evt"],value="{{"+funcName+"("+params.join(",")+")}}";try{value=eval("(function (evt) { return "+(0,_exp.default)(value,!1).replace("this.evt","evt")+"})")}catch(e){throw e.isExpressionError=!0,e.expression=originValue,e}}output.result.events=output.result.events||{},output.result.events[eventName]=value}}function checkAttr(e,t,a,n,s,r){if(e&&(0,_utils.isValidValue)(t)){if(shouldConvertPath(e,t,n)){(0,_utils.fileExists)(t,r.filePath)||a.log.push({line:s.line,column:s.column,reason:"WARNING: "+n+" 属性 "+e+" 的值 "+t+" 下不存在对应的文件资源"}),t=(0,_utils.resolvePath)(t,r.filePath),a.depFiles.push(t)}a.result.attr=a.result.attr||{},a.result.attr[(0,_utils.hyphenedToCamelCase)(e)]=(0,_exp.default)(t),"value"===e&&"text"===n&&a.log.push({line:s.line,column:s.column,reason:"WARNING: `value` 应该写在<text>标签中"})}}function shouldConvertPath(e,t,a){return!("alt"===e&&"blank"===t)&&!(!(["src","alt"].includes(e)&&t&&["img","video"].indexOf(a)>-1)||/^(data:|http|{{)/.test(t))}function isReservedTag(e){return tagReserved.hasOwnProperty(e)}function isTextContentAtomic(e){return tagTextContent.hasOwnProperty(e)&&tagAtomics.hasOwnProperty(e)}function isNotTextContentAtomic(e){return!tagTextContent.hasOwnProperty(e)&&!tagAtomics.hasOwnProperty(e)}function isSupportSpan(e){if(e&&"string"==typeof e)return tagChildrenMap[e]&&tagChildrenMap[e].indexOf("span")>-1}function getTagChildren(e){if(e&&"string"==typeof e)return tagChildrenMap[e]||[]}function isSupportedSelfClosing(e){if(e&&"string"==typeof e)return e=tagAliasMap[e]||e,tagNatives[e]&&!!tagNatives[e].selfClosing}function hasTagDefined(e){return tagNativeKeys.indexOf(e)>-1||tagComponents.indexOf(e)>-1}function isEmptyElement(e){return e=tagAliasMap[e]||e,tagNatives[e]&&!0===tagNatives[e].empty}function buildConditionExp(e){return e.map(e=>`!(${e})`).join(" && ")}function hasIfOrFor(e){let t=!1;return e.find(e=>(e.attrs||[]).findIndex(e=>["for","if"].indexOf(e.name)>-1)>-1?(t=!0,t):Array.isArray(e.childNodes)?(t=hasIfOrFor(e.childNodes),t):void 0),t}tagNativeKeys.forEach((function(e){tagReserved[e]=!0;const t=tagNatives[e];t.atomic&&(tagAtomics[e]=!0),t.textContent&&(tagTextContent[e]=!0),t.empty&&(tagEmpty[e]=!0),t.alias&&t.alias.length&&t.alias.forEach((function(t){tagAliasMap[t]=e})),!0===t.excludeRoot&&(tagNotRoot[e]=!0);let a=(0,_utils.extend)({},t.attrs);const n={},s={},r=[];t.attrs&&!1===t.attrs.extendCommon||(a=(0,_utils.extend)(a,tagCommon.attrs)),"extendCommon"in a&&delete a.extendCommon,Object.keys(a).forEach((function(e){const t=a[e];t.enum&&t.enum.length>0&&(n[e]=t.enum,s[e]=t.enum[0]),t.def&&(s[e]=t.def),!0===t.required&&r.push(e)})),tagAttrMap[e]=a,tagEnumAttrMap[e]=n,tagDefaultAttrMap[e]=s,tagRequireAttrMap[e]=r,tagChildrenMap[e]=t.children?(0,_utils.merge)([],tagCommon.children,t.children):null,tagOnceChildrenMap[e]=t.onceChildren?(0,_utils.merge)([],t.onceChildren):null,tagParentsMap[e]=t.parents?(0,_utils.merge)([],tagCommon.parents,t.parents):null,tagEventsMap[e]=(0,_utils.merge)([],tagCommon.events,t.events)}));var _default={checkTagName:checkTagName,checkId:checkId,checkClass:checkClass,checkStyle:checkStyle,checkIs:checkIs,checkIf:checkIf,checkElse:checkElse,checkElif:checkElif,checkFor:checkFor,checkEvent:checkEvent,checkAttr:checkAttr,checkBuild:checkBuild,checkModel:_model.default,isReservedTag:isReservedTag,isTextContentAtomic:isTextContentAtomic,isSupportSpan:isSupportSpan,getTagChildren:getTagChildren,isSupportedSelfClosing:isSupportedSelfClosing,isEmptyElement:isEmptyElement,isNotTextContentAtomic:isNotTextContentAtomic};exports.default=_default; | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = void 0; | ||
var _path = _interopRequireDefault(require("path")); | ||
var _sharedUtils = require("@hap-toolkit/shared-utils"); | ||
var _exp = _interopRequireDefault(require("./exp")); | ||
var _style = _interopRequireDefault(require("../style")); | ||
var _model = _interopRequireDefault(require("./model")); | ||
var _utils = require("../utils"); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/* | ||
* Copyright (C) 2017, hapjs.org. All rights reserved. | ||
*/ | ||
// 支持的后缀名列表 | ||
const extList = ['.mix', '.ux', '.vue']; // 富文本支持的类型 | ||
const richtextType = ['mix', 'ux']; // 标签拥有data属性 | ||
const REG_TAG_DATA_ATTR = /^data-\w+/; // class属性 | ||
const REG_CLASS_VALUE = /[^{}\s]+((?=\s)|$)|[^{}\s]*\{\{[^]*?\}\}[^\s]*/g; // 保留标签名 | ||
const RESERVED_TAGS = Object.keys(_utils.FRAG_TYPE).map(k => _utils.FRAG_TYPE[k]); // 公共属性定义 | ||
const tagCommon = { | ||
events: ['click', 'focus', 'blur', 'key', 'longpress', 'appear', 'disappear', 'swipe', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'resize', 'animationstart', 'animationiteration', 'animationend'], | ||
attrs: { | ||
id: {}, | ||
style: {}, | ||
class: {}, | ||
disabled: { | ||
enum: ['false', 'true'] | ||
}, | ||
if: { | ||
def: 'true' // 缺省值 | ||
}, | ||
elif: { | ||
def: 'true' | ||
}, | ||
else: {}, | ||
for: {}, | ||
tid: {}, | ||
show: { | ||
def: 'true' | ||
}, | ||
'aria-label': {}, | ||
'aria-unfocusable': { | ||
enum: ['false', 'true'] | ||
}, | ||
forcedark: { | ||
enum: ['true', 'false'] | ||
}, | ||
focusable: { | ||
enum: ['false', 'true'] | ||
}, | ||
vtags: {}, | ||
vindex: {}, | ||
autofocus: { | ||
enum: ['false', 'true'] | ||
}, | ||
descendantfocusability: { | ||
enum: ['before', 'after', 'block'] | ||
} | ||
}, | ||
children: ['block', 'slot', 'component'], | ||
// 通用控制组件 | ||
parents: ['block'] // 通用父组件 | ||
}; // 原生标签定义 | ||
// atomic: 不允许有子节点,但允许#text内容 | ||
// empty: 是否为空元素——不可能存在子节点(内嵌的元素或文本) | ||
// supportCard:是否支持卡片,默认不支持卡片 | ||
const tagNatives = { | ||
div: { | ||
supportCard: true, | ||
attrs: { | ||
enablevideofullscreencontainer: { | ||
enum: ['false', 'true'] | ||
} | ||
} | ||
}, | ||
a: { | ||
supportCard: true, | ||
textContent: true, | ||
children: ['span'], | ||
attrs: { | ||
visited: { | ||
enum: ['false', 'true'] | ||
}, | ||
href: {} | ||
} | ||
}, | ||
text: { | ||
supportCard: true, | ||
textContent: true, | ||
children: ['a', 'span'], | ||
attrs: { | ||
type: { | ||
enum: ['text', 'html'] | ||
} | ||
} | ||
}, | ||
span: { | ||
supportCard: true, | ||
textContent: true, | ||
excludeRoot: true, | ||
children: ['span'], | ||
parents: ['text', 'a', 'span'], | ||
attrs: { | ||
extendCommon: false, | ||
// 不支持通用属性 | ||
id: {}, | ||
style: {}, | ||
class: {}, | ||
for: {}, | ||
tid: {}, | ||
if: { | ||
def: 'true' | ||
}, | ||
elif: { | ||
def: 'true' | ||
}, | ||
else: {} | ||
} | ||
}, | ||
label: { | ||
supportCard: true, | ||
textContent: true, | ||
atomic: true, | ||
attrs: { | ||
target: {} | ||
} | ||
}, | ||
image: { | ||
events: ['complete', 'error'], | ||
empty: true, | ||
supportCard: true, | ||
selfClosing: true, | ||
alias: ['img'], | ||
atomic: true, | ||
attrs: { | ||
src: {}, | ||
alt: {}, | ||
enablenightmode: { | ||
enum: ['true', 'false'] | ||
}, | ||
autoplay: { | ||
enum: ['true', 'false'] | ||
} | ||
} | ||
}, | ||
slider: { | ||
selfClosing: true, | ||
atomic: true, | ||
attrs: { | ||
enabled: { | ||
enum: ['true', 'false'] | ||
}, | ||
min: { | ||
def: 0 | ||
}, | ||
max: { | ||
def: 100 | ||
}, | ||
step: { | ||
def: 1 | ||
}, | ||
value: { | ||
def: 0 | ||
} | ||
}, | ||
events: ['change'] | ||
}, | ||
web: { | ||
atomic: true, | ||
events: ['pagestart', 'pagefinish', 'titlereceive', 'error', 'message', 'progress'], | ||
attrs: { | ||
src: {}, | ||
trustedurl: {}, | ||
allowthirdpartycookies: { | ||
enum: ['false', 'true'] | ||
}, | ||
enablenightmode: { | ||
enum: ['true', 'false'] | ||
}, | ||
showloadingdialog: { | ||
enum: ['false', 'true'] | ||
}, | ||
supportzoom: { | ||
enum: ['true', 'false'] | ||
} | ||
} | ||
}, | ||
list: { | ||
children: ['list-item'], | ||
attrs: { | ||
scrollpage: { | ||
enum: ['false', 'true'] | ||
}, | ||
focusbehavior: { | ||
enum: ['aligned', 'edged'] | ||
} | ||
}, | ||
events: ['scroll', 'scrollbottom', 'scrolltop', 'scrollend', 'scrolltouchup', 'selected'] | ||
}, | ||
'list-item': { | ||
excludeRoot: true, | ||
parents: ['list'], | ||
attrs: { | ||
type: { | ||
required: true | ||
} | ||
} | ||
}, | ||
block: { | ||
excludeRoot: true, | ||
supportCard: true, | ||
attrs: { | ||
extendCommon: false, | ||
// 不支持通用属性 | ||
for: {}, | ||
tid: {}, | ||
if: { | ||
def: 'true' | ||
}, | ||
elif: { | ||
def: 'true' | ||
}, | ||
else: {} | ||
} | ||
}, | ||
component: { | ||
excludeRoot: true, | ||
attrs: { | ||
extendCommon: false, | ||
// 不支持通用属性 | ||
is: { | ||
required: true | ||
}, | ||
for: {}, | ||
tid: {}, | ||
if: { | ||
def: 'true' | ||
}, | ||
elif: { | ||
def: 'true' | ||
}, | ||
else: {} | ||
} | ||
}, | ||
slot: { | ||
excludeRoot: true, | ||
attrs: { | ||
name: { | ||
def: 'default' | ||
}, | ||
extendCommon: false, | ||
// 不支持通用属性 | ||
content: {} | ||
} | ||
}, | ||
input: { | ||
supportCard: true, | ||
selfClosing: true, | ||
atomic: true, | ||
empty: true, | ||
attrs: { | ||
type: { | ||
enum: ['text', 'button', 'checkbox', 'radio', 'email', 'date', 'time', 'number', 'password', 'tel', 'eventbutton'] | ||
}, | ||
autocomplete: { | ||
enum: ['on', 'off'] | ||
}, | ||
enterkeytype: { | ||
enum: ['default', 'next', 'go', 'done', 'send', 'search'] | ||
}, | ||
eventtype: { | ||
enum: ['shortcut'] | ||
}, | ||
model: {}, | ||
'true-value': {}, | ||
'false-value': {}, | ||
maxlength: {}, | ||
checked: { | ||
enum: ['false', 'true'] | ||
}, | ||
name: {}, | ||
value: {}, | ||
placeholder: {} | ||
}, | ||
events: ['change', 'enterkeyclick', 'selectionchange'] | ||
}, | ||
button: { | ||
supportCard: true, | ||
textContent: true, | ||
atomic: true | ||
}, | ||
refresh: { | ||
attrs: { | ||
offset: { | ||
def: '132px' | ||
}, | ||
refreshing: { | ||
enum: ['false', 'true'] | ||
}, | ||
type: { | ||
enum: ['auto', 'pulldown'] | ||
}, | ||
'enable-refresh': { | ||
enum: ['true', 'false'] | ||
} | ||
}, | ||
events: ['refresh'] | ||
}, | ||
refresh2: { | ||
attrs: { | ||
pulldownrefreshing: { | ||
enum: ['false', 'true'] | ||
}, | ||
pulluprefreshing: { | ||
enum: ['false', 'true'] | ||
}, | ||
animationduration: { | ||
def: 300 | ||
}, | ||
enablepulldown: { | ||
enum: ['true', 'false'] | ||
}, | ||
enablepullup: { | ||
enum: ['false', 'true'] | ||
}, | ||
reboundable: { | ||
enum: ['false', 'true'] | ||
}, | ||
gesture: { | ||
enum: ['true', 'false'] | ||
}, | ||
offset: { | ||
def: '132px' | ||
}, | ||
refreshing: { | ||
enum: ['false', 'true'] | ||
}, | ||
type: { | ||
enum: ['auto', 'pulldown'] | ||
} | ||
}, | ||
events: ['pulldownrefresh', 'pulluprefresh', 'refresh'], | ||
// 对于只允许出现一次的子节点,保留最后一个 | ||
onceChildren: ['refresh-header', 'refresh-footer'] | ||
}, | ||
'refresh-header': { | ||
attrs: { | ||
dragrate: { | ||
def: 300 | ||
}, | ||
triggerratio: { | ||
def: 300 | ||
}, | ||
tiggersize: { | ||
def: 300 | ||
}, | ||
maxdragratio: { | ||
def: 300 | ||
}, | ||
maxdragsize: { | ||
def: 300 | ||
}, | ||
refreshdisplayratio: { | ||
def: 300 | ||
}, | ||
refreshdisplaysize: { | ||
def: 300 | ||
}, | ||
spinnerstyle: { | ||
enum: ['translation', 'front', 'behind'] | ||
}, | ||
autorefresh: { | ||
enum: ['false', 'true'] | ||
}, | ||
translationwithcontent: { | ||
enum: ['false', 'true'] | ||
} | ||
}, | ||
events: ['move'], | ||
parents: ['refresh2'] | ||
}, | ||
'refresh-footer': { | ||
attrs: { | ||
dragrate: { | ||
def: 300 | ||
}, | ||
triggerratio: { | ||
def: 300 | ||
}, | ||
tiggersize: { | ||
def: 300 | ||
}, | ||
maxdragratio: { | ||
def: 300 | ||
}, | ||
maxdragdize: { | ||
def: 300 | ||
}, | ||
refreshdisplayratio: { | ||
def: 300 | ||
}, | ||
refreshdisplaysize: { | ||
def: 300 | ||
}, | ||
spinnerstyle: { | ||
enum: ['translation', 'front', 'behind'] | ||
}, | ||
autorefresh: { | ||
enum: ['false', 'true'] | ||
}, | ||
translationwithcontent: { | ||
enum: ['true', 'false'] | ||
} | ||
}, | ||
events: ['move'], | ||
parents: ['refresh2'] | ||
}, | ||
ad: { | ||
attrs: { | ||
unitid: { | ||
def: '' | ||
}, | ||
type: { | ||
def: 'native' | ||
} | ||
}, | ||
events: ['load', 'error'] | ||
}, | ||
swiper: { | ||
attrs: { | ||
index: { | ||
def: 0 | ||
}, | ||
autoplay: { | ||
enum: ['false', 'true'] | ||
}, | ||
interval: { | ||
def: 3000 | ||
}, | ||
indicator: { | ||
enum: ['true', 'false'] | ||
}, | ||
loop: { | ||
enum: ['true', 'false'] | ||
}, | ||
duration: {}, | ||
vertical: { | ||
enum: ['false', 'true'] | ||
}, | ||
previousmargin: { | ||
def: '0px' | ||
}, | ||
nextmargin: { | ||
def: '0px' | ||
}, | ||
enableswipe: { | ||
enum: ['true', 'false'] | ||
} | ||
}, | ||
events: ['change'] | ||
}, | ||
progress: { | ||
supportCard: true, | ||
selfClosing: true, | ||
atomic: true, | ||
attrs: { | ||
percent: { | ||
def: 0 | ||
}, | ||
type: { | ||
enum: ['horizontal', 'circular'] | ||
} | ||
} | ||
}, | ||
picker: { | ||
supportCard: true, | ||
selfClosing: true, | ||
atomic: true, | ||
attrs: { | ||
type: { | ||
required: true, | ||
enum: ['text', 'date', 'time', 'multi-text'] | ||
}, | ||
start: { | ||
def: '1970-1-1' | ||
}, | ||
end: { | ||
def: '2100-12-31' | ||
}, | ||
range: {}, | ||
selected: {}, | ||
value: {} | ||
}, | ||
events: ['change', 'columnchange', 'cancel'] | ||
}, | ||
switch: { | ||
supportCard: true, | ||
selfClosing: true, | ||
atomic: true, | ||
attrs: { | ||
checked: { | ||
enum: ['false', 'true'] | ||
} | ||
}, | ||
events: ['change'] | ||
}, | ||
textarea: { | ||
supportCard: true, | ||
atomic: true, | ||
textContent: true, | ||
attrs: { | ||
placeholder: {}, | ||
maxlength: {}, | ||
model: {} | ||
}, | ||
events: ['change', 'selectionchange', 'linechange'] | ||
}, | ||
video: { | ||
empty: true, | ||
attrs: { | ||
src: {}, | ||
muted: { | ||
enum: ['false', 'true'] | ||
}, | ||
autoplay: { | ||
enum: ['false', 'true'] | ||
}, | ||
controls: { | ||
enum: ['true', 'false'] | ||
}, | ||
poster: {}, | ||
orientation: { | ||
enum: ['landscape', 'portrait'] | ||
}, | ||
titlebar: { | ||
enum: ['true', 'false'] | ||
}, | ||
title: {}, | ||
playcount: {} | ||
}, | ||
events: ['prepared', 'start', 'pause', 'finish', 'error', 'seeking', 'seeked', 'timeupdate', 'fullscreenchange'] | ||
}, | ||
camera: { | ||
atomic: true, | ||
selfClosing: true, | ||
attrs: { | ||
deviceposition: { | ||
enum: ['back', 'front'] | ||
}, | ||
flash: { | ||
enum: ['auto', 'on', 'off', 'torch'] | ||
}, | ||
framesize: { | ||
enum: ['low', 'normal', 'high'] | ||
}, | ||
autoexposurelock: { | ||
enum: ['false', 'true'] | ||
}, | ||
autowhitebalancelock: { | ||
enum: ['false', 'true'] | ||
} | ||
}, | ||
events: ['error', 'camerainitdone', 'cameraframe'] | ||
}, | ||
map: { | ||
children: ['custommarker'], | ||
attrs: { | ||
latitude: {}, | ||
longitude: {}, | ||
coordtype: {}, | ||
scale: { | ||
def: 0 | ||
}, | ||
rotate: { | ||
def: 0 | ||
}, | ||
markers: {}, | ||
showmylocation: { | ||
enum: ['true', 'false'] | ||
}, | ||
polylines: {}, | ||
polygons: {}, | ||
circles: {}, | ||
controls: {}, | ||
groundoverlays: {}, | ||
includepoints: {}, | ||
heatmaplayer: {}, | ||
showcompass: { | ||
enum: ['true', 'false'] | ||
}, | ||
enableoverlooking: { | ||
enum: ['false', 'true'] | ||
}, | ||
enablezoom: { | ||
enum: ['true', 'false'] | ||
}, | ||
enablescroll: { | ||
enum: ['true', 'false'] | ||
}, | ||
enablerotate: { | ||
enum: ['true', 'false'] | ||
}, | ||
showscale: { | ||
enum: ['false', 'true'] | ||
}, | ||
showzoom: { | ||
enum: ['false', 'true'] | ||
} | ||
}, | ||
events: ['loaded', 'regionchange', 'tap', 'markertap', 'callouttap', 'controltap', 'poitap'] | ||
}, | ||
custommarker: { | ||
parents: ['map'], | ||
attrs: { | ||
custommarkerattr: {} | ||
} | ||
}, | ||
canvas: { | ||
atomic: true | ||
}, | ||
stack: { | ||
supportCard: true, | ||
events: ['fullscreenchange'] | ||
}, | ||
richtext: { | ||
textContent: true, | ||
atomic: true, | ||
attrs: { | ||
type: { | ||
required: true, | ||
enum: ['html'].concat(richtextType) | ||
} | ||
}, | ||
events: ['start', 'complete'] | ||
}, | ||
tabs: { | ||
children: ['tab-bar', 'tab-content'], | ||
attrs: { | ||
index: { | ||
def: 0 | ||
} | ||
}, | ||
events: ['change'] | ||
}, | ||
'tab-content': { | ||
parents: ['tabs'], | ||
attrs: { | ||
scrollable: { | ||
enum: ['true', 'false'] | ||
} | ||
} | ||
}, | ||
'tab-bar': { | ||
parents: ['tabs'], | ||
attrs: { | ||
mode: { | ||
enum: ['fixed', 'scrollable'] | ||
} | ||
} | ||
}, | ||
popup: { | ||
supportCard: true, | ||
children: ['text'], | ||
attrs: { | ||
target: { | ||
required: true | ||
}, | ||
placement: { | ||
enum: ['left', 'top', 'right', 'bottom', 'topLeft', 'topRight', 'bottomLeft', 'bottomRight'], | ||
def: 'bottom' | ||
} | ||
}, | ||
events: ['visibilitychange'] | ||
}, | ||
rating: { | ||
supportCard: true, | ||
atomic: true, | ||
attrs: { | ||
numstars: { | ||
def: '5' | ||
}, | ||
rating: { | ||
def: '0' | ||
}, | ||
stepsize: { | ||
def: '0.5' | ||
}, | ||
indicator: { | ||
enum: ['false', 'true'] | ||
} | ||
}, | ||
events: ['change'] | ||
}, | ||
select: { | ||
supportCard: true, | ||
children: ['option'], | ||
events: ['change'], | ||
excludeRoot: true, | ||
attrs: { | ||
model: {} | ||
} | ||
}, | ||
option: { | ||
supportCard: true, | ||
parents: ['select'], | ||
atomic: true, | ||
textContent: true, | ||
attrs: { | ||
selected: { | ||
def: false | ||
}, | ||
value: {} | ||
}, | ||
excludeRoot: true | ||
}, | ||
marquee: { | ||
supportCard: true, | ||
textContent: true, | ||
atomic: true, | ||
attrs: { | ||
scrollamount: { | ||
def: 6 | ||
}, | ||
loop: { | ||
def: -1 | ||
}, | ||
direction: { | ||
enum: ['left', 'right'] | ||
}, | ||
value: { | ||
def: '6px' | ||
} | ||
}, | ||
events: ['bounce', 'finish', 'start'] | ||
}, | ||
scrollview: { | ||
attrs: { | ||
'scroll-direction': { | ||
enum: ['vertical', 'horizontal', 'vertical_horizontal'] | ||
}, | ||
'show-scrollbar': { | ||
enum: ['true', 'false'] | ||
} | ||
}, | ||
events: ['scroll', 'scrollbegindrag', 'scrollenddrag', 'scrollstart', 'scrollstop'] | ||
}, | ||
drawer: { | ||
attrs: { | ||
enableswipe: { | ||
enum: ['true', 'false'] | ||
} | ||
}, | ||
events: ['change', 'scroll'] | ||
}, | ||
lottie: { | ||
empty: true, | ||
attrs: { | ||
source: { | ||
required: true | ||
}, | ||
progress: {}, | ||
speed: {}, | ||
loop: { | ||
enum: [true, false] | ||
}, | ||
autoplay: { | ||
enum: [false, true] | ||
}, | ||
rendermode: { | ||
enum: [`AUTOMATIC`, `HARDWARE`, `SOFTWARE`] | ||
} | ||
}, | ||
events: ['complete', 'error', 'change'] | ||
}, | ||
'drawer-navigation': { | ||
parents: ['drawer'], | ||
attrs: { | ||
direction: { | ||
enum: ['start', 'end'] | ||
} | ||
} | ||
}, | ||
'slide-view': { | ||
attrs: { | ||
edge: { | ||
enum: ['right', 'left'] | ||
}, | ||
enableslide: { | ||
enum: ['true', 'false'] | ||
}, | ||
isopen: { | ||
enum: ['false', 'true'] | ||
}, | ||
layer: { | ||
enum: ['above', 'same'] | ||
}, | ||
buttons: {} | ||
}, | ||
events: ['open', 'close', 'slide', 'buttonclick'] | ||
}, | ||
'section-list': { | ||
children: ['section-group', 'section-item'], | ||
events: ['scroll', 'scrollend', 'scrolltouchup', 'scrolltop', 'scrollbottom'] | ||
}, | ||
'section-group': { | ||
children: ['section-group', 'section-item', 'section-header'], | ||
attrs: { | ||
expand: { | ||
enum: ['false', 'true'] | ||
} | ||
}, | ||
events: ['change'] | ||
}, | ||
'section-header': { | ||
parents: ['section-group'] | ||
}, | ||
'section-item': {} | ||
}; // 保留标签 | ||
const tagReserved = {}; // 自定义组件标签名 | ||
let tagComponents = []; // 支持的组件列表 | ||
const tagNativeKeys = Object.keys(tagNatives); // 标签别名 | ||
const tagAliasMap = {}; // 标签属性 | ||
const tagAttrMap = {}; | ||
const tagEnumAttrMap = {}; | ||
const tagDefaultAttrMap = {}; | ||
const tagRequireAttrMap = {}; // 原子标签(允许子组件有text子组件) | ||
const tagAtomics = {}; // 空元素标签 | ||
// 不允许有子组件,也不允许标签间嵌套文字 | ||
// 因为没有子组件了,所以他们也允许写成自闭合标签 | ||
const tagEmpty = {}; // 带文本内容的标签 | ||
const tagTextContent = {}; // 允许子节点 | ||
const tagChildrenMap = {}; // 只允许出现一次的子节点 | ||
const tagOnceChildrenMap = {}; // 允许父节点 | ||
const tagParentsMap = {}; // 允许事件 | ||
const tagEventsMap = {}; // 不能作为根节点 | ||
const tagNotRoot = []; | ||
(function initRules() { | ||
tagNativeKeys.forEach(function (tagName) { | ||
tagReserved[tagName] = true; | ||
const tagInfo = tagNatives[tagName]; | ||
if (tagInfo.atomic) { | ||
tagAtomics[tagName] = true; | ||
} | ||
if (tagInfo.textContent) { | ||
tagTextContent[tagName] = true; | ||
} | ||
if (tagInfo.empty) { | ||
tagEmpty[tagName] = true; | ||
} | ||
if (tagInfo.alias && tagInfo.alias.length) { | ||
tagInfo.alias.forEach(function (n) { | ||
tagAliasMap[n] = tagName; | ||
}); | ||
} | ||
if (tagInfo.excludeRoot === true) { | ||
tagNotRoot[tagName] = true; | ||
} // 处理属性 | ||
let attrsMap = (0, _utils.extend)({}, tagInfo.attrs); | ||
const enumAttr = {}; | ||
const defaultAttr = {}; | ||
const requireAttr = []; // 合并标签的通用属性 | ||
if (!(tagInfo.attrs && tagInfo.attrs.extendCommon === false)) { | ||
attrsMap = (0, _utils.extend)(attrsMap, tagCommon.attrs); | ||
} // 从属性中去除通用属性标志位 | ||
if ('extendCommon' in attrsMap) { | ||
delete attrsMap.extendCommon; | ||
} | ||
Object.keys(attrsMap).forEach(function (name) { | ||
const attr = attrsMap[name]; | ||
if (attr.enum && attr.enum.length > 0) { | ||
enumAttr[name] = attr.enum; | ||
defaultAttr[name] = attr.enum[0]; | ||
} | ||
if (attr.def) { | ||
defaultAttr[name] = attr.def; | ||
} | ||
if (attr.required === true) { | ||
requireAttr.push(name); | ||
} | ||
}); | ||
tagAttrMap[tagName] = attrsMap; | ||
tagEnumAttrMap[tagName] = enumAttr; | ||
tagDefaultAttrMap[tagName] = defaultAttr; | ||
tagRequireAttrMap[tagName] = requireAttr; // 处理子节点 | ||
tagChildrenMap[tagName] = tagInfo.children ? (0, _utils.merge)([], tagCommon.children, tagInfo.children) : null; | ||
tagOnceChildrenMap[tagName] = tagInfo.onceChildren ? (0, _utils.merge)([], tagInfo.onceChildren) : null; // 处理父节点 | ||
tagParentsMap[tagName] = tagInfo.parents ? (0, _utils.merge)([], tagCommon.parents, tagInfo.parents) : null; // 处理事件 | ||
tagEventsMap[tagName] = (0, _utils.merge)([], tagCommon.events, tagInfo.events); | ||
}); | ||
})(); | ||
/** | ||
* 检查标签名 | ||
* @param {Object} node - 页面template模块的编译后的树对象 | ||
* @param {Object} output | ||
* @param {Object} output.result - 结果收集 | ||
* @param {Array} output.log - 日志收集 | ||
* @param {Object} options | ||
* @param {String} options.uxType - 文件类型 | ||
* @param {String} options.filePath - 当前执行文件的绝对路径 | ||
* @param {Array} options.importNames - 页面引入的自定义组件的name | ||
*/ | ||
function checkTagName(node, output, options = {}) { | ||
const result = output.result; | ||
const log = output.log; | ||
const type = _path.default.extname(options.filePath); | ||
let tagName = node.tagName; | ||
const childNodes = node.childNodes || []; | ||
const location = node.__location || {}; // 获取自定义组件名 | ||
tagComponents = options.importNames || []; // 处理别名 | ||
if (tagAliasMap[tagName]) { | ||
if (tagName !== 'img') { | ||
// `parse5`自动将image转换为img | ||
log.push({ | ||
line: location.line || 1, | ||
column: location.col || 1, | ||
reason: 'NOTE: 组件名 `' + tagName + '` 自动转换为 `' + tagAliasMap[tagName] + '`' | ||
}); | ||
} | ||
tagName = tagAliasMap[tagName]; | ||
} // 如果是组件标签 | ||
result.type = tagName; | ||
if (RESERVED_TAGS.indexOf(tagName) >= 0) { | ||
log.push({ | ||
line: location.line || 1, | ||
column: location.col || 1, | ||
reason: 'ERROR: 组件名 `' + tagName + '` 是系统保留的组件名, 请修改' | ||
}); | ||
} else if (!hasTagDefined(tagName)) { | ||
log.push({ | ||
line: location.line || 1, | ||
column: location.col || 1, | ||
reason: `WARN: 未识别的标签名 '${tagName}',如果是自定义组件,请确认已经引入` | ||
}); | ||
} | ||
if (options.uxType === _utils.ENTRY_TYPE.CARD && tagNatives[tagName] && !tagNatives[tagName].supportCard) { | ||
log.push({ | ||
line: location.line || 1, | ||
column: location.col || 1, | ||
reason: 'ERROR: 卡片不支持组件名 `' + tagName + '`, 请修改' | ||
}); | ||
} // 检测根组件合法性 | ||
if (node._isroot && tagNotRoot.hasOwnProperty(tagName)) { | ||
log.push({ | ||
line: location.line || 1, | ||
column: location.col || 1, | ||
reason: 'ERROR: 组件 `' + tagName + '` 不能作为根组件' | ||
}); | ||
} // 空组件标签,不允许有子元素 | ||
if (tagEmpty.hasOwnProperty(tagName)) { | ||
if (childNodes.length > 0) { | ||
log.push({ | ||
line: location.line || 1, | ||
column: location.col || 1, | ||
reason: 'ERROR: 组件 `' + tagName + '` 内不能有子节点与文字,请修改' | ||
}); | ||
} | ||
} // 如果是原子标签 | ||
if (tagAtomics.hasOwnProperty(tagName)) { | ||
// 如果没有文本内容 | ||
if (tagTextContent.hasOwnProperty(tagName)) { | ||
// 非文本节点 | ||
if (childNodes.length > 0) { | ||
// 不处理#text子节点 | ||
childNodes.every(child => { | ||
if (child.nodeName !== '#text') { | ||
log.push({ | ||
line: location.line || 1, | ||
column: location.col || 1, | ||
reason: 'ERROR: 组件 `' + tagName + '` 是原子类型,不应该有子节点' | ||
}); | ||
return false; | ||
} | ||
return true; | ||
}); | ||
} | ||
} else { | ||
// 文本节点 | ||
if (childNodes.length > 1 || childNodes[0] && childNodes[0].nodeName !== '#text') { | ||
// 只能是文本内容,否则报错 | ||
log.push({ | ||
line: location.line || 1, | ||
column: location.col || 1, | ||
reason: 'ERROR: 组件 `' + tagName + '` 只能有一个文字子节点' | ||
}); | ||
} | ||
} | ||
} // 检测非文本组件是否包含文本 | ||
// attr为空时,设置返回的attr的默认值 | ||
result.attr = result.attr || {}; // 处理必需属性 | ||
const attrs = node.attrs || []; // 标签的属性列表 | ||
const attrlist = []; // 转换为小写 | ||
attrs.forEach(function (item) { | ||
attrlist.push(item.name.toLowerCase()); | ||
}); // 处理缺省属性(如果值为空, 则设置为缺省值) | ||
if (tagDefaultAttrMap[tagName]) { | ||
Object.keys(tagDefaultAttrMap[tagName]).forEach(name => { | ||
const index = attrlist.indexOf(name); | ||
if (index >= 0 && attrs[index].value === '') { | ||
attrs[index].value = tagDefaultAttrMap[tagName][name]; | ||
log.push({ | ||
line: location.line || 1, | ||
column: location.col || 1, | ||
reason: 'ERROR: 组件 `' + tagName + '` 属性 `' + name + '` 值为空, 默认设置为缺省值 `' + tagDefaultAttrMap[tagName][name] + '`' | ||
}); | ||
} else if (tagName === 'slot' && index < 0) { | ||
node.attrs.push({ | ||
name, | ||
value: tagDefaultAttrMap[tagName][name] | ||
}); | ||
_sharedUtils.colorconsole.warn(`标签${tagName}的属性 \`${name}\` 值为空, 默认设置为缺省值 \`${tagDefaultAttrMap[tagName][name]}\` ${options.filePath}@${location.line || 1},${location.col || 1}`); | ||
} | ||
}); | ||
} // 检查根节点属性合法性 | ||
if (node._isroot) { | ||
const rootExcludeAttrList = ['for', 'if', 'elif', 'else', 'show']; | ||
attrlist.forEach(name => { | ||
if (rootExcludeAttrList.indexOf(name) >= 0) { | ||
log.push({ | ||
line: location.line || 1, | ||
column: location.col || 1, | ||
reason: 'ERROR: 根节点 `' + tagName + '` 不能使用属性 `' + name + '`' | ||
}); | ||
} | ||
}); | ||
} // 检查必需的属性 | ||
if (tagRequireAttrMap[tagName]) { | ||
tagRequireAttrMap[tagName].forEach(name => { | ||
if (attrlist.indexOf(name) < 0) { | ||
log.push({ | ||
line: location.line || 1, | ||
column: location.col || 1, | ||
reason: 'ERROR: 组件 `' + tagName + '` 没有定义属性 `' + name + '`' | ||
}); | ||
} | ||
}); | ||
} // 检查属性枚举值的合法性 | ||
if (tagEnumAttrMap[tagName]) { | ||
Object.keys(tagEnumAttrMap[tagName]).forEach(name => { | ||
const index = attrlist.indexOf(name); | ||
if (index >= 0) { | ||
const value = attrs[index].value; | ||
if (!_exp.default.isExpr(value)) { | ||
// 如果是表达式,则跳过检测 | ||
const enums = tagEnumAttrMap[tagName][name]; | ||
if (enums.indexOf(value) < 0) { | ||
attrs[index].value = enums[0]; | ||
log.push({ | ||
line: location.line || 1, | ||
column: location.col || 1, | ||
reason: 'ERROR: 组件 `' + tagName + '` 属性 `' + name + '` 的值 `' + value + '`非法, 默认设置为缺省值 `' + enums[0] + '`' | ||
}); | ||
} | ||
} | ||
} | ||
}); | ||
} // 检查属性的合法性 | ||
if (tagAttrMap[tagName] && tagName !== 'component') { | ||
attrlist.forEach(item => { | ||
if (!item.match(/^(on|@)/)) { | ||
// 对vue的属性检测适配 | ||
if (type.indexOf(extList[2]) >= -1) { | ||
if (/^(:|v-|key|ref|is|slot|slot-scope)/.test(item)) return; | ||
} | ||
const isDataAttr = checkDataAttr(item); | ||
const isTagAttr = Object.prototype.hasOwnProperty.call(tagAttrMap[tagName], item); // 避开事件和自定义属性(data-*),校验属性值 | ||
if (!(isDataAttr || isTagAttr)) { | ||
log.push({ | ||
line: location.line || 1, | ||
column: location.col || 1, | ||
reason: 'ERROR: 组件 `' + tagName + '` 不支持属性 `' + item + '`,支持的属性有 [' + Object.keys(tagAttrMap[tagName]).join(', ') + ']' | ||
}); | ||
} | ||
} | ||
}); | ||
} // 检查事件合法性 | ||
if (tagEventsMap[tagName] && tagName !== 'component') { | ||
const events = tagEventsMap[tagName]; | ||
attrlist.forEach(name => { | ||
if (name.match(/^(on|@)/)) { | ||
const eventName = name.replace(/^(on|@)/, ''); // 对卡片不支持的事件进行判断 | ||
const cardEventBlackList = ['appear', 'disappear', 'swipe']; | ||
if (options.uxType === _utils.ENTRY_TYPE.CARD && cardEventBlackList.indexOf(eventName.toLowerCase()) > -1) { | ||
log.push({ | ||
line: location.line || 1, | ||
column: location.col || 1, | ||
reason: 'ERROR: 卡片组件 `' + tagName + '` 不支持事件 `' + eventName + '`' | ||
}); | ||
} | ||
if (events.indexOf(eventName.toLowerCase()) < 0) { | ||
log.push({ | ||
line: location.line || 1, | ||
column: location.col || 1, | ||
reason: 'ERROR: 组件 `' + tagName + '` 不支持事件 `' + eventName + '`' | ||
}); | ||
} | ||
} | ||
}); | ||
} // 检查子组件合法性 | ||
if (childNodes.length > 0) { | ||
const slotSet = new Set(); | ||
const nodeNameMap = {}; | ||
childNodes.forEach((child, index) => { | ||
// 只有父组件和子组件均为保留标签时,才需要检测合法性 | ||
if (isReservedTag(tagName) && isReservedTag(child.nodeName)) { | ||
const tagParents = tagParentsMap[child.nodeName]; | ||
const tagChildren = tagChildrenMap[tagName]; | ||
const tagOnceChildren = tagOnceChildrenMap[tagName]; | ||
if (child.nodeName === 'slot') { | ||
let currentNode = child.parentNode; | ||
while (currentNode) { | ||
if (currentNode.tagName === 'slot') { | ||
_sharedUtils.colorconsole.warn(`slot标签内不应该嵌套slot ${options.filePath}@${location.line || 1},${location.col || 1}`); | ||
break; | ||
} | ||
currentNode = currentNode.parentNode; | ||
} | ||
const attrsMap = {}; | ||
child.attrs.map(item => { | ||
attrsMap[item.name] = item.value; | ||
}); | ||
if (!attrsMap.hasOwnProperty('name')) { | ||
attrsMap.name = 'default'; | ||
} else if (/\{\{\s*[\w$]+\s*\}\}/.test(attrsMap.name)) { | ||
_sharedUtils.colorconsole.warn(`标签${child.nodeName}的name属性暂时不支持动态绑定 ${options.filePath}@${location.line || 1},${location.col || 1}`); | ||
} | ||
if (slotSet.has(attrsMap.name)) { | ||
_sharedUtils.colorconsole.warn(`标签${tagName}内存在name为 \`${attrsMap.name}\` 重复slot ${options.filePath}@${location.line || 1},${location.col || 1}`); | ||
} else { | ||
slotSet.add(attrsMap.name); | ||
} | ||
} // 若子组件有parents属性,检查tagName是否为子组件合法的父组件;若父组件有children属性,检查child.nodeName是否为父组件合法的子组件 | ||
// 此处 if 条件不修改: 默认不包含在 tagOnceChildrenMap[child.nodeName] 中的组件也允许作为 tagName 组件的子组件 | ||
if (tagParents && tagParents.indexOf(tagName) < 0 || tagChildren && tagChildren.indexOf(child.nodeName) < 0) { | ||
log.push({ | ||
line: location.line || 1, | ||
column: location.col || 1, | ||
reason: 'ERROR: 组件 `' + tagName + '` 不支持子组件 `' + child.nodeName + '`' | ||
}); | ||
} else if (tagOnceChildren && tagOnceChildren.indexOf(child.nodeName) > -1) { | ||
let duplicated; | ||
if (nodeNameMap[child.nodeName] || nodeNameMap[child.nodeName] === 0) { | ||
duplicated = true; | ||
} // 对于只允许出现一次的子节点,保留最后一个的索引 | ||
nodeNameMap[child.nodeName] = { | ||
index, | ||
duplicated | ||
}; | ||
} | ||
} | ||
}); | ||
const keys = Object.keys(nodeNameMap); | ||
if (keys.length) { | ||
keys.filter(childName => nodeNameMap[childName].duplicated).forEach(childName => { | ||
log.push({ | ||
line: location.line || 1, | ||
column: location.col || 1, | ||
reason: `'WARNING: 组件 \`${childName}\` 只允许在 \`${tagName}\` 组件中出现一次` | ||
}); | ||
}); | ||
node.childNodes = childNodes.filter((child, index) => !(nodeNameMap[child.nodeName] && nodeNameMap[child.nodeName].duplicated && index !== nodeNameMap[child.nodeName].index)); | ||
} | ||
} // 检查是否漏写了结束标签(仅针对不支持自闭合的标签,如 <div>) | ||
if (!isSupportedSelfClosing(tagName) && location.startTag && !location.endTag) { | ||
log.push({ | ||
line: location.line || 1, | ||
column: location.col || 1, | ||
reason: 'ERROR: 组件 `' + tagName + '` 缺少闭合标签,请检查' | ||
}); | ||
} // 检查 list-item 内是否使用了指令 if 或者 for | ||
if (tagName === 'list-item' && hasIfOrFor(childNodes)) { | ||
log.push({ | ||
line: location.line || 1, | ||
column: location.col || 1, | ||
reason: 'WARN: `list-item` 内部需谨慎使用指令 if 或 for,相同 type 属性的 list-item, DOM 结构必须完全相同`' | ||
}); | ||
} | ||
} | ||
/** | ||
* 检测Id属性 | ||
* @param id | ||
* @param output | ||
*/ | ||
function checkId(id, output) { | ||
if (id) { | ||
output.result.id = _exp.default.isExpr(id) ? (0, _exp.default)(id) : id; | ||
} | ||
} | ||
/** | ||
* 判断自定义(data-*)属性 | ||
* @param attrName | ||
* @returns {boolean} | ||
*/ | ||
function checkDataAttr(attrName) { | ||
return REG_TAG_DATA_ATTR.test(attrName); | ||
} | ||
/** | ||
* 检测构造模式 | ||
* @param id | ||
* @param output | ||
*/ | ||
function checkBuild(mode, output) { | ||
if (mode) { | ||
output.result.append = mode === 'tree' ? 'tree' : 'single'; | ||
} | ||
} | ||
/** | ||
* 检测class属性 | ||
* 'a b c' -> ['a', 'b', 'c'] | ||
* 'a {{b}} c' -> function () {return ['a', this.b, 'c']} | ||
* @param className | ||
* @param output | ||
*/ | ||
function checkClass(className, output) { | ||
let hasBinding; | ||
let classList = []; | ||
className = className.trim(); | ||
if (className) { | ||
let start = 0; | ||
let end = 0; | ||
const segs = []; | ||
const matched = className.match(REG_CLASS_VALUE); | ||
if (matched) { | ||
let staticString; // 拆解 class 属性,将静态 class 和插值表达式分离 | ||
matched.forEach(exp => { | ||
end = className.indexOf(exp, start); | ||
staticString = className.slice(start, end); | ||
if (staticString.length) { | ||
segs.push(staticString); | ||
} | ||
segs.push(exp); | ||
start += staticString.length + exp.length; | ||
}); | ||
} | ||
segs.push(className.slice(start)); // trailing static classes | ||
classList = segs.reduce((list, seg) => { | ||
if (_exp.default.isExpr(seg)) { | ||
hasBinding = true; | ||
list.push((0, _exp.default)(seg, false)); | ||
return list; | ||
} | ||
return list.concat(seg.split(/\s+/g).filter(s => s).map(s => `'${s}'`)); | ||
}, []); | ||
classList = classList.filter(klass => klass.trim()); | ||
if (hasBinding) { | ||
const code = '(function () {return [' + classList.join(', ') + ']})'; | ||
try { | ||
/* eslint-disable no-eval */ | ||
output.result.classList = eval(code); | ||
/* eslint-enable no-eval */ | ||
} catch (err) { | ||
err.isExpressionError = true; | ||
err.expression = className; | ||
throw err; | ||
} | ||
} else { | ||
output.result.classList = classList.map( // 去掉引号 | ||
klass => klass.slice(1, -1)); | ||
} | ||
} | ||
} | ||
/** | ||
* 检查样式 | ||
* '{{a}}' --> function () { return this.a; } | ||
* 'b:{{v}};' --> {b: function() { return this.v; }} | ||
* @param cssText | ||
* @param output | ||
* @param locationInfo | ||
*/ | ||
function checkStyle(cssText, output, locationInfo, options) { | ||
let style = {}; | ||
const log = output.log; | ||
if (cssText) { | ||
if (_exp.default.singleExpr(cssText)) { | ||
// 检测是否嵌套{{}} | ||
const incText = _exp.default.removeExprffix(cssText); | ||
if (_exp.default.isExpr(incText)) { | ||
log.push({ | ||
line: locationInfo.line || 1, | ||
column: locationInfo.col || 1, | ||
reason: 'ERROR: style 属性不能嵌套多层{{}}' | ||
}); | ||
} else { | ||
style = (0, _exp.default)(cssText); | ||
} | ||
output.result.style = style; | ||
return; | ||
} // 如果是 a: {{}}; b: {{}};, 则分解处理 | ||
cssText.split(';').forEach(function (declarationText) { | ||
let k, v, vResult; | ||
let pair = declarationText.trim().split(':'); // 如果出现xxx:xxx:xxx的情况, 则将第一个:之后文本作为value | ||
if (pair.length > 2) { | ||
pair[1] = pair.slice(1).join(':'); | ||
pair = pair.slice(0, 2); | ||
} | ||
if (pair.length === 2) { | ||
k = pair[0].trim(); | ||
k = (0, _utils.hyphenedToCamelCase)(k); | ||
v = pair[1].trim(); | ||
v = (0, _exp.default)(v); // 处理值表达式 | ||
vResult = _style.default.validateDelaration(k, v, options); | ||
v = vResult.value; | ||
v.forEach(t => { | ||
// 如果校验成功,则保存转换后的属性值 | ||
if ((0, _utils.isValidValue)(t.v) || typeof t.v === 'function') { | ||
style[t.n] = t.v; | ||
} | ||
}); | ||
if (vResult.log) { | ||
log.push({ | ||
line: locationInfo.line || 1, | ||
column: locationInfo.col || 1, | ||
reason: vResult.log.reason | ||
}); | ||
} | ||
} | ||
}); | ||
if (typeof style === 'object') { | ||
for (let key in style) { | ||
if (_style.default.shouldAddToDependency(key, style[key])) { | ||
// 内联样式中的静态资源引用 | ||
output.depFiles.push(style[key]); | ||
} | ||
} | ||
} | ||
output.result.style = style; | ||
} | ||
} | ||
/** | ||
* 检查if语句 | ||
* @param value | ||
* @param output | ||
* @param not 是否取反 | ||
*/ | ||
function checkIs(value, output, locationInfo) { | ||
const log = output.log; | ||
if (value) { | ||
// 如果没有,补充上{{}} | ||
value = _exp.default.addExprffix(value); // 将表达式转换为function | ||
output.result.is = (0, _exp.default)(value); | ||
} else { | ||
log.push({ | ||
line: locationInfo.line || 1, | ||
column: locationInfo.col || 1, | ||
reason: 'WARNING: is 属性为空' | ||
}); | ||
} | ||
} | ||
/** | ||
* 检查if语句 | ||
* @param value | ||
* @param output | ||
* @param not 是否取反 | ||
*/ | ||
function checkIf(value, output, not, locationInfo, conditionList) { | ||
const log = output.log; | ||
if (value) { | ||
// 如果没有,补充上{{}} | ||
value = _exp.default.addExprffix(value); | ||
if (not) { | ||
value = '{{' + buildConditionExp(conditionList) + '}}'; | ||
} else { | ||
// if动作前需要清除conditionList之前的结构 | ||
conditionList.length > 0 && (conditionList.length = 0); | ||
conditionList.push(`${value.substr(2, value.length - 4)}`); | ||
} // 将表达式转换为function | ||
output.result.shown = (0, _exp.default)(value); | ||
} else { | ||
if (!not) { | ||
log.push({ | ||
line: locationInfo.line || 1, | ||
column: locationInfo.col || 1, | ||
reason: 'WARNING: if 属性为空' | ||
}); | ||
} | ||
} | ||
} | ||
/** | ||
* 检查else语句 | ||
* @param value | ||
* @param output | ||
* @param not | ||
*/ | ||
function checkElse(value, output, locationInfo, conditionList) { | ||
checkIf(value, output, true, locationInfo, conditionList); // else动作之后需清除conditionList之前的结构 | ||
conditionList.length = 0; | ||
} | ||
/** | ||
* 检查else if语句 | ||
* @param value | ||
* @param output | ||
* @param not | ||
*/ | ||
function checkElif(value, cond, output, locationInfo, conditionList) { | ||
const log = output.log; | ||
let newcond = cond; | ||
if (value) { | ||
// 如果没有,补充上{{}} | ||
value = _exp.default.addExprffix(value); | ||
cond = _exp.default.addExprffix(cond); | ||
newcond = '{{(' + value.substr(2, value.length - 4) + ') && ' + buildConditionExp(conditionList) + '}}'; // 将表达式转换为function | ||
output.result.shown = (0, _exp.default)(newcond); | ||
conditionList.push(`${value.substr(2, value.length - 4)}`); | ||
} else { | ||
log.push({ | ||
line: locationInfo.line || 1, | ||
column: locationInfo.col || 1, | ||
reason: 'WARNING: Elif 属性为空' | ||
}); | ||
} | ||
return newcond; | ||
} | ||
/** | ||
* 检查循环 | ||
* @param value | ||
* @param output | ||
*/ | ||
function checkFor(value, output, locationInfo) { | ||
const log = output.log; | ||
if (value) { | ||
// 如果是单一表达式,去除{{}} | ||
value = _exp.default.removeExprffix(value); // 如果是'key in values'的形式 | ||
let key; | ||
let val; | ||
const inMatch = value.match(/(.*) (?:in) (.*)/); | ||
if (inMatch) { | ||
// 如果key是以'(key,value)'格式 | ||
const itMatch = inMatch[1].match(/\((.*),(.*)\)/); | ||
if (itMatch) { | ||
key = itMatch[1].trim(); | ||
val = itMatch[2].trim(); | ||
} else { | ||
val = inMatch[1].trim(); | ||
} | ||
value = inMatch[2]; | ||
} | ||
value = '{{' + value + '}}'; | ||
let repeat; | ||
if (!key && !val) { | ||
repeat = (0, _exp.default)(value); | ||
} else { | ||
// 如果指定key,value | ||
repeat = { | ||
exp: (0, _exp.default)(value) | ||
}; | ||
if (key) { | ||
repeat.key = key; | ||
} | ||
if (val) { | ||
repeat.value = val; | ||
} | ||
} | ||
output.result.repeat = repeat; | ||
} else { | ||
log.push({ | ||
line: locationInfo.line || 1, | ||
column: locationInfo.col || 1, | ||
reason: 'WARNING: for 属性为空' | ||
}); | ||
} | ||
} | ||
/** | ||
* 检查事件属性 | ||
* @param {string} name 事件名以on开头 | ||
* @param {string} value | ||
* @param {object} output{result, deps[], log[]} | ||
*/ | ||
function checkEvent(name, value, output) { | ||
const originValue = value; // 去除开头的'on' | ||
const eventName = name.replace(/^(on|@)/, ''); | ||
if (eventName && value) { | ||
// 去除表达式的{{}} | ||
value = _exp.default.removeExprffix(value); // 如果表达式形式为XXX(xxxx) | ||
const paramsMatch = value.match(/(.*)\((.*)\)/); | ||
if (paramsMatch) { | ||
const funcName = paramsMatch[1]; | ||
let params = paramsMatch[2]; // 解析','分隔的参数 | ||
if (params) { | ||
params = params.split(/\s*,\s*/); // 如果参数中没有'$evt',则自动在末尾添加'$evt' | ||
if (params.indexOf('evt') === -1) { | ||
params[params.length] = 'evt'; | ||
} | ||
} else { | ||
// 否则默认有一个参数'$evt' | ||
params = ['evt']; | ||
} | ||
value = '{{' + funcName + '(' + params.join(',') + ')}}'; | ||
try { | ||
// 将事件转换为函数对象 | ||
/* eslint-disable no-eval */ | ||
value = eval('(function (evt) { return ' + (0, _exp.default)(value, false).replace('this.evt', 'evt') + '})'); | ||
/* eslint-enable no-eval */ | ||
} catch (err) { | ||
err.isExpressionError = true; | ||
err.expression = originValue; | ||
throw err; | ||
} | ||
} | ||
output.result.events = output.result.events || {}; | ||
output.result.events[eventName] = value; | ||
} | ||
} | ||
/** | ||
* @param {string} name | ||
* @param {string} value | ||
* @param {object} output{result, deps[], log[]} | ||
* @param {String} tagName | ||
* @param {object} locationInfo{line, column} | ||
* @param {object} options | ||
*/ | ||
function checkAttr(name, value, output, tagName, locationInfo, options) { | ||
if (name && (0, _utils.isValidValue)(value)) { | ||
if (shouldConvertPath(name, value, tagName)) { | ||
// 判断路径下资源是否存在 | ||
const hasFile = (0, _utils.fileExists)(value, options.filePath); | ||
if (!hasFile) { | ||
output.log.push({ | ||
line: locationInfo.line, | ||
column: locationInfo.column, | ||
reason: 'WARNING: ' + tagName + ' 属性 ' + name + ' 的值 ' + value + ' 下不存在对应的文件资源' | ||
}); | ||
} // 转换为以项目源码为根的绝对路径 | ||
value = (0, _utils.resolvePath)(value, options.filePath); | ||
output.depFiles.push(value); | ||
} | ||
output.result.attr = output.result.attr || {}; | ||
output.result.attr[(0, _utils.hyphenedToCamelCase)(name)] = (0, _exp.default)(value); | ||
if (name === 'value' && tagName === 'text') { | ||
output.log.push({ | ||
line: locationInfo.line, | ||
column: locationInfo.column, | ||
reason: 'WARNING: `value` 应该写在<text>标签中' | ||
}); | ||
} | ||
} | ||
} | ||
/** | ||
* 是否转换资源引用路径 | ||
* @param name {string} - 属性名 | ||
* @param value {string} - 属性值 | ||
* @param tagName {string} - 标签名 | ||
* @returns {boolean} | ||
*/ | ||
function shouldConvertPath(name, value, tagName) { | ||
const skip = name === 'alt' && value === 'blank'; | ||
if (skip) { | ||
return false; | ||
} | ||
if (['src', 'alt'].includes(name) && value && ['img', 'video'].indexOf(tagName) > -1) { | ||
if (!/^(data:|http|{{)/.test(value)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
/** | ||
* 是否为保留标签 | ||
*/ | ||
function isReservedTag(tag) { | ||
return tagReserved.hasOwnProperty(tag); | ||
} | ||
/** | ||
* 是否为包含text内容的原子组件 | ||
*/ | ||
function isTextContentAtomic(tag) { | ||
return tagTextContent.hasOwnProperty(tag) && tagAtomics.hasOwnProperty(tag); | ||
} | ||
/** | ||
* 非文本标签且非原子组件 | ||
*/ | ||
function isNotTextContentAtomic(tag) { | ||
return !tagTextContent.hasOwnProperty(tag) && !tagAtomics.hasOwnProperty(tag); | ||
} | ||
/** | ||
* 判断标签是否支持span | ||
* @param tag | ||
*/ | ||
function isSupportSpan(tag) { | ||
if (!tag || typeof tag !== 'string') { | ||
return; | ||
} | ||
return tagChildrenMap[tag] && tagChildrenMap[tag].indexOf('span') > -1; | ||
} | ||
/** | ||
* 获取标签支持的子节点 | ||
* @param tag | ||
* @returns {*|Array} | ||
*/ | ||
function getTagChildren(tag) { | ||
if (!tag || typeof tag !== 'string') { | ||
return; | ||
} | ||
return tagChildrenMap[tag] || []; | ||
} | ||
/** | ||
*判断标签是否支持自闭合 | ||
* @param tag | ||
*/ | ||
function isSupportedSelfClosing(tag) { | ||
if (!tag || typeof tag !== 'string') { | ||
return; | ||
} | ||
tag = tagAliasMap[tag] || tag; | ||
return tagNatives[tag] && !!tagNatives[tag].selfClosing; | ||
} | ||
/** | ||
* 判断标签名是否支持 | ||
* @param {string} tag - 标签的name | ||
*/ | ||
function hasTagDefined(tag) { | ||
return tagNativeKeys.indexOf(tag) > -1 || tagComponents.indexOf(tag) > -1; | ||
} | ||
/** | ||
* 检查是否为空元素 | ||
* @param {String} tagName - 标签名 | ||
* @returns {Boolean} | ||
*/ | ||
function isEmptyElement(tagName) { | ||
tagName = tagAliasMap[tagName] || tagName; | ||
return tagNatives[tagName] && tagNatives[tagName].empty === true; | ||
} | ||
/** | ||
* 输出条件取反的字符串表示 | ||
* @param list | ||
* @return {string} | ||
*/ | ||
function buildConditionExp(list) { | ||
return list.map(exp => { | ||
return `!(${exp})`; | ||
}).join(' && '); | ||
} | ||
/** | ||
* 检查节点中是否含有指令 if 或者 for | ||
* @param {Array} nodes | ||
*/ | ||
function hasIfOrFor(nodes) { | ||
let flag = false; | ||
nodes.find(item => { | ||
const index = (item.attrs || []).findIndex(attr => { | ||
return ['for', 'if'].indexOf(attr.name) > -1; | ||
}); | ||
if (index > -1) { | ||
flag = true; | ||
return flag; | ||
} | ||
if (Array.isArray(item.childNodes)) { | ||
flag = hasIfOrFor(item.childNodes); | ||
return flag; | ||
} | ||
}); | ||
return flag; | ||
} | ||
var _default = { | ||
checkTagName, | ||
checkId, | ||
checkClass, | ||
checkStyle, | ||
checkIs, | ||
checkIf, | ||
checkElse, | ||
checkElif, | ||
checkFor, | ||
checkEvent, | ||
checkAttr, | ||
checkBuild, | ||
checkModel: _model.default, | ||
isReservedTag, | ||
isTextContentAtomic, | ||
isSupportSpan, | ||
getTagChildren, | ||
isSupportedSelfClosing, | ||
isEmptyElement, | ||
isNotTextContentAtomic | ||
}; | ||
exports.default = _default; | ||
//# sourceMappingURL=validator.js.map |
321
lib/utils.js
@@ -1,2 +0,321 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.extend=extend,exports.merge=merge,exports.hyphenedToCamelCase=hyphenedToCamelCase,exports.camelCaseToHyphened=camelCaseToHyphened,exports.isEmptyObject=isEmptyObject,exports.serialize=serialize,exports.splitAttr=splitAttr,exports.isValidValue=isValidValue,exports.resolvePath=resolvePath,exports.fileExists=fileExists,exports.FRAG_TYPE=exports.ENTRY_TYPE=void 0;var _path=_interopRequireDefault(require("path")),_fs=_interopRequireDefault(require("fs")),_config=_interopRequireDefault(require("@hap-toolkit/shared-utils/config"));function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function extend(e,...t){if("function"==typeof Object.assign)Object.assign(e,...t);else{const r=t.shift();for(const t in r)e[t]=r[t];t.length&&extend(e,...t)}return e}function merge(e,...t){return t.length&&t.forEach(t=>{e=e.concat(t)}),e}function hyphenedToCamelCase(e){return e.replace(/-([a-z])/g,(function(e,t){return t.toUpperCase()}))}function camelCaseToHyphened(e){return e.replace(/([A-Z])/g,(function(e,t){return"-"+t.toLowerCase()}))}function isEmptyObject(e){if(!e)return!0;for(const t in e)return!1;return!0}function serialize(e,t){const r=typeof e;if("undefined"===r)return e;if("function"===r)return e.toString();const n=[];let o=-1,i=`__FKS_${Math.random().toString(16).slice(2,10)}_FKE__`;const s=JSON.stringify(e,(e,t)=>"function"==typeof t?"":t);for(;s.indexOf(i)>-1;)i=`_${i}_`;let a=JSON.stringify(e,(function(e,t){return"function"==typeof t?(n.push(t),o++,i+o):t}),t);const f=new RegExp(`"${i}(\\d+)"`,"g");return a=a.replace(f,(e,t)=>n[t].toString()),a}function splitAttr(e,t){const r=[];if(t)switch(e.forEach((e,t)=>{r[t]={},r[t].n=e}),t.length){case 1:e.forEach((e,n)=>{r[n].v=t[0]});break;case 2:e.forEach((e,n)=>{r[n].v=n%2?t[1]:t[0]});break;case 3:e.forEach((e,n)=>{r[n].v=n%2?t[1]:t[n]});break;default:e.forEach((e,n)=>{r[n].v=t[n]})}return r}function isValidValue(e){return"number"==typeof e||"string"==typeof e}function resolvePath(e,t){if(t&&!_path.default.isAbsolute(e)){const r=_path.default.join(_path.default.dirname(t),e),n=_path.default.relative(_path.default.resolve(_config.default.projectPath,"./src"),r);e=_path.default.join("/",n).replace(/\\/g,"/")}return e}function fileExists(e,t){let r;if(_path.default.isAbsolute(e)){const t=_config.default.projectPath;r=_path.default.join(t,"./src",e)}else r=_path.default.resolve(t,"../",e);return _fs.default.existsSync(r)}const ENTRY_TYPE={APP:"app",PAGE:"page",COMP:"comp",CARD:"card",JS:"js"};exports.ENTRY_TYPE=ENTRY_TYPE;const FRAG_TYPE={IMPORT:"import",TEMPLATE:"template",STYLE:"style",SCRIPT:"script"};exports.FRAG_TYPE=FRAG_TYPE; | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.extend = extend; | ||
exports.merge = merge; | ||
exports.hyphenedToCamelCase = hyphenedToCamelCase; | ||
exports.camelCaseToHyphened = camelCaseToHyphened; | ||
exports.isEmptyObject = isEmptyObject; | ||
exports.serialize = serialize; | ||
exports.splitAttr = splitAttr; | ||
exports.isValidValue = isValidValue; | ||
exports.resolvePath = resolvePath; | ||
exports.fileExists = fileExists; | ||
exports.FRAG_TYPE = exports.ENTRY_TYPE = void 0; | ||
var _path = _interopRequireDefault(require("path")); | ||
var _fs = _interopRequireDefault(require("fs")); | ||
var _config = _interopRequireDefault(require("@hap-toolkit/shared-utils/config")); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/* | ||
* Copyright (C) 2017, hapjs.org. All rights reserved. | ||
*/ | ||
// TODO duplicate of packager/src/common/utils.js | ||
/** | ||
* 扩展对象属性 | ||
* @param dest | ||
* @param src | ||
*/ | ||
function extend(target, ...src) { | ||
if (typeof Object.assign === 'function') { | ||
Object.assign(target, ...src); | ||
} else { | ||
const first = src.shift(); // 覆盖旧值 | ||
for (const key in first) { | ||
target[key] = first[key]; | ||
} | ||
if (src.length) { | ||
extend(target, ...src); | ||
} | ||
} | ||
return target; | ||
} | ||
/** | ||
* 合并数组属性 | ||
* @param dest | ||
* @param src | ||
*/ | ||
function merge(target, ...src) { | ||
if (src.length) { | ||
src.forEach(item => { | ||
target = target.concat(item); | ||
}); | ||
} | ||
return target; | ||
} | ||
/** | ||
* -xxx-xxx转换为XxxXxx | ||
* @param value | ||
* @returns {void|string|XML|*} | ||
*/ | ||
function hyphenedToCamelCase(value) { | ||
return value.replace(/-([a-z])/g, function (s, m) { | ||
return m.toUpperCase(); | ||
}); | ||
} | ||
/** | ||
* XxxXxx转换为-xxx-xxx | ||
* @param value | ||
* @returns {void|string|XML|*} | ||
*/ | ||
function camelCaseToHyphened(value) { | ||
return value.replace(/([A-Z])/g, function (s, m) { | ||
return '-' + m.toLowerCase(); | ||
}); | ||
} | ||
/** | ||
* 判断对象是否为空(没有任何属性) | ||
* @param e | ||
* @returns {boolean} | ||
*/ | ||
function isEmptyObject(obj) { | ||
if (!obj) { | ||
return !0; | ||
} | ||
/* eslint-disable no-unused-vars */ | ||
for (const t in obj) { | ||
return !1; | ||
} | ||
/* eslint-enable no-unused-vars */ | ||
return !0; | ||
} | ||
/** | ||
* 可序列化包含函数的的数据 | ||
* JSON.stringify 不能直接对函数进行序列化 | ||
* 序列化结果的函数两侧不会带有双引号 | ||
* | ||
* 如: | ||
* serialize({ | ||
* func: function foo () { | ||
* | ||
* } | ||
* }, 2) | ||
* => | ||
* // 字符串 | ||
* { | ||
* "func": function foo () { | ||
* | ||
* } | ||
* } | ||
* 参考 [Yahoo][1] 的实现,但解决了“碰撞”问题 | ||
* | ||
* [1]: https://github.com/yahoo/serialize-javascript/blob/master/index.js | ||
* | ||
* @param {*} target - 转换对象 | ||
* @param {Number | String} [space] - JSON.stringify 的第三个参数 | ||
* @returns {String} | ||
*/ | ||
function serialize(target, space) { | ||
const type = typeof target; | ||
if (type === 'undefined') { | ||
return target; | ||
} | ||
if (type === 'function') { | ||
return target.toString(); | ||
} // 用于保存出现的函数 | ||
const functions = []; // 函数在上面数组中的序号(index) | ||
let id = -1; | ||
/* | ||
* 1, 生成一个用户数据中不会出现的占位符,标记函数出现的位置 | ||
* 先将数据做简单序列化(函数的 key 也会计入检查),用于检查占位符, | ||
* 确保不会出现“碰撞” (用户数据中正好包含占位符) | ||
*/ | ||
let PLACEHOLDER = `__FKS_${Math.random().toString(16).slice(2, 10)}_FKE__`; | ||
const origin = JSON.stringify(target, (key, value) => typeof value === 'function' ? '' : value); | ||
while (origin.indexOf(PLACEHOLDER) > -1) { | ||
PLACEHOLDER = `_${PLACEHOLDER}_`; | ||
} | ||
/* | ||
* 2, 使用 JSON.stringify 做初步的序列化 | ||
* 保存出现的 function,并用占位符暂时替代 | ||
*/ | ||
let code = JSON.stringify(target, function (key, value) { | ||
if (typeof value === 'function') { | ||
functions.push(value); | ||
id++; | ||
return PLACEHOLDER + id; // 每个函数对应的占位符 | ||
} | ||
return value; | ||
}, space); | ||
/* | ||
* 3, 将 functions 中保存的函数转成字符串,替换对应占位符(包括双引号) | ||
*/ | ||
const PLACEHOLDER_RE = new RegExp(`"${PLACEHOLDER}(\\d+)"`, 'g'); | ||
code = code.replace(PLACEHOLDER_RE, (_, id) => functions[id].toString()); | ||
return code; | ||
} | ||
/** | ||
* 拆分上下左右类型简写属性 | ||
* @param names 属性名数组 | ||
* @param values 属性值数组 | ||
* @returns {array} | ||
*/ | ||
function splitAttr(names, values) { | ||
const resultArray = []; | ||
if (values) { | ||
names.forEach((n, idx) => { | ||
resultArray[idx] = {}; | ||
resultArray[idx].n = n; | ||
}); | ||
switch (values.length) { | ||
case 1: | ||
names.forEach((n, idx) => { | ||
resultArray[idx].v = values[0]; | ||
}); | ||
break; | ||
case 2: | ||
names.forEach((n, idx) => { | ||
if (idx % 2) { | ||
resultArray[idx].v = values[1]; | ||
} else { | ||
resultArray[idx].v = values[0]; | ||
} | ||
}); | ||
break; | ||
case 3: | ||
names.forEach((n, idx) => { | ||
if (idx % 2) { | ||
resultArray[idx].v = values[1]; | ||
} else { | ||
resultArray[idx].v = values[idx]; | ||
} | ||
}); | ||
break; | ||
default: | ||
names.forEach((n, idx) => { | ||
resultArray[idx].v = values[idx]; | ||
}); | ||
} | ||
} | ||
return resultArray; | ||
} | ||
/** | ||
* 值的有效性检验 | ||
* @param value 值 | ||
*/ | ||
function isValidValue(value) { | ||
return typeof value === 'number' || typeof value === 'string'; | ||
} | ||
/** | ||
* @param relativePath | ||
* @param filePath | ||
* @returns {*} | ||
* @desc 将文件相对路径转为项目根目录('src/')下的绝对路径 | ||
*/ | ||
function resolvePath(relativePath, filePath) { | ||
if (filePath && !_path.default.isAbsolute(relativePath)) { | ||
const absolutePath = _path.default.join(_path.default.dirname(filePath), relativePath); | ||
const relativeProjectPath = _path.default.relative(_path.default.resolve(_config.default.projectPath, './src'), absolutePath); | ||
const newAbsolutePath = _path.default.join('/', relativeProjectPath); // 兼容windows | ||
relativePath = newAbsolutePath.replace(/\\/g, '/'); | ||
} | ||
return relativePath; | ||
} | ||
/** | ||
* 判断文件是否存在 | ||
* @param {string} resourcePath | ||
* @param {string} filePath | ||
* @returns {boolean} | ||
*/ | ||
function fileExists(resourcePath, filePath) { | ||
let absolutePath; | ||
if (_path.default.isAbsolute(resourcePath)) { | ||
const projectPath = _config.default.projectPath; | ||
absolutePath = _path.default.join(projectPath, './src', resourcePath); | ||
} else { | ||
absolutePath = _path.default.resolve(filePath, '../', resourcePath); | ||
} | ||
return _fs.default.existsSync(absolutePath); | ||
} | ||
/** | ||
* ux文件的类型 | ||
*/ | ||
const ENTRY_TYPE = { | ||
APP: 'app', | ||
PAGE: 'page', | ||
COMP: 'comp', | ||
CARD: 'card', | ||
JS: 'js' | ||
}; | ||
/** | ||
* 片段的类型 | ||
*/ | ||
exports.ENTRY_TYPE = ENTRY_TYPE; | ||
const FRAG_TYPE = { | ||
IMPORT: 'import', | ||
TEMPLATE: 'template', | ||
STYLE: 'style', | ||
SCRIPT: 'script' | ||
}; | ||
exports.FRAG_TYPE = FRAG_TYPE; | ||
//# sourceMappingURL=utils.js.map |
{ | ||
"name": "@hap-toolkit/compiler", | ||
"version": "1.9.3-beta.1", | ||
"version": "1.9.3-beta.2", | ||
"description": "compiler of hap-toolkit", | ||
@@ -21,3 +21,3 @@ "engines": { | ||
"dependencies": { | ||
"@hap-toolkit/shared-utils": "1.9.3-beta.1", | ||
"@hap-toolkit/shared-utils": "1.9.3-beta.2", | ||
"css": "^2.2.4", | ||
@@ -27,3 +27,3 @@ "css-what": "^2.1.3", | ||
}, | ||
"gitHead": "e6d13c5b1825854d462fff9646f14a76a48d1334" | ||
"gitHead": "b8a1dcd1a9bb285635d09b8020460389da67ed6c" | ||
} |
Sorry, the diff of this file is too big to display
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
197795
6245
2
+ Added@hap-toolkit/shared-utils@1.9.3-beta.2(transitive)
- Removed@hap-toolkit/shared-utils@1.9.3-beta.1(transitive)