diff --git a/packages/hap-compiler/src/style/validator.js b/packages/hap-compiler/src/style/validator.js index 262cad8f..862b99f9 100755 --- a/packages/hap-compiler/src/style/validator.js +++ b/packages/hap-compiler/src/style/validator.js @@ -2656,7 +2656,24 @@ const validatorMap = { starSecondary: validator.url, // font fontSrc: validator.fontSrc, - fontFamily: validator.fontFamily + fontFamily: validator.fontFamily, + themeColor: makeEnumValidator([ + 'uxCardColorTheme', + 'uxCardColorAccent', + 'uxCardColorPrimary', + 'uxCardColorSecondary', + 'uxCardColorSecondaryVariant', + 'uxCardColorTertiary', + 'uxCardColorQuaternary', + 'uxCardColorContainer', + 'uxCardBackground', + 'uxCardColorHue', + 'uxCardColorHueSecondary', + 'uxIconColorAccent', + 'uxIconColorPrimary', + 'uxIconColorSecondary', + 'uxIconColorBackground' + ]) } /** @@ -2672,7 +2689,7 @@ function validate(name, value, options) { const validator = validatorMap[name] if (typeof validator === 'function') { - if (typeof value !== 'function') { + if (typeof value !== 'function' && value.indexOf('function') < 0) { if (mightReferlocalResource(name)) { result = validator(value, options) } else { diff --git a/packages/hap-compiler/src/template/exp.js b/packages/hap-compiler/src/template/exp.js index ee5ac312..c5c86e15 100644 --- a/packages/hap-compiler/src/template/exp.js +++ b/packages/hap-compiler/src/template/exp.js @@ -35,7 +35,7 @@ function trimhtml(str) { * @param isLite is lite card * @returns {*} */ -function transExpr(expContent, toFunc, isLite) { +function transExpr(expContent, toFunc, isLite, isCard) { let ret const trimExpContent = expContent.trim() if (!textParser.isExpr(trimExpContent)) { @@ -64,8 +64,12 @@ function transExpr(expContent, toFunc, isLite) { ret = ret.join(' + ') if (toFunc !== false) { try { - /* eslint-disable no-eval */ - ret = eval('(function () {return ' + ret + '})') + if (isCard) { + ret = 'function () {return ' + ret + '}' + } else { + /* eslint-disable no-eval */ + ret = eval('(function () {return ' + ret + '})') + } /* eslint-enable no-eval */ } catch (err) { err.isExpressionError = true diff --git a/packages/hap-compiler/src/template/index.js b/packages/hap-compiler/src/template/index.js index 329db3e8..828e4238 100644 --- a/packages/hap-compiler/src/template/index.js +++ b/packages/hap-compiler/src/template/index.js @@ -362,7 +362,13 @@ function parse(source, options) { { treeAdapter: parse5.treeAdapters.default, locationInfo: true }, options.filePath ) - const output = { result: {}, log: [], depFiles: [], isLite: !!options.lite } + const output = { + result: {}, + log: [], + depFiles: [], + isCard: !!options.card, + isLite: !!options.lite + } // 模板为空或解析失败 /* istanbul ignore if */ diff --git a/packages/hap-compiler/src/template/model.js b/packages/hap-compiler/src/template/model.js index 4ab6dd4a..e8cf07fc 100644 --- a/packages/hap-compiler/src/template/model.js +++ b/packages/hap-compiler/src/template/model.js @@ -102,9 +102,16 @@ function genCheckboxModel(node, attrName, value, output) { ${expValue} = checked ? ${trueValueBinding} : ${falseValueBinding} }` - addAttr(output.result, attrName, eval(`(function() {${attrCheckedCode}})`)) - addHandler(output.result, 'change', eval(`(function(evt) {${eventChangeCode}})`)) - + const isCard = output.isCard + const isLite = output.isLite + if (isCard && !isLite) { + addAttr(output.result, attrName, `(function() {${attrCheckedCode}})`) + addAttr(output.result, attrName + 'Raw', value) + addHandler(output.result, 'change', `function(evt) {${eventChangeCode}}`) + } else { + addAttr(output.result, attrName, eval(`(function() {${attrCheckedCode}})`)) + addHandler(output.result, 'change', eval(`(function(evt) {${eventChangeCode}})`)) + } return { attr: { checked: attrCheckedCode }, events: { change: eventChangeCode } @@ -125,8 +132,16 @@ function genRadioModel(node, attrName, value, output) { const attrCheckedCode = `return ${exp(value, false)} === ${valueBinding}` const eventChangeCode = `${exp(value, false)} = ${valueBinding}` - addAttr(output.result, attrName, eval(`(function() {${attrCheckedCode}})`)) - addHandler(output.result, 'change', eval(`(function(evt) {${eventChangeCode}})`)) + const isCard = output.isCard + const isLite = output.isLite + if (isCard && !isLite) { + addAttr(output.result, attrName, `(function() {${attrCheckedCode}})`) + addAttr(output.result, attrName + 'Raw', value) + addHandler(output.result, 'change', `function(evt) {${eventChangeCode}}`) + } else { + addAttr(output.result, attrName, eval(`(function() {${attrCheckedCode}})`)) + addHandler(output.result, 'change', eval(`(function(evt) {${eventChangeCode}})`)) + } return { attr: { checked: attrCheckedCode }, @@ -140,11 +155,17 @@ function genRadioModel(node, attrName, value, output) { * @param {object} output 构建的输出结果 */ function genSelectModel(value, output) { - addHandler( - output.result, - 'change', - eval(`(function(evt) { ${exp(value, false)} = evt.newValue})`) - ) + const isCard = output.isCard + const isLite = output.isLite + if (isCard && !isLite) { + addHandler(output.result, 'change', `function(evt) { ${exp(value, false)} = evt.newValue}`) + } else { + addHandler( + output.result, + 'change', + eval(`(function(evt) { ${exp(value, false)} = evt.newValue})`) + ) + } } /** @@ -156,10 +177,16 @@ function genSelectModel(value, output) { */ function genDefaultModel(attrName, value, output) { const eventChangeCode = `${exp(value, false)} = evt.target.value` - - addAttr(output.result, attrName, exp(value)) - addHandler(output.result, 'change', eval(`(function(evt) {${eventChangeCode}})`)) - + const isCard = output.isCard + const isLite = output.isLite + if (isCard && !isLite) { + addAttr(output.result, attrName, exp(value, true, isLite, isCard)) + addAttr(output.result, attrName + 'Raw', value) + addHandler(output.result, 'change', `function(evt) {${eventChangeCode}}`) + } else { + addAttr(output.result, attrName, exp(value)) + addHandler(output.result, 'change', eval(`(function(evt) {${eventChangeCode}})`)) + } return { events: { change: eventChangeCode } } } @@ -176,12 +203,22 @@ function genComponentModel(node, attrName, value, output, locationInfo, options) // 自定义组件model指令绑定的属性,依然作为普通属性处理 validator.checkAttr(attrName, value, output, node.tagName, locationInfo, options) - // 为自定义组件绑定update:${attrName}事件,接收组件内部emit的update:${attrName}事件 - addHandler( - output.result, - `update:${attrName}`, - eval(`(function(evt) { ${exp(value, false)} = evt.detail})`) - ) + const isCard = output.isCard + const isLite = output.isLite + if (isCard && !isLite) { + addHandler( + output.result, + `update:${attrName}`, + `function(evt) { ${exp(value, false)} = evt.detail}` + ) + } else { + // 为自定义组件绑定update:${attrName}事件,接收组件内部emit的update:${attrName}事件 + addHandler( + output.result, + `update:${attrName}`, + eval(`(function(evt) { ${exp(value, false)} = evt.detail})`) + ) + } } /** @@ -196,40 +233,76 @@ function genDynamicModel(node, attrName, value, output, expType) { const checkboxCode = genCheckboxModel(node, attrName, value, output) const radioCode = genRadioModel(node, attrName, value, output) const textCode = genDefaultModel(attrName, value, output) + const isCard = output.isCard + const isLite = output.isLite + if (isCard && !isLite) { + addAttr(output.result, attrName, exp(value, true, isLite, isCard)) + addAttr(output.result, attrName + 'Raw', value) - addAttr(output.result, attrName, exp(value)) + addAttr( + output.result, + 'checked', + `(function() { + if (${expType} === 'checkbox') { + ${checkboxCode.attr.checked} + } else if (${expType} === 'radio') { + ${radioCode.attr.checked} + } else { + return false + } + }) + ` + ) - addAttr( - output.result, - 'checked', - eval(` - (function() { - if (${expType} === 'checkbox') { - ${checkboxCode.attr.checked} - } else if (${expType} === 'radio') { - ${radioCode.attr.checked} - } else { - return false - } - }) - `) - ) + addAttr(output.result, 'checkedRaw', value) + addHandler( + output.result, + 'change', + `function(evt) { + if (${expType} === 'checkbox') { + ${checkboxCode.events.change} + } else if (${expType} === 'radio') { + ${radioCode.events.change} + } else { + ${textCode.events.change} + } + }` + ) + } else { + addAttr(output.result, attrName, exp(value)) - addHandler( - output.result, - 'change', - eval(` - (function(evt) { - if (${expType} === 'checkbox') { - ${checkboxCode.events.change} - } else if (${expType} === 'radio') { - ${radioCode.events.change} - } else { - ${textCode.events.change} - } - }) - `) - ) + 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} + } + }) + `) + ) + } } /** diff --git a/packages/hap-compiler/src/template/validator.js b/packages/hap-compiler/src/template/validator.js index 4c747dc1..1b540f54 100644 --- a/packages/hap-compiler/src/template/validator.js +++ b/packages/hap-compiler/src/template/validator.js @@ -1420,7 +1420,12 @@ function checkTagName(node, output, options = {}) { */ function checkId(id, output) { if (id) { - output.result.id = exp.isExpr(id) ? exp(id, true, output.isLite) : id + const isLite = output.isLite + const isCard = output.isCard + output.result.id = exp.isExpr(id) ? exp(id, true, isLite, isCard) : id + if (isCard && !isLite) { + output.result.idRaw = id + } } } @@ -1456,7 +1461,8 @@ function checkClass(className, output) { let classList = [] className = className.trim() - + const isLite = output.isLite + const isCard = output.isCard if (className) { let start = 0 let end = 0 @@ -1477,7 +1483,6 @@ function checkClass(className, output) { } segs.push(className.slice(start)) // trailing static classes - const isLite = output.isLite classList = segs.reduce((list, seg) => { if (exp.isExpr(seg)) { hasBinding = true @@ -1500,14 +1505,19 @@ function checkClass(className, output) { err.expression = className throw err } - output.result.class = className output.result.classList = classList } else if (hasBinding) { - const code = '(function () {return [' + classList.join(', ') + ']})' try { - /* eslint-disable no-eval */ - output.result.classList = eval(code) - /* eslint-enable no-eval */ + let code = '' + if (isCard) { + code = 'function () {return [' + classList.join(', ') + ']}' + output.result.classList = code + } else { + code = '(function () {return [' + classList.join(', ') + ']})' + /* eslint-disable no-eval */ + output.result.classList = eval(code) + /* eslint-enable no-eval */ + } } catch (err) { err.isExpressionError = true err.expression = className @@ -1520,6 +1530,12 @@ function checkClass(className, output) { ) } } + if (isCard) { + output.result.class = className + if (!isLite) { + output.result.classListRaw = className + } + } } /** @@ -1532,9 +1548,11 @@ function checkClass(className, output) { */ function checkStyle(cssText, output, locationInfo, options) { let style = {} + let styleRaw = {} const log = output.log if (cssText) { const isLite = output.isLite + const isCard = output.isCard if (exp.singleExpr(cssText)) { // 检测是否嵌套{{}} const incText = exp.removeExprffix(cssText) @@ -1545,9 +1563,12 @@ function checkStyle(cssText, output, locationInfo, options) { reason: 'ERROR: style 属性不能嵌套多层{{}}' }) } else { - style = exp(cssText, true, isLite) + style = exp(cssText, true, isLite, isCard) } output.result.style = style + if (isCard && !isLite) { + output.result.styleRaw = cssText + } return } // 如果是 a: {{}}; b: {{}};, 则分解处理 @@ -1563,13 +1584,18 @@ function checkStyle(cssText, output, locationInfo, options) { k = pair[0].trim() k = hyphenedToCamelCase(k) v = pair[1].trim() - v = exp(v, true, isLite) // 处理值表达式 + + const valueRaw = v + v = exp(v, true, isLite, isCard) // 处理值表达式 vResult = styler.validateDelaration(k, v, options) v = vResult.value v.forEach((t) => { // 如果校验成功,则保存转换后的属性值 if (isValidValue(t.v) || typeof t.v === 'function') { style[t.n] = t.v + if (isCard && !isLite) { + styleRaw[t.n] = valueRaw + } } }) if (vResult.log) { @@ -1590,6 +1616,9 @@ function checkStyle(cssText, output, locationInfo, options) { } } output.result.style = style + if (isCard && !isLite) { + output.result.styleRaw = styleRaw + } } } @@ -1604,9 +1633,14 @@ function checkIs(value, output, locationInfo) { if (value) { // 如果没有,补充上{{}} value = exp.addExprffix(value) + const isLite = output.isLite + const isCard = output.isCard // 将表达式转换为function - output.result.is = exp(value, true, output.isLite) + output.result.is = exp(value, true, isLite, isCard) + if (isCard && !isLite) { + output.result.isRaw = value + } } else { log.push({ line: locationInfo.line || 1, @@ -1628,6 +1662,7 @@ function checkIf(value, output, not, locationInfo, conditionList) { // 如果没有,补充上{{}} value = exp.addExprffix(value) const isLite = output.isLite + const isCard = output.isCard if (not) { value = '{{' + buildConditionExp(conditionList) + '}}' } else { @@ -1636,7 +1671,10 @@ function checkIf(value, output, not, locationInfo, conditionList) { conditionList.push(`${value.substr(2, value.length - 4)}`) } // 将表达式转换为function - output.result.shown = isLite ? value : exp(value, true) + output.result.shown = isLite ? value : exp(value, true, isLite, isCard) + if (isCard && !isLite) { + output.result.shownRaw = value + } } else { if (!not) { log.push({ @@ -1674,11 +1712,15 @@ function checkElif(value, cond, output, locationInfo, conditionList) { value = exp.addExprffix(value) cond = exp.addExprffix(cond) const isLite = output.isLite + const isCard = output.isCard newcond = '{{(' + value.substr(2, value.length - 4) + ') && ' + buildConditionExp(conditionList) + '}}' // 将表达式转换为function - output.result.shown = isLite ? newcond : exp(newcond) + output.result.shown = isLite ? newcond : exp(newcond, true, isLite, isCard) + if (isCard && !isLite) { + output.result.shownRaw = newcond + } conditionList.push(`${value.substr(2, value.length - 4)}`) } else { log.push({ @@ -1719,12 +1761,15 @@ function checkFor(value, output, locationInfo) { value = '{{' + value + '}}' const isLite = output.isLite - let repeat + const isCard = output.isCard + let repeat, repeatRaw if (!key && !val) { - repeat = exp(value, true, isLite) + repeat = exp(value, true, isLite, isCard) + repeatRaw = value } else { // 如果指定key,value - repeat = { exp: exp(value, true, isLite) } + repeat = { exp: exp(value, true, isLite, isCard) } + repeatRaw = { expRaw: value } if (key) { repeat.key = key } @@ -1733,6 +1778,9 @@ function checkFor(value, output, locationInfo) { } } output.result.repeat = repeat + if (isCard && !isLite) { + output.result.repeatRaw = repeatRaw + } } else { log.push({ line: locationInfo.line || 1, @@ -1759,6 +1807,12 @@ function checkEvent(name, value, output) { // 如果表达式形式为XXX(xxxx) const paramsMatch = value.match(/(.*)\((.*)\)/) if (paramsMatch) { + if (output.isLite) { + const err = new Error('轻卡不支持带参数的事件') + err.isExpressionError = true + err.expression = value + throw err + } const funcName = paramsMatch[1] let params = paramsMatch[2] // 解析','分隔的参数 @@ -1775,11 +1829,15 @@ function checkEvent(name, value, output) { value = '{{' + funcName + '(' + params.join(',') + ')}}' try { // 将事件转换为函数对象 - /* eslint-disable no-eval */ - value = output.isLite - ? value - : eval('(function (evt) { return ' + exp(value, false).replace('this.evt', 'evt') + '})') - /* eslint-enable no-eval */ + if (output.isCard && !output.isLite) { + value = 'function (evt) { return ' + exp(value, false).replace('this.evt', 'evt') + '}' + } else { + /* eslint-disable no-eval */ + value = eval( + '(function (evt) { return ' + exp(value, false).replace('this.evt', 'evt') + '})' + ) + /* eslint-enable no-eval */ + } } catch (err) { err.isExpressionError = true err.expression = originValue @@ -1805,12 +1863,24 @@ function checkCustomDirective(name, value, output, node) { colorconsole.warn(`\`${node.tagName}\` 组件自定义指令名称不能为空`) return false } - + const isCard = output.isCard + const isLite = output.isLite output.result.directives = output.result.directives || [] - output.result.directives.push({ - name: dirName, - value: exp.isExpr(value) ? exp(value, true, output.isLite) : value - }) + if (isCard && !isLite) { + // 补全绑定值的双花括号,如:dir:指令名称="data"补全为dir:指令名称="{{data}}" + value = exp.addExprffix(value) + + output.result.directives.push({ + name: dirName, + value: exp.isExpr(value) ? exp(value, true, output.isLite, output.isCard) : value, + valueRaw: value + }) + } else { + output.result.directives.push({ + name: dirName, + value: exp.isExpr(value) ? exp(value, true, output.isLite, output.isCard) : value + }) + } } /** @@ -1840,8 +1910,13 @@ function checkAttr(name, value, output, tagName, locationInfo, options) { value = resolvePath(value, options.filePath) output.depFiles.push(value) } + const isLite = output.isLite + const isCard = output.isCard output.result.attr = output.result.attr || {} - output.result.attr[hyphenedToCamelCase(name)] = exp(value, true, output.isLite) + output.result.attr[hyphenedToCamelCase(name)] = exp(value, true, isLite, isCard) + if (isCard && !isLite) { + output.result.attr[hyphenedToCamelCase(name) + 'Raw'] = value + } if (name === 'value' && tagName === 'text') { output.log.push({ line: locationInfo.line, @@ -1982,6 +2057,9 @@ function hasIfOrFor(nodes) { return flag } +/** + * 检查class数组是否为常量和变量混合的方式,如 "clazz1 {{myClass}}" + */ function isValidClassArray(arr) { const filterArr = arr.filter((clazz) => clazz.length > 0) diff --git a/packages/hap-dsl-xvm/src/loaders/style-loader.js b/packages/hap-dsl-xvm/src/loaders/style-loader.js index 9adf42db..ae12c6d8 100644 --- a/packages/hap-dsl-xvm/src/loaders/style-loader.js +++ b/packages/hap-dsl-xvm/src/loaders/style-loader.js @@ -30,6 +30,7 @@ const componentId = (() => { export default function styleLoader(code) { const self = this + const options = loaderUtils.parseQuery(this.resourceQuery) const loaderQuery = loaderUtils.parseQuery(this.query) const suppresslogs = !!getWebpackOptions(this).suppresslogs const resourcePath = this.resourcePath // 当前文件绝对路径 @@ -40,7 +41,7 @@ export default function styleLoader(code) { query: loaderQuery }) - if (compileOptionsObject.enableExtractCss) { + if (compileOptionsObject.enableExtractCss && !options.card) { componentId.add(resourcePath) if (jsonStyle) { jsonStyle[`@info`] = { diff --git a/packages/hap-dsl-xvm/src/loaders/ux-fragment-utils.js b/packages/hap-dsl-xvm/src/loaders/ux-fragment-utils.js index 0d95ce15..e6486996 100644 --- a/packages/hap-dsl-xvm/src/loaders/ux-fragment-utils.js +++ b/packages/hap-dsl-xvm/src/loaders/ux-fragment-utils.js @@ -51,7 +51,7 @@ const defaultLoaders = { * @param uxType * @returns {*} */ -function makeLoaderString(type, config, uxType) { +function makeLoaderString(type, config, isCard, uxType) { config = config || {} let loaders @@ -146,10 +146,11 @@ function makeLoaderString(type, config, uxType) { } ] - compileOptionsObject.enableExtractCss && + if (compileOptionsObject.enableExtractCss && !isCard) { loaders.unshift({ name: defaultLoaders['extract-css'] }) + } let lang = config.lang if (lang && lang !== 'css') { @@ -263,12 +264,14 @@ function makeLoaderString(type, config, uxType) { * @param $loader * @param imports - 外部导入的组件列表 * @param importNames - 外部导入的组件名列表 - * @param {number} lite 1:轻卡; 0:普通卡 + * @param {number} card 1:卡片 + * @param {number} lite 1:轻卡 * @returns {string} */ -function processImportFrag($loader, imports, importNames, lite) { +function processImportFrag($loader, imports, importNames, card, lite) { let retStr = '' if (imports.length) { + const cardParam = card ? `&card=${card}` : '' const liteParam = lite ? `&lite=${lite}` : '' for (let i = 0; i < imports.length; i++) { const imp = imports[i] @@ -305,8 +308,8 @@ function processImportFrag($loader, imports, importNames, lite) { }) let reqStr = makeRequireString( $loader, - makeLoaderString(FRAG_TYPE.IMPORT), - `${importSrc}?uxType=${ENTRY_TYPE.COMP}&name=${importName}${liteParam}` + makeLoaderString(FRAG_TYPE.IMPORT, null, card), + `${importSrc}?uxType=${ENTRY_TYPE.COMP}&name=${importName}${cardParam}${liteParam}` ) if (compileOptionsObject.stats) { @@ -326,9 +329,10 @@ function processImportFrag($loader, imports, importNames, lite) { * @param templates * @param uxType * @param importNames - * @param {number} lite 1:轻卡; 0:普通卡 + * @param {number} card 1:卡片 + * @param {number} lite 1:轻卡 */ -function processTemplateFrag($loader, templates, uxType, importNames, lite) { +function processTemplateFrag($loader, templates, uxType, importNames, card, lite) { let retStr = '{}' if (!templates.length) { $loader.emitError(new Error('需要模板