@mpxjs/webpack-plugin 2.10.18-beta.1 → 2.10.18-beta.10

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
@@ -654,7 +654,7 @@ class MpxWebpackPlugin {
654
654
  }, (chunk, set) => {
655
655
  compilation.addRuntimeModule(
656
656
  chunk,
657
- new LoadAsyncChunkModule(this.options.rnConfig && this.options.rnConfig.asyncChunk && this.options.rnConfig.asyncChunk.timeout)
657
+ new LoadAsyncChunkModule()
658
658
  )
659
659
  return true
660
660
  })
@@ -422,7 +422,7 @@ module.exports = function getSpec({ warn, error }) {
422
422
  if (Array.isArray(value) || cssVariableExp.test(value)) return { prop, value }
423
423
  const values = parseValues(value)
424
424
  // Todo transform 排序不一致时,transform动画会闪烁,故这里同样的排序输出 transform
425
- values.sort()
425
+ // values.sort()
426
426
  const transform = []
427
427
  values.forEach(item => {
428
428
  const match = item.match(/([/\w]+)\((.+)\)/)
@@ -0,0 +1,12 @@
1
+ const TAG_NAME = 'titlebar'
2
+
3
+ module.exports = function () {
4
+ return {
5
+ // 匹配标签名,可传递正则
6
+ test: TAG_NAME,
7
+ web (tag, { el }) {
8
+ el.isBuiltIn = true
9
+ return 'mpx-titlebar'
10
+ }
11
+ }
12
+ }
@@ -3,9 +3,8 @@ const Template = require('webpack/lib/Template')
3
3
  const HelperRuntimeModule = require('webpack/lib/runtime/HelperRuntimeModule')
4
4
 
5
5
  class LoadAsyncChunkRuntimeModule extends HelperRuntimeModule {
6
- constructor (timeout) {
6
+ constructor () {
7
7
  super('load async chunk')
8
- this.timeout = timeout || 10000
9
8
  }
10
9
 
11
10
  generate () {
@@ -32,7 +31,7 @@ class LoadAsyncChunkRuntimeModule extends HelperRuntimeModule {
32
31
  ]),
33
32
  '}',
34
33
  'inProgress[url] = [done]',
35
- 'var callback = function (type, result) {',
34
+ 'var callback = function (type) {',
36
35
  Template.indent([
37
36
  'var event = {',
38
37
  Template.indent([
@@ -45,7 +44,6 @@ class LoadAsyncChunkRuntimeModule extends HelperRuntimeModule {
45
44
  ]),
46
45
  Template.indent([
47
46
  'var doneFns = inProgress[url]',
48
- 'clearTimeout(timeout)',
49
47
  'delete inProgress[url]',
50
48
  `doneFns && doneFns.forEach(${runtimeTemplate.returningFunction(
51
49
  'fn(event)',
@@ -53,7 +51,6 @@ class LoadAsyncChunkRuntimeModule extends HelperRuntimeModule {
53
51
  )})`
54
52
  ]),
55
53
  '}',
56
- `var timeout = setTimeout(callback.bind(null, 'timeout'), ${this.timeout})`,
57
54
  `var loadChunkAsyncFn = ${RuntimeGlobals.global}.__mpx.config.rnConfig && ${RuntimeGlobals.global}.__mpx.config.rnConfig.loadChunkAsync`,
58
55
  'try {',
59
56
  Template.indent([
@@ -145,7 +145,10 @@ const Input = forwardRef((props, ref) => {
145
145
  };
146
146
  const setKeyboardAvoidContext = () => {
147
147
  if (keyboardAvoid) {
148
- keyboardAvoid.current = { cursorSpacing, ref: nodeRef, adjustPosition, holdKeyboard, readyToShow: true };
148
+ // readyToShow 仅在从另一个输入框切换聚焦时为 true(ref 不同),
149
+ // 避免同一个输入框重复调用(onTouchStart + useEffect)或单次聚焦时误设为 true 导致无法正常失焦
150
+ const readyToShow = !!(keyboardAvoid.current && keyboardAvoid.current.ref !== nodeRef);
151
+ keyboardAvoid.current = { cursorSpacing, ref: nodeRef, adjustPosition, holdKeyboard, readyToShow };
149
152
  }
150
153
  };
151
154
  const onTouchStart = () => {
@@ -54,7 +54,7 @@ import {
54
54
  NativeTouchEvent
55
55
  } from 'react-native'
56
56
  import { warn } from '@mpxjs/utils'
57
- import { useUpdateEffect, useTransformStyle, useLayout, extendObject, isAndroid } from './utils'
57
+ import { useUpdateEffect, useTransformStyle, useLayout, extendObject, isIOS } from './utils'
58
58
  import useInnerProps, { getCustomEvent } from './getInnerListeners'
59
59
  import useNodesRef, { HandlerRef } from './useNodesRef'
60
60
  import { FormContext, FormFieldValue, KeyboardAvoidContext } from './context'
@@ -285,7 +285,10 @@ const Input = forwardRef<HandlerRef<TextInput, FinalInputProps>, FinalInputProps
285
285
 
286
286
  const setKeyboardAvoidContext = () => {
287
287
  if (keyboardAvoid) {
288
- keyboardAvoid.current = { cursorSpacing, ref: nodeRef, adjustPosition, holdKeyboard, readyToShow: true }
288
+ // readyToShow 仅在从另一个输入框切换聚焦时为 true(ref 不同),
289
+ // 避免同一个输入框重复调用(onTouchStart + useEffect)或单次聚焦时误设为 true 导致无法正常失焦
290
+ const readyToShow = !!(keyboardAvoid.current && keyboardAvoid.current.ref !== nodeRef)
291
+ keyboardAvoid.current = { cursorSpacing, ref: nodeRef, adjustPosition, holdKeyboard, readyToShow }
289
292
  }
290
293
  }
291
294
 
@@ -469,12 +472,6 @@ const Input = forwardRef<HandlerRef<TextInput, FinalInputProps>, FinalInputProps
469
472
  : (nodeRef.current as TextInput)?.blur()
470
473
  }, [isAutoFocus])
471
474
 
472
- // 使用 multiline 来修复光标位置问题
473
- // React Native 的 TextInput 在 textAlign center + placeholder 时光标会跑到右边
474
- // 这个问题只在 Android 上出现
475
- // 参考:https://github.com/facebook/react-native/issues/28794 (Android only)
476
- const needMultilineFix = isAndroid && !multiline
477
-
478
475
  const innerProps = useInnerProps(
479
476
  extendObject(
480
477
  {},
@@ -498,7 +495,7 @@ const Input = forwardRef<HandlerRef<TextInput, FinalInputProps>, FinalInputProps
498
495
  underlineColorAndroid: 'rgba(0,0,0,0)',
499
496
  textAlignVertical: textAlignVertical,
500
497
  placeholderTextColor: placeholderStyle?.color,
501
- multiline: multiline || needMultilineFix,
498
+ multiline: !!multiline,
502
499
  onTouchStart,
503
500
  onTouchEnd,
504
501
  onFocus,
@@ -508,7 +505,6 @@ const Input = forwardRef<HandlerRef<TextInput, FinalInputProps>, FinalInputProps
508
505
  onContentSizeChange,
509
506
  onSubmitEditing: bindconfirm && onSubmitEditing
510
507
  },
511
- needMultilineFix ? { numberOfLines: 1 } : {},
512
508
  !!multiline && confirmType === 'return' ? {} : { enterKeyHint: confirmType }
513
509
  ),
514
510
  [
@@ -136,6 +136,16 @@ const _PickerViewColumn = forwardRef<HandlerRef<ScrollView & View, ColumnProps>,
136
136
  }
137
137
  }, [])
138
138
 
139
+ // `contentOffset` prop sets visual position but does not fire scroll events,
140
+ // so `offsetYShared` (from `useScrollViewOffset`) stays at 0 until the user scrolls.
141
+ // Directly sync it whenever `itemRawH` is established so wheel animation renders correctly.
142
+ useEffect(() => {
143
+ if (!itemRawH || dragging.current || scrolling.current) {
144
+ return
145
+ }
146
+ offsetYShared.value = activeIndex.current * itemRawH
147
+ }, [itemRawH])
148
+
139
149
  useEffect(() => {
140
150
  if (
141
151
  !scrollViewRef.current ||
@@ -79,6 +79,7 @@ interface SwiperProps {
79
79
  disableGesture?: boolean
80
80
  'display-multiple-items'?: number
81
81
  bindchange?: (event: NativeSyntheticEvent<TouchEvent> | unknown) => void
82
+ bindchangestart?: (event: NativeSyntheticEvent<TouchEvent> | unknown) => void
82
83
  }
83
84
 
84
85
  /**
@@ -159,7 +160,8 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
159
160
  circular = false,
160
161
  disableGesture = false,
161
162
  current: propCurrent = 0,
162
- bindchange
163
+ bindchange,
164
+ bindchangestart
163
165
  } = props
164
166
 
165
167
  const dotCommonStyle = {
@@ -425,6 +427,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
425
427
  nextIndex += 1
426
428
  // targetOffset = -nextIndex * step.value - preMarginShared.value
427
429
  targetOffset = -nextIndex * step.value
430
+ runOnJSCallback('handleSwiperChangeStart', nextIndex)
428
431
  offset.value = withTiming(targetOffset, {
429
432
  duration: easeDuration,
430
433
  easing: easeMap[easeingFunc]
@@ -438,6 +441,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
438
441
  nextIndex = 0
439
442
  targetOffset = -(childrenLength.value + patchElmNumShared.value) * step.value + preMarginShared.value
440
443
  // 执行动画到下一帧
444
+ runOnJSCallback('handleSwiperChangeStart', nextIndex)
441
445
  offset.value = withTiming(targetOffset, {
442
446
  duration: easeDuration
443
447
  }, () => {
@@ -451,6 +455,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
451
455
  nextIndex = currentIndex.value + 1
452
456
  targetOffset = -(nextIndex + patchElmNumShared.value) * step.value + preMarginShared.value
453
457
  // 执行动画到下一帧
458
+ runOnJSCallback('handleSwiperChangeStart', nextIndex)
454
459
  offset.value = withTiming(targetOffset, {
455
460
  duration: easeDuration,
456
461
  easing: easeMap[easeingFunc]
@@ -489,11 +494,17 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
489
494
  bindchange && bindchange(eventData)
490
495
  }
491
496
 
497
+ function handleSwiperChangeStart (current: number) {
498
+ const eventData = getCustomEvent('changestart', {}, { detail: { current }, layoutRef: layoutRef })
499
+ bindchangestart && bindchangestart(eventData)
500
+ }
501
+
492
502
  const runOnJSCallbackRef = useRef({
493
503
  loop,
494
504
  pauseLoop,
495
505
  resumeLoop,
496
- handleSwiperChange
506
+ handleSwiperChange,
507
+ handleSwiperChangeStart
497
508
  })
498
509
  const runOnJSCallback = useRunOnJSCallback(runOnJSCallbackRef)
499
510
 
@@ -514,6 +525,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
514
525
  if (targetOffset !== offset.value) {
515
526
  // 内部基于props.current!==currentIndex.value决定是否使用动画及更新currentIndex.value
516
527
  if (propCurrent !== undefined && propCurrent !== currentIndex.value) {
528
+ runOnJSCallback('handleSwiperChangeStart', propCurrent)
517
529
  offset.value = withTiming(targetOffset, {
518
530
  duration: easeDuration,
519
531
  easing: easeMap[easeingFunc]
@@ -805,7 +817,10 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
805
817
  const offsetHalf = computeHalf()
806
818
  if (childrenLength.value > 1 && offsetHalf) {
807
819
  const { selectedIndex } = getTargetPosition({ transdir: moveDistance } as EventEndType)
808
- currentIndex.value = selectedIndex
820
+ if (selectedIndex !== currentIndex.value) {
821
+ currentIndex.value = selectedIndex
822
+ runOnJS(runOnJSCallback)('handleSwiperChangeStart', selectedIndex)
823
+ }
809
824
  }
810
825
  // 2. 非循环: 处理用户一直拖拽到临界点的场景,如果放到onFinalize无法阻止offset.value更新为越界的值
811
826
  if (!circularShared.value) {
@@ -335,7 +335,7 @@ export function parseValues (str: string, char = ' ') {
335
335
  function parseTransform (transformStr: string) {
336
336
  const values = parseValues(transformStr)
337
337
  // Todo transform 排序不一致时,transform动画会闪烁,故这里同样的排序输出 transform
338
- values.sort()
338
+ // values.sort()
339
339
  const transform: { [propName: string]: string | number | number[] }[] = []
340
340
  values.forEach(item => {
341
341
  const match = item.match(/([/\w]+)\((.+)\)/)
@@ -0,0 +1,244 @@
1
+ <script>
2
+ import mpx from '@mpxjs/core'
3
+ export default {
4
+ name: 'mpx-titlebar',
5
+ props: {
6
+ // 来自 app.json 中 window 的 titlebar 相关配置
7
+ windowConfig: {
8
+ type: Object,
9
+ default: () => global.__mpxPageConfig
10
+ },
11
+ // 来自 页面 json 中的 titlebar 相关配置,会覆盖 windowConfig
12
+ pageConfig: {
13
+ type: Object,
14
+ default: () => ({})
15
+ }
16
+ },
17
+ computed: {
18
+ // 合并全局 window 配置与页面配置(页面配置覆盖全局配置)
19
+ cfg () {
20
+ return Object.assign({}, this.windowConfig || {}, this.pageConfig || {})
21
+ },
22
+ // 标题文本(兼容常见字段名)
23
+ titleText () {
24
+ return this.cfg.navigationBarTitleText || this.cfg.title || ''
25
+ },
26
+ // 背景色(兼容常见字段)
27
+ backgroundColor () {
28
+ return this.cfg.navigationBarBackgroundColor || '#ffffff'
29
+ },
30
+ // 文本颜色,微信小程序中 navigationBarTextStyle 为 white 或 black
31
+ textColor () {
32
+ const style = this.cfg.navigationBarTextStyle || 'black'
33
+ return style === 'white' ? '#ffffff' : '#000000'
34
+ },
35
+ // navigationStyle: 'default' | 'custom',custom 表示需要自定义绘制
36
+ navigationStyle () {
37
+ return this.cfg.navigationStyle || 'default'
38
+ },
39
+ // 是否隐藏(navigationStyle 为 'custom' 时也应隐藏)
40
+ hidden () {
41
+ return mpx.config?.webConfig?.enableTitleBar !== true || this.navigationStyle === 'custom'
42
+ },
43
+ // 是否展示返回按钮:根据浏览器历史判断(不依赖额外 page 配置)
44
+ showBack () {
45
+ console.log('showBack', this.$router.stack.length)
46
+ try {
47
+ return this.$router.stack.length > 1
48
+ } catch (e) {
49
+ return false
50
+ }
51
+ },
52
+ // safe area 顶部 padding,使用 env(safe-area-inset-top)
53
+ safeStyle () {
54
+ // 多数浏览器支持 env(), 为兼容也使用 constant() 备选(旧 iOS Safari)
55
+ return {
56
+ paddingTop: 'env(safe-area-inset-top, 0px)'
57
+ }
58
+ },
59
+ // 内部标题栏高度(遵循小程序常见平台差异)
60
+ innerHeight () {
61
+ const isIOS = /iP(hone|od|ad)/.test(navigator.userAgent)
62
+ return (isIOS ? 44 : 48) + 'px'
63
+ },
64
+ rootStyle () {
65
+ return {
66
+ background: this.backgroundColor,
67
+ color: this.textColor
68
+ }
69
+ },
70
+ innerStyle () {
71
+ return {
72
+ height: this.innerHeight
73
+ }
74
+ }
75
+ ,
76
+ // content wrapper style: padding-top to avoid being covered by fixed titlebar
77
+ contentStyle () {
78
+ // use calc to combine innerHeight and safe-area inset
79
+ return {
80
+ paddingTop: this.hidden ? '0px' : `calc(${this.innerHeight} + env(safe-area-inset-top, 0px))`,
81
+ // create its own layer to avoid overlapping issues
82
+ transform: 'translateZ(0)',
83
+ willChange: 'transform'
84
+ }
85
+ }
86
+ },
87
+ methods: {
88
+ // 左侧点击:派发事件并在可回退时回退
89
+ onLeftClick (e) {
90
+ this.$emit('click-left', e)
91
+ if (this.showBack) {
92
+ try { window.history.back() } catch (err) {}
93
+ }
94
+ }
95
+ },
96
+ render (h) {
97
+ console.log('render mpx-titlebar', this.cfg, 'windowConfig', this.windowConfig, 'pageConfig', this.pageConfig)
98
+ const leftChildren = []
99
+
100
+ // default back button (SVG) — no left slot support
101
+ if (this.showBack) {
102
+ leftChildren.push(
103
+ h('button', {
104
+ class: 'mpx-titlebar__back',
105
+ attrs: { 'aria-label': 'back', type: 'button' }
106
+ }, [
107
+ h('svg', {
108
+ attrs: {
109
+ viewBox: '0 0 24 24',
110
+ width: '20',
111
+ height: '20',
112
+ fill: 'none',
113
+ xmlns: 'http://www.w3.org/2000/svg',
114
+ focusable: 'false',
115
+ 'aria-hidden': 'true'
116
+ }
117
+ }, [
118
+ h('path', {
119
+ attrs: {
120
+ d: 'M15 18l-6-6 6-6',
121
+ stroke: 'currentColor',
122
+ 'stroke-width': '2',
123
+ 'stroke-linecap': 'round',
124
+ 'stroke-linejoin': 'round'
125
+ }
126
+ })
127
+ ])
128
+ ])
129
+ )
130
+ }
131
+
132
+ // center shows title; only default slot (page content) is supported
133
+ const centerChildren = [
134
+ h('div', { class: 'mpx-titlebar__title', style: { color: this.textColor } }, [this.titleText])
135
+ ]
136
+
137
+ // top-level wrapper: contains fixed titlebar and page content wrapper
138
+ return h('page', { class: 'mpx-titlebar-wrapper' }, [
139
+ // fixed titlebar
140
+ h('div', {
141
+ class: ['mpx-titlebar', { 'mpx-titlebar--hidden': this.hidden }],
142
+ style: this.rootStyle
143
+ }, [
144
+ h('div', { class: 'mpx-titlebar__safe', style: this.safeStyle }, [
145
+ h('div', { class: 'mpx-titlebar__inner', style: this.innerStyle }, [
146
+ h('div', { class: 'mpx-titlebar__left', on: { click: this.onLeftClick } }, leftChildren),
147
+ h('div', { class: 'mpx-titlebar__center' }, centerChildren),
148
+ h('div', { class: 'mpx-titlebar__right' }, [])
149
+ ])
150
+ ])
151
+ ]),
152
+
153
+ // page content wrapper: default slot is page content
154
+ h('div', { class: 'mpx-titlebar__content', style: this.contentStyle }, this.$slots.default || [])
155
+ ])
156
+ }
157
+ }
158
+ </script>
159
+
160
+ <style scoped>
161
+ .mpx-titlebar {
162
+ width: 100%;
163
+ box-sizing: border-box;
164
+ -webkit-font-smoothing: antialiased;
165
+ }
166
+ .mpx-titlebar--hidden {
167
+ display: none;
168
+ }
169
+ .mpx-titlebar__safe {
170
+ /* safe area handled by padding-top; include both env and constant for broader iOS support */
171
+ padding-top: env(safe-area-inset-top, 0px);
172
+ padding-top: constant(safe-area-inset-top, 0px);
173
+ }
174
+ .mpx-titlebar__inner {
175
+ display: flex;
176
+ align-items: center;
177
+ justify-content: space-between;
178
+ padding: 0 12px;
179
+ box-sizing: border-box;
180
+ }
181
+ .mpx-titlebar__left,
182
+ .mpx-titlebar__right {
183
+ flex: 0 0 auto;
184
+ min-width: 44px;
185
+ display: flex;
186
+ align-items: center;
187
+ }
188
+ .mpx-titlebar__center {
189
+ flex: 1 1 auto;
190
+ display: flex;
191
+ align-items: center;
192
+ justify-content: center;
193
+ overflow: hidden;
194
+ padding: 0 8px;
195
+ }
196
+ .mpx-titlebar__title {
197
+ font-size: 17px;
198
+ white-space: nowrap;
199
+ text-overflow: ellipsis;
200
+ overflow: hidden;
201
+ font-weight: 500;
202
+ }
203
+ .mpx-titlebar__back {
204
+ background: none;
205
+ border: none;
206
+ font-size: 20px;
207
+ color: inherit;
208
+ padding: 6px;
209
+ cursor: pointer;
210
+ }
211
+
212
+ /* wrapper and content layout */
213
+ .mpx-titlebar-wrapper {
214
+ position: relative;
215
+ width: 100%;
216
+ height: 100%;
217
+ }
218
+
219
+ .mpx-titlebar {
220
+ position: fixed;
221
+ top: 0;
222
+ left: 0;
223
+ right: 0;
224
+ z-index: 1000; /* ensure above page content */
225
+ }
226
+
227
+ .mpx-titlebar__content {
228
+ position: relative;
229
+ width: 100%;
230
+ min-height: 100%;
231
+ box-sizing: border-box;
232
+ background: transparent;
233
+ }
234
+
235
+ /* SVG icon sizing and inherit color */
236
+ .mpx-titlebar__back svg {
237
+ display: block;
238
+ width: 20px;
239
+ height: 20px;
240
+ }
241
+ .mpx-titlebar__back path {
242
+ stroke: currentColor;
243
+ }
244
+ </style>
@@ -91,7 +91,7 @@ function parse(cssString) {
91
91
  return ast
92
92
  }
93
93
 
94
- function evaluateCondition(condition, defs) {
94
+ function evaluateCondition(condition, defs, filePath) {
95
95
  try {
96
96
  const keys = Object.keys(defs)
97
97
  const values = keys.map(key => defs[key])
@@ -99,12 +99,12 @@ function evaluateCondition(condition, defs) {
99
99
  const func = new Function(...keys, `return (${condition});`)
100
100
  return func(...values)
101
101
  } catch (e) {
102
- console.error(`[Mpx style error]:Error evaluating condition: ${condition}`, e)
102
+ console.error(`[Mpx style error] File: ${filePath}, Error evaluating condition: ${condition}`, e)
103
103
  return false
104
104
  }
105
105
  }
106
106
 
107
- function traverseAndEvaluate(ast, defs) {
107
+ function traverseAndEvaluate(ast, defs, filePath) {
108
108
  let output = ''
109
109
  let batchedIf = false
110
110
  function traverse(nodes) {
@@ -114,12 +114,12 @@ function traverseAndEvaluate(ast, defs) {
114
114
  } else if (node.type === 'If') {
115
115
  // 直接判断 If 节点
116
116
  batchedIf = false
117
- if (evaluateCondition(node.condition, defs)) {
117
+ if (evaluateCondition(node.condition, defs, filePath)) {
118
118
  traverse(node.children)
119
119
  batchedIf = true
120
120
  }
121
121
  } else if (node.type === 'ElseIf' && !batchedIf) {
122
- if (evaluateCondition(node.condition, defs)) {
122
+ if (evaluateCondition(node.condition, defs, filePath)) {
123
123
  traverse(node.children)
124
124
  batchedIf = true
125
125
  }
@@ -136,11 +136,12 @@ function traverseAndEvaluate(ast, defs) {
136
136
  *
137
137
  * @param {string} content
138
138
  * @param {Record<string, any>} defs
139
+ * @param {string} [filePath]
139
140
  * @returns
140
141
  */
141
- function stripCondition(content, defs) {
142
+ function stripCondition(content, defs, filePath) {
142
143
  const ast = parse(content)
143
- return traverseAndEvaluate(ast, defs)
144
+ return traverseAndEvaluate(ast, defs, filePath || 'unknown')
144
145
  }
145
146
 
146
147
  let proxyReadFileSync
@@ -185,10 +186,10 @@ function startFSStripForCss(defs) {
185
186
  if (shouldStrip(path)) {
186
187
  try {
187
188
  if (typeof content === 'string') {
188
- return stripCondition(content, defs)
189
+ return stripCondition(content, defs, path)
189
190
  } else if (Buffer.isBuffer(content)) {
190
191
  const str = content.toString('utf-8')
191
- const result = stripCondition(str, defs)
192
+ const result = stripCondition(str, defs, path)
192
193
  if (result !== str) {
193
194
  return Buffer.from(result, 'utf-8')
194
195
  }
@@ -215,11 +216,11 @@ function startFSStripForCss(defs) {
215
216
  if (shouldStrip(path)) {
216
217
  try {
217
218
  if (typeof data === 'string') {
218
- const result = stripCondition(data, defs)
219
+ const result = stripCondition(data, defs, path)
219
220
  return callback(null, result)
220
221
  } else if (Buffer.isBuffer(data)) {
221
222
  const content = data.toString('utf-8')
222
- const result = stripCondition(content, defs)
223
+ const result = stripCondition(content, defs, path)
223
224
  if (result !== content) {
224
225
  return callback(null, Buffer.from(result, 'utf-8'))
225
226
  }
@@ -2616,6 +2616,78 @@ function postProcessAliComponentRootView (el, options, meta) {
2616
2616
  }
2617
2617
  }
2618
2618
 
2619
+ function getWebTitleBarContainer(root, options, meta) {
2620
+ // titlebar 容器(替代 .wx-titlebar)
2621
+ const titlebarStyle = 'position:fixed;top:0;left:0;right:0;width:100%;box-sizing:border-box;z-index:99999;background-color:#ffffff;height:calc(44px + env(safe-area-inset-top, 0px));padding-top:env(safe-area-inset-top, 0px);display:flex;align-items:center;justify-content:center;user-select:none;'
2622
+ const titlebar = createASTElement('view', [
2623
+ { name: 'style', value: titlebarStyle },
2624
+ { name: 'wx:if', value: '{{($options?.__mpxPageConfig?.navigationStyle || global?.__mpxPageConfig?.navigationStyle) !== "custom"}}' }
2625
+ ])
2626
+
2627
+ // 左侧返回按钮容器(替代 .wx-titlebar__left)
2628
+ const leftStyle = 'position:absolute;left:12px;top:env(safe-area-inset-top, 0px);height:44px;display:flex;align-items:center;cursor:pointer;opacity:0.9;transition:opacity 0.15s;'
2629
+ const left = createASTElement('view', [
2630
+ { name: 'style', value: leftStyle },
2631
+ { name: '@tap', value: '() => this.$router.back()' }
2632
+ ])
2633
+
2634
+ // svg 图标(替代 .wx-titlebar__back-icon)
2635
+ const svg = createASTElement('svg', [
2636
+ { name: 'style', value: 'width:18px;height:18px;fill:#000;' },
2637
+ { name: 'viewBox', value: '0 0 1024 1024' }
2638
+ ])
2639
+
2640
+ const path = createASTElement('path', [
2641
+ { name: 'd', value: 'M621.6 170.4L408.8 384l212.8 213.6-59.2 59.2L290.4 384 562.4 111.2z' }
2642
+ ])
2643
+
2644
+ // 标题(替代 .wx-titlebar__title)
2645
+ const titleStyle = 'font-size:17px;font-weight:500;color:#111;line-height:44px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:70%;text-align:center;'
2646
+ const title = createASTElement('view', [
2647
+ { name: 'style', value: titleStyle }
2648
+ ])
2649
+
2650
+ const text = {
2651
+ type: 3,
2652
+ // 支付宝小程序模板解析中未对Mustache进行特殊处理,无论是否decode都会解析失败,无解,只能支付宝侧进行修复
2653
+ text: '{{$options.__mpxPageConfig.navigationBarTitleText}}'
2654
+ }
2655
+
2656
+ // 页面内容容器(替代 .page-content),并保留原始内部 transform
2657
+ const contentStyle = 'transform: translateX(0);margin-top:calc(44px + env(safe-area-inset-top, 0px));padding:16px;'
2658
+ const content = createASTElement('view', [
2659
+ { name: 'style', value: contentStyle }
2660
+ ])
2661
+
2662
+ // 组装节点树
2663
+ addChild(svg, path)
2664
+ addChild(left, svg)
2665
+
2666
+ addChild(title, text)
2667
+
2668
+ addChild(titlebar, left)
2669
+ addChild(titlebar, title)
2670
+
2671
+ addChild(root, titlebar)
2672
+ addChild(root, content)
2673
+
2674
+ // processElement(root, root, options, meta)
2675
+ processElement(titlebar, root, options, meta)
2676
+ closeElement(titlebar, options, meta)
2677
+ processElement(left, root, options, meta)
2678
+ // closeElement(left, options, meta)
2679
+ processElement(svg, root, options, meta)
2680
+ processElement(path, root, options, meta)
2681
+ processElement(title, root, options, meta)
2682
+ processElement(content, root, options, meta)
2683
+ processText(text, options, meta)
2684
+
2685
+ return {
2686
+ content,
2687
+ titlebar
2688
+ }
2689
+ }
2690
+
2619
2691
  // 有virtualHost情况wx组件注入virtualHost。无virtualHost阿里组件注入root-view。其他跳过。
2620
2692
  function getVirtualHostRoot (options, meta) {
2621
2693
  if (srcMode === 'wx') {
@@ -2652,7 +2724,14 @@ function getVirtualHostRoot (options, meta) {
2652
2724
  }
2653
2725
  }
2654
2726
  if (isWeb(mode) && ctorType === 'page') {
2655
- return createASTElement('page')
2727
+ const rootView = createASTElement('mpx-titlebar', [
2728
+ {
2729
+ name: 'pageConfig',
2730
+ value: '{{ this.$options.__mpxPageConfig }}'
2731
+ }
2732
+ ])
2733
+ processElement(rootView, rootView, options, meta)
2734
+ return rootView
2656
2735
  }
2657
2736
  if (isReact(mode) && ctorType === 'page') {
2658
2737
  const rootView = createASTElement('view', [
@@ -60,7 +60,9 @@ Vue.use(VueRouter)\n`
60
60
  globalTabBar
61
61
  })
62
62
 
63
- output += `var App = require(${stringifyRequest(loaderContext, addQuery(loaderContext.resource, { isApp: true }))}).default\n`
63
+ output += `var App = require(${stringifyRequest(loaderContext, addQuery(loaderContext.resource, { isApp: true }))}).default;\n`
64
+
65
+ output += `Vue.component('mpx-titlebar', require(${stringifyRequest(loaderContext, normalize.lib('runtime/components/web/mpx-titlebar.vue'))}).default);\n`
64
66
 
65
67
  output += `
66
68
  export default processAppOption({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpxjs/webpack-plugin",
3
- "version": "2.10.18-beta.1",
3
+ "version": "2.10.18-beta.10",
4
4
  "description": "mpx compile core",
5
5
  "keywords": [
6
6
  "mpx"