@mpxjs/webpack-plugin 2.9.57 → 2.9.59

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/config.js CHANGED
@@ -5,9 +5,7 @@ const reactConfig = {
5
5
  if (match) {
6
6
  return {
7
7
  prefix: match[1],
8
- eventName: match[2].replace(/^./, function (match) {
9
- return match.toLowerCase()
10
- }),
8
+ eventName: match[2],
11
9
  modifier: match[3]
12
10
  }
13
11
  }
@@ -60,7 +60,7 @@ module.exports = function getSpec ({ warn, error }) {
60
60
  default: 'default' // 不校验
61
61
  }
62
62
  // number 类型支持的单位(包含auto)
63
- const numberRegExp = /^\s*((\d+(\.\d+)?)(rpx|px|%)?)|(auto)\s*$/
63
+ const numberRegExp = /^\s*((-?\d+(\.\d+)?)(rpx|px|%)?)|(auto)\s*$/
64
64
  // RN 不支持的颜色格式
65
65
  const colorRegExp = /^\s*(lab|lch|oklab|oklch|color-mix|color|hwb|lch|light-dark).*$/
66
66
 
@@ -101,6 +101,26 @@ module.exports = function getSpec ({ warn, error }) {
101
101
  borderStyle: ValueType.default,
102
102
  borderColor: ValueType.color
103
103
  },
104
+ 'border-left': { // 仅支持 width | style | color 这种排序
105
+ borderLeftWidth: ValueType.number,
106
+ borderLeftStyle: ValueType.default,
107
+ borderLeftColor: ValueType.color
108
+ },
109
+ 'border-right': { // 仅支持 width | style | color 这种排序
110
+ borderRightWidth: ValueType.number,
111
+ borderRightStyle: ValueType.default,
112
+ borderRightColor: ValueType.color
113
+ },
114
+ 'border-top': { // 仅支持 width | style | color 这种排序
115
+ borderTopWidth: ValueType.number,
116
+ borderTopStyle: ValueType.default,
117
+ borderTopColor: ValueType.color
118
+ },
119
+ 'border-bottom': { // 仅支持 width | style | color 这种排序
120
+ borderBottomWidth: ValueType.number,
121
+ borderBottomStyle: ValueType.default,
122
+ borderBottomColor: ValueType.color
123
+ },
104
124
  'box-shadow': { // 仅支持 offset-x | offset-y | blur-radius | color 排序
105
125
  'shadowOffset.width': ValueType.number,
106
126
  'shadowOffset.height': ValueType.number,
@@ -133,17 +153,21 @@ module.exports = function getSpec ({ warn, error }) {
133
153
  const cssMap = []
134
154
  const props = Object.getOwnPropertyNames(keyMap)
135
155
  let idx = 0
156
+ let propsIdx = 0
136
157
  // 按值的个数循环赋值
137
- while (idx < values.length && idx < props.length) {
138
- const prop = props[idx]
158
+ while (idx < values.length || propsIdx < props.length) {
159
+ const prop = props[propsIdx]
139
160
  const valueType = keyMap[prop]
140
161
  const dashProp = hump2dash(prop)
141
- // 校验 value 类型
142
- verifyValues({ prop, value: values[idx], valueType })
143
162
  const value = values[idx]
144
163
  if (isIllegalValue({ prop: dashProp, value })) {
145
- // 过滤不支持 value
164
+ // 过滤 rn prop 不支持 value
146
165
  unsupportedValueError({ prop: dashProp, value })
166
+ idx += 1
167
+ propsIdx += 1
168
+ } else if (!verifyValues({ prop, value, valueType })) {
169
+ // 校验 value 类型,类型不符则匹配下一个value
170
+ idx += 1
147
171
  } else if (prop.includes('.')) {
148
172
  // 多个属性值的prop
149
173
  const [main, sub] = prop.split('.')
@@ -158,14 +182,17 @@ module.exports = function getSpec ({ warn, error }) {
158
182
  }
159
183
  })
160
184
  }
185
+ idx += 1
186
+ propsIdx += 1
161
187
  } else {
162
188
  // 单个值的属性
163
189
  cssMap.push({
164
190
  prop,
165
191
  value
166
192
  })
193
+ idx += 1
194
+ propsIdx += 1
167
195
  }
168
- idx += 1
169
196
  }
170
197
  return cssMap
171
198
  }
@@ -220,7 +247,7 @@ module.exports = function getSpec ({ warn, error }) {
220
247
 
221
248
  return {
222
249
  prop,
223
- value: /\d+(\.\d+)?$/.test(value) ? `${Math.round(value * 100)}%` : value
250
+ value: /^\s*-?\d+(\.\d+)?\s*$/.test(value) ? `${Math.round(value * 100)}%` : value
224
251
  }
225
252
  }
226
253
 
@@ -335,11 +362,74 @@ module.exports = function getSpec ({ warn, error }) {
335
362
  }
336
363
  }
337
364
 
365
+ const formatTransform = ({ prop, value }, { mode }) => {
366
+ if (Array.isArray(value)) return { prop, value }
367
+ const values = value.trim().split(/\s(?![^()]*\))/)
368
+ const transform = []
369
+ values.forEach(item => {
370
+ const match = item.match(/([/\w]+)\(([^)]+)\)/)
371
+ if (match.length >= 3) {
372
+ let key = match[1]
373
+ const val = match[2]
374
+ switch (key) {
375
+ case 'translateX':
376
+ case 'translateY':
377
+ case 'scaleX':
378
+ case 'scaleY':
379
+ case 'rotateX':
380
+ case 'rotateY':
381
+ case 'rotateZ':
382
+ case 'rotate':
383
+ case 'skewX':
384
+ case 'skewY':
385
+ // 单个值处理
386
+ transform.push({ [key]: val })
387
+ break
388
+ case 'matrix':
389
+ case 'matrix3d':
390
+ transform.push({ [key]: val.split(',').map(val => +val) })
391
+ break
392
+ case 'translate':
393
+ case 'scale':
394
+ case 'skew':
395
+ case 'rotate3d': // x y z angle
396
+ case 'translate3d': // x y 支持 z不支持
397
+ case 'scale3d': // x y 支持 z不支持
398
+ {
399
+ // 2 个以上的值处理
400
+ key = key.replace('3d', '')
401
+ const vals = val.split(',').splice(0, key === 'rotate' ? 4 : 3)
402
+ const xyz = ['X', 'Y', 'Z']
403
+ transform.push(...vals.map((v, index) => {
404
+ if (key !== 'rotate' && index > 1) {
405
+ unsupportedPropError({ prop: `${key}Z`, mode })
406
+ }
407
+ return { [`${key}${xyz[index] || ''}`]: v.trim() }
408
+ }))
409
+ break
410
+ }
411
+ case 'translateZ':
412
+ case 'scaleZ':
413
+ default:
414
+ // 不支持的属性处理
415
+ unsupportedPropError({ prop: key, mode })
416
+ break
417
+ }
418
+ } else {
419
+ error(`Property [${prop}] is invalid, please check the value!`)
420
+ }
421
+ })
422
+ return {
423
+ prop,
424
+ value: transform
425
+ }
426
+ }
427
+
338
428
  const spec = {
339
429
  supportedModes: ['ios', 'android'],
340
430
  rules: [
341
431
  { // 背景相关属性的处理
342
- test: /.*background*./,
432
+ test: /^(background|background-image|background-color|background-size|background-repeat|background-position)$/,
343
433
  ios: checkBackgroundImage,
344
434
  android: checkBackgroundImage
345
435
  },
@@ -372,7 +462,7 @@ module.exports = function getSpec ({ warn, error }) {
372
462
  android: getAbbreviationAndroid
373
463
  },
374
464
  {
375
- test: /.*font-variant.*/,
465
+ test: /^(font-variant|font-variant-caps|font-variant-numeric|font-variant-east-asian|font-variant-alternates|font-variant-ligatures)$/,
376
466
  ios: getFontVariant,
377
467
  android: getFontVariant
378
468
  },
@@ -382,7 +472,7 @@ module.exports = function getSpec ({ warn, error }) {
382
472
  android: getBorderRadius
383
473
  },
384
474
  { // margin padding 内外边距的处理
385
- test: /.*(margin|padding).*/,
475
+ test: /^(margin|padding)$/,
386
476
  ios: formatMargins,
387
477
  android: formatMargins
388
478
  },
@@ -397,14 +487,19 @@ module.exports = function getSpec ({ warn, error }) {
397
487
  ios: formatLineHeight,
398
488
  android: formatLineHeight
399
489
  },
490
+ {
491
+ test: 'transform',
492
+ ios: formatTransform,
493
+ android: formatTransform
494
+ },
400
495
  // 值类型校验放到最后
401
- { // color 颜色值校验
402
- test: /.*color.*/i,
496
+ { // color 颜色值校验 color xx-color 等
497
+ test: /^(color|(.+-color))$/,
403
498
  ios: checkCommonValue(ValueType.color),
404
499
  android: checkCommonValue(ValueType.color)
405
500
  },
406
- { // number 值校验
407
- test: /.*width|height|left|right|top|bottom|radius|margin|padding|spacing|offset|size.*/i,
501
+ { // number 值校验 // width height xx-left xx-top 等
502
+ test: /^((width|height)|(.+-(left|right|top|bottom|radius|spacing|size)))$/,
408
503
  ios: checkCommonValue(ValueType.number),
409
504
  android: checkCommonValue(ValueType.number)
410
505
  }
@@ -6,6 +6,14 @@ module.exports = function () {
6
6
  web (tag, { el }) {
7
7
  el.isBuiltIn = true
8
8
  return 'mpx-web-view'
9
+ },
10
+ ios (tag, { el }) {
11
+ el.isBuiltIn = true
12
+ return 'mpx-web-view'
13
+ },
14
+ android (tag, { el }) {
15
+ el.isBuiltIn = true
16
+ return 'mpx-web-view'
9
17
  }
10
18
  }
11
19
  }
@@ -57,7 +57,6 @@ module.exports = function ({
57
57
  (callback) => {
58
58
  processStyles(parts.styles, {
59
59
  loaderContext,
60
- srcMode,
61
60
  ctorType,
62
61
  autoScope,
63
62
  moduleId
@@ -27,12 +27,16 @@ import { getComponent } from ${stringifyRequest(loaderContext, optionProcessorPa
27
27
  import { NavigationContainer, createNavigationContainerRef, StackActions } from '@react-navigation/native'
28
28
  import { createNativeStackNavigator } from '@react-navigation/native-stack'
29
29
  import { Provider } from '@ant-design/react-native'
30
+ import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context'
31
+
30
32
  global.__navigationHelper = {
31
33
  NavigationContainer: NavigationContainer,
32
34
  createNavigationContainerRef: createNavigationContainerRef,
33
35
  createNativeStackNavigator: createNativeStackNavigator,
34
36
  StackActions: StackActions,
35
- Provider: Provider
37
+ Provider: Provider,
38
+ SafeAreaProvider: SafeAreaProvider,
39
+ useSafeAreaInsets: useSafeAreaInsets
36
40
  }\n`
37
41
  const { pagesMap, firstPage } = buildPagesMap({
38
42
  localPagesMap,
@@ -5,7 +5,6 @@ const shallowStringify = require('../utils/shallow-stringify')
5
5
 
6
6
  module.exports = function (styles, {
7
7
  loaderContext,
8
- srcMode,
9
8
  ctorType,
10
9
  autoScope,
11
10
  moduleId
@@ -14,7 +13,17 @@ module.exports = function (styles, {
14
13
  let content = ''
15
14
  let output = '/* styles */\n'
16
15
  if (styles.length) {
17
- const { mode } = loaderContext.getMpx()
16
+ const warn = (msg) => {
17
+ loaderContext.emitWarning(
18
+ new Error('[style compiler][' + loaderContext.resource + ']: ' + msg)
19
+ )
20
+ }
21
+ const error = (msg) => {
22
+ loaderContext.emitError(
23
+ new Error('[style compiler][' + loaderContext.resource + ']: ' + msg)
24
+ )
25
+ }
26
+ const { mode, srcMode } = loaderContext.getMpx()
18
27
  async.eachOfSeries(styles, (style, i, callback) => {
19
28
  const scoped = style.scoped || autoScope
20
29
  const extraOptions = {
@@ -41,7 +50,9 @@ module.exports = function (styles, {
41
50
  content,
42
51
  filename: loaderContext.resourcePath,
43
52
  mode,
44
- srcMode
53
+ srcMode,
54
+ warn,
55
+ error
45
56
  })
46
57
  if (ctorType === 'app') {
47
58
  output += `global.__getAppClassMap = function() {
@@ -2,10 +2,10 @@ const postcss = require('postcss')
2
2
  const selectorParser = require('postcss-selector-parser')
3
3
  const getRulesRunner = require('../platform/index')
4
4
  const dash2hump = require('../utils/hump-dash').dash2hump
5
- const rpxRegExp = /^\s*(\d+(\.\d+)?)rpx\s*$/
6
- const pxRegExp = /^\s*(\d+(\.\d+)?)(px)?\s*$/
5
+ const rpxRegExp = /^\s*(-?\d+(\.\d+)?)rpx\s*$/
6
+ const pxRegExp = /^\s*(-?\d+(\.\d+)?)(px)?\s*$/
7
7
  const cssPrefixExp = /^-(webkit|moz|ms|o)-/
8
- function getClassMap ({ content, filename, mode, srcMode }) {
8
+ function getClassMap ({ content, filename, mode, srcMode, warn, error }) {
9
9
  const classMap = {}
10
10
 
11
11
  const root = postcss.parse(content, {
@@ -30,12 +30,8 @@ function getClassMap ({ content, filename, mode, srcMode }) {
30
30
  srcMode,
31
31
  type: 'style',
32
32
  testKey: 'prop',
33
- warn: (msg) => {
34
- console.warn('[style compiler warn]: ' + msg)
35
- },
36
- error: (msg) => {
37
- console.error('[style compiler error]: ' + msg)
38
- }
33
+ warn,
34
+ error
39
35
  })
40
36
 
41
37
  root.walkRules(rule => {
@@ -51,7 +47,16 @@ function getClassMap ({ content, filename, mode, srcMode }) {
51
47
  prop = dash2hump(item.prop)
52
48
  value = item.value
53
49
  if (Array.isArray(value)) {
54
- value = value.map(item => formatValue(item))
50
+ value = value.map(val => {
51
+ if (typeof val === 'object') {
52
+ for (const key in val) {
53
+ val[key] = formatValue(val[key])
54
+ }
55
+ return val
56
+ } else {
57
+ return formatValue(val)
58
+ }
59
+ })
55
60
  } else if (typeof value === 'object') {
56
61
  for (const key in value) {
57
62
  value[key] = formatValue(value[key])
@@ -70,7 +75,7 @@ function getClassMap ({ content, filename, mode, srcMode }) {
70
75
  if (selector.nodes.length === 1 && selector.nodes[0].type === 'class') {
71
76
  classMapKeys.push(selector.nodes[0].value)
72
77
  } else {
73
- rule.error('Only single class selector is supported in react native mode temporarily.')
78
+ error('Only single class selector is supported in react native mode temporarily.')
74
79
  }
75
80
  })
76
81
  }).processSync(rule.selector)
@@ -10,8 +10,8 @@ const getTouchEvent = (type, event, props, config) => {
10
10
  ...event,
11
11
  type,
12
12
  timeStamp: timestamp,
13
- target: {
14
- ...(event.target || {}),
13
+ currentTarget: {
14
+ ...(event.currentTarget || {}),
15
15
  id: id || '',
16
16
  dataset: getDataSet(props),
17
17
  offsetLeft: layoutRef?.current?.offsetLeft || 0,
@@ -8,13 +8,13 @@ import { useRef, useEffect, forwardRef } from 'react';
8
8
  import useInnerProps from './getInnerListeners';
9
9
  // @ts-ignore
10
10
  import useNodesRef from './useNodesRef'; // 引入辅助函数
11
- import { PERCENT_REGX } from './utils';
11
+ import { PERCENT_REGEX } from './utils';
12
12
  const DEFAULT_STYLE = {
13
13
  fontSize: 16
14
14
  };
15
15
  const transformStyle = (styleObj) => {
16
16
  let { lineHeight } = styleObj;
17
- if (typeof lineHeight === 'string' && PERCENT_REGX.test(lineHeight)) {
17
+ if (typeof lineHeight === 'string' && PERCENT_REGEX.test(lineHeight)) {
18
18
  lineHeight = (parseFloat(lineHeight) / 100) * (styleObj.fontSize || DEFAULT_STYLE.fontSize);
19
19
  styleObj.lineHeight = lineHeight;
20
20
  }
@@ -10,7 +10,7 @@ import { useRef, useState, useEffect, forwardRef } from 'react';
10
10
  import useInnerProps from './getInnerListeners';
11
11
  // @ts-ignore
12
12
  import useNodesRef from './useNodesRef'; // 引入辅助函数
13
- import { parseUrl, TEXT_STYLE_REGEX, PERCENT_REGX, isText } from './utils';
13
+ import { parseUrl, TEXT_STYLE_REGEX, PERCENT_REGEX, isText } from './utils';
14
14
  const IMAGE_STYLE_REGEX = /^background(Image|Size|Repeat|Position)$/;
15
15
  function groupBy(style, callback, group = {}) {
16
16
  let groupKey = '';
@@ -33,7 +33,7 @@ const applyHandlers = (handlers, args) => {
33
33
  };
34
34
  const checkNeedLayout = (style) => {
35
35
  const [width, height] = style.sizeList;
36
- return (PERCENT_REGX.test(`${height}`) && width === 'auto') || (PERCENT_REGX.test(`${width}`) && height === 'auto');
36
+ return (PERCENT_REGEX.test(`${height}`) && width === 'auto') || (PERCENT_REGEX.test(`${width}`) && height === 'auto');
37
37
  };
38
38
  /**
39
39
  * h - 用户设置的高度
@@ -42,7 +42,7 @@ const checkNeedLayout = (style) => {
42
42
  * **/
43
43
  function calculateSize(h, lh, ratio) {
44
44
  let height, width;
45
- if (PERCENT_REGX.test(`${h}`)) { // auto px/rpx
45
+ if (PERCENT_REGEX.test(`${h}`)) { // auto px/rpx
46
46
  if (!lh)
47
47
  return null;
48
48
  height = (parseFloat(`${h}`) / 100) * lh;
@@ -97,8 +97,8 @@ function backgroundSize(imageProps, preImageInfo, imageSize, layoutInfo) {
97
97
  else { // 数值类型 ImageStyle
98
98
  // 数值类型设置为 stretch
99
99
  imageProps.style.resizeMode = 'stretch';
100
- newWidth = PERCENT_REGX.test(`${width}`) ? width : +width;
101
- newHeight = PERCENT_REGX.test(`${width}`) ? height : +height;
100
+ newWidth = PERCENT_REGEX.test(`${width}`) ? width : +width;
101
+ newHeight = PERCENT_REGEX.test(`${width}`) ? height : +height;
102
102
  }
103
103
  // 样式合并
104
104
  imageProps.style = {
@@ -0,0 +1,115 @@
1
+ import { forwardRef, useEffect } from 'react';
2
+ // @ts-ignore
3
+ import { noop } from '@mpxjs/utils';
4
+ import { Portal } from '@ant-design/react-native';
5
+ import { getCustomEvent } from './getInnerListeners';
6
+ import { promisify, redirectTo, navigateTo, navigateBack, reLaunch, switchTab } from '@mpxjs/api-proxy';
7
+ // @ts-ignore
8
+ import { WebView } from 'react-native-webview';
9
+ import useNodesRef from './useNodesRef';
10
+ import { StyleSheet } from 'react-native';
11
+ const _WebView = forwardRef((props, ref) => {
12
+ const { src, bindmessage = noop, bindload = noop, binderror = noop } = props;
13
+ const defaultWebViewStyle = [
14
+ {
15
+ position: 'absolute',
16
+ left: 0,
17
+ right: 0,
18
+ top: 0,
19
+ bottom: 0
20
+ }
21
+ ];
22
+ const { nodeRef: webViewRef } = useNodesRef(props, ref, {
23
+ defaultStyle: StyleSheet.flatten([
24
+ ...defaultWebViewStyle
25
+ ])
26
+ });
27
+ const _messageList = [];
28
+ const handleUnload = () => {
29
+ // 这里是 WebView 销毁前执行的逻辑
30
+ bindmessage(getCustomEvent('messsage', {}, {
31
+ detail: {
32
+ data: _messageList
33
+ },
34
+ layoutRef: webViewRef
35
+ }));
36
+ };
37
+ useEffect(() => {
38
+ // 组件卸载时执行
39
+ return () => {
40
+ handleUnload();
41
+ };
42
+ }, []);
43
+ const _load = function (res) {
44
+ const result = {
45
+ type: 'load',
46
+ timeStamp: res.timeStamp,
47
+ detail: {
48
+ src: res.nativeEvent?.url
49
+ }
50
+ };
51
+ bindload(result);
52
+ };
53
+ const _error = function (res) {
54
+ const result = {
55
+ type: 'error',
56
+ timeStamp: res.timeStamp,
57
+ detail: {
58
+ src: ''
59
+ }
60
+ };
61
+ binderror(result);
62
+ };
63
+ const _message = function (res) {
64
+ let data;
65
+ let asyncCallback;
66
+ const navObj = promisify({ redirectTo, navigateTo, navigateBack, reLaunch, switchTab });
67
+ try {
68
+ const nativeEventData = res.nativeEvent?.data;
69
+ data = JSON.parse(nativeEventData);
70
+ }
71
+ catch (e) {
72
+ data = {};
73
+ }
74
+ const postData = data.payload || {};
75
+ switch (data.type) {
76
+ case 'postMessage':
77
+ _messageList.push(postData.data);
78
+ asyncCallback = Promise.resolve({
79
+ errMsg: 'invokeWebappApi:ok'
80
+ });
81
+ break;
82
+ case 'navigateTo':
83
+ asyncCallback = navObj.navigateTo(postData);
84
+ break;
85
+ case 'navigateBack':
86
+ asyncCallback = navObj.navigateBack(postData);
87
+ break;
88
+ case 'redirectTo':
89
+ asyncCallback = navObj.redirectTo(postData);
90
+ break;
91
+ case 'switchTab':
92
+ asyncCallback = navObj.switchTab(postData);
93
+ break;
94
+ case 'reLaunch':
95
+ asyncCallback = navObj.reLaunch(postData);
96
+ break;
97
+ }
98
+ asyncCallback && asyncCallback.then((res) => {
99
+ if (webViewRef.current?.postMessage) {
100
+ const test = JSON.stringify({
101
+ type: data.type,
102
+ callbackId: data.callbackId,
103
+ result: res
104
+ });
105
+ webViewRef.current.postMessage(test);
106
+ }
107
+ });
108
+ };
109
+ // @ts-ignore
110
+ return (<Portal>
111
+ <WebView style={[...defaultWebViewStyle]} source={{ uri: src }} ref={webViewRef} onLoad={_load} onError={_error} onMessage={_message} javaScriptEnabled={true}></WebView>
112
+ </Portal>);
113
+ });
114
+ _WebView.displayName = 'mpx-web-view';
115
+ export default _WebView;
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useRef, Children, isValidElement } from 'react';
2
2
  import { StyleSheet } from 'react-native';
3
3
  export const TEXT_STYLE_REGEX = /color|font.*|text.*|letterSpacing|lineHeight|includeFontPadding|writingDirection/;
4
- export const PERCENT_REGX = /\d+(\.\d+)?%$/;
4
+ export const PERCENT_REGEX = /^\s*-?\d+(\.\d+)?%\s*$/;
5
5
  const URL_REGEX = /url\(["']?(.*?)["']?\)/;
6
6
  export function omit(obj, fields) {
7
7
  const shallowCopy = Object.assign({}, obj);
@@ -33,8 +33,8 @@ const getTouchEvent = (
33
33
  ...event,
34
34
  type,
35
35
  timeStamp: timestamp,
36
- target: {
37
- ...(event.target || {}),
36
+ currentTarget: {
37
+ ...(event.currentTarget || {}),
38
38
  id: id || '',
39
39
  dataset: getDataSet(props),
40
40
  offsetLeft: layoutRef?.current?.offsetLeft || 0,
@@ -9,7 +9,7 @@ import { useRef, useEffect, forwardRef, ReactNode, ForwardedRef, JSX } from 'rea
9
9
  import useInnerProps from './getInnerListeners'
10
10
  // @ts-ignore
11
11
  import useNodesRef, { HandlerRef } from './useNodesRef' // 引入辅助函数
12
- import { PERCENT_REGX } from './utils'
12
+ import { PERCENT_REGEX } from './utils'
13
13
 
14
14
 
15
15
  interface _TextProps extends TextProps {
@@ -28,7 +28,7 @@ const DEFAULT_STYLE = {
28
28
 
29
29
  const transformStyle = (styleObj: TextStyle) => {
30
30
  let { lineHeight } = styleObj
31
- if (typeof lineHeight === 'string' && PERCENT_REGX.test(lineHeight)) {
31
+ if (typeof lineHeight === 'string' && PERCENT_REGEX.test(lineHeight)) {
32
32
  lineHeight = (parseFloat(lineHeight)/100) * (styleObj.fontSize || DEFAULT_STYLE.fontSize)
33
33
  styleObj.lineHeight = lineHeight
34
34
  }
@@ -5,13 +5,13 @@
5
5
  * ✔ hover-stay-time
6
6
  */
7
7
  import { View, Text, StyleProp, TextStyle, ViewStyle, NativeSyntheticEvent, ViewProps, ImageStyle, ImageResizeMode, StyleSheet, Image, LayoutChangeEvent } from 'react-native'
8
- import { useRef, useState, useEffect, forwardRef, ForwardedRef, ReactNode, JSX } from 'react'
8
+ import { useRef, useState, useEffect, forwardRef, ReactNode, JSX } from 'react'
9
9
  // @ts-ignore
10
10
  import useInnerProps from './getInnerListeners'
11
11
  // @ts-ignore
12
12
  import useNodesRef, { HandlerRef } from './useNodesRef' // 引入辅助函数
13
13
 
14
- import { parseUrl, TEXT_STYLE_REGEX, PERCENT_REGX, isText} from './utils'
14
+ import { parseUrl, TEXT_STYLE_REGEX, PERCENT_REGEX, isText} from './utils'
15
15
 
16
16
 
17
17
  type ExtendedViewStyle = ViewStyle & {
@@ -79,7 +79,7 @@ const applyHandlers = (handlers: Handler[] , args: any [] ) => {
79
79
 
80
80
  const checkNeedLayout = (style: PreImageInfo) => {
81
81
  const [width, height] = style.sizeList
82
- return (PERCENT_REGX.test(`${height}`) && width === 'auto') || (PERCENT_REGX.test(`${width}`) && height === 'auto')
82
+ return (PERCENT_REGEX.test(`${height}`) && width === 'auto') || (PERCENT_REGEX.test(`${width}`) && height === 'auto')
83
83
  }
84
84
 
85
85
  /**
@@ -89,7 +89,7 @@ const checkNeedLayout = (style: PreImageInfo) => {
89
89
  * **/
90
90
  function calculateSize(h: number, lh: number, ratio: number) {
91
91
  let height, width
92
- if (PERCENT_REGX.test(`${h}`)) { // auto px/rpx
92
+ if (PERCENT_REGEX.test(`${h}`)) { // auto px/rpx
93
93
  if (!lh) return null
94
94
  height = (parseFloat(`${h}`) / 100) * lh
95
95
  width = height * ratio
@@ -136,8 +136,8 @@ function backgroundSize (imageProps: ImageProps, preImageInfo: PreImageInfo, ima
136
136
  } else { // 数值类型 ImageStyle
137
137
  // 数值类型设置为 stretch
138
138
  (imageProps.style as ImageStyle).resizeMode = 'stretch'
139
- newWidth = PERCENT_REGX.test(`${width}`) ? width : +width! as DimensionValue
140
- newHeight = PERCENT_REGX.test(`${width}`) ? height : +height! as DimensionValue
139
+ newWidth = PERCENT_REGEX.test(`${width}`) ? width : +width! as DimensionValue
140
+ newHeight = PERCENT_REGEX.test(`${width}`) ? height : +height! as DimensionValue
141
141
  }
142
142
  // 样式合并
143
143
  imageProps.style = {
@@ -0,0 +1,171 @@
1
+ import { forwardRef, JSX, useRef, useEffect } from 'react'
2
+ // @ts-ignore
3
+ import { noop } from '@mpxjs/utils'
4
+ import { Portal } from '@ant-design/react-native'
5
+ import { getCustomEvent } from './getInnerListeners'
6
+ import { promisify, redirectTo, navigateTo, navigateBack, reLaunch, switchTab } from '@mpxjs/api-proxy'
7
+ // @ts-ignore
8
+ import { WebView } from 'react-native-webview'
9
+ import useNodesRef, { HandlerRef } from './useNodesRef'
10
+ import { StyleSheet } from 'react-native'
11
+
12
+ type OnMessageCallbackEvent = {
13
+ detail: {
14
+ data: any[]
15
+ }
16
+ }
17
+
18
+ type CommonCallbackEvent = {
19
+ detail: {
20
+ src?: string
21
+ }
22
+ }
23
+
24
+ interface WebViewProps {
25
+ src: string
26
+ bindmessage?: (event: OnMessageCallbackEvent) => void
27
+ bindload?: (event: CommonCallbackEvent) => void
28
+ binderror?: (event: CommonCallbackEvent) => void
29
+ }
30
+
31
+ interface PayloadData {
32
+ data?: Record<string, any>
33
+ }
34
+
35
+ type MessageData = {
36
+ payload?: PayloadData,
37
+ type?: string,
38
+ callbackId?: number
39
+ }
40
+
41
+ interface NativeEvent {
42
+ url: string,
43
+ data: string
44
+ }
45
+
46
+ interface LoadRes {
47
+ timeStamp: string,
48
+ nativeEvent: NativeEvent
49
+ }
50
+
51
+ interface FormRef {
52
+ postMessage: (value: any) => void;
53
+ }
54
+
55
+ const _WebView = forwardRef<HandlerRef<WebView, WebViewProps>, WebViewProps>((props, ref): JSX.Element => {
56
+ const { src, bindmessage = noop, bindload = noop, binderror = noop } = props
57
+
58
+ const defaultWebViewStyle = [
59
+ {
60
+ position: 'absolute',
61
+ left: 0,
62
+ right: 0,
63
+ top: 0,
64
+ bottom: 0
65
+ }
66
+ ]
67
+ const { nodeRef: webViewRef } = useNodesRef<WebView, WebViewProps>(props, ref, {
68
+ defaultStyle: StyleSheet.flatten([
69
+ ...defaultWebViewStyle
70
+ ])
71
+ })
72
+ const _messageList:any[] = []
73
+ const handleUnload = () => {
74
+ // 这里是 WebView 销毁前执行的逻辑
75
+ bindmessage(getCustomEvent('messsage', {}, {
76
+ detail: {
77
+ data: _messageList
78
+ },
79
+ layoutRef: webViewRef
80
+ }))
81
+ }
82
+
83
+ useEffect(() => {
84
+ // 组件卸载时执行
85
+ return () => {
86
+ handleUnload()
87
+ }
88
+ }, [])
89
+ const _load = function(res:LoadRes) {
90
+ const result = {
91
+ type: 'load',
92
+ timeStamp: res.timeStamp,
93
+ detail: {
94
+ src: res.nativeEvent?.url
95
+ }
96
+ }
97
+ bindload(result)
98
+ }
99
+ const _error = function(res:LoadRes) {
100
+ const result = {
101
+ type: 'error',
102
+ timeStamp: res.timeStamp,
103
+ detail: {
104
+ src: ''
105
+ }
106
+ }
107
+ binderror(result)
108
+ }
109
+ const _message = function(res:LoadRes) {
110
+ let data: MessageData
111
+ let asyncCallback
112
+ const navObj = promisify({ redirectTo, navigateTo, navigateBack, reLaunch, switchTab })
113
+ try {
114
+ const nativeEventData = res.nativeEvent?.data
115
+ data = JSON.parse(nativeEventData)
116
+ } catch (e) {
117
+ data = {}
118
+ }
119
+ const postData:PayloadData = data.payload || {}
120
+ switch (data.type) {
121
+ case 'postMessage':
122
+ _messageList.push(postData.data)
123
+ asyncCallback = Promise.resolve({
124
+ errMsg: 'invokeWebappApi:ok'
125
+ })
126
+ break
127
+ case 'navigateTo':
128
+ asyncCallback = navObj.navigateTo(postData)
129
+ break
130
+ case 'navigateBack':
131
+ asyncCallback = navObj.navigateBack(postData)
132
+ break
133
+ case 'redirectTo':
134
+ asyncCallback = navObj.redirectTo(postData)
135
+ break
136
+ case 'switchTab':
137
+ asyncCallback = navObj.switchTab(postData)
138
+ break
139
+ case 'reLaunch':
140
+ asyncCallback = navObj.reLaunch(postData)
141
+ break
142
+ }
143
+
144
+ asyncCallback && asyncCallback.then((res: any) => {
145
+ if (webViewRef.current?.postMessage) {
146
+ const test = JSON.stringify({
147
+ type: data.type,
148
+ callbackId: data.callbackId,
149
+ result: res
150
+ })
151
+ webViewRef.current.postMessage(test)
152
+ }
153
+ })
154
+ }
155
+ // @ts-ignore
156
+ return(<Portal>
157
+ <WebView
158
+ style={[ ...defaultWebViewStyle ]}
159
+ source={{ uri: src }}
160
+ ref={webViewRef}
161
+ onLoad={_load}
162
+ onError={_error}
163
+ onMessage={_message}
164
+ javaScriptEnabled={true}
165
+ ></WebView>
166
+ </Portal>)
167
+ })
168
+
169
+ _WebView.displayName = 'mpx-web-view'
170
+
171
+ export default _WebView
@@ -3,7 +3,7 @@ import { StyleProp, StyleSheet, TextStyle, ViewStyle } from 'react-native'
3
3
 
4
4
  export const TEXT_STYLE_REGEX = /color|font.*|text.*|letterSpacing|lineHeight|includeFontPadding|writingDirection/
5
5
 
6
- export const PERCENT_REGX = /\d+(\.\d+)?%$/
6
+ export const PERCENT_REGEX = /^\s*-?\d+(\.\d+)?%\s*$/
7
7
 
8
8
  const URL_REGEX = /url\(["']?(.*?)["']?\)/
9
9
 
@@ -5,7 +5,7 @@ module.exports = function (style) {
5
5
  }
6
6
  const transRpxFn = global.__mpxTransRpxFn || defaultTransRpxFn
7
7
  const parsedStyleObj = {}
8
- const rpxRegExpG = /\b(\d+(\.\d+)?)rpx\b/g
8
+ const rpxRegExpG = /\b(-?\d+(\.\d+)?)rpx\b/g
9
9
  const parseStyleText = (cssText) => {
10
10
  const listDelimiter = /;(?![^(]*\))/g
11
11
  const propertyDelimiter = /:(.+)/
@@ -1,5 +1,5 @@
1
- const pxRegExp = /\b(\d+(\.\d+)?)px\b/
2
- const pxRegExpG = /\b(\d+(\.\d+)?)px\b/g
1
+ const pxRegExp = /\b(-?\d+(\.\d+)?)px\b/
2
+ const pxRegExpG = /\b(-?\d+(\.\d+)?)px\b/g
3
3
  // rpx
4
4
  module.exports = (options = {}) => {
5
5
  return {
@@ -1,5 +1,5 @@
1
- const rpxRegExp = /\b(\d+(\.\d+)?)rpx\b/
2
- const rpxRegExpG = /\b(\d+(\.\d+)?)rpx\b/g
1
+ const rpxRegExp = /\b(-?\d+(\.\d+)?)rpx\b/
2
+ const rpxRegExpG = /\b(-?\d+(\.\d+)?)rpx\b/g
3
3
 
4
4
  module.exports = (options = {}) => {
5
5
  return {
@@ -1052,7 +1052,7 @@ function stringifyWithResolveComputed (modelValue) {
1052
1052
  return result.join('+')
1053
1053
  }
1054
1054
 
1055
- function processStyleReact (el) {
1055
+ function processStyleReact (el, options) {
1056
1056
  // process class/wx:class/style/wx:style/wx:show for react native
1057
1057
  const dynamicClass = getAndRemoveAttr(el, config[mode].directive.dynamicClass).val
1058
1058
  let staticClass = getAndRemoveAttr(el, 'class').val || ''
@@ -1088,10 +1088,24 @@ function processStyleReact (el) {
1088
1088
  const staticClassExp = parseMustacheWithContext(staticHoverClass).result
1089
1089
  addAttrs(el, [{
1090
1090
  name: 'hoverStyle',
1091
- // runtime helper
1092
1091
  value: `{{this.__getStyle(${staticClassExp})}}`
1093
1092
  }])
1094
1093
  }
1094
+
1095
+ // 处理externalClasses,将其转换为style作为props传递
1096
+ if (options.externalClasses) {
1097
+ options.externalClasses.forEach((className) => {
1098
+ let externalClass = getAndRemoveAttr(el, className).val || ''
1099
+ externalClass = externalClass.replace(/\s+/g, ' ')
1100
+ if (externalClass) {
1101
+ const externalClassExp = parseMustacheWithContext(externalClass).result
1102
+ addAttrs(el, [{
1103
+ name: className,
1104
+ value: `{{this.__getStyle(${externalClassExp})}}`
1105
+ }])
1106
+ }
1107
+ })
1108
+ }
1095
1109
  }
1096
1110
 
1097
1111
  function getModelConfig (el, match) {
@@ -1121,12 +1135,12 @@ function getModelConfig (el, match) {
1121
1135
  }
1122
1136
  }
1123
1137
 
1124
- function processEventReact (el, options, meta) {
1138
+ function processEventReact (el) {
1125
1139
  const eventConfigMap = {}
1126
1140
  el.attrsList.forEach(function ({ name, value }) {
1127
1141
  const parsedEvent = config[mode].event.parseEvent(name)
1128
1142
  if (parsedEvent) {
1129
- const type = parsedEvent.eventName
1143
+ const type = config[mode].event.getEvent(parsedEvent.eventName, parsedEvent.prefix)
1130
1144
  const parsedFunc = parseFuncStr(value)
1131
1145
  if (parsedFunc) {
1132
1146
  if (!eventConfigMap[type]) {
@@ -1153,12 +1167,14 @@ function processEventReact (el, options, meta) {
1153
1167
  // } else {
1154
1168
  // stringifiedModelValue = stringify(modelValue)
1155
1169
  // }
1156
- if (!eventConfigMap[modelEvent]) {
1157
- eventConfigMap[modelEvent] = {
1170
+ // todo 未来可能需要支持类似modelEventPrefix这样的配置来声明model事件的绑定方式
1171
+ const modelEventType = config[mode].event.getEvent(modelEvent)
1172
+ if (!eventConfigMap[modelEventType]) {
1173
+ eventConfigMap[modelEventType] = {
1158
1174
  configs: []
1159
1175
  }
1160
1176
  }
1161
- eventConfigMap[modelEvent].configs.unshift({
1177
+ eventConfigMap[modelEventType].configs.unshift({
1162
1178
  hasArgs: true,
1163
1179
  expStr: `[${stringify('__model')},${stringifiedModelValue},${stringify(eventIdentifier)},${stringify(modelValuePathArr)}${modelFilter ? `,${stringify(modelFilter)}` : ''}]`
1164
1180
  })
@@ -1172,11 +1188,8 @@ function processEventReact (el, options, meta) {
1172
1188
  }
1173
1189
 
1174
1190
  // let wrapper
1175
-
1176
1191
  for (const type in eventConfigMap) {
1177
1192
  let { configs } = eventConfigMap[type]
1178
-
1179
- let resultName
1180
1193
  configs.forEach(({ name }) => {
1181
1194
  if (name) {
1182
1195
  // 清空原始事件绑定
@@ -1184,21 +1197,15 @@ function processEventReact (el, options, meta) {
1184
1197
  do {
1185
1198
  has = getAndRemoveAttr(el, name).has
1186
1199
  } while (has)
1187
-
1188
- if (!resultName) {
1189
- // 清除修饰符
1190
- resultName = name.replace(/\..*/, '')
1191
- }
1192
1200
  }
1193
1201
  })
1194
1202
  configs = configs.map((item) => {
1195
1203
  return item.expStr
1196
1204
  })
1197
- const name = resultName || config[mode].event.getEvent(type)
1198
1205
  const value = `{{(e)=>this.__invoke(e, [${configs}])}}`
1199
1206
  addAttrs(el, [
1200
1207
  {
1201
- name,
1208
+ name: type,
1202
1209
  value
1203
1210
  }
1204
1211
  ])
@@ -1698,23 +1705,49 @@ function processFor (el) {
1698
1705
  }
1699
1706
 
1700
1707
  function processRefReact (el, meta) {
1701
- const val = getAndRemoveAttr(el, config[mode].directive.ref).val
1708
+ const { val, has } = getAndRemoveAttr(el, config[mode].directive.ref)
1709
+
1702
1710
  // rn中只有内建组件能被作为node ref处理
1703
1711
  const type = el.isBuiltIn ? 'node' : 'component'
1704
- if (val) {
1712
+ if (has) {
1705
1713
  if (!meta.refs) {
1706
1714
  meta.refs = []
1707
1715
  }
1708
1716
  const all = !!forScopes.length
1709
- meta.refs.push({
1717
+ const refConf = {
1710
1718
  key: val,
1711
1719
  all,
1712
1720
  type
1713
- })
1721
+ }
1722
+
1723
+ if (!val) {
1724
+ refConf.key = `ref_rn_${++refId}`
1725
+ refConf.sKeys = []
1726
+ const rawId = el.attrsMap.id
1727
+ const rawClass = el.attrsMap.class
1728
+ const rawDynamicClass = el.attrsMap[config[mode].directive.dynamicClass]
1729
+
1730
+ meta.computed = meta.computed || []
1731
+ if (rawId) {
1732
+ const staticId = parseMustacheWithContext(rawId).result
1733
+ const computedIdKey = `_ri${refId}`
1734
+ refConf.sKeys.push({ key: computedIdKey, prefix: '#' })
1735
+ meta.computed.push(`${computedIdKey}() {\n return ${staticId}}`)
1736
+ }
1737
+ if (rawClass || rawDynamicClass) {
1738
+ const staticClass = parseMustacheWithContext(rawClass).result
1739
+ const dynamicClass = parseMustacheWithContext(rawDynamicClass).result
1740
+ const computedClassKey = `_rc${refId}`
1741
+ refConf.sKeys.push({ key: computedClassKey, prefix: '.' })
1742
+ meta.computed.push(`${computedClassKey}() {\n return this.__getClass(${staticClass}, ${dynamicClass})}`)
1743
+ }
1744
+ }
1745
+
1746
+ meta.refs.push(refConf)
1714
1747
 
1715
1748
  addAttrs(el, [{
1716
1749
  name: 'ref',
1717
- value: `{{ this.__getRefVal('${val}') }}`
1750
+ value: `{{ this.__getRefVal('${refConf.key}') }}`
1718
1751
  }])
1719
1752
  }
1720
1753
  }
@@ -2571,8 +2604,8 @@ function processElement (el, root, options, meta) {
2571
2604
  processIf(el)
2572
2605
  processFor(el)
2573
2606
  processRefReact(el, meta)
2574
- processStyleReact(el)
2575
- processEventReact(el, options, meta)
2607
+ processStyleReact(el, options)
2608
+ processEventReact(el)
2576
2609
  processComponentIs(el, options)
2577
2610
  processSlotReact(el)
2578
2611
  processAttrs(el, options)
@@ -6,7 +6,7 @@ module.exports = function shallowStringify (obj, isTemplateExp) {
6
6
  if (hasOwn(obj, key)) {
7
7
  let value = obj[key]
8
8
  if (Array.isArray(value)) {
9
- value = `[${value.join(',')}]`
9
+ value = `[${value.map((item) => typeof item === 'object' ? shallowStringify(item, isTemplateExp) : item).join(',')}]`
10
10
  } else if (typeof value === 'object') {
11
11
  value = shallowStringify(value, isTemplateExp)
12
12
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpxjs/webpack-plugin",
3
- "version": "2.9.57",
3
+ "version": "2.9.59",
4
4
  "description": "mpx compile core",
5
5
  "keywords": [
6
6
  "mpx"
@@ -80,6 +80,7 @@
80
80
  "build": "rimraf ./lib/runtime/components/react/dist && tsc"
81
81
  },
82
82
  "devDependencies": {
83
+ "@ant-design/react-native": "^5.2.2",
83
84
  "@types/babel-traverse": "^6.25.4",
84
85
  "@types/babel-types": "^7.0.4",
85
86
  "@types/react": "^18.2.79",
@@ -89,5 +90,5 @@
89
90
  "engines": {
90
91
  "node": ">=14.14.0"
91
92
  },
92
- "gitHead": "f52627676fd798818980c2d1a6890c5ea99e2862"
93
+ "gitHead": "aa001c11cc7b21772fc6f9f5bcdd13118fc6d67c"
93
94
  }