@mpxjs/core 2.9.36 → 2.9.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,289 @@
1
+ import cssSelect from './css-select'
2
+ // todo: stringify wxs 模块只能放到逻辑层执行,主要还是因为生成 vdom tree 需要根据 class 去做匹配,需要看下这个代码从哪引入
3
+ import stringify from '@mpxjs/webpack-plugin/lib/runtime/stringify.wxs'
4
+ import Interpreter from './interpreter'
5
+ import { dash2hump, isString, error } from '@mpxjs/utils'
6
+
7
+ const deepCloneNode = function (val) {
8
+ return JSON.parse(JSON.stringify(val))
9
+ }
10
+
11
+ function simpleNormalizeChildren (children) {
12
+ for (let i = 0; i < children.length; i++) {
13
+ if (Array.isArray(children[i])) {
14
+ return Array.prototype.concat.apply([], children)
15
+ }
16
+ }
17
+ return children
18
+ }
19
+
20
+ export default function _genVnodeTree (astData, contextScope, options) {
21
+ const { template = {}, styles = [] } = astData || {}
22
+ const { moduleId, location } = options || {}
23
+ // 解除引用
24
+ const templateAst = deepCloneNode(template)
25
+ // 获取实例 uid
26
+ const uid = contextScope[0]?.__mpxProxy?.uid || contextScope[0]?.uid
27
+ // 动态化组件 slots 通过上下文传递,相当于 props
28
+ const slots = contextScope[0]?.slots || {}
29
+
30
+ function createEmptyNode () {
31
+ return createNode('block')
32
+ }
33
+
34
+ function genVnodeTree (node) {
35
+ if (node.type === 1) {
36
+ // wxs 模块不需要动态渲染
37
+ if (node.tag === 'wxs') {
38
+ return createEmptyNode()
39
+ } else if (node.for && !node.forProcessed) {
40
+ return genFor(node)
41
+ } else if (node.if && !node.ifProcessed) {
42
+ return genIf(node)
43
+ } else if (node.tag === 'slot') {
44
+ return genSlot(node)
45
+ } else {
46
+ const data = genData(node)
47
+ let children = genChildren(node)
48
+ // 运行时组件的子组件都通过 slots 属性传递,样式规则在当前组件内匹配后挂载
49
+ if (node.dynamic) {
50
+ data.slots = resolveSlot(children.map(item => genVnodeWithStaticCss(deepCloneNode(item))))
51
+ children = []
52
+ }
53
+ return createNode(node.tag, data, children)
54
+ }
55
+ } else if (node.type === 3) {
56
+ return genText(node)
57
+ }
58
+ }
59
+
60
+ function evalExps (exps) {
61
+ const interpreter = new Interpreter(contextScope)
62
+ // 消除引用关系
63
+ let value
64
+ try {
65
+ value = interpreter.eval(JSON.parse(JSON.stringify(exps)))
66
+ } catch (e) {
67
+ const errmsg = e.message
68
+ console.warn(errmsg)
69
+ error('interprete the expression wrong: ', location, {
70
+ errType: 'mpx-dynamic-interprete',
71
+ errmsg
72
+ })
73
+ }
74
+ return value
75
+ }
76
+
77
+ function createNode (tag, data = {}, children = []) {
78
+ if (Array.isArray(data)) {
79
+ children = data
80
+ data = {}
81
+ }
82
+ if (typeof tag === 'object') {
83
+ return tag
84
+ }
85
+
86
+ // 处理 for 循环产生的数组,同时清除空节点
87
+ children = simpleNormalizeChildren(children).filter(node => !!node?.nt)
88
+
89
+ return {
90
+ nt: tag,
91
+ d: data,
92
+ c: children
93
+ }
94
+ }
95
+
96
+ /**
97
+ *
98
+ * 样式隔离的匹配策略优化:
99
+ *
100
+ * 条件1: 子组件不能影响到父组件的样式
101
+ * 条件2: slot 的内容必须在父组件的上下文当中完成样式匹配
102
+ * 条件3: 匹配过程只能进行一次
103
+ *
104
+ * 方案一:根据 moduleId 即作用域来进行匹配
105
+ * 方案二:根据虚拟树来进行匹配
106
+ */
107
+ // function createDynamicNode (moduleId, data = {}, children = []) {
108
+ // const { template = {}, styles = [] } = staticMap[moduleId]
109
+ // data.$slots = resolveSlot(children) // 将 slot 通过上下文传递到子组件的渲染流程中
110
+ // const vnodeTree = _genVnodeTree(template, [data], styles, moduleId)
111
+ // return vnodeTree
112
+ // }
113
+
114
+ function resolveSlot (children) {
115
+ const slots = {}
116
+ if (children.length) {
117
+ for (let i = 0; i < children.length; i++) {
118
+ const child = children[i]
119
+ const name = child.d?.slot
120
+ if (name) {
121
+ const slot = (slots[name] || (slots[name] = []))
122
+ if (child.tag === 'template') {
123
+ slot.push.apply(slot, child.children || [])
124
+ } else {
125
+ slot.push(child)
126
+ }
127
+ } else {
128
+ (slots.default || (slots.default = [])).push(child)
129
+ }
130
+ }
131
+ }
132
+ return slots
133
+ }
134
+
135
+ function genData (node) {
136
+ const res = {
137
+ uid,
138
+ moduleId
139
+ }
140
+ if (!node.attrsList) {
141
+ return res
142
+ }
143
+
144
+ node.attrsList.forEach((attr) => {
145
+ if (attr.name === 'class' || attr.name === 'style') {
146
+ // class/style 的表达式为数组形式,class/style的计算过程需要放到逻辑层,主要是因为有逻辑匹配的过程去生成 vnodeTree
147
+ const helper = attr.name === 'class' ? stringify.stringifyClass : stringify.stringifyStyle
148
+ let value = ''
149
+ if (attr.__exp) {
150
+ let valueArr = evalExps(attr.__exp)
151
+ valueArr = Array.isArray(valueArr) ? valueArr : [valueArr]
152
+ value = helper(...valueArr)
153
+ // dynamic style + wx:show
154
+ const showStyle = valueArr[2]
155
+ if (showStyle) {
156
+ value = value + ';' + showStyle
157
+ }
158
+ } else {
159
+ value = attr.value
160
+ }
161
+ res[attr.name] = value
162
+ } else {
163
+ res[dash2hump(attr.name)] = attr.__exp
164
+ ? evalExps(attr.__exp)
165
+ : attr.value
166
+ }
167
+ })
168
+
169
+ return res
170
+ }
171
+
172
+ function genChildren (node) {
173
+ const res = []
174
+ const children = node.children || []
175
+ if (children.length) {
176
+ children.forEach((item) => {
177
+ res.push(genNode(item))
178
+ })
179
+ }
180
+ return res
181
+ }
182
+
183
+ function genNode (node) {
184
+ if (node.type === 1) {
185
+ return genVnodeTree(node)
186
+ } else if (node.type === 3 && node.isComment) {
187
+ return ''
188
+ // TODO: 注释暂不处理
189
+ // return _genComment(node)
190
+ } else {
191
+ return genText(node) // 文本节点统一通过 _genText 来生成,type = 2(带有表达式的文本,在 mpx 统一处理为了3) || type = 3(纯文本,非注释)
192
+ }
193
+ }
194
+
195
+ function genText (node) {
196
+ return {
197
+ nt: '#text',
198
+ ct: node.__exp ? evalExps(node.__exp) : node.text
199
+ }
200
+ }
201
+
202
+ function genFor (node) {
203
+ node.forProcessed = true
204
+
205
+ const itemKey = node.for.item || 'item'
206
+ const indexKey = node.for.index || 'index'
207
+ const scope = {
208
+ [itemKey]: null,
209
+ [indexKey]: null
210
+ }
211
+ const forExp = node.for
212
+ const res = []
213
+ let forValue = evalExps(forExp.__exp)
214
+
215
+ // 和微信的模版渲染策略保持一致:当 wx:for 的值为字符串时,会将字符串解析成字符串数组
216
+ if (isString(forValue)) {
217
+ forValue = forValue.split('')
218
+ }
219
+
220
+ if (Array.isArray(forValue)) {
221
+ forValue.forEach((item, index) => {
222
+ // item、index 模板当中如果没申明,需要给到默认值
223
+ scope[itemKey] = item
224
+ scope[indexKey] = index
225
+
226
+ contextScope.push(scope)
227
+
228
+ // 针对 for 循环避免每次都操作的同一个 node 导致数据的污染的问题
229
+ res.push(deepCloneNode(genVnodeTree(deepCloneNode(node))))
230
+
231
+ contextScope.pop()
232
+ })
233
+ }
234
+
235
+ return res
236
+ }
237
+
238
+ // 对于 if 而言最终生成 <= 1 节点
239
+ function genIf (node) {
240
+ if (!node.ifConditions) {
241
+ node.ifConditions = []
242
+ return {} // 一个空节点
243
+ }
244
+ node.ifProcessed = true
245
+
246
+ const ifConditions = node.ifConditions.slice()
247
+
248
+ let res = {} // 空节点
249
+ for (let i = 0; i < ifConditions.length; i++) {
250
+ const condition = ifConditions[i]
251
+ // 非 else 节点
252
+ if (condition.ifExp) {
253
+ const identifierValue = evalExps(condition.__exp)
254
+ if (identifierValue) {
255
+ res = genVnodeTree(condition.block === 'self' ? node : condition.block)
256
+ break
257
+ }
258
+ } else { // else 节点
259
+ res = genVnodeTree(condition.block)
260
+ break
261
+ }
262
+ }
263
+ return res
264
+ }
265
+
266
+ // 暂时不支持作用域插槽
267
+ function genSlot (node) {
268
+ const data = genData(node) // 计算属性值
269
+ const slotName = data.name || 'default'
270
+ return slots[slotName] || null
271
+ }
272
+
273
+ function genVnodeWithStaticCss (vnodeTree) {
274
+ styles.forEach((item) => {
275
+ const [selector, style] = item
276
+ const nodes = cssSelect(selector, { moduleId })(vnodeTree)
277
+ nodes?.forEach((node) => {
278
+ // todo style 合并策略问题:合并过程中缺少了权重关系 style, class 的判断,需要优化
279
+ node.d.style = node.d.style ? style + node.d.style : style
280
+ })
281
+ })
282
+
283
+ return vnodeTree
284
+ }
285
+
286
+ const interpreteredVnodeTree = genVnodeTree(templateAst)
287
+
288
+ return genVnodeWithStaticCss(interpreteredVnodeTree)
289
+ }
package/src/index.js CHANGED
@@ -60,6 +60,8 @@ export {
60
60
 
61
61
  export { getMixin } from './core/mergeOptions'
62
62
 
63
+ export { dynamic } from './dynamic/astCache'
64
+
63
65
  export function toPureObject (obj) {
64
66
  return diffAndCloneA(obj).clone
65
67
  }
@@ -10,6 +10,7 @@ import pageScrollMixin from './pageScrollMixin'
10
10
  import componentGenericsMixin from './componentGenericsMixin'
11
11
  import getTabBarMixin from './getTabBarMixin'
12
12
  import pageRouteMixin from './pageRouteMixin'
13
+ import { dynamicRefsMixin, dynamicRenderHelperMixin, dynamicSlotMixin } from '../../dynamic/dynamicRenderMixin'
13
14
 
14
15
  export default function getBuiltInMixins (options, type) {
15
16
  let bulitInMixins = []
@@ -41,6 +42,13 @@ export default function getBuiltInMixins (options, type) {
41
42
  showMixin(type),
42
43
  i18nMixin()
43
44
  ])
45
+ if (__mpx_dynamic_runtime__) {
46
+ bulitInMixins = bulitInMixins.concat([
47
+ dynamicRenderHelperMixin(),
48
+ dynamicSlotMixin(),
49
+ dynamicRefsMixin()
50
+ ])
51
+ }
44
52
  }
45
53
  }
46
54
  return bulitInMixins.filter(item => item)
@@ -1,19 +1,10 @@
1
- import { setByPath, error, hasOwn, dash2hump } from '@mpxjs/utils'
1
+ import { setByPath, error, dash2hump, collectDataset } from '@mpxjs/utils'
2
2
  import Mpx from '../../index'
3
+ import contextMap from '../../dynamic/vnode/context'
3
4
 
4
- const datasetReg = /^data-(.+)$/
5
-
6
- function collectDataset (props) {
7
- const dataset = {}
8
- for (const key in props) {
9
- if (hasOwn(props, key)) {
10
- const matched = datasetReg.exec(key)
11
- if (matched) {
12
- dataset[matched[1]] = props[key]
13
- }
14
- }
15
- }
16
- return dataset
5
+ function logCallbackNotFound (context, callbackName) {
6
+ const location = context.__mpxProxy && context.__mpxProxy.options.mpxFileResource
7
+ error(`Instance property [${callbackName}] is not function, please check.`, location)
17
8
  }
18
9
 
19
10
  export default function proxyEventMixin () {
@@ -48,6 +39,9 @@ export default function proxyEventMixin () {
48
39
  }
49
40
  const eventConfigs = target.dataset.eventconfigs || {}
50
41
  const curEventConfig = eventConfigs[type] || eventConfigs[fallbackType] || []
42
+ // 如果有 mpxuid 说明是运行时组件,那么需要设置对应的上下文
43
+ const rootRuntimeContext = contextMap.get(target.dataset.mpxuid)
44
+ const context = rootRuntimeContext || this
51
45
  let returnedValue
52
46
  curEventConfig.forEach((item) => {
53
47
  const callbackName = item[0]
@@ -71,10 +65,10 @@ export default function proxyEventMixin () {
71
65
  }
72
66
  })
73
67
  : [$event]
74
- if (typeof this[callbackName] === 'function') {
75
- returnedValue = this[callbackName].apply(this, params)
68
+ if (typeof context[callbackName] === 'function') {
69
+ returnedValue = context[callbackName].apply(context, params)
76
70
  } else {
77
- error(`Instance property [${callbackName}] is not function, please check.`, location)
71
+ logCallbackNotFound(context, callbackName)
78
72
  }
79
73
  }
80
74
  })
@@ -36,8 +36,8 @@ export default function renderHelperMixin () {
36
36
  _sc (key) {
37
37
  return (this.__mpxProxy.renderData[key] = this[key])
38
38
  },
39
- _r (skipPre) {
40
- this.__mpxProxy.renderWithData(skipPre)
39
+ _r (skipPre, vnode) {
40
+ this.__mpxProxy.renderWithData(skipPre, vnode)
41
41
  }
42
42
  }
43
43
  }
@@ -63,6 +63,26 @@ function transformApiForProxy (context, currentInject) {
63
63
  }
64
64
  })
65
65
  }
66
+ if (currentInject.moduleId) {
67
+ Object.defineProperties(context, {
68
+ __moduleId: {
69
+ get () {
70
+ return currentInject.moduleId
71
+ },
72
+ configurable: false
73
+ }
74
+ })
75
+ }
76
+ if (currentInject.dynamic) {
77
+ Object.defineProperties(context, {
78
+ __dynamic: {
79
+ get () {
80
+ return currentInject.dynamic
81
+ },
82
+ configurable: false
83
+ }
84
+ })
85
+ }
66
86
  }
67
87
  }
68
88
 
@@ -101,6 +101,26 @@ function transformApiForProxy (context, currentInject) {
101
101
  }
102
102
  })
103
103
  }
104
+ if (currentInject.moduleId) {
105
+ Object.defineProperties(context, {
106
+ __moduleId: {
107
+ get () {
108
+ return currentInject.moduleId
109
+ },
110
+ configurable: false
111
+ }
112
+ })
113
+ }
114
+ if (currentInject.dynamic) {
115
+ Object.defineProperties(context, {
116
+ __dynamic: {
117
+ get () {
118
+ return currentInject.dynamic
119
+ },
120
+ configurable: false
121
+ }
122
+ })
123
+ }
104
124
  }
105
125
  }
106
126
 
package/src/vuePlugin.js CHANGED
@@ -1,4 +1,4 @@
1
- import { walkChildren, parseSelector, error, hasOwn } from '@mpxjs/utils'
1
+ import { walkChildren, parseSelector, error, hasOwn, collectDataset } from '@mpxjs/utils'
2
2
  import { createSelectorQuery, createIntersectionObserver } from '@mpxjs/api-proxy'
3
3
  import { EffectScope } from 'vue'
4
4
  import { PausedState } from './helper/const'
@@ -55,22 +55,25 @@ const hackEffectScope = () => {
55
55
  }
56
56
  }
57
57
 
58
- const datasetReg = /^data-(.+)$/
59
-
60
- function collectDataset (attrs) {
61
- const dataset = {}
62
- for (const key in attrs) {
63
- if (hasOwn(attrs, key)) {
64
- const matched = datasetReg.exec(key)
65
- if (matched) {
66
- dataset[matched[1]] = attrs[key]
58
+ export default function install (Vue) {
59
+ Object.defineProperties(Vue.prototype, {
60
+ data: {
61
+ get () {
62
+ return Object.assign({}, this.$props, this.$data)
63
+ }
64
+ },
65
+ dataset: {
66
+ get () {
67
+ return collectDataset(this.$attrs, true)
68
+ }
69
+ },
70
+ id: {
71
+ get () {
72
+ return this.$attrs.id || ''
67
73
  }
68
74
  }
69
- }
70
- return dataset
71
- }
75
+ })
72
76
 
73
- export default function install (Vue) {
74
77
  Vue.prototype.triggerEvent = function (eventName, eventDetail) {
75
78
  // 输出Web时自定义组件绑定click事件会和web原生事件冲突,组件内部triggerEvent时会导致事件执行两次,将click事件改为_click来规避此问题
76
79
  const escapeEvents = ['click']
@@ -78,8 +81,8 @@ export default function install (Vue) {
78
81
  eventName = '_' + eventName
79
82
  }
80
83
  let eventObj = {}
81
- const dataset = collectDataset(this.$attrs)
82
- const id = this.$attrs.id || ''
84
+ const dataset = this.dataset
85
+ const id = this.id
83
86
  const timeStamp = +new Date()
84
87
  eventObj = {
85
88
  type: eventName,