@mpxjs/webpack-plugin 2.10.17-beta.7 → 2.10.17-beta.8

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/lib/index.js CHANGED
@@ -805,6 +805,7 @@ class MpxWebpackPlugin {
805
805
  // 若配置disableRequireAsync=true, 则全平台构建不支持异步分包
806
806
  supportRequireAsync: !this.options.disableRequireAsync && (this.options.mode === 'wx' || this.options.mode === 'ali' || this.options.mode === 'tt' || isWeb(this.options.mode) || (isReact(this.options.mode) && this.options.rnConfig.supportSubpackage)),
807
807
  partialCompileRules: this.options.partialCompileRules,
808
+ useExtendComponents: this.options.useExtendComponents,
808
809
  collectDynamicEntryInfo: ({ resource, packageName, filename, entryType, hasAsync }) => {
809
810
  const curInfo = mpx.dynamicEntryInfo[packageName] = mpx.dynamicEntryInfo[packageName] || {
810
811
  hasPage: false,
@@ -13,6 +13,7 @@ const createJSONHelper = require('./helper')
13
13
  const RecordIndependentDependency = require('../dependencies/RecordIndependentDependency')
14
14
  const RecordRuntimeInfoDependency = require('../dependencies/RecordRuntimeInfoDependency')
15
15
  const { MPX_DISABLE_EXTRACTOR_CACHE, RESOLVE_IGNORED_ERR, JSON_JS_EXT } = require('../utils/const')
16
+ const { processExtendComponents } = require('../utils/process-extend-components')
16
17
  const resolve = require('../utils/resolve')
17
18
  const resolveTabBarPath = require('../utils/resolve-tab-bar-path')
18
19
  const resolveMpxCustomElementPath = require('../utils/resolve-mpx-custom-element-path')
@@ -75,13 +76,17 @@ module.exports = function (content) {
75
76
  const { getRequestString } = createHelpers(this)
76
77
 
77
78
  let currentName
78
-
79
+ let hasApp = true
79
80
  if (isApp) {
80
81
  currentName = appInfo.name
81
82
  } else {
82
83
  currentName = componentsMap[resourcePath] || pagesMap[resourcePath]
83
84
  }
84
85
 
86
+ if (!appInfo.name) {
87
+ hasApp = false
88
+ }
89
+
85
90
  const relativePath = useRelativePath ? publicPath + path.dirname(currentName) : ''
86
91
 
87
92
  const copydir = (dir, context, callback) => {
@@ -160,6 +165,18 @@ module.exports = function (content) {
160
165
  json.usingComponents = json.usingComponents || {}
161
166
  }
162
167
 
168
+ if (mode === 'wx' || mode === 'ali') {
169
+ const { useExtendComponents = {} } = mpx
170
+ if ((isApp || !hasApp) && useExtendComponents[mode]) {
171
+ const extendComponents = processExtendComponents({
172
+ useExtendComponents,
173
+ mode,
174
+ emitWarning
175
+ })
176
+ json.usingComponents = Object.assign({}, extendComponents, json.usingComponents)
177
+ }
178
+ }
179
+
163
180
  // 快应用补全json配置,必填项
164
181
  if (mode === 'qa' && isApp) {
165
182
  const defaultConf = {
@@ -12,6 +12,7 @@ const { transSubpackage } = require('../utils/trans-async-sub-rules')
12
12
  const createJSONHelper = require('../json-compiler/helper')
13
13
  const getRulesRunner = require('../platform/index')
14
14
  const { RESOLVE_IGNORED_ERR } = require('../utils/const')
15
+ const { processExtendComponents } = require('../utils/process-extend-components')
15
16
  const RecordResourceMapDependency = require('../dependencies/RecordResourceMapDependency')
16
17
  const RecordPageConfigsMapDependency = require('../dependencies/RecordPageConfigsMapDependency')
17
18
 
@@ -32,11 +33,19 @@ module.exports = function (jsonContent, {
32
33
  mode,
33
34
  srcMode,
34
35
  env,
35
- projectRoot
36
+ projectRoot,
37
+ useExtendComponents = {},
38
+ appInfo
36
39
  } = mpx
37
40
 
38
41
  const context = loaderContext.context
39
42
 
43
+ let hasApp = true
44
+
45
+ if (!appInfo.name) {
46
+ hasApp = false
47
+ }
48
+
40
49
  const emitWarning = (msg) => {
41
50
  loaderContext.emitWarning(
42
51
  new Error('[Mpx json warning][' + loaderContext.resource + ']: ' + msg)
@@ -118,6 +127,16 @@ module.exports = function (jsonContent, {
118
127
  if (ctorType !== 'app') {
119
128
  rulesRunnerOptions.mainKey = ctorType
120
129
  }
130
+ if (!hasApp || ctorType === 'app') {
131
+ if (useExtendComponents[mode]) {
132
+ const extendComponents = processExtendComponents({
133
+ useExtendComponents,
134
+ mode,
135
+ emitWarning
136
+ })
137
+ jsonObj.usingComponents = Object.assign({}, extendComponents, jsonObj.usingComponents)
138
+ }
139
+ }
121
140
 
122
141
  const rulesRunner = getRulesRunner(rulesRunnerOptions)
123
142
 
@@ -68,6 +68,7 @@ import { getComponent, getAsyncSuspense } from ${stringifyRequest(loaderContext,
68
68
  output += buildI18n({ loaderContext })
69
69
  }
70
70
  output += getRequireScript({ ctorType, script, loaderContext })
71
+
71
72
  output += `export default global.__mpxOptionsMap[${JSON.stringify(moduleId)}]\n`
72
73
  }
73
74
 
@@ -4,7 +4,6 @@ const parseRequest = require('../utils/parse-request')
4
4
  const shallowStringify = require('../utils/shallow-stringify')
5
5
  const normalize = require('../utils/normalize')
6
6
  const addQuery = require('../utils/add-query')
7
-
8
7
  function stringifyRequest (loaderContext, request) {
9
8
  return loaderUtils.stringifyRequest(loaderContext, request)
10
9
  }
@@ -0,0 +1,518 @@
1
+ <template>
2
+ <view class="mpx-recycle-view">
3
+ <scroll-view
4
+ id="scrollViewContainer"
5
+ class="scroll-view-container"
6
+ wx:ref="mpxRecycleView"
7
+ scroll-y="{{true}}"
8
+ scroll-top="{{scrollTop}}"
9
+ enhanced="{{enhanced}}"
10
+ scroll-with-animation="{{scrollWithAnimation}}"
11
+ scroll-animation-duration="{{scrollAnimationDuration}}"
12
+ refresher-enabled="{{refresherEnabled}}"
13
+ refresher-triggered="{{refresherTriggered}}"
14
+ bindscroll="onScroll"
15
+ bindscrolltolower="onScrolltolower"
16
+ bindrefresherrefresh="onRefresherrefresh"
17
+ wx:style="{{scrollViewStyle}}"
18
+ >
19
+ <view class="content-wrapper">
20
+ <block wx:if="{{useListHeader}}">
21
+ <list-header listHeaderData="{{listHeaderData}}"></list-header>
22
+ </block>
23
+ <view
24
+ class="infinite-list-placeholder"
25
+ ref="infinitePlaceholder"
26
+ wx:style="{{placeholderStyle}}"
27
+ ></view>
28
+ <view class="infinite-list" ref="infiniteList" wx:style="{{infiniteListStyle}}">
29
+ <block wx:for="{{visibleData}}">
30
+ <section-header
31
+ wx:if="{{item.itemData.isSectionHeader}}"
32
+ wx:key="header{{_index}}"
33
+ itemData="{{item.itemData}}"
34
+ />
35
+ <recycle-item
36
+ wx:if="{{!item.itemData.isSectionHeader}}"
37
+ wx:key="item{{_index}}"
38
+ itemData="{{item.itemData}}"
39
+ />
40
+ </block>
41
+ </view>
42
+ </view>
43
+ <block
44
+ wx:if="{{ _stickyHeaders &&
45
+ _stickyHeaders.length &&
46
+ positions.length &&
47
+ enableSticky
48
+ }}"
49
+ >
50
+ <sticky-header
51
+ scroll-view-id="scrollViewContainer"
52
+ sticky-id="mpx-sticky-header-{{index}}"
53
+ wx:ref="stickyHeader"
54
+ class="sticky-section"
55
+ wx:style="top: {{(positions[stickyItem._index] && positions[stickyItem._index].top) || 0}}px"
56
+ wx:for="{{_stickyHeaders}}"
57
+ wx:for-item="stickyItem"
58
+ wx:key="_index"
59
+ enable-polling="{{enablePolling}}"
60
+ polling-duration="{{pollingDuration}}"
61
+ >
62
+ <section-header
63
+ wx:key="{{'header' + stickyItem._index}}"
64
+ itemData="{{stickyItem.itemData}}"
65
+ />
66
+ </sticky-header>
67
+ </block>
68
+ </scroll-view>
69
+ </view>
70
+ </template>
71
+
72
+ <script>
73
+ import mpx, { createComponent } from '@mpxjs/core'
74
+ createComponent({
75
+ properties: {
76
+ width: String | Number,
77
+ height: String | Number,
78
+ listData: {
79
+ type: Array,
80
+ value: () => {
81
+ return []
82
+ }
83
+ },
84
+ scrollOptions: {
85
+ type: Object,
86
+ value: () => {
87
+ return {}
88
+ }
89
+ },
90
+ minRenderCount: {
91
+ type: Number,
92
+ value: 10
93
+ },
94
+ bufferScale: {
95
+ type: Number,
96
+ value: 1
97
+ },
98
+ itemHeight: {
99
+ type: Object,
100
+ value: () => {
101
+ return {}
102
+ }
103
+ },
104
+ listHeaderHeight: {
105
+ type: Object,
106
+ value: () => {
107
+ return {}
108
+ }
109
+ },
110
+ sectionHeaderHeight: {
111
+ type: Object,
112
+ value: () => {
113
+ return {}
114
+ }
115
+ },
116
+ listHeaderData: {
117
+ type: Object,
118
+ value: () => {
119
+ return {}
120
+ }
121
+ },
122
+ enhanced: {
123
+ type: Boolean,
124
+ value: false
125
+ },
126
+ refresherEnabled: {
127
+ type: Boolean,
128
+ value: false
129
+ },
130
+ refresherTriggered: {
131
+ type: Boolean,
132
+ value: false
133
+ },
134
+ enableSticky: {
135
+ type: Boolean,
136
+ value: false
137
+ },
138
+ scrollWithAnimation: {
139
+ type: Boolean,
140
+ value: false
141
+ },
142
+ scrollAnimationDuration: {
143
+ type: Number,
144
+ value: 300
145
+ },
146
+ enablePolling: {
147
+ type: Boolean,
148
+ value: false
149
+ },
150
+ pollingDuration: {
151
+ type: Number,
152
+ value: 3000
153
+ },
154
+ useListHeader: {
155
+ type: Boolean,
156
+ value: false
157
+ }
158
+ },
159
+ data: {
160
+ start: 0,
161
+ end: 0,
162
+ containerHeight: 0,
163
+ positions: [],
164
+ visibleCounts: [],
165
+ infiniteListStyle: '',
166
+ placeholderStyle: '',
167
+ scrollTop: 0
168
+ },
169
+ computed: {
170
+ _listData() {
171
+ return this.listData.map((item, index) => {
172
+ return {
173
+ itemData: item,
174
+ _index: `_${index}`
175
+ }
176
+ })
177
+ },
178
+ _stickyHeaders() {
179
+ const data = []
180
+ this.listData.forEach((item, index) => {
181
+ if (item.isSectionHeader) {
182
+ data.push({
183
+ itemData: item,
184
+ _index: index
185
+ })
186
+ }
187
+ })
188
+ return data
189
+ },
190
+ scrollViewStyle() {
191
+ return `height: ${this.formatDimension(
192
+ this.height
193
+ )};width: ${this.formatDimension(this.width)}`
194
+ },
195
+ visibleCount() {
196
+ if (!this.visibleCounts.length) return this.minRenderCount
197
+ return Math.max(this.visibleCounts[this.start], this.minRenderCount)
198
+ },
199
+ aboveCount() {
200
+ if (!this._listData.length || !this.visibleCounts.length) return 0
201
+ let count = 0
202
+ const startIndex = Math.max(0, this.start)
203
+ const endIndex = Math.max(0, startIndex - this.bufferScale)
204
+
205
+ for (let i = startIndex; i > endIndex; i--) {
206
+ count += this.visibleCounts[i] || 0
207
+ }
208
+
209
+ return count
210
+ },
211
+ belowCount() {
212
+ if (!this._listData.length || !this.visibleCounts.length) return 0
213
+ let count = 0
214
+ const startIndex = Math.min(this.start, this._listData.length - 1)
215
+ const endIndex = Math.min(startIndex + this.bufferScale, this._listData.length - 1)
216
+
217
+ for (let i = startIndex; i < endIndex; i++) {
218
+ count += this.visibleCounts[i] || 0
219
+ }
220
+
221
+ return count
222
+ },
223
+ visibleData() {
224
+ if (!this._listData.length) return []
225
+
226
+ const start = Math.min(Math.max(0, this.start - this.aboveCount), this._listData.length - 1)
227
+
228
+ let end = Math.min(this._listData.length, this.start + this.visibleCount + this.belowCount)
229
+
230
+ // 如果接近列表末尾,确保显示所有剩余项目
231
+ if (end > this._listData.length - 3) {
232
+ end = this._listData.length
233
+ }
234
+
235
+ return this._listData.slice(start, end).map((item, idx) => {
236
+ const realIndex = start + idx
237
+ return {
238
+ ...item,
239
+ _index: `_${realIndex}`
240
+ }
241
+ })
242
+ },
243
+ _listHeaderHeight() {
244
+ let listHeaderHeight = 0
245
+ if (this.useListHeader) {
246
+ listHeaderHeight = this.getItemHeight(this.listHeaderData, 0, 'listHeaderHeight') || 0
247
+ }
248
+ return listHeaderHeight
249
+ },
250
+ placeholderHeight() {
251
+ if (!this.positions.length) return 0
252
+ return this.positions[this.positions.length - 1].bottom - this._listHeaderHeight || 0
253
+ }
254
+ },
255
+ watch: {
256
+ listData: {
257
+ handler() {
258
+ this.initPositions()
259
+ this.setPlaceholderStyle()
260
+ // 更新真实偏移量
261
+ this.setStartOffset()
262
+ }
263
+ },
264
+ containerHeight() {
265
+ this.calculateVisibleCounts()
266
+ }
267
+ },
268
+ ready() {
269
+ this.initPositions()
270
+ this.getContainerHeight()
271
+ this.setPlaceholderStyle()
272
+ if (!this.positions || !this.positions.length) {
273
+ return
274
+ }
275
+ this.start = this.getStartIndex()
276
+ this.end = this.start + this.visibleCount
277
+ this.setStartOffset()
278
+ },
279
+ methods: {
280
+ getContainerHeight() {
281
+ mpx
282
+ .createSelectorQuery()
283
+ .select('#scrollViewContainer')
284
+ .boundingClientRect((rect) => {
285
+ if (!rect) return
286
+ this.containerHeight = rect.height
287
+ })
288
+ .exec()
289
+ },
290
+ formatDimension(value) {
291
+ return typeof value === 'number' ? `${value}px` : value || '100%'
292
+ },
293
+ setPlaceholderStyle() {
294
+ this.placeholderStyle = `height: ${this.placeholderHeight}px`
295
+ },
296
+ initPositions() {
297
+ let bottom = this._listHeaderHeight || 0
298
+ this.positions = this._listData.map((item, index) => {
299
+ const height = this.getItemHeight(
300
+ item.itemData,
301
+ index,
302
+ item.itemData.isSectionHeader ? 'sectionHeaderHeight' : 'itemHeight'
303
+ )
304
+ const position = {
305
+ index,
306
+ height: height,
307
+ top: bottom,
308
+ bottom: bottom + height
309
+ }
310
+ bottom = position.bottom
311
+ return position
312
+ })
313
+
314
+ if (this.containerHeight) {
315
+ this.calculateVisibleCounts()
316
+ }
317
+ },
318
+ calculateVisibleCounts() {
319
+ this.visibleCounts = this.positions.map((_, startIndex) => {
320
+ let count = 0
321
+ let totalHeight = 0
322
+
323
+ for (let i = startIndex; i < this.positions.length; i++) {
324
+ totalHeight += this.positions[i].height
325
+ if (totalHeight > this.containerHeight) {
326
+ break
327
+ }
328
+ count++
329
+ }
330
+
331
+ // 如果是最后几个项目,确保全部显示
332
+ if (startIndex + count > this.positions.length - 3) {
333
+ count = this.positions.length - startIndex
334
+ }
335
+
336
+ return count
337
+ })
338
+ },
339
+
340
+ getStartIndex(scrollTop = 0) {
341
+ // 确保不会返回超出范围的索引
342
+ if (!this.positions.length) {
343
+ return 0
344
+ }
345
+
346
+ // 如果滚动位置为0,直接返回0
347
+ if (scrollTop <= 0) {
348
+ return 0
349
+ }
350
+ const index = this.binarySearch(this.positions, scrollTop)
351
+ return Math.max(0, Math.min(index, this._listData.length - 1))
352
+ },
353
+ binarySearch(list, value) {
354
+ if (!list.length) return 0
355
+
356
+ // 如果 scrollTop 超过了最后一个元素的底部
357
+ if (value >= list[list.length - 1].bottom) {
358
+ return list.length - 1
359
+ }
360
+
361
+ let start = 0
362
+ let end = list.length - 1
363
+
364
+ while (start <= end) {
365
+ const midIndex = Math.floor((start + end) / 2)
366
+ const midValue = list[midIndex]
367
+
368
+ if (value >= midValue.top && value < midValue.bottom) {
369
+ return midIndex
370
+ }
371
+
372
+ if (value < midValue.top) {
373
+ end = midIndex - 1
374
+ } else {
375
+ start = midIndex + 1
376
+ }
377
+ }
378
+
379
+ return Math.min(Math.max(0, start - 1), list.length - 1)
380
+ },
381
+ setStartOffset() {
382
+ if (!this.positions.length) return
383
+ if (this.start >= 1) {
384
+ // 确保 startIndex 不会超出范围
385
+ const startIndex = Math.min(
386
+ Math.max(0, this.start - this.aboveCount),
387
+ this.positions.length - 1
388
+ )
389
+
390
+ const offset = this.positions[startIndex].top
391
+ this.infiniteListStyle = `transform: translateY(${offset}px)`
392
+ } else {
393
+ this.infiniteListStyle = `transform: translateY(${this.positions[0].top}px)`
394
+ }
395
+ },
396
+ getItemHeight(item, index, key) {
397
+ const { value, getter } = this[key]
398
+ if (typeof getter === 'function') {
399
+ return getter(item, index) || 0
400
+ } else {
401
+ return value || 0
402
+ }
403
+ },
404
+ onScroll(e) {
405
+ const { scrollTop } = e.detail
406
+ const newStart = this.getStartIndex(scrollTop)
407
+ // 只有当start发生足够变化时才更新,避免滚动触发重渲染
408
+ if (Math.abs(newStart - this.end) >= Math.floor(this.belowCount / 2)) {
409
+ this.start = newStart
410
+ this.end = this.start + this.visibleCount
411
+ this.setStartOffset()
412
+ }
413
+
414
+ this.triggerEvent('scroll', e)
415
+ },
416
+ onScrolltolower(e) {
417
+ this.triggerEvent('scrolltolower', e)
418
+ },
419
+ onRefresherrefresh(e) {
420
+ this.triggerEvent('refresherrefresh', e)
421
+ },
422
+ scrollToIndex({ index, viewPosition = 0 }) {
423
+ const isStickyHeader = this._listData[index].itemData?.isSectionHeader
424
+ let prevHeaderHeight = 0
425
+ // 如果不是sticky header 查找最近一个吸顶的 sticky header
426
+ if (!isStickyHeader && this.enableSticky) {
427
+ for (let i = index - 1; i >= 0; i--) {
428
+ if (this._listData[i].itemData?.isSectionHeader) {
429
+ prevHeaderHeight = this.positions[i].height
430
+ break
431
+ }
432
+ }
433
+ }
434
+
435
+ const itemTop = (this.positions[index]?.top || 0) - prevHeaderHeight
436
+ const itemHeight = this.positions[index]?.height || 0
437
+ const containerHeight = this.containerHeight
438
+
439
+ let targetTop = itemTop
440
+ if (viewPosition === 1) {
441
+ // 滚动到可视区底部
442
+ targetTop = itemTop - (containerHeight - itemHeight)
443
+ } else if (viewPosition === 0.5) {
444
+ // 滚动到可视区中央
445
+ targetTop = itemTop - (containerHeight - itemHeight) / 2
446
+ }
447
+ // 支付宝小程序不支持 animated 参数,也不支持 scrollTo 方法滚动
448
+ this.scrollTop = targetTop
449
+
450
+ setTimeout(() => {
451
+ this.refreshStickyHeader()
452
+ }, this.scrollAnimationDuration + 100)
453
+ },
454
+ refreshStickyHeader() {
455
+ if (!this.$refs.stickyHeader) return
456
+
457
+ this.$refs.stickyHeader.forEach((header) => {
458
+ if (header && header.refresh) {
459
+ header.refresh()
460
+ }
461
+ })
462
+ }
463
+ }
464
+ })
465
+ </script>
466
+
467
+ <style scoped>
468
+ .scroll-view-container {
469
+ position: relative;
470
+ }
471
+ .mpx-recycle-view {
472
+ position: relative;
473
+ overflow: hidden;
474
+ }
475
+
476
+ .content-wrapper {
477
+ position: relative;
478
+ width: 100%;
479
+ }
480
+
481
+ .infinite-list {
482
+ left: 0;
483
+ right: 0;
484
+ top: 0;
485
+ position: absolute;
486
+ will-change: transform;
487
+ -webkit-backface-visibility: hidden;
488
+ backface-visibility: hidden;
489
+ /* 支付宝环境需要设置允许事件穿透, 否则滑不动 */
490
+ /* @mpx-if (__mpx_mode__ === 'ali') */
491
+ pointer-events: none;
492
+ /* @mpx-endif */
493
+ }
494
+ .sticky-section {
495
+ position: absolute;
496
+ width: 100%;
497
+ height: 100%;
498
+ }
499
+ </style>
500
+ <script type="application/json">
501
+ {
502
+ "disableScroll": true,
503
+ "usingComponents": {
504
+ "sticky-header": "./mpx-sticky-header.mpx"
505
+ },
506
+ "componentGenerics": {
507
+ "recycle-item": {
508
+ "default": "../wx/mpx-recycle-item-default.mpx"
509
+ },
510
+ "section-header": {
511
+ "default": "../wx/mpx-section-header-default.mpx"
512
+ },
513
+ "list-header": {
514
+ "default": "../wx/mpx-list-header-default.mpx"
515
+ }
516
+ }
517
+ }
518
+ </script>