diff --git a/docs-vuepress/.vuepress/config.js b/docs-vuepress/.vuepress/config.js index 6152782617..5f708253f6 100644 --- a/docs-vuepress/.vuepress/config.js +++ b/docs-vuepress/.vuepress/config.js @@ -19,7 +19,8 @@ const sidebar = { 'basic/event', 'basic/two-way-binding', 'basic/component', - 'basic/refs' + 'basic/refs', + 'basic/option-chain' ] }, { diff --git a/docs-vuepress/guide/basic/option-chain.md b/docs-vuepress/guide/basic/option-chain.md new file mode 100644 index 0000000000..8d7475d6c8 --- /dev/null +++ b/docs-vuepress/guide/basic/option-chain.md @@ -0,0 +1,33 @@ +# 模版内可选链表达式 + +Mpx提供了在模版中使用可选链`?.`访问变量属性的能力,用法/能力和JS的可选链基本一致,可以在任意被`{{}}`所包裹的模版语法内使用。 +> 实现原理是在编译时将使用`?.`的部分转化为`wxs`函数调用,运行时通过`wxs`访问变量属性,模拟出可选链的效果。 + +使用示例: +```html + + + +``` diff --git a/packages/webpack-plugin/lib/runtime/oc.wxs b/packages/webpack-plugin/lib/runtime/oc.wxs new file mode 100644 index 0000000000..5c3b1feb94 --- /dev/null +++ b/packages/webpack-plugin/lib/runtime/oc.wxs @@ -0,0 +1,16 @@ +// 可选链wxs +module.exports.g = function (val, valKeyArr) { + var res = val + if (typeof val !== 'object') { + return undefined + } + var len = valKeyArr.length + var i = 0 + while (i < len) { + if ((res = res[valKeyArr[i]]) === undefined) { + break + } + i++ + } + return res +} diff --git a/packages/webpack-plugin/lib/template-compiler/compiler.js b/packages/webpack-plugin/lib/template-compiler/compiler.js index 630db5a4b7..9a9870e48c 100644 --- a/packages/webpack-plugin/lib/template-compiler/compiler.js +++ b/packages/webpack-plugin/lib/template-compiler/compiler.js @@ -104,6 +104,7 @@ let env let platformGetTagNamespace let filePath let refId +let haveOptionChain = false function updateForScopesMap () { forScopes.forEach((scope) => { @@ -164,6 +165,8 @@ const i18nWxsRequest = '~' + i18nWxsLoaderPath + '!' + i18nWxsPath const i18nModuleName = '__i18n__' const stringifyWxsPath = '~' + normalize.lib('runtime/stringify.wxs') const stringifyModuleName = '__stringify__' +const optionsChainWxsPath = '~' + normalize.lib('runtime/oc.wxs') +const optionsChainWxsName = '__oc__' const tagRES = /(\{\{(?:.|\n|\r)+?\}\})(?!})/ const tagRE = /\{\{((?:.|\n|\r)+?)\}\}(?!})/ @@ -637,6 +640,7 @@ function parse (template, options) { forScopes = [] forScopesMap = {} hasI18n = false + haveOptionChain = false platformGetTagNamespace = options.getTagNamespace || no @@ -761,6 +765,10 @@ function parse (template, options) { } } + if (haveOptionChain) { + injectWxs(meta, optionsChainWxsName, optionsChainWxsPath) + } + injectNodes.forEach((node) => { addChild(root, node, true) }) @@ -1156,6 +1164,7 @@ function parseMustacheWithContext (raw = '') { }) } + exp = parseOptionChain(exp) if (i18n) { for (const i18nFuncName of i18nFuncNames) { const funcNameRE = new RegExp(`(? { + mapValue.forEach(({ key, value }) => { + this[key] = this.changeState(mapKey, value) + }) + }) + }, + changeState (key, value) { + if (!this.count[key]) { + this.count[key] = 0 + } + return () => { + this.count[key] = this.count[key] + value + return this.count[key] + } + }, + checkState () { + return Object.values(this.count).find(i => i) + } + } + let leftIndex = optionsRes.index + let rightIndex = leftIndex + 2 + let haveNotGetValue = true + let chainValue = '' + let chainKey = '' + let notCheck = false + grammarMap.init() + // 查 ?. 左边值 + while (haveNotGetValue && leftIndex > 0) { + const left = str[leftIndex - 1] + const grammar = grammarMap[left] + if (notCheck) { + // 处于表达式内 + chainValue = left + chainValue + if (grammar) { + grammar() + if (!grammarMap.checkState()) { + // 表达式结束 + notCheck = false + } + } + } else if (~[']', ')'].indexOf(left)) { + // 命中表达式,开始记录表达式 + chainValue = left + chainValue + notCheck = true + grammar() + } else if (left !== ' ') { + if (!/[A-Za-z0-9_$.]/.test(left)) { + // 结束 + haveNotGetValue = false + leftIndex++ + } else { + // 正常语法 + chainValue = left + chainValue + } + } + leftIndex-- + } + if (grammarMap.checkState() && haveNotGetValue) { + // 值查找结束但是语法未闭合或者处理到边界还未结束,抛异常 + throw new Error('[optionChain] option value illegal!!!') + } + haveNotGetValue = true + let keyValue = '' + // 查 ?. 右边key + while (haveNotGetValue && rightIndex < strLength) { + const right = str[rightIndex] + const grammar = grammarMap[right] + if (notCheck) { + // 处于表达式内 + if (grammar) { + grammar() + if (grammarMap.checkState()) { + keyValue += right + } else { + // 表达式结束 + notCheck = false + chainKey += `,${keyValue}` + keyValue = '' + } + } else { + keyValue += right + } + } else if (~['[', '('].indexOf(right)) { + // 命中表达式,开始记录表达式 + grammar() + if (keyValue) { + chainKey += `,'${keyValue}'` + keyValue = '' + } + notCheck = true + } else if (!/[A-Za-z0-9_$.?]/.test(right)) { + // 结束 + haveNotGetValue = false + rightIndex-- + } else if (right !== '?') { + // 正常语法 + if (right === '.') { + if (keyValue) { + chainKey += `,'${keyValue}'` + } + keyValue = '' + } else { + keyValue += right + } + } + rightIndex++ + } + if (grammarMap.checkState() && haveNotGetValue) { + // key值查找结束但是语法未闭合或者处理到边界还未结束,抛异常 + throw new Error('[optionChain] option key illegal!!!') + } + if (keyValue) { + chainKey += `,'${keyValue}'` + } + str = str.slice(0, leftIndex) + `${wxsName}(${chainValue},[${chainKey.slice(1)}])` + str.slice(rightIndex) + if (!haveOptionChain) { + haveOptionChain = true + } + } + return str +} + module.exports = { parseComponent, parse,