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
+
+ {{ a?.c + a?.d }}
+
+ {{ a?.d ? 'a' : 'b' }}
+ {{ a?.g[e?.d] }}
+
+
+
+```
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,