@mpxjs/webpack-plugin 2.9.65 → 2.9.66

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.
@@ -1,7 +1,8 @@
1
- import { ViewStyle, ImageResizeMode } from 'react-native'
1
+ import { ViewStyle } from 'react-native'
2
+ import { FunctionComponent } from 'react'
2
3
 
3
4
  type NumberVal = number | `${number}%`
4
- type backgroundPositionList = [ 'left'| 'right', NumberVal, 'top' | 'bottom', NumberVal ] | []
5
+ type backgroundPositionList = ['left' | 'right', NumberVal, 'top' | 'bottom', NumberVal] | []
5
6
 
6
7
  export type ExtendedViewStyle = ViewStyle & {
7
8
  backgroundImage?: string
@@ -9,4 +10,9 @@ export type ExtendedViewStyle = ViewStyle & {
9
10
  borderRadius?: string | number
10
11
  backgroundPosition?: backgroundPositionList
11
12
  [key: string]: any
13
+ transform?: {[key: string]: number | string}[]
14
+ }
15
+
16
+ export type ExtendedFunctionComponent = FunctionComponent & {
17
+ isCustomText?: boolean
12
18
  }
@@ -0,0 +1,248 @@
1
+ import { useEffect, useMemo, useRef } from 'react'
2
+ import { TransformsStyle } from 'react-native'
3
+ import {
4
+ Easing,
5
+ useSharedValue,
6
+ withTiming,
7
+ useAnimatedStyle,
8
+ withSequence,
9
+ withDelay,
10
+ makeMutable,
11
+ cancelAnimation,
12
+ SharedValue,
13
+ WithTimingConfig,
14
+ AnimationCallback
15
+ } from 'react-native-reanimated'
16
+ import { ExtendedViewStyle } from './types/common'
17
+ import type { _ViewProps } from './mpx-view'
18
+
19
+ // type TransformKey = 'translateX' | 'translateY' | 'rotate' | 'rotateX' | 'rotateY' | 'rotateZ' | 'scaleX' | 'scaleY' | 'skewX' | 'skewY'
20
+ // type NormalKey = 'opacity' | 'backgroundColor' | 'width' | 'height' | 'top' | 'right' | 'bottom' | 'left' | 'transformOrigin'
21
+ // type RuleKey = TransformKey | NormalKey
22
+ type AnimatedOption = {
23
+ duration: number
24
+ delay: number
25
+ useNativeDriver: boolean
26
+ timingFunction: 'linear' | 'ease' | 'ease-in' | 'ease-in-out'| 'ease-out'
27
+ transformOrigin: string
28
+ }
29
+ type ExtendWithTimingConfig = WithTimingConfig & {
30
+ delay: number
31
+ }
32
+ export type AnimationStepItem = {
33
+ animatedOption: AnimatedOption
34
+ rules: Map<string, number | string>
35
+ transform: Map<string, number>
36
+ }
37
+ export type AnimationProp = {
38
+ id: number,
39
+ actions: AnimationStepItem[]
40
+ }
41
+
42
+ // 微信 timingFunction 和 RN Easing 对应关系
43
+ const EasingKey = {
44
+ linear: Easing.linear,
45
+ ease: Easing.ease,
46
+ 'ease-in': Easing.in(Easing.ease),
47
+ 'ease-in-out': Easing.inOut(Easing.ease),
48
+ 'ease-out': Easing.out(Easing.ease)
49
+ // 'step-start': '',
50
+ // 'step-end': ''
51
+ }
52
+ const TransformInitial: ExtendedViewStyle = {
53
+ // matrix: 0,
54
+ // matrix3d: 0,
55
+ rotate: '0deg',
56
+ rotateX: '0deg',
57
+ rotateY: '0deg',
58
+ rotateZ: '0deg',
59
+ // rotate3d:[0,0,0]
60
+ scale: 1,
61
+ // scale3d: [1, 1, 1],
62
+ scaleX: 1,
63
+ scaleY: 1,
64
+ // scaleZ: 1,
65
+ skew: 0,
66
+ skewX: '0deg',
67
+ skewY: '0deg',
68
+ translate: 0,
69
+ // translate3d: 0,
70
+ translateX: 0,
71
+ translateY: 0
72
+ // translateZ: 0,
73
+ }
74
+ // 动画默认初始值
75
+ const InitialValue: ExtendedViewStyle = Object.assign({
76
+ opacity: 1,
77
+ backgroundColor: 'transparent',
78
+ width: 0,
79
+ height: 0,
80
+ top: 0,
81
+ right: 0,
82
+ bottom: 0,
83
+ left: 0,
84
+ transformOrigin: ['50%', '50%', 0]
85
+ }, TransformInitial)
86
+ const TransformOrigin = 'transformOrigin'
87
+ // deg 角度
88
+ // const isDeg = (key: RuleKey) => ['rotateX', 'rotateY', 'rotateZ', 'rotate', 'skewX', 'skewY'].includes(key)
89
+ // 背景色
90
+ // const isBg = (key: RuleKey) => key === 'backgroundColor'
91
+ // transform
92
+ const isTransform = (key: string) => Object.keys(TransformInitial).includes(key)
93
+
94
+ export default function useAnimationHooks<T, P> (props: _ViewProps) {
95
+ const { style: originalStyle = {}, animation } = props
96
+ // id 标识
97
+ const id = animation?.id || -1
98
+ // 有动画样式的 style key
99
+ const animatedStyleKeys = useSharedValue([] as (string|string[])[])
100
+ const animatedKeys = useRef({} as {[propName: keyof ExtendedViewStyle]: Boolean})
101
+ // ** 全量 style prop sharedValue
102
+ // 不能做增量的原因:
103
+ // 1 尝试用 useRef,但 useAnimatedStyle 访问后的 ref 不能在增加新的值,被冻结
104
+ // 2 尝试用 useSharedValue,因为实际触发的 style prop 需要是 sharedValue 才能驱动动画,若外层 shareValMap 也是 sharedValue,动画无法驱动。
105
+ const shareValMap = useMemo(() => {
106
+ return Object.keys(InitialValue).reduce((valMap, key) => {
107
+ const defaultVal = getInitialVal(key, isTransform(key))
108
+ valMap[key] = makeMutable(defaultVal)
109
+ return valMap
110
+ }, {} as { [propName: keyof ExtendedViewStyle]: SharedValue<string|number> })
111
+ }, [])
112
+ // ** 获取动画样式prop & 驱动动画
113
+ useEffect(() => {
114
+ if (id === -1) return
115
+ // 更新动画样式 key map
116
+ animatedKeys.current = getAnimatedStyleKeys()
117
+ const keys = Object.keys(animatedKeys.current)
118
+ animatedStyleKeys.value = formatAnimatedKeys([TransformOrigin, ...keys])
119
+ // 驱动动画
120
+ createAnimation(keys)
121
+ }, [id])
122
+ // ** 清空动画
123
+ useEffect(() => {
124
+ return () => {
125
+ Object.values(shareValMap).forEach((value) => {
126
+ cancelAnimation(value)
127
+ })
128
+ }
129
+ }, [])
130
+ // 根据 animation action 创建&驱动动画 key => wi
131
+ function createAnimation (animatedKeys: string[] = []) {
132
+ const actions = animation?.actions || []
133
+ const sequence = {} as { [propName: keyof ExtendedViewStyle]: (string|number)[] }
134
+ const lastValueMap = {} as { [propName: keyof ExtendedViewStyle]: string|number }
135
+ actions.forEach(({ animatedOption, rules, transform }, index) => {
136
+ const { delay, duration, timingFunction, transformOrigin } = animatedOption
137
+ const easing = EasingKey[timingFunction] || Easing.inOut(Easing.quad)
138
+ let needSetCallback = true
139
+ const setTransformOrigin: AnimationCallback = (finished: boolean) => {
140
+ 'worklet'
141
+ // 动画结束后设置下一次transformOrigin
142
+ if (finished) {
143
+ if (index < actions.length - 1) {
144
+ const transformOrigin = actions[index + 1].animatedOption?.transformOrigin
145
+ transformOrigin && (shareValMap[TransformOrigin].value = transformOrigin)
146
+ }
147
+ }
148
+ }
149
+ if (index === 0) {
150
+ // 设置当次中心
151
+ shareValMap[TransformOrigin].value = transformOrigin
152
+ }
153
+ // 添加每个key的多次step动画
154
+ animatedKeys.forEach(key => {
155
+ let toVal = (rules.get(key) || transform.get(key)) as number|string
156
+ // key不存在,第一轮取shareValMap[key]value,非第一轮取上一轮的
157
+ if (!toVal) {
158
+ toVal = index > 0 ? lastValueMap[key] : shareValMap[key].value
159
+ }
160
+ const animation = getAnimation({ key, value: toVal }, { delay, duration, easing }, needSetCallback ? setTransformOrigin : undefined)
161
+ needSetCallback = false
162
+ if (!sequence[key]) {
163
+ sequence[key] = [animation]
164
+ } else {
165
+ sequence[key].push(animation)
166
+ }
167
+ // 更新一下 lastValueMap
168
+ lastValueMap[key] = toVal
169
+ })
170
+ // 赋值驱动动画
171
+ animatedKeys.forEach((key) => {
172
+ const animations = sequence[key]
173
+ shareValMap[key].value = withSequence(...animations)
174
+ })
175
+ })
176
+ }
177
+ // 创建单个animation
178
+ function getAnimation ({ key, value }: { key: string, value: string|number }, { delay, duration, easing }: ExtendWithTimingConfig, callback?: AnimationCallback) {
179
+ const animation = typeof callback === 'function'
180
+ ? withTiming(value, { duration, easing }, callback)
181
+ : withTiming(value, { duration, easing })
182
+ return delay ? withDelay(delay, animation) : animation
183
+ }
184
+ // 获取初始值(prop style or 默认值)
185
+ function getInitialVal (key: keyof ExtendedViewStyle, isTransform = false) {
186
+ if (isTransform && originalStyle.transform?.length) {
187
+ let initialVal = InitialValue[key]
188
+ // 仅支持 { transform: [{rotateX: '45deg'}, {rotateZ: '0.785398rad'}] } 格式的初始样式
189
+ originalStyle.transform.forEach(item => {
190
+ if (item[key] !== undefined) initialVal = item[key]
191
+ })
192
+ return initialVal
193
+ }
194
+ return originalStyle[key] === undefined ? InitialValue[key] : originalStyle[key]
195
+ }
196
+ // 循环 animation actions 获取所有有动画的 style prop name
197
+ function getAnimatedStyleKeys () {
198
+ return (animation?.actions || []).reduce((keyMap, action) => {
199
+ const { rules, transform } = action
200
+ const ruleArr = [...rules.keys(), ...transform.keys()]
201
+ ruleArr.forEach(key => {
202
+ if (!keyMap[key]) keyMap[key] = true
203
+ })
204
+ return keyMap
205
+ }, animatedKeys.current)
206
+ }
207
+ // animated key transform 格式化
208
+ function formatAnimatedKeys (keys: string[] = []) {
209
+ const animatedKeys = [] as (string|string[])[]
210
+ const transforms = [] as string[]
211
+ keys.forEach(key => {
212
+ if (isTransform(key)) {
213
+ transforms.push(key)
214
+ } else {
215
+ animatedKeys.push(key)
216
+ }
217
+ })
218
+ if (transforms.length) animatedKeys.push(transforms)
219
+ return animatedKeys
220
+ }
221
+ // transform 数组转对象
222
+ function getTransformObj () {
223
+ 'worklet'
224
+ const transforms = originalStyle.transform || []
225
+ return transforms.reduce((transformObj, item) => {
226
+ return Object.assign(transformObj, item)
227
+ }, {} as { [propName: string]: string | number })
228
+ }
229
+ // ** 生成动画样式
230
+ return useAnimatedStyle(() => {
231
+ // console.info(`useAnimatedStyle styles=`, originalStyle)
232
+ return animatedStyleKeys.value.reduce((styles, key) => {
233
+ // console.info('getAnimationStyles', key, shareValMap[key].value)
234
+ if (Array.isArray(key)) {
235
+ const transformStyle = getTransformObj()
236
+ key.forEach((transformKey) => {
237
+ transformStyle[transformKey] = shareValMap[transformKey].value
238
+ })
239
+ styles.transform = Object.entries(transformStyle).map(([key, value]) => {
240
+ return { [key]: value }
241
+ }) as Extract<'transform', TransformsStyle>
242
+ } else {
243
+ styles[key] = shareValMap[key].value
244
+ }
245
+ return styles
246
+ }, Object.assign({}, originalStyle) as ExtendedViewStyle)
247
+ })
248
+ }
@@ -1,9 +1,10 @@
1
- import { useEffect, useRef, ReactNode, ReactElement, FunctionComponent, isValidElement, useContext, useState, Dispatch, SetStateAction, Children, cloneElement } from 'react'
1
+ import { useEffect, useRef, ReactNode, ReactElement, isValidElement, useContext, useState, Dispatch, SetStateAction, Children, cloneElement } from 'react'
2
2
  import { LayoutChangeEvent, TextStyle } from 'react-native'
3
3
  import { isObject, hasOwn, diffAndCloneA, error, warn, getFocusedNavigation } from '@mpxjs/utils'
4
4
  import { VarContext } from './context'
5
5
  import { ExpressionParser, parseFunc, ReplaceSource } from './parser'
6
6
  import { initialWindowMetrics } from 'react-native-safe-area-context'
7
+ import type { ExtendedFunctionComponent } from './types/common'
7
8
 
8
9
  export const TEXT_STYLE_REGEX = /color|font.*|text.*|letterSpacing|lineHeight|includeFontPadding|writingDirection/
9
10
  export const PERCENT_REGEX = /^\s*-?\d+(\.\d+)?%\s*$/
@@ -96,15 +97,16 @@ export const getRestProps = (transferProps: any = {}, originProps: any = {}, del
96
97
 
97
98
  export function isText (ele: ReactNode): ele is ReactElement {
98
99
  if (isValidElement(ele)) {
99
- const displayName = (ele.type as FunctionComponent)?.displayName
100
- return displayName === 'mpx-text' || displayName === 'Text'
100
+ const displayName = (ele.type as ExtendedFunctionComponent)?.displayName
101
+ const isCustomText = (ele.type as ExtendedFunctionComponent)?.isCustomText
102
+ return displayName === 'mpx-text' || displayName === 'Text' || !!isCustomText
101
103
  }
102
104
  return false
103
105
  }
104
106
 
105
107
  export function isEmbedded (ele: ReactNode): ele is ReactElement {
106
108
  if (isValidElement(ele)) {
107
- const displayName = (ele.type as FunctionComponent)?.displayName || ''
109
+ const displayName = (ele.type as ExtendedFunctionComponent)?.displayName || ''
108
110
  return ['mpx-checkbox', 'mpx-radio', 'mpx-switch'].includes(displayName)
109
111
  }
110
112
  return false
@@ -101,6 +101,7 @@ let moduleId
101
101
  let isNative
102
102
  let hasScoped
103
103
  let hasVirtualHost
104
+ let isCustomText
104
105
  let runtimeCompile
105
106
  let rulesRunner
106
107
  let currentEl
@@ -617,6 +618,7 @@ function parse (template, options) {
617
618
  isNative = options.isNative
618
619
  hasScoped = options.hasScoped
619
620
  hasVirtualHost = options.hasVirtualHost
621
+ isCustomText = options.isCustomText
620
622
  filePath = options.filePath
621
623
  i18n = options.i18n
622
624
  runtimeCompile = options.runtimeCompile
@@ -661,6 +663,14 @@ function parse (template, options) {
661
663
  const stack = []
662
664
  let root
663
665
  const meta = {}
666
+ if (isCustomText) {
667
+ meta.options = meta.options || {}
668
+ meta.options.isCustomText = true
669
+ }
670
+ if (hasVirtualHost) {
671
+ meta.options = meta.options || {}
672
+ meta.options.virtualHost = true
673
+ }
664
674
  let currentParent
665
675
  let multiRootError
666
676
  // 用于记录模板用到的组件,匹配引用组件,看是否有冗余
@@ -736,23 +746,22 @@ function parse (template, options) {
736
746
  const children = currentParent.children
737
747
  if (currentParent.tag !== 'text') {
738
748
  text = text.trim()
749
+ } else {
750
+ text = text.trim() ? text : ''
739
751
  }
740
-
741
752
  if ((!config[mode].wxs || currentParent.tag !== config[mode].wxs.tag) && options.decodeHTMLText) {
742
753
  text = he.decode(text)
743
754
  }
744
755
 
745
756
  if (text) {
746
- if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
747
- const el = {
748
- type: 3,
749
- // 支付宝小程序模板解析中未对Mustache进行特殊处理,无论是否decode都会解析失败,无解,只能支付宝侧进行修复
750
- text: decodeInMustache(text),
751
- parent: currentParent
752
- }
753
- children.push(el)
754
- runtimeCompile ? processTextDynamic(el) : processText(el)
757
+ const el = {
758
+ type: 3,
759
+ // 支付宝小程序模板解析中未对Mustache进行特殊处理,无论是否decode都会解析失败,无解,只能支付宝侧进行修复
760
+ text: decodeInMustache(text),
761
+ parent: currentParent
755
762
  }
763
+ children.push(el)
764
+ runtimeCompile ? processTextDynamic(el) : processText(el)
756
765
  }
757
766
  },
758
767
  comment: function comment (text) {
@@ -2311,11 +2320,6 @@ function postProcessAliComponentRootView (el, options, meta) {
2311
2320
  function getVirtualHostRoot (options, meta) {
2312
2321
  if (srcMode === 'wx') {
2313
2322
  if (ctorType === 'component') {
2314
- if (mode === 'wx' && hasVirtualHost) {
2315
- // wx组件注入virtualHost配置
2316
- meta.options = meta.options || {}
2317
- meta.options.virtualHost = true
2318
- }
2319
2323
  if (isWeb(mode) && !hasVirtualHost) {
2320
2324
  // ali组件根节点实体化
2321
2325
  const rootView = createASTElement('view', [
@@ -2332,7 +2336,8 @@ function getVirtualHostRoot (options, meta) {
2332
2336
  return rootView
2333
2337
  }
2334
2338
  if (isReact(mode) && !hasVirtualHost) {
2335
- const rootView = createASTElement('view', [
2339
+ const tagName = isCustomText ? 'text' : 'view'
2340
+ const rootView = createASTElement(tagName, [
2336
2341
  {
2337
2342
  name: 'class',
2338
2343
  value: `${MPX_ROOT_VIEW} host-${moduleId}`
@@ -2526,7 +2531,7 @@ function processDuplicateAttrsList (el) {
2526
2531
  }
2527
2532
 
2528
2533
  // 处理wxs注入逻辑
2529
- function processInjectWxs (el, meta, options) {
2534
+ function processInjectWxs (el, meta) {
2530
2535
  if (el.injectWxsProps && el.injectWxsProps.length) {
2531
2536
  el.injectWxsProps.forEach((injectWxsProp) => {
2532
2537
  const { injectWxsPath, injectWxsModuleName } = injectWxsProp
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpxjs/webpack-plugin",
3
- "version": "2.9.65",
3
+ "version": "2.9.66",
4
4
  "description": "mpx compile core",
5
5
  "keywords": [
6
6
  "mpx"
@@ -82,13 +82,14 @@
82
82
  },
83
83
  "devDependencies": {
84
84
  "@ant-design/react-native": "^5.2.2",
85
- "@mpxjs/api-proxy": "^2.9.65",
85
+ "@mpxjs/api-proxy": "^2.9.66",
86
86
  "@types/babel-traverse": "^6.25.4",
87
87
  "@types/babel-types": "^7.0.4",
88
88
  "@types/react": "^18.2.79",
89
89
  "react-native": "^0.74.5",
90
90
  "react-native-gesture-handler": "^2.18.1",
91
91
  "react-native-linear-gradient": "^2.8.3",
92
+ "react-native-reanimated": "^3.15.2",
92
93
  "react-native-safe-area-context": "^4.12.0",
93
94
  "react-native-webview": "^13.12.2",
94
95
  "rimraf": "^6.0.1"
@@ -96,5 +97,5 @@
96
97
  "engines": {
97
98
  "node": ">=14.14.0"
98
99
  },
99
- "gitHead": "24efa90e90b4d42c285ca61739cb9e4d0696976c"
100
+ "gitHead": "ff9eb06a3be28538870823cebf813ed56f39bbd7"
100
101
  }