@mpxjs/core 2.9.66 → 2.9.69

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.
@@ -4,6 +4,8 @@ declare let __mpx_mode__: 'wx' | 'ali' | 'swan' | 'qq' | 'tt' | 'web' | 'dd' | '
4
4
  // declaration for mpx env
5
5
  declare let __mpx_env__: string
6
6
 
7
+ declare const Mixin: WechatMiniprogram.Behavior.Constructor
8
+
7
9
  // Wildcard module declarations for ?resolve case
8
10
  declare module '*?resolve' {
9
11
  const resourcePath: string
package/@types/index.d.ts CHANGED
@@ -137,6 +137,11 @@ interface ComponentOpt<D extends Data, P extends Properties, C, M extends Method
137
137
 
138
138
  initData?: Record<string, any>
139
139
 
140
+ provide?: Record<string, any> | (() => Record<string, any>)
141
+ inject?:
142
+ | { [key: string]: string | Symbol | { from?: string | Symbol; default?: any } }
143
+ | Array<string>
144
+
140
145
  [index: string]: any
141
146
  }
142
147
 
@@ -259,13 +264,15 @@ interface MpxConfig {
259
264
  ignoreWarning: boolean | string | RegExp | ((msg: string, location: string, e: Error) => boolean)
260
265
  ignoreProxyWhiteList: Array<string>
261
266
  observeClassInstance: boolean | Array<AnyConstructor>
262
- errorHandler: (e: Error, target: ComponentIns<{}, {}, {}, {}, []>, hookName: string) => any | null
267
+ errorHandler: (msg: String, location: String, e: Error) => any | null
268
+ warnHandler: (msg: String, location: String, e: Error) => any | null
263
269
  proxyEventHandler: (e: WechatMiniprogram.CustomEvent) => any | null
264
270
  setDataHandler: (data: object, target: ComponentIns<{}, {}, {}, {}, []>) => any | null
265
271
  forceFlushSync: boolean,
266
272
  webRouteConfig: object,
267
273
  webConfig: object,
268
- webviewConfig?: WebviewConfig
274
+ webviewConfig: WebviewConfig,
275
+ rnConfig: object,
269
276
  }
270
277
 
271
278
  type SupportedMode = 'wx' | 'ali' | 'qq' | 'swan' | 'tt' | 'web' | 'qa'
@@ -306,6 +313,8 @@ export interface Mpx {
306
313
 
307
314
  delete: typeof del
308
315
 
316
+ provide: typeof provide
317
+
309
318
  config: MpxConfig
310
319
 
311
320
  i18n: {
@@ -570,6 +579,13 @@ export function onScopeDispose (fn: () => void): void
570
579
  export function set<T extends object> (target: T, key: string | number, value: any): void
571
580
  export function del<T extends object> (target: T, key: keyof T): void
572
581
 
582
+ // provide & inject
583
+ export declare function provide<T>(key: InjectionKey<T> | string | number, value: T): void;
584
+ export declare function inject<T>(key: InjectionKey<T> | string): T | undefined;
585
+ export declare function inject<T>(key: InjectionKey<T> | string, defaultValue: T, treatDefaultAsFactory?: false): T;
586
+ export declare function inject<T>(key: InjectionKey<T> | string, defaultValue: T | (() => T), treatDefaultAsFactory: true): T;
587
+ export declare interface InjectionKey<T> extends Symbol {}
588
+
573
589
  // nextTick
574
590
  export function nextTick (fn: () => any): void
575
591
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpxjs/core",
3
- "version": "2.9.66",
3
+ "version": "2.9.69",
4
4
  "description": "mpx runtime core",
5
5
  "keywords": [
6
6
  "miniprogram",
@@ -19,21 +19,23 @@
19
19
  ],
20
20
  "main": "src/index.js",
21
21
  "dependencies": {
22
- "@mpxjs/utils": "^2.9.65",
22
+ "@mpxjs/utils": "^2.9.69",
23
23
  "lodash": "^4.1.1",
24
24
  "miniprogram-api-typings": "^3.10.0"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "@ant-design/react-native": "^5.1.3",
28
+ "@d11/react-native-fast-image": "^8.6.12",
28
29
  "@mpxjs/api-proxy": "^2.9.0",
29
30
  "@mpxjs/store": "^2.9.0",
30
- "@react-navigation/native": "^6.1.17",
31
- "@react-navigation/native-stack": "^6.9.26",
31
+ "@react-navigation/native": "^7.0.3",
32
+ "@react-navigation/stack": "^7.0.4",
32
33
  "react": "*",
33
34
  "react-native": "*",
34
35
  "react-native-gesture-handler": "^2.19.0",
35
36
  "react-native-linear-gradient": "^2.8.3",
36
- "react-native-safe-area-context": "^4.10.1",
37
+ "react-native-safe-area-context": "^4.14.0",
38
+ "react-native-screens": "^4.1.0",
37
39
  "react-native-webview": "^13.10.5",
38
40
  "vue": "^2.7.10",
39
41
  "vue-demi": "^0.14.6",
@@ -62,7 +64,7 @@
62
64
  "@react-navigation/native": {
63
65
  "optional": true
64
66
  },
65
- "@react-navigation/native-stack": {
67
+ "@react-navigation/stack": {
66
68
  "optional": true
67
69
  },
68
70
  "@ant-design/react-native": {
@@ -71,6 +73,9 @@
71
73
  "react-native-safe-area-context": {
72
74
  "optional": true
73
75
  },
76
+ "react-native-screens": {
77
+ "optional": true
78
+ },
74
79
  "react-native-webview": {
75
80
  "optional": true
76
81
  },
@@ -79,6 +84,9 @@
79
84
  },
80
85
  "react-native-linear-gradient": {
81
86
  "optional": true
87
+ },
88
+ "@d11/react-native-fast-image": {
89
+ "optional": true
82
90
  }
83
91
  },
84
92
  "publishConfig": {
@@ -97,5 +105,5 @@
97
105
  "url": "https://github.com/didi/mpx/issues"
98
106
  },
99
107
  "sideEffects": false,
100
- "gitHead": "ff9eb06a3be28538870823cebf813ed56f39bbd7"
108
+ "gitHead": "e23c51acc4c2ffdd31fcc6c27aae1aed42d1ade2"
101
109
  }
@@ -5,7 +5,7 @@ import {
5
5
  import { implemented } from '../core/implement'
6
6
 
7
7
  // 暂不支持的wx选项,后期需要各种花式支持
8
- const unsupported = ['relations', 'moved', 'definitionFilter', 'onShareAppMessage']
8
+ const unsupported = ['relations', 'moved', 'definitionFilter']
9
9
 
10
10
  function convertErrorDesc (key) {
11
11
  error(`Options.${key} is not supported in runtime conversion from wx to react native.`, global.currentResource)
@@ -354,7 +354,7 @@ function transformHOOKS (options) {
354
354
  const componentHooksMap = makeMap(convertRule.lifecycle.component)
355
355
  for (const key in options) {
356
356
  // 使用Component创建page实例,页面专属生命周期&自定义方法需写在methods内部
357
- if (typeof options[key] === 'function' && key !== 'dataFn' && key !== 'setup' && !componentHooksMap[key]) {
357
+ if (typeof options[key] === 'function' && key !== 'dataFn' && key !== 'setup' && key !== 'provide' && !componentHooksMap[key]) {
358
358
  if (!options.methods) options.methods = {}
359
359
  options.methods[key] = options[key]
360
360
  delete options[key]
package/src/core/proxy.js CHANGED
@@ -8,10 +8,12 @@ import Mpx from '../index'
8
8
  import {
9
9
  noop,
10
10
  type,
11
+ isArray,
11
12
  isFunction,
12
13
  isObject,
13
14
  isEmptyObject,
14
15
  isPlainObject,
16
+ isWeb,
15
17
  doGetByPath,
16
18
  getByPath,
17
19
  setByPath,
@@ -25,6 +27,7 @@ import {
25
27
  processUndefined,
26
28
  getFirstKey,
27
29
  callWithErrorHandling,
30
+ wrapMethodsWithErrorHandling,
28
31
  warn,
29
32
  error,
30
33
  getEnvObj
@@ -47,6 +50,7 @@ import {
47
50
  } from './innerLifecycle'
48
51
  import contextMap from '../dynamic/vnode/context'
49
52
  import { getAst } from '../dynamic/astCache'
53
+ import { inject, provide } from '../platform/export/apiInject'
50
54
 
51
55
  let uid = 0
52
56
 
@@ -160,11 +164,15 @@ export default class MpxProxy {
160
164
  // web中BEFORECREATE钩子通过vue的beforeCreate钩子单独驱动
161
165
  this.callHook(BEFORECREATE)
162
166
  setCurrentInstance(this)
167
+ // 在 props/data 初始化之前初始化 inject
168
+ this.initInject()
163
169
  this.initProps()
164
170
  this.initSetup()
165
171
  this.initData()
166
172
  this.initComputed()
167
173
  this.initWatch()
174
+ // 在 props/data 初始化之后初始化 provide
175
+ this.initProvide()
168
176
  unsetCurrentInstance()
169
177
  }
170
178
 
@@ -219,6 +227,16 @@ export default class MpxProxy {
219
227
  // 页面/组件销毁清除上下文的缓存
220
228
  contextMap.remove(this.uid)
221
229
  }
230
+ if (!isWeb && this.options.__type__ === 'page') {
231
+ // 小程序页面销毁时移除对应的 provide
232
+ if (isFunction(this.target.getPageId)) {
233
+ const pageId = this.target.getPageId()
234
+ const providesMap = global.__mpxProvidesMap
235
+ if (providesMap.__pages[pageId]) {
236
+ delete providesMap.__pages[pageId]
237
+ }
238
+ }
239
+ }
222
240
  this.callHook(BEFOREUNMOUNT)
223
241
  this.scope?.stop()
224
242
  if (this.update) this.update.active = false
@@ -277,7 +295,7 @@ export default class MpxProxy {
277
295
  initSetup () {
278
296
  const setup = this.options.setup
279
297
  if (setup) {
280
- const setupResult = callWithErrorHandling(setup, this, 'setup function', [
298
+ let setupResult = callWithErrorHandling(setup, this, 'setup function', [
281
299
  this.props,
282
300
  {
283
301
  triggerEvent: this.target.triggerEvent ? this.target.triggerEvent.bind(this.target) : noop,
@@ -294,6 +312,7 @@ export default class MpxProxy {
294
312
  error(`Setup() should return a object, received: ${type(setupResult)}.`, this.options.mpxFileResource)
295
313
  return
296
314
  }
315
+ setupResult = wrapMethodsWithErrorHandling(setupResult, this)
297
316
  proxy(this.target, setupResult, undefined, false, this.createProxyConflictHandler('setup result'))
298
317
  this.collectLocalKeys(setupResult, (key, val) => !isFunction(val))
299
318
  }
@@ -350,6 +369,55 @@ export default class MpxProxy {
350
369
  }
351
370
  }
352
371
 
372
+ initProvide () {
373
+ const provideOpt = this.options.provide
374
+ if (provideOpt) {
375
+ const provided = isFunction(provideOpt)
376
+ ? callWithErrorHandling(provideOpt.bind(this.target), this, 'provide function')
377
+ : provideOpt
378
+ if (!isObject(provided)) {
379
+ return
380
+ }
381
+ Object.keys(provided).forEach(key => {
382
+ provide(key, provided[key])
383
+ })
384
+ }
385
+ }
386
+
387
+ initInject () {
388
+ const injectOpt = this.options.inject
389
+ if (injectOpt) {
390
+ this.resolveInject(injectOpt)
391
+ }
392
+ }
393
+
394
+ resolveInject (injectOpt) {
395
+ if (isArray(injectOpt)) {
396
+ const normalized = {}
397
+ for (let i = 0; i < injectOpt.length; i++) {
398
+ normalized[injectOpt[i]] = injectOpt[i]
399
+ }
400
+ injectOpt = normalized
401
+ }
402
+ const injectObj = {}
403
+ for (const key in injectOpt) {
404
+ const opt = injectOpt[key]
405
+ let injected
406
+ if (isObject(opt)) {
407
+ if ('default' in opt) {
408
+ injected = inject(opt.from || key, opt.default, true)
409
+ } else {
410
+ injected = inject(opt.from || key)
411
+ }
412
+ } else {
413
+ injected = inject(opt)
414
+ }
415
+ injectObj[key] = injected
416
+ }
417
+ proxy(this.target, injectObj, undefined, false, this.createProxyConflictHandler('inject'))
418
+ this.collectLocalKeys(injectObj)
419
+ }
420
+
353
421
  watch (source, cb, options) {
354
422
  const target = this.target
355
423
  const getter = isString(source)
@@ -1,19 +1,22 @@
1
- import { isObject, isArray, dash2hump, isFunction, cached, getFocusedNavigation } from '@mpxjs/utils'
1
+ import { isObject, isArray, dash2hump, cached } from '@mpxjs/utils'
2
2
  import { Dimensions, StyleSheet } from 'react-native'
3
3
 
4
+ let { width, height } = Dimensions.get('screen')
5
+
6
+ Dimensions.addEventListener('change', ({ screen }) => {
7
+ width = screen.width
8
+ height = screen.height
9
+ })
10
+
4
11
  function rpx (value) {
5
- const { width } = Dimensions.get('screen')
6
12
  // rn 单位 dp = 1(css)px = 1 物理像素 * pixelRatio(像素比)
7
13
  // px = rpx * (750 / 屏幕宽度)
8
14
  return value * width / 750
9
15
  }
10
16
  function vw (value) {
11
- const { width } = Dimensions.get('screen')
12
17
  return value * width / 100
13
18
  }
14
19
  function vh (value) {
15
- const navigation = getFocusedNavigation()
16
- const height = navigation?.layout?.height || Dimensions.get('screen').height
17
20
  return value * height / 100
18
21
  }
19
22
 
@@ -24,14 +27,15 @@ const unit = {
24
27
  }
25
28
 
26
29
  function formatValue (value) {
27
- let matched
28
- if ((matched = numberRegExp.exec(value))) {
29
- value = +matched[1]
30
- } else if ((matched = unitRegExp.exec(value))) {
31
- value = unit[matched[2]](+matched[1])
32
- } else if (hairlineRegExp.test(value)) {
33
- value = StyleSheet.hairlineWidth
30
+ const matched = unitRegExp.exec(value)
31
+ if (matched) {
32
+ if (!matched[2] || matched[2] === 'px') {
33
+ return +matched[1]
34
+ } else {
35
+ return unit[matched[2]](+matched[1])
36
+ }
34
37
  }
38
+ if (hairlineRegExp.test(value)) return StyleSheet.hairlineWidth
35
39
  return value
36
40
  }
37
41
 
@@ -106,8 +110,7 @@ function stringifyDynamicClass (value) {
106
110
 
107
111
  const listDelimiter = /;(?![^(]*[)])/g
108
112
  const propertyDelimiter = /:(.+)/
109
- const unitRegExp = /^\s*(-?\d+(?:\.\d+)?)(rpx|vw|vh)\s*$/
110
- const numberRegExp = /^\s*(-?\d+(\.\d+)?)(px)?\s*$/
113
+ const unitRegExp = /^\s*(-?\d+(?:\.\d+)?)(rpx|vw|vh|px)?\s*$/
111
114
  const hairlineRegExp = /^\s*hairlineWidth\s*$/
112
115
  const varRegExp = /^--/
113
116
 
@@ -164,14 +167,8 @@ export default function styleHelperMixin () {
164
167
  },
165
168
  __getStyle (staticClass, dynamicClass, staticStyle, dynamicStyle, hide) {
166
169
  const result = {}
167
- const classMap = {}
168
- // todo 全局样式在每个页面和组件中生效,以支持全局原子类,后续支持样式模块复用后可考虑移除
169
- if (isFunction(global.__getAppClassMap)) {
170
- Object.assign(classMap, global.__getAppClassMap())
171
- }
172
- if (isFunction(this.__getClassMap)) {
173
- Object.assign(classMap, this.__getClassMap())
174
- }
170
+ const classMap = this.__getClassMap?.() || {}
171
+ const appClassMap = global.__getAppClassMap?.() || {}
175
172
 
176
173
  if (staticClass || dynamicClass) {
177
174
  // todo 当前为了复用小程序unocss产物,暂时进行mpEscape,等后续正式支持unocss后可不进行mpEscape
@@ -179,6 +176,9 @@ export default function styleHelperMixin () {
179
176
  classString.split(/\s+/).forEach((className) => {
180
177
  if (classMap[className]) {
181
178
  Object.assign(result, classMap[className])
179
+ } else if (appClassMap[className]) {
180
+ // todo 全局样式在每个页面和组件中生效,以支持全局原子类,后续支持样式模块复用后可考虑移除
181
+ Object.assign(result, appClassMap[className])
182
182
  } else if (this.props[className] && isObject(this.props[className])) {
183
183
  // externalClasses必定以对象形式传递下来
184
184
  Object.assign(result, this.props[className])
@@ -1,6 +1,6 @@
1
1
  import transferOptions from '../core/transferOptions'
2
2
  import builtInKeysMap from './patch/builtInKeysMap'
3
- import { makeMap, spreadProp } from '@mpxjs/utils'
3
+ import { makeMap, spreadProp, parseUrlQuery, getFocusedNavigation } from '@mpxjs/utils'
4
4
  import { mergeLifecycle } from '../convertor/mergeLifecycle'
5
5
  import * as wxLifecycle from '../platform/patch/wx/lifecycle'
6
6
  import Mpx from '../index'
@@ -40,7 +40,7 @@ function createAppInstance (appData) {
40
40
  export default function createApp (option, config = {}) {
41
41
  const appData = {}
42
42
 
43
- const { NavigationContainer, createNavigationContainerRef, createNativeStackNavigator, SafeAreaProvider } = global.__navigationHelper
43
+ const { NavigationContainer, createStackNavigator, SafeAreaProvider } = global.__navigationHelper
44
44
  // app选项目前不需要进行转换
45
45
  const { rawOptions, currentInject } = transferOptions(option, 'app', false)
46
46
  const defaultOptions = filterOptions(spreadProp(rawOptions, 'methods'), appData)
@@ -51,16 +51,25 @@ export default function createApp (option, config = {}) {
51
51
  }
52
52
  const pages = currentInject.getPages() || {}
53
53
  const firstPage = currentInject.firstPage
54
- const Stack = createNativeStackNavigator()
55
- const navigationRef = createNavigationContainerRef()
56
- const pageScreens = Object.entries(pages).map(([key, item]) => {
57
- return createElement(Stack.Screen, {
58
- name: key,
59
- component: item
54
+ const Stack = createStackNavigator()
55
+ const getPageScreens = (initialRouteName, initialParams) => {
56
+ return Object.entries(pages).map(([key, item]) => {
57
+ if (key === initialRouteName) {
58
+ return createElement(Stack.Screen, {
59
+ name: key,
60
+ component: item,
61
+ initialParams
62
+ })
63
+ }
64
+ return createElement(Stack.Screen, {
65
+ name: key,
66
+ component: item
67
+ })
60
68
  })
61
- })
69
+ }
62
70
  global.__mpxOptionsMap = global.__mpxOptionsMap || {}
63
- const onStateChange = () => {
71
+ const onStateChange = (state) => {
72
+ Mpx.config.rnConfig.onStateChange?.(state)
64
73
  if (global.__navigationHelper.lastSuccessCallback) {
65
74
  global.__navigationHelper.lastSuccessCallback()
66
75
  global.__navigationHelper.lastSuccessCallback = null
@@ -81,26 +90,48 @@ export default function createApp (option, config = {}) {
81
90
  error: []
82
91
  }
83
92
 
93
+ global.__mpxAppLaunched = false
94
+
84
95
  global.__mpxAppFocusedState = ref('show')
85
- global.__mpxOptionsMap[currentInject.moduleId] = memo(() => {
96
+ global.__mpxOptionsMap[currentInject.moduleId] = memo((props) => {
86
97
  const instanceRef = useRef(null)
87
98
  if (!instanceRef.current) {
88
99
  instanceRef.current = createAppInstance(appData)
89
100
  }
90
101
  const instance = instanceRef.current
91
- useEffect(() => {
92
- const current = navigationRef.isReady() ? navigationRef.getCurrentRoute() : {}
93
- const options = {
94
- path: current.name,
95
- query: current.params,
96
- scene: 0,
97
- shareTicket: '',
98
- referrerInfo: {}
102
+ const initialRouteRef = useRef({
103
+ initialRouteName: firstPage,
104
+ initialParams: {}
105
+ })
106
+
107
+ if (!global.__mpxAppLaunched) {
108
+ const parsed = Mpx.config.rnConfig.parseAppProps?.(props) || {}
109
+ if (parsed.url) {
110
+ const { path, queryObj } = parseUrlQuery(parsed.url)
111
+ Object.assign(initialRouteRef.current, {
112
+ initialRouteName: path.startsWith('/') ? path.slice(1) : path,
113
+ initialParams: queryObj
114
+ })
115
+ }
116
+ global.__mpxAppOnLaunch = (navigation) => {
117
+ global.__mpxAppLaunched = true
118
+ const state = navigation.getState()
119
+ Mpx.config.rnConfig.onStateChange?.(state)
120
+ const current = state.routes[state.index]
121
+ global.__mpxEnterOptions = {
122
+ path: current.name,
123
+ query: current.params,
124
+ scene: 0,
125
+ shareTicket: '',
126
+ referrerInfo: {}
127
+ }
128
+ defaultOptions.onLaunch && defaultOptions.onLaunch.call(instance, global.__mpxEnterOptions)
129
+ defaultOptions.onShow && defaultOptions.onShow.call(instance, global.__mpxEnterOptions)
99
130
  }
100
- global.__mpxEnterOptions = options
101
- defaultOptions.onLaunch && defaultOptions.onLaunch.call(instance, options)
131
+ }
132
+
133
+ useEffect(() => {
102
134
  if (defaultOptions.onShow) {
103
- defaultOptions.onShow.call(instance, options)
104
135
  global.__mpxAppCbs.show.push(defaultOptions.onShow.bind(instance))
105
136
  }
106
137
  if (defaultOptions.onHide) {
@@ -112,6 +143,19 @@ export default function createApp (option, config = {}) {
112
143
 
113
144
  const changeSubscription = ReactNative.AppState.addEventListener('change', (currentState) => {
114
145
  if (currentState === 'active') {
146
+ let options = global.__mpxEnterOptions
147
+ const navigation = getFocusedNavigation()
148
+ if (navigation) {
149
+ const state = navigation.getState()
150
+ const current = state.routes[state.index]
151
+ options = {
152
+ path: current.name,
153
+ query: current.params,
154
+ scene: 0,
155
+ shareTicket: '',
156
+ referrerInfo: {}
157
+ }
158
+ }
115
159
  global.__mpxAppCbs.show.forEach((cb) => {
116
160
  cb(options)
117
161
  })
@@ -138,26 +182,33 @@ export default function createApp (option, config = {}) {
138
182
  }
139
183
  }, [])
140
184
 
185
+ const { initialRouteName, initialParams } = initialRouteRef.current
141
186
  return createElement(SafeAreaProvider,
142
187
  null,
143
188
  createElement(NavigationContainer,
144
189
  {
145
- ref: navigationRef,
146
190
  onStateChange,
147
191
  onUnhandledAction
148
192
  },
149
193
  createElement(Stack.Navigator,
150
194
  {
151
- initialRouteName: firstPage
195
+ initialRouteName,
196
+ screenOptions: {
197
+ gestureEnabled: true,
198
+ // 7.x替换headerBackTitleVisible
199
+ // headerBackButtonDisplayMode: 'minimal',
200
+ headerBackTitleVisible: false,
201
+ headerMode: 'float'
202
+ }
152
203
  },
153
- ...pageScreens
204
+ ...getPageScreens(initialRouteName, initialParams)
154
205
  )
155
206
  )
156
207
  )
157
208
  })
158
209
 
159
210
  global.getCurrentPages = function () {
160
- const navigation = Object.values(global.__mpxPagesMap || {})[0]?.[1]
211
+ const navigation = getFocusedNavigation()
161
212
  if (navigation) {
162
213
  return navigation.getState().routes.map((route) => {
163
214
  return global.__mpxPagesMap[route.key] && global.__mpxPagesMap[route.key][0]
@@ -5,6 +5,7 @@ import { makeMap, spreadProp, isBrowser } from '@mpxjs/utils'
5
5
  import { mergeLifecycle } from '../convertor/mergeLifecycle'
6
6
  import * as webLifecycle from '../platform/patch/web/lifecycle'
7
7
  import Mpx from '../index'
8
+ import { initAppProvides } from './export/apiInject'
8
9
 
9
10
  const webAppHooksMap = makeMap(mergeLifecycle(webLifecycle.LIFECYCLE).app)
10
11
 
@@ -14,7 +15,7 @@ function filterOptions (options, appData) {
14
15
  if (builtInKeysMap[key]) {
15
16
  return
16
17
  }
17
- if (__mpx_mode__ === 'web' && !webAppHooksMap[key]) {
18
+ if (__mpx_mode__ === 'web' && !webAppHooksMap[key] && key !== 'provide') {
18
19
  appData[key] = options[key]
19
20
  } else {
20
21
  newOptions[key] = options[key]
@@ -87,6 +88,7 @@ export default function createApp (option, config = {}) {
87
88
  return appData
88
89
  }
89
90
  } else {
91
+ initAppProvides(rawOptions)
90
92
  defaultOptions.onAppInit && defaultOptions.onAppInit()
91
93
  const ctor = config.customCtor || global.currentCtor || App
92
94
  ctor(defaultOptions)
@@ -0,0 +1,68 @@
1
+ import { callWithErrorHandling, isFunction, isObject, warn } from '@mpxjs/utils'
2
+ import { currentInstance } from '../../core/proxy'
3
+
4
+ const providesMap = {
5
+ /** 全局 scope */
6
+ __app: Object.create(null),
7
+ /** 页面 scope */
8
+ __pages: Object.create(null)
9
+ }
10
+
11
+ global.__mpxProvidesMap = providesMap
12
+
13
+ /** @internal createApp() 初始化应用层 scope provide */
14
+ export function initAppProvides (appOptions) {
15
+ const provideOpt = appOptions.provide
16
+ if (provideOpt) {
17
+ const provided = isFunction(provideOpt)
18
+ ? callWithErrorHandling(provideOpt.bind(appOptions), appOptions, 'createApp provide function')
19
+ : provideOpt
20
+ if (isObject(provided)) {
21
+ providesMap.__app = provided
22
+ } else {
23
+ warn('App provides must be an object or a function that returns an object.')
24
+ }
25
+ }
26
+ }
27
+
28
+ function resolvePageId (context) {
29
+ if (context && isFunction(context.getPageId)) {
30
+ return context.getPageId()
31
+ }
32
+ }
33
+
34
+ function resolvePageProvides (context) {
35
+ const pageId = resolvePageId(context)
36
+ return providesMap.__pages[pageId] || (providesMap.__pages[pageId] = Object.create(null))
37
+ }
38
+
39
+ export function provide (key, value) {
40
+ const instance = currentInstance
41
+ if (!instance) {
42
+ warn('provide() can only be used inside setup().')
43
+ return
44
+ }
45
+ // 小程序无法实现组件父级引用,所以 provide scope 设置为组件所在页面
46
+ const provides = resolvePageProvides(instance.target)
47
+ provides[key] = value
48
+ }
49
+
50
+ export function inject (key, defaultValue, treatDefaultAsFactory = false) {
51
+ const instance = currentInstance
52
+ if (!instance) {
53
+ warn('inject() can only be used inside setup()')
54
+ return
55
+ }
56
+ const provides = resolvePageProvides(instance.target)
57
+ if (key in provides) {
58
+ return provides[key]
59
+ } else if (key in providesMap.__app) {
60
+ return providesMap.__app[key]
61
+ } else if (arguments.length > 1) {
62
+ return treatDefaultAsFactory && isFunction(defaultValue)
63
+ ? defaultValue.call(instance && instance.target)
64
+ : defaultValue
65
+ } else {
66
+ warn(`injection "${String(key)}" not found.`)
67
+ }
68
+ }
@@ -0,0 +1 @@
1
+ export { provide, inject } from 'vue'
@@ -42,3 +42,8 @@ export {
42
42
  export {
43
43
  useI18n
44
44
  } from '../../platform/builtInMixins/i18nMixin'
45
+
46
+ export {
47
+ provide,
48
+ inject
49
+ } from './apiInject'
@@ -27,7 +27,9 @@ export {
27
27
  // effectScope
28
28
  effectScope,
29
29
  getCurrentScope,
30
- onScopeDispose
30
+ onScopeDispose,
31
+ provide,
32
+ inject
31
33
  } from 'vue'
32
34
 
33
35
  export {
@@ -1,7 +1,7 @@
1
1
  import MpxProxy from '../../../core/proxy'
2
2
  import builtInKeysMap from '../builtInKeysMap'
3
3
  import mergeOptions from '../../../core/mergeOptions'
4
- import { error, diffAndCloneA, hasOwn, noop } from '@mpxjs/utils'
4
+ import { error, diffAndCloneA, hasOwn, noop, wrapMethodsWithErrorHandling } from '@mpxjs/utils'
5
5
 
6
6
  function transformApiForProxy (context, currentInject) {
7
7
  const rawSetData = context.setData.bind(context)
@@ -100,12 +100,25 @@ function filterOptions (options, type) {
100
100
  if (!hasOwn(newOptions, 'props')) {
101
101
  newOptions.props = Object.assign({}, options.props, options.properties)
102
102
  }
103
- } else if (key === 'methods' && type === 'page') {
104
- Object.assign(newOptions, options[key])
103
+ } else if (key === 'methods') {
104
+ const newMethods = wrapMethodsWithErrorHandling(options[key])
105
+ if (type === 'page') {
106
+ // 构造器为Page时抽取所有methods方法到顶层
107
+ Object.assign(newOptions, newMethods)
108
+ } else {
109
+ newOptions[key] = newMethods
110
+ }
111
+ } else if (key === 'behaviors') {
112
+ newOptions.mixins = options[key]
105
113
  } else {
106
114
  newOptions[key] = options[key]
107
115
  }
108
116
  })
117
+ if (newOptions.relations) {
118
+ // ali relations 需要设置 options.relations = true
119
+ newOptions.options = newOptions.options || {}
120
+ newOptions.options.relations = true
121
+ }
109
122
  return newOptions
110
123
  }
111
124
 
@@ -24,6 +24,8 @@ if (__mpx_mode__ === 'web') {
24
24
  'initData',
25
25
  'watch',
26
26
  'computed',
27
+ 'provide',
28
+ 'inject',
27
29
  'mpxCustomKeysForBlend',
28
30
  'mpxConvertMode',
29
31
  'mpxFileResource',
@@ -1,15 +1,15 @@
1
- import { useEffect, useLayoutEffect, useSyncExternalStore, useRef, useMemo, useCallback, createElement, memo, forwardRef, useImperativeHandle, useContext, createContext, Fragment, cloneElement } from 'react'
1
+ import { useEffect, useLayoutEffect, useSyncExternalStore, useRef, useMemo, useCallback, createElement, memo, forwardRef, useImperativeHandle, useContext, Fragment, cloneElement } from 'react'
2
2
  import * as ReactNative from 'react-native'
3
3
  import { ReactiveEffect } from '../../../observer/effect'
4
4
  import { watch } from '../../../observer/watch'
5
5
  import { reactive, set, del } from '../../../observer/reactive'
6
- import { hasOwn, isFunction, noop, isObject, getByPath, collectDataset, hump2dash } from '@mpxjs/utils'
6
+ import { hasOwn, isFunction, noop, isObject, isArray, getByPath, collectDataset, hump2dash, wrapMethodsWithErrorHandling } from '@mpxjs/utils'
7
7
  import MpxProxy from '../../../core/proxy'
8
8
  import { BEFOREUPDATE, ONLOAD, UPDATED, ONSHOW, ONHIDE, ONRESIZE, REACTHOOKSEXEC } from '../../../core/innerLifecycle'
9
9
  import mergeOptions from '../../../core/mergeOptions'
10
10
  import { queueJob } from '../../../observer/scheduler'
11
11
  import { createSelectorQuery, createIntersectionObserver } from '@mpxjs/api-proxy'
12
- import { IntersectionObserverContext } from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/context'
12
+ import { IntersectionObserverContext, RouteContext } from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/context'
13
13
 
14
14
  function getSystemInfo () {
15
15
  const window = ReactNative.Dimensions.get('window')
@@ -105,26 +105,33 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
105
105
  this.__refs = {}
106
106
  this.__dispatchedSlotSet = new WeakSet()
107
107
  },
108
- __getSlot (name) {
108
+ __getSlot (name, slot) {
109
109
  const { children } = propsRef.current
110
110
  if (children) {
111
- const result = []
112
- if (Array.isArray(children)) {
111
+ let result = []
112
+ if (isArray(children) && !hasOwn(children, '__slot')) {
113
113
  children.forEach(child => {
114
- if (child?.props?.slot === name) {
114
+ if (isObject(child) && hasOwn(child, '__slot')) {
115
+ if (child.__slot === name) result.push(...child)
116
+ } else if (child?.props?.slot === name) {
115
117
  result.push(child)
116
118
  }
117
119
  })
118
120
  } else {
119
- if (children?.props?.slot === name) {
121
+ if (isObject(children) && hasOwn(children, '__slot')) {
122
+ if (children.__slot === name) result.push(...children)
123
+ } else if (children?.props?.slot === name) {
120
124
  result.push(children)
121
125
  }
122
126
  }
123
- return result.filter(item => {
127
+ result = result.filter(item => {
124
128
  if (!isObject(item) || this.__dispatchedSlotSet.has(item)) return false
125
129
  this.__dispatchedSlotSet.add(item)
126
130
  return true
127
131
  })
132
+ if (!result.length) return null
133
+ result.__slot = slot
134
+ return result
128
135
  }
129
136
  return null
130
137
  },
@@ -134,7 +141,7 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
134
141
  _i (val, fn) {
135
142
  let i, l, keys, key
136
143
  const result = []
137
- if (Array.isArray(val) || typeof val === 'string') {
144
+ if (isArray(val) || typeof val === 'string') {
138
145
  for (i = 0, l = val.length; i < l; i++) {
139
146
  result.push(fn.call(this, val[i], i))
140
147
  }
@@ -266,8 +273,6 @@ function hasPageHook (mpxProxy, hookNames) {
266
273
  })
267
274
  }
268
275
 
269
- const RouteContext = createContext(null)
270
-
271
276
  const triggerPageStatusHook = (mpxProxy, event) => {
272
277
  mpxProxy.callHook(event === 'show' ? ONSHOW : ONHIDE)
273
278
  const pageLifetimes = mpxProxy.options.pageLifetimes
@@ -347,6 +352,7 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
347
352
  rawOptions = mergeOptions(rawOptions, type, false)
348
353
  const components = Object.assign({}, rawOptions.components, currentInject.getComponents())
349
354
  const validProps = Object.assign({}, rawOptions.props, rawOptions.properties)
355
+ if (rawOptions.methods) rawOptions.methods = wrapMethodsWithErrorHandling(rawOptions.methods)
350
356
  const defaultOptions = memo(forwardRef((props, ref) => {
351
357
  const instanceRef = useRef(null)
352
358
  const propsRef = useRef(null)
@@ -365,7 +371,21 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
365
371
 
366
372
  const proxy = instance.__mpxProxy
367
373
 
368
- proxy.callHook(REACTHOOKSEXEC)
374
+ let hooksResult = proxy.callHook(REACTHOOKSEXEC, [props])
375
+ if (isObject(hooksResult)) {
376
+ hooksResult = wrapMethodsWithErrorHandling(hooksResult, proxy)
377
+ if (isFirst) {
378
+ const onConflict = proxy.createProxyConflictHandler('react hooks result')
379
+ Object.keys(hooksResult).forEach((key) => {
380
+ if (key in proxy.target) {
381
+ onConflict(key)
382
+ }
383
+ proxy.target[key] = hooksResult[key]
384
+ })
385
+ } else {
386
+ Object.assign(proxy.target, hooksResult)
387
+ }
388
+ }
369
389
 
370
390
  useEffect(() => {
371
391
  if (!isFirst) {
@@ -391,6 +411,9 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
391
411
 
392
412
  useEffect(() => {
393
413
  if (type === 'page') {
414
+ if (!global.__mpxAppLaunched && global.__mpxAppOnLaunch) {
415
+ global.__mpxAppOnLaunch(props.navigation)
416
+ }
394
417
  proxy.callHook(ONLOAD, [props.route.params || {}])
395
418
  }
396
419
  proxy.mounted()
@@ -429,29 +452,20 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
429
452
 
430
453
  useLayoutEffect(() => {
431
454
  const isCustom = pageConfig.navigationStyle === 'custom'
432
- let opt = {}
433
- if (__mpx_mode__ === 'android') {
434
- opt = {
435
- statusBarTranslucent: isCustom,
436
- statusBarStyle: pageConfig.statusBarStyle, // 枚举值 'auto' | 'dark' | 'light' 控制statusbar字体颜色
437
- statusBarColor: isCustom ? 'transparent' : pageConfig.statusBarColor // 控制statusbar背景颜色
438
- }
439
- } else if (__mpx_mode__ === 'ios') {
440
- opt = {
441
- headerBackTitleVisible: false
442
- }
443
- }
444
455
  navigation.setOptions({
445
456
  headerShown: !isCustom,
446
- headerShadowVisible: false,
447
- headerTitle: pageConfig.navigationBarTitleText || '',
457
+ title: pageConfig.navigationBarTitleText || '',
448
458
  headerStyle: {
449
459
  backgroundColor: pageConfig.navigationBarBackgroundColor || '#000000'
450
460
  },
451
- headerTitleAlign: 'center',
452
- headerTintColor: pageConfig.navigationBarTextStyle || 'white',
453
- ...opt
461
+ headerTintColor: pageConfig.navigationBarTextStyle || 'white'
454
462
  })
463
+ if (__mpx_mode__ === 'android') {
464
+ ReactNative.StatusBar.setBarStyle(pageConfig.barStyle || 'dark-content')
465
+ ReactNative.StatusBar.setTranslucent(isCustom) // 控制statusbar是否占位
466
+ const color = isCustom ? 'transparent' : pageConfig.statusBarColor
467
+ color && ReactNative.StatusBar.setBackgroundColor(color)
468
+ }
455
469
  }, [])
456
470
 
457
471
  const rootRef = useRef(null)
@@ -484,9 +498,9 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
484
498
  value: currentPageId
485
499
  },
486
500
  createElement(IntersectionObserverContext.Provider,
487
- {
488
- value: intersectionObservers.current
489
- },
501
+ {
502
+ value: intersectionObservers.current
503
+ },
490
504
  createElement(defaultOptions,
491
505
  {
492
506
  navigation,
@@ -1,6 +1,6 @@
1
1
  import builtInKeysMap from '../builtInKeysMap'
2
2
  import mergeOptions from '../../../core/mergeOptions'
3
- import { diffAndCloneA, hasOwn } from '@mpxjs/utils'
3
+ import { diffAndCloneA, hasOwn, wrapMethodsWithErrorHandling } from '@mpxjs/utils'
4
4
  import { getCurrentInstance as getCurrentVueInstance } from '../../export/index'
5
5
  import MpxProxy, { setCurrentInstance, unsetCurrentInstance } from '../../../core/proxy'
6
6
  import {
@@ -27,6 +27,8 @@ function filterOptions (options) {
27
27
  )
28
28
  }
29
29
  }
30
+ } else if (key === 'methods') {
31
+ newOptions[key] = wrapMethodsWithErrorHandling(options[key])
30
32
  } else {
31
33
  newOptions[key] = options[key]
32
34
  }
@@ -62,7 +64,7 @@ export function getDefaultOptions ({ type, rawOptions = {} }) {
62
64
  }
63
65
  const setupRes = rawSetup(props, newContext)
64
66
  unsetCurrentInstance(instance.__mpxProxy)
65
- return setupRes
67
+ return wrapMethodsWithErrorHandling(setupRes, instance.__mpxProxy)
66
68
  }
67
69
  }
68
70
  const rootMixins = [{
@@ -1,4 +1,4 @@
1
- import { hasOwn, noop, isFunction } from '@mpxjs/utils'
1
+ import { hasOwn, noop, isFunction, wrapMethodsWithErrorHandling } from '@mpxjs/utils'
2
2
  import MpxProxy from '../../../core/proxy'
3
3
  import builtInKeysMap from '../builtInKeysMap'
4
4
  import mergeOptions from '../../../core/mergeOptions'
@@ -140,9 +140,14 @@ export function filterOptions (options) {
140
140
  if (!hasOwn(newOptions, 'properties')) {
141
141
  newOptions.properties = transformProperties(Object.assign({}, options.props, options.properties))
142
142
  }
143
- } else if (key === 'methods' && options.__pageCtor__) {
144
- // 构造器为Page时抽取所有methods方法到顶层
145
- Object.assign(newOptions, options[key])
143
+ } else if (key === 'methods') {
144
+ const newMethods = wrapMethodsWithErrorHandling(options[key])
145
+ if (options.__pageCtor__) {
146
+ // 构造器为Page时抽取所有methods方法到顶层
147
+ Object.assign(newOptions, newMethods)
148
+ } else {
149
+ newOptions[key] = newMethods
150
+ }
146
151
  } else {
147
152
  newOptions[key] = options[key]
148
153
  }
@@ -6,6 +6,9 @@ const factoryMap = {
6
6
 
7
7
  module.exports = (type) => (...args) => {
8
8
  if (type === 'Behavior') {
9
+ if (__mpx_mode__ === 'ali') {
10
+ return Mixin.apply(null, args)
11
+ }
9
12
  if (args[0]) {
10
13
  Object.defineProperty(args[0], '__mpx_behaviors_to_mixins__', {
11
14
  configurable: true,
@@ -1,2 +0,0 @@
1
- import directiveHelperMixin from './directiveHelperMixin.ios'
2
- export default directiveHelperMixin
@@ -1,2 +0,0 @@
1
- import proxyEventMixin from './proxyEventMixin.ios'
2
- export default proxyEventMixin
@@ -1,2 +0,0 @@
1
- import refsMixin from './refsMixin.ios'
2
- export default refsMixin
@@ -1,2 +0,0 @@
1
- import styleHelperMixin from './styleHelperMixin.ios'
2
- export default styleHelperMixin
@@ -1,2 +0,0 @@
1
- import createApp from './createApp.ios'
2
- export default createApp
@@ -1 +0,0 @@
1
- export { getDefaultOptions } from './getDefaultOptions.ios'