@mpxjs/core 2.9.38 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpxjs/core",
3
- "version": "2.9.38",
3
+ "version": "2.9.39",
4
4
  "description": "mpx runtime core",
5
5
  "keywords": [
6
6
  "miniprogram",
@@ -19,7 +19,7 @@
19
19
  ],
20
20
  "main": "src/index.js",
21
21
  "dependencies": {
22
- "@mpxjs/utils": "^2.9.38",
22
+ "@mpxjs/utils": "^2.9.39",
23
23
  "lodash": "^4.1.1",
24
24
  "miniprogram-api-typings": "^3.10.0"
25
25
  },
@@ -47,5 +47,5 @@
47
47
  "url": "https://github.com/didi/mpx/issues"
48
48
  },
49
49
  "sideEffects": false,
50
- "gitHead": "5e2740e2091edda91eda9ffe775989c7fd9838df"
50
+ "gitHead": "87957d360b18806f16d169612d924d383ac043c4"
51
51
  }
package/src/core/proxy.js CHANGED
@@ -44,6 +44,8 @@ import {
44
44
  ONHIDE,
45
45
  ONRESIZE
46
46
  } from './innerLifecycle'
47
+ import contextMap from '../dynamic/vnode/context'
48
+ import { getAst } from '../dynamic/astCache'
47
49
 
48
50
  let uid = 0
49
51
 
@@ -131,6 +133,8 @@ export default class MpxProxy {
131
133
  }
132
134
 
133
135
  created () {
136
+ // 缓存上下文,在 destoryed 阶段删除
137
+ contextMap.set(this.uid, this.target)
134
138
  if (__mpx_mode__ !== 'web') {
135
139
  // web中BEFORECREATE钩子通过vue的beforeCreate钩子单独驱动
136
140
  this.callHook(BEFORECREATE)
@@ -190,6 +194,8 @@ export default class MpxProxy {
190
194
  }
191
195
 
192
196
  unmounted () {
197
+ // 页面/组件销毁清除上下文的缓存
198
+ contextMap.remove(this.uid)
193
199
  this.callHook(BEFOREUNMOUNT)
194
200
  this.scope?.stop()
195
201
  if (this.update) this.update.active = false
@@ -379,7 +385,10 @@ export default class MpxProxy {
379
385
  this.doRender(this.processRenderDataWithStrictDiff(renderData))
380
386
  }
381
387
 
382
- renderWithData (skipPre) {
388
+ renderWithData (skipPre, vnode) {
389
+ if (vnode) {
390
+ return this.doRenderWithVNode(vnode)
391
+ }
383
392
  const renderData = skipPre ? this.renderData : preProcessRenderData(this.renderData)
384
393
  this.doRender(this.processRenderDataWithStrictDiff(renderData))
385
394
  // 重置renderData准备下次收集
@@ -478,6 +487,25 @@ export default class MpxProxy {
478
487
  return result
479
488
  }
480
489
 
490
+ doRenderWithVNode (vnode) {
491
+ if (!this._vnode) {
492
+ this.target.__render({ r: vnode })
493
+ } else {
494
+ let diffPath = diffAndCloneA(vnode, this._vnode).diffData
495
+ if (!isEmptyObject(diffPath)) {
496
+ // 构造 diffPath 数据
497
+ diffPath = Object.keys(diffPath).reduce((preVal, curVal) => {
498
+ const key = 'r' + curVal
499
+ preVal[key] = diffPath[curVal]
500
+ return preVal
501
+ }, {})
502
+ this.target.__render(diffPath)
503
+ }
504
+ }
505
+ // 缓存本地的 vnode 用以下一次 diff
506
+ this._vnode = diffAndCloneA(vnode).clone
507
+ }
508
+
481
509
  doRender (data, cb) {
482
510
  if (typeof this.target.__render !== 'function') {
483
511
  error('Please specify a [__render] function to render view.', this.options.mpxFileResource)
@@ -545,12 +573,30 @@ export default class MpxProxy {
545
573
  const _c = this.target._c.bind(this.target)
546
574
  const _r = this.target._r.bind(this.target)
547
575
  const _sc = this.target._sc.bind(this.target)
576
+ const _g = this.target._g?.bind(this.target)
577
+ const __getAst = this.target.__getAst?.bind(this.target)
578
+ const moduleId = this.target.__moduleId
579
+ const dynamicTarget = this.target.__dynamic
580
+
548
581
  const effect = this.effect = new ReactiveEffect(() => {
549
582
  // pre render for props update
550
583
  if (this.propsUpdatedFlag) {
551
584
  this.updatePreRender()
552
585
  }
553
-
586
+ if (dynamicTarget || __getAst) {
587
+ try {
588
+ const ast = getAst(__getAst, moduleId)
589
+ return _r(false, _g(ast, moduleId))
590
+ } catch (e) {
591
+ e.errType = 'mpx-dynamic-render'
592
+ e.errmsg = e.message
593
+ if (!__mpx_dynamic_runtime__) {
594
+ return error('Please make sure you have set dynamicRuntime true in mpx webpack plugin config because you have use the dynamic runtime feature.', this.options.mpxFileResource, e)
595
+ } else {
596
+ return error('Dynamic rendering error', this.options.mpxFileResource, e)
597
+ }
598
+ }
599
+ }
554
600
  if (this.target.__injectedRender) {
555
601
  try {
556
602
  return this.target.__injectedRender(_i, _c, _r, _sc)
@@ -0,0 +1,25 @@
1
+ import { isFunction, isObject, error } from '@mpxjs/utils'
2
+
3
+ class DynamicAstCache {
4
+ #astCache = {}
5
+
6
+ getAst (id) {
7
+ return this.#astCache[id]
8
+ }
9
+
10
+ setAst (id, ast) {
11
+ this.#astCache[id] = ast
12
+ }
13
+ }
14
+
15
+ export const dynamic = new DynamicAstCache()
16
+
17
+ export const getAst = (__getAst, moduleId) => {
18
+ if ((__getAst && isFunction(__getAst))) {
19
+ const ast = __getAst()
20
+ if (!isObject(ast)) return error('__getAst returned data is not of type object')
21
+ return Object.values(ast)[0]
22
+ } else {
23
+ return dynamic.getAst(moduleId)
24
+ }
25
+ }
@@ -0,0 +1,77 @@
1
+ import { hasOwn, isObject, error } from '@mpxjs/utils'
2
+ import genVnodeTree from './vnode/render'
3
+ import contextMap from './vnode/context'
4
+ import { CREATED } from '../core/innerLifecycle'
5
+
6
+ function dynamicRefsMixin () {
7
+ return {
8
+ [CREATED] () {
9
+ // 处理ref场景,如果是在容器组件的上下文渲染
10
+ if (this.mpxCustomElement) {
11
+ this._getRuntimeRefs()
12
+ }
13
+ },
14
+ methods: {
15
+ _getRuntimeRefs () {
16
+ const vnodeContext = contextMap.get(this.uid)
17
+ if (vnodeContext) {
18
+ const refsArr = vnodeContext.__getRefsData && vnodeContext.__getRefsData()
19
+ if (Array.isArray(refsArr)) {
20
+ refsArr.forEach((ref) => {
21
+ const all = ref.all
22
+ if (!vnodeContext.$refs[ref.key] || (all && !vnodeContext.$refs[ref.key].length)) {
23
+ const refNode = this.__getRefNode(ref)
24
+ if ((all && refNode.length) || refNode) {
25
+ Object.defineProperty(vnodeContext.$refs, ref.key, {
26
+ enumerable: true,
27
+ configurable: true,
28
+ get: () => {
29
+ return refNode
30
+ }
31
+ })
32
+ }
33
+ }
34
+ })
35
+ }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+
42
+ function dynamicSlotMixin () {
43
+ if (__mpx_mode__ === 'ali') {
44
+ return {
45
+ props: { slots: {} }
46
+ }
47
+ } else {
48
+ return {
49
+ properties: { slots: { type: Object } }
50
+ }
51
+ }
52
+ }
53
+
54
+ function dynamicRenderHelperMixin () {
55
+ return {
56
+ methods: {
57
+ _g (astData, moduleId) {
58
+ const location = this.__mpxProxy && this.__mpxProxy.options.mpxFileResource
59
+ if (astData && isObject(astData) && hasOwn(astData, 'template')) {
60
+ const vnodeTree = genVnodeTree(astData, [this], { moduleId, location })
61
+ return vnodeTree
62
+ } else {
63
+ error('Dynamic component get the wrong json ast data, please check.', location, {
64
+ errType: 'mpx-dynamic-render',
65
+ errmsg: 'invalid json ast data'
66
+ })
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+
73
+ export {
74
+ dynamicRefsMixin,
75
+ dynamicSlotMixin,
76
+ dynamicRenderHelperMixin
77
+ }
@@ -0,0 +1,17 @@
1
+ const cache = {}
2
+
3
+ const contextMap = {
4
+ set (id, context) {
5
+ cache[id] = context
6
+ },
7
+ get (id) {
8
+ return cache[id]
9
+ },
10
+ remove (id) {
11
+ if (cache[id]) {
12
+ delete cache[id]
13
+ }
14
+ }
15
+ }
16
+
17
+ export default contextMap
@@ -0,0 +1,445 @@
1
+ import tokenizer from './tokenizer'
2
+
3
+ export default function language (lookups, matchComparison) {
4
+ return function (selector, moduleId) {
5
+ return parse(
6
+ selector,
7
+ remap(lookups),
8
+ moduleId,
9
+ matchComparison || caseSensitiveComparison
10
+ )
11
+ }
12
+ }
13
+
14
+ function remap (opts) {
15
+ // 对于字符串类型的 value 转化为函数
16
+ for (const key in opts) {
17
+ if (opt_okay(opts, key)) {
18
+ /* eslint-disable-next-line */
19
+ opts[key] = Function(
20
+ 'return function(node, attr) { return node.' + opts[key] + ' }'
21
+ )
22
+ opts[key] = opts[key]()
23
+ }
24
+ }
25
+
26
+ return opts
27
+ }
28
+
29
+ function opt_okay (opts, key) {
30
+ return Object.prototype.hasOwnProperty.call(opts, key) && typeof opts[key] === 'string'
31
+ }
32
+
33
+ function parse (selector, options, moduleId, matchComparison) {
34
+ const stream = tokenizer()
35
+ // const default_subj = true
36
+ const selectors = [[]]
37
+ let bits = selectors[0]
38
+
39
+ // 逆向关系
40
+ const traversal = {
41
+ '': any_parents,
42
+ '>': direct_parent,
43
+ '+': direct_sibling,
44
+ '~': any_sibling
45
+ }
46
+
47
+ stream.on('data', group)
48
+ stream.end(selector)
49
+
50
+ function group (token) {
51
+ if (token.type === 'comma') {
52
+ selectors.unshift((bits = []))
53
+
54
+ return
55
+ }
56
+
57
+ // 获取节点之间的关系路径,匹配的规则从右往左依次进行,因此在后面的匹配规则需要存储在栈结构的前面
58
+ if (token.type === 'op' || token.type === 'any-child') {
59
+ bits.unshift(traversal[token.data]) // 获取节点之间关系的操作数
60
+ bits.unshift(check()) // 添加空的匹配操作数
61
+
62
+ return
63
+ }
64
+
65
+ bits[0] = bits[0] || check()
66
+ const crnt = bits[0]
67
+
68
+ if (token.type === '!') {
69
+ crnt.subject = selectors[0].subject = true
70
+
71
+ return
72
+ }
73
+
74
+ crnt.push(
75
+ token.type === 'class'
76
+ ? listContains(token.type, token.data)
77
+ : token.type === 'attr'
78
+ ? attr(token)
79
+ : token.type === ':' || token.type === '::'
80
+ ? pseudo(token)
81
+ : token.type === '*'
82
+ ? Boolean
83
+ : matches(token.type, token.data, matchComparison)
84
+ )
85
+ }
86
+
87
+ return selector_fn
88
+
89
+ // 单节点对比
90
+ function selector_fn (node, as_boolean) {
91
+ if (node.data?.moduleId !== moduleId) {
92
+ return
93
+ }
94
+ let current, length, subj
95
+
96
+ const orig = node
97
+ const set = []
98
+
99
+ for (let i = 0, len = selectors.length; i < len; ++i) {
100
+ bits = selectors[i]
101
+ current = entry // 当前 bits 检测规则
102
+ length = bits.length
103
+ node = orig // 引用赋值
104
+ subj = []
105
+
106
+ let j = 0
107
+ // 步长为2,因为2个节点之间的关系中间会有一个 OP 操作符
108
+ for (j = 0; j < length; j += 2) {
109
+ node = current(node, bits[j], subj)
110
+
111
+ if (!node) {
112
+ break
113
+ }
114
+
115
+ // todo 这里的规则和步长设计的很巧妙
116
+ current = bits[j + 1] // 改变当前的 bits 检测规则
117
+ }
118
+
119
+ if (j >= length) {
120
+ if (as_boolean) {
121
+ return true
122
+ }
123
+
124
+ add(!bits.subject ? [orig] : subj)
125
+ }
126
+ }
127
+
128
+ if (as_boolean) {
129
+ return false
130
+ }
131
+
132
+ return !set.length ? false : set.length === 1 ? set[0] : set
133
+
134
+ function add (items) {
135
+ let next
136
+
137
+ while (items.length) {
138
+ next = items.shift()
139
+
140
+ if (set.indexOf(next) === -1) {
141
+ set.push(next)
142
+ }
143
+ }
144
+ }
145
+ }
146
+
147
+ // 匹配操作数
148
+ function check () {
149
+ _check.bits = []
150
+ _check.subject = false
151
+ _check.push = function (token) {
152
+ _check.bits.push(token)
153
+ }
154
+
155
+ return _check
156
+
157
+ function _check (node, subj) {
158
+ for (let i = 0, len = _check.bits.length; i < len; ++i) {
159
+ if (!_check.bits[i](node)) {
160
+ return false
161
+ }
162
+ }
163
+
164
+ if (_check.subject) {
165
+ subj.push(node)
166
+ }
167
+
168
+ return true
169
+ }
170
+ }
171
+
172
+ function listContains (type, data) {
173
+ return function (node) {
174
+ let val = options[type](node)
175
+ val = Array.isArray(val) ? val : val ? val.toString().split(/\s+/) : []
176
+ return val.indexOf(data) >= 0
177
+ }
178
+ }
179
+
180
+ function attr (token) {
181
+ return token.data.lhs
182
+ ? valid_attr(options.attr, token.data.lhs, token.data.cmp, token.data.rhs)
183
+ : valid_attr(options.attr, token.data)
184
+ }
185
+
186
+ function matches (type, data, matchComparison) {
187
+ return function (node) {
188
+ return matchComparison(type, options[type](node), data)
189
+ }
190
+ }
191
+
192
+ function any_parents (node, next, subj) {
193
+ do {
194
+ node = options.parent(node)
195
+ } while (node && !next(node, subj))
196
+
197
+ return node
198
+ }
199
+
200
+ function direct_parent (node, next, subj) {
201
+ node = options.parent(node)
202
+
203
+ return node && next(node, subj) ? node : null
204
+ }
205
+
206
+ function direct_sibling (node, next, subj) {
207
+ const parent = options.parent(node)
208
+ let idx = 0
209
+
210
+ const children = options.children(parent)
211
+
212
+ for (let i = 0, len = children.length; i < len; ++i) {
213
+ if (children[i] === node) {
214
+ idx = i
215
+
216
+ break
217
+ }
218
+ }
219
+
220
+ return children[idx - 1] && next(children[idx - 1], subj)
221
+ ? children[idx - 1]
222
+ : null
223
+ }
224
+
225
+ function any_sibling (node, next, subj) {
226
+ const parent = options.parent(node)
227
+
228
+ const children = options.children(parent)
229
+
230
+ for (let i = 0, len = children.length; i < len; ++i) {
231
+ if (children[i] === node) {
232
+ return null
233
+ }
234
+
235
+ if (next(children[i], subj)) {
236
+ return children[i]
237
+ }
238
+ }
239
+
240
+ return null
241
+ }
242
+
243
+ function pseudo (token) {
244
+ return valid_pseudo(options, token.data, matchComparison)
245
+ }
246
+ }
247
+
248
+ function entry (node, next, subj) {
249
+ return next(node, subj) ? node : null
250
+ }
251
+
252
+ function valid_pseudo (options, match, matchComparison) {
253
+ switch (match) {
254
+ case 'empty':
255
+ return valid_empty(options)
256
+ case 'first-child':
257
+ return valid_first_child(options)
258
+ case 'last-child':
259
+ return valid_last_child(options)
260
+ case 'root':
261
+ return valid_root(options)
262
+ }
263
+
264
+ if (match.indexOf('contains') === 0) {
265
+ return valid_contains(options, match.slice(9, -1))
266
+ }
267
+
268
+ if (match.indexOf('any') === 0) {
269
+ return valid_any_match(options, match.slice(4, -1), matchComparison)
270
+ }
271
+
272
+ if (match.indexOf('not') === 0) {
273
+ return valid_not_match(options, match.slice(4, -1), matchComparison)
274
+ }
275
+
276
+ if (match.indexOf('nth-child') === 0) {
277
+ return valid_nth_child(options, match.slice(10, -1))
278
+ }
279
+
280
+ return function () {
281
+ return false
282
+ }
283
+ }
284
+
285
+ function valid_not_match (options, selector, matchComparison) {
286
+ const fn = parse(selector, options, matchComparison)
287
+
288
+ return not_function
289
+
290
+ function not_function (node) {
291
+ return !fn(node, true)
292
+ }
293
+ }
294
+
295
+ function valid_any_match (options, selector, matchComparison) {
296
+ const fn = parse(selector, options, matchComparison)
297
+
298
+ return fn
299
+ }
300
+
301
+ function valid_attr (fn, lhs, cmp, rhs) {
302
+ return function (node) {
303
+ const attr = fn(node, lhs)
304
+
305
+ if (!cmp) {
306
+ return !!attr
307
+ }
308
+
309
+ if (cmp.length === 1) {
310
+ return attr === rhs
311
+ }
312
+
313
+ if (attr === undefined || attr === null) {
314
+ return false
315
+ }
316
+
317
+ return checkattr[cmp.charAt(0)](attr, rhs)
318
+ }
319
+ }
320
+
321
+ function valid_first_child (options) {
322
+ return function (node) {
323
+ return options.children(options.parent(node))[0] === node
324
+ }
325
+ }
326
+
327
+ function valid_last_child (options) {
328
+ return function (node) {
329
+ const children = options.children(options.parent(node))
330
+
331
+ return children[children.length - 1] === node
332
+ }
333
+ }
334
+
335
+ function valid_empty (options) {
336
+ return function (node) {
337
+ return options.children(node).length === 0
338
+ }
339
+ }
340
+
341
+ function valid_root (options) {
342
+ return function (node) {
343
+ return !options.parent(node)
344
+ }
345
+ }
346
+
347
+ function valid_contains (options, contents) {
348
+ return function (node) {
349
+ return options.contents(node).indexOf(contents) !== -1
350
+ }
351
+ }
352
+
353
+ function valid_nth_child (options, nth) {
354
+ let test = function () {
355
+ return false
356
+ }
357
+ if (nth === 'odd') {
358
+ nth = '2n+1'
359
+ } else if (nth === 'even') {
360
+ nth = '2n'
361
+ }
362
+ const regexp = /( ?([-|+])?(\d*)n)? ?((\+|-)? ?(\d+))? ?/
363
+ const matches = nth.match(regexp)
364
+ if (matches) {
365
+ let growth = 0
366
+ if (matches[1]) {
367
+ const positiveGrowth = matches[2] !== '-'
368
+ growth = parseInt(matches[3] === '' ? 1 : matches[3])
369
+ growth = growth * (positiveGrowth ? 1 : -1)
370
+ }
371
+ let offset = 0
372
+ if (matches[4]) {
373
+ offset = parseInt(matches[6])
374
+ const positiveOffset = matches[5] !== '-'
375
+ offset = offset * (positiveOffset ? 1 : -1)
376
+ }
377
+ if (growth === 0) {
378
+ if (offset !== 0) {
379
+ test = function (children, node) {
380
+ return children[offset - 1] === node
381
+ }
382
+ }
383
+ } else {
384
+ test = function (children, node) {
385
+ const validPositions = []
386
+ const len = children.length
387
+ for (let position = 1; position <= len; position++) {
388
+ const divisible = (position - offset) % growth === 0
389
+ if (divisible) {
390
+ if (growth > 0) {
391
+ validPositions.push(position)
392
+ } else {
393
+ if ((position - offset) / growth >= 0) {
394
+ validPositions.push(position)
395
+ }
396
+ }
397
+ }
398
+ }
399
+ for (let i = 0; i < validPositions.length; i++) {
400
+ if (children[validPositions[i] - 1] === node) {
401
+ return true
402
+ }
403
+ }
404
+ return false
405
+ }
406
+ }
407
+ }
408
+ return function (node) {
409
+ const children = options.children(options.parent(node))
410
+
411
+ return test(children, node)
412
+ }
413
+ }
414
+
415
+ const checkattr = {
416
+ $: check_end,
417
+ '^': check_beg,
418
+ '*': check_any,
419
+ '~': check_spc,
420
+ '|': check_dsh
421
+ }
422
+
423
+ function check_end (l, r) {
424
+ return l.slice(l.length - r.length) === r
425
+ }
426
+
427
+ function check_beg (l, r) {
428
+ return l.slice(0, r.length) === r
429
+ }
430
+
431
+ function check_any (l, r) {
432
+ return l.indexOf(r) > -1
433
+ }
434
+
435
+ function check_spc (l, r) {
436
+ return l.split(/\s+/).indexOf(r) > -1
437
+ }
438
+
439
+ function check_dsh (l, r) {
440
+ return l.split('-').indexOf(r) > -1
441
+ }
442
+
443
+ function caseSensitiveComparison (type, pattern, data) {
444
+ return pattern === data
445
+ }