Skip to content

Commit ea9d461

Browse files
authored
Merge pull request #2111 from didi/feat-rn-require-on-demand
输出rn支持组件&页面模块按需执行,等同于微信的lazyCodeLoading
2 parents f8941df + 31037cf commit ea9d461

File tree

9 files changed

+164
-148
lines changed

9 files changed

+164
-148
lines changed

packages/core/src/platform/createApp.ios.js

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -53,44 +53,38 @@ export default function createApp (options) {
5353
defaultOptions.onUnhandledRejection && global.__mpxAppCbs.rejection.push(defaultOptions.onUnhandledRejection.bind(appInstance))
5454
defaultOptions.onAppInit && defaultOptions.onAppInit()
5555

56-
const pages = currentInject.getPages() || {}
56+
const pagesMap = currentInject.pagesMap || {}
5757
const firstPage = currentInject.firstPage
5858
const Stack = createNativeStackNavigator()
59-
const withHeader = (wrappedComponent, { pageConfig = {} }) => {
60-
return ({ navigation, ...props }) => {
61-
return createElement(GestureHandlerRootView,
62-
{
63-
style: {
64-
flex: 1
65-
}
66-
},
67-
createElement(innerNav, {
68-
pageConfig: pageConfig,
69-
navigation
70-
}),
71-
createElement(wrappedComponent, { navigation, ...props })
72-
)
73-
}
74-
}
7559
const getPageScreens = (initialRouteName, initialParams) => {
76-
return Object.entries(pages).map(([key, item]) => {
77-
// const options = {
78-
// // __mpxPageStatusMap 为编译注入的全局变量
79-
// headerShown: !(Object.assign({}, global.__mpxPageConfig, global.__mpxPageConfigsMap[key]).navigationStyle === 'custom')
80-
// }
60+
return Object.entries(pagesMap).map(([key, item]) => {
8161
const pageConfig = Object.assign({}, global.__mpxPageConfig, global.__mpxPageConfigsMap[key])
62+
const headerLayout = ({ navigation, children }) => {
63+
return createElement(GestureHandlerRootView,
64+
{
65+
style: {
66+
flex: 1
67+
}
68+
},
69+
createElement(innerNav, {
70+
pageConfig: pageConfig,
71+
navigation
72+
}),
73+
children
74+
)
75+
}
8276
if (key === initialRouteName) {
8377
return createElement(Stack.Screen, {
8478
name: key,
85-
component: withHeader(item, { pageConfig }),
86-
initialParams
87-
// options
79+
getComponent: () => item(),
80+
initialParams,
81+
layout: headerLayout
8882
})
8983
}
9084
return createElement(Stack.Screen, {
9185
name: key,
92-
component: withHeader(item, { pageConfig })
93-
// options
86+
getComponent: () => item(),
87+
layout: headerLayout
9488
})
9589
})
9690
}
@@ -239,7 +233,7 @@ export default function createApp (options) {
239233
headerShown: false,
240234
statusBarTranslucent: true,
241235
statusBarBackgroundColor: 'transparent'
242-
}
236+
}
243237

244238
return createElement(SafeAreaProvider,
245239
null,

packages/core/src/platform/env/nav.js

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { createElement, useState, useMemo } from 'react'
1+
import { createElement, useState, useMemo, memo } from 'react'
22
import { useSafeAreaInsets } from 'react-native-safe-area-context'
3-
import { StatusBar, processColor, TouchableOpacity, Image, View, StyleSheet, Text } from 'react-native'
3+
import { StatusBar, processColor, TouchableWithoutFeedback, Image, View, StyleSheet, Text } from 'react-native'
44
import Mpx from '../../index'
55

66
function convertToHex (color) {
@@ -76,7 +76,7 @@ const validBarTextStyle = (textStyle) => {
7676
return NavColor.White
7777
}
7878
}
79-
export function innerNav ({ pageConfig, navigation }) {
79+
export const innerNav = memo(({ pageConfig, navigation }) => {
8080
const [innerPageConfig, setPageConfig] = useState(pageConfig || {})
8181
navigation.setPageConfig = (config) => {
8282
const newConfig = Object.assign({}, innerPageConfig, config)
@@ -100,39 +100,38 @@ export function innerNav ({ pageConfig, navigation }) {
100100

101101
// 回退按钮与图标
102102
const backElement = stackLength > 1 || isHandleStackTopBack
103-
? createElement(TouchableOpacity, {
104-
style: [styles.backButton],
105-
onPress: () => {
106-
if (stackLength <= 1) {
107-
if (isHandleStackTopBack) {
103+
? createElement(TouchableWithoutFeedback, {
104+
onPress: () => {
105+
if (stackLength <= 1 && isHandleStackTopBack) {
108106
onStackTopBack()
107+
return
109108
}
110-
return
109+
navigation.goBack()
111110
}
112-
113-
navigation.goBack()
114-
}
115-
}, createElement(Image, {
116-
source: { uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAABICAYAAACqT5alAAAA2UlEQVR4nO3bMQrCUBRE0Yla6AYEN2nnBrTL+izcitW3MRDkEUWSvPzJvfCqgMwhZbAppWhNbbIHzB1g9wATERFRVyvpkj1irlpJ5X326D7WHh1hbdFD2CLpLmmftm7kfsEe09aNHFiBrT+wAlt/YAW2/sAKbP2BFdj6Ayuwy+ufz6XPL893krZ//O6iu2n4LT8kndLWTRTo4EC7BDo40C6BDg60S6CDA+0S6OBAuwQ6uNWiD2nrJmoIfU7cNWkR2hbb1UfbY7uuWhGWiIg+a/iHuHmA3QPs3gu4JW9Gan+OJAAAAABJRU5ErkJggg==' },
117-
// 回退按钮的颜色与设置的title文案颜色一致
118-
style: [styles.backButtonImage, { tintColor: navigationBarTextStyle }]
119-
}))
120-
: null
111+
}, createElement(View, {
112+
style: [styles.backButton]
113+
}, createElement(Image, {
114+
source: { uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAABICAYAAACqT5alAAAA2UlEQVR4nO3bMQrCUBRE0Yla6AYEN2nnBrTL+izcitW3MRDkEUWSvPzJvfCqgMwhZbAppWhNbbIHzB1g9wATERFRVyvpkj1irlpJ5X326D7WHh1hbdFD2CLpLmmftm7kfsEe09aNHFiBrT+wAlt/YAW2/sAKbP2BFdj6Ayuwy+ufz6XPL893krZ//O6iu2n4LT8kndLWTRTo4EC7BDo40C6BDg60S6CDA+0S6OBAuwQ6uNWiD2nrJmoIfU7cNWkR2hbb1UfbY7uuWhGWiIg+a/iHuHmA3QPs3gu4JW9Gan+OJAAAAABJRU5ErkJggg==' },
115+
// 回退按钮的颜色与设置的title文案颜色一致
116+
style: [styles.backButtonImage, { tintColor: navigationBarTextStyle }]
117+
})
118+
))
119+
: null
121120

122121
return createElement(View, {
123-
style: [styles.header, {
124-
paddingTop: safeAreaTop,
125-
backgroundColor: innerPageConfig.navigationBarBackgroundColor || '#000000'
126-
}]
127-
},
128-
statusBarElement,
129-
createElement(View, {
130-
style: styles.headerContent,
131-
height: titleHeight
132-
}, backElement,
122+
style: [styles.header, {
123+
paddingTop: safeAreaTop,
124+
backgroundColor: innerPageConfig.navigationBarBackgroundColor || '#000000'
125+
}]
126+
},
127+
statusBarElement,
128+
createElement(View, {
129+
style: styles.headerContent,
130+
height: titleHeight
131+
}, backElement,
133132
createElement(Text, {
134133
style: [styles.title, { color: navigationBarTextStyle }],
135134
numberOfLines: 1
136135
}, innerPageConfig.navigationBarTitleText?.trim() || ''))
137-
)
138-
}
136+
)
137+
})

packages/core/src/platform/patch/getDefaultOptions.ios.js

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ function getSystemInfo () {
3333
}
3434
}
3535

36-
function createEffect (proxy, components) {
36+
function createEffect (proxy, componentsMap) {
3737
const update = proxy.update = () => {
3838
// react update props in child render(async), do not need exec pre render
3939
// if (proxy.propsUpdatedFlag) {
@@ -50,10 +50,11 @@ function createEffect (proxy, components) {
5050
const getComponent = (tagName) => {
5151
if (!tagName) return null
5252
if (tagName === 'block') return Fragment
53-
const appComponents = global.__getAppComponents?.() || {}
53+
const appComponentsMap = global.__appComponentsMap || {}
5454
const generichash = proxy.target.generichash || ''
55-
const genericComponents = global.__mpxGenericsMap?.[generichash] || noop
56-
return components[tagName] || genericComponents(tagName) || appComponents[tagName] || getByPath(ReactNative, tagName)
55+
const genericComponentsMap = global.__mpxGenericsMap?.[generichash] || {}
56+
const componentGetter = componentsMap[tagName] || genericComponentsMap[tagName] || appComponentsMap[tagName]
57+
return componentGetter ? componentGetter() : getByPath(ReactNative, tagName)
5758
}
5859
const innerCreateElement = (type, ...rest) => {
5960
if (!type) return null
@@ -203,7 +204,7 @@ const instanceProto = {
203204
}
204205
}
205206

206-
function createInstance ({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx, relation, parentProvides }) {
207+
function createInstance ({ propsRef, type, rawOptions, currentInject, validProps, componentsMap, pageId, intersectionCtx, relation, parentProvides }) {
207208
const instance = Object.create(instanceProto, {
208209
dataset: {
209210
get () {
@@ -319,7 +320,7 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
319320
stateVersion: Symbol(),
320321
subscribe: (onStoreChange) => {
321322
if (!proxy.effect) {
322-
createEffect(proxy, components)
323+
createEffect(proxy, componentsMap)
323324
proxy.stateVersion = Symbol()
324325
}
325326
proxy.onStoreChange = onStoreChange
@@ -335,7 +336,7 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
335336
})
336337
// react数据响应组件更新管理器
337338
if (!proxy.effect) {
338-
createEffect(proxy, components)
339+
createEffect(proxy, componentsMap)
339340
}
340341

341342
return instance
@@ -571,7 +572,12 @@ export function PageWrapperHOC (WrappedComponent, pageConfig = {}) {
571572
}
572573
export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
573574
rawOptions = mergeOptions(rawOptions, type, false)
574-
const components = Object.assign({}, rawOptions.components, currentInject.getComponents())
575+
const componentsMap = currentInject.componentsMap
576+
if (rawOptions.components) {
577+
Object.entries(rawOptions.components).forEach(([key, item]) => {
578+
componentsMap[key] = () => item
579+
})
580+
}
575581
const validProps = Object.assign({}, rawOptions.props, rawOptions.properties)
576582
const { hasDescendantRelation, hasAncestorRelation } = checkRelation(rawOptions)
577583
if (rawOptions.methods) rawOptions.methods = wrapMethodsWithErrorHandling(rawOptions.methods)
@@ -589,7 +595,7 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
589595
let isFirst = false
590596
if (!instanceRef.current) {
591597
isFirst = true
592-
instanceRef.current = createInstance({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx, relation, parentProvides })
598+
instanceRef.current = createInstance({ propsRef, type, rawOptions, currentInject, validProps, componentsMap, pageId, intersectionCtx, relation, parentProvides })
593599
}
594600
const instance = instanceRef.current
595601
useImperativeHandle(ref, () => {

packages/webpack-plugin/lib/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1180,7 +1180,9 @@ class MpxWebpackPlugin {
11801180
hackModuleIdentifier(module)
11811181
return rawCallback(null, module)
11821182
}
1183-
} else if (isReact(mpx.mode) && module instanceof ExternalModule) {
1183+
}
1184+
1185+
if (isReact(mpx.mode) && module instanceof ExternalModule) {
11841186
module.hasChunkCondition = () => false // 跳过 EnsureChunkConditionsPlugin 的过滤,避免被输出到包含 entry module 的 chunk 当中
11851187
module.chunkCondition = () => true // 可以正常被 SplitChunkPlugin 处理
11861188
}

packages/webpack-plugin/lib/react/LoadAsyncChunkModule.js

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,30 @@ class LoadAsyncChunkRuntimeModule extends HelperRuntimeModule {
1313
const { runtimeTemplate } = compilation
1414
const loadScriptFn = RuntimeGlobals.loadScript
1515
return Template.asString([
16-
'var inProgress = {};',
16+
'var inProgress = {}',
1717
`${loadScriptFn} = ${runtimeTemplate.basicFunction(
1818
'url, done, key, chunkId',
1919
[
2020
`var packageName = ${RuntimeGlobals.getChunkScriptFilename}(chunkId) || ''`,
21-
'packageName = packageName.split("/")[0]',
21+
'packageName = packageName.split(\'/\').slice(0, -1).join(\'/\')',
2222
'var config = {',
2323
Template.indent([
2424
'url: url,',
2525
'package: packageName'
2626
]),
2727
'}',
28-
'if(inProgress[url]) { inProgress[url].push(done); return; }',
29-
'inProgress[url] = [done];',
28+
'if(inProgress[url]) {',
29+
Template.indent([
30+
'inProgress[url].push(done)',
31+
'return'
32+
]),
33+
'}',
34+
'inProgress[url] = [done]',
3035
'var callback = function (type, result) {',
3136
Template.indent([
3237
'var event = {',
3338
Template.indent([
34-
'type: type,',
39+
'type: type || \'fail\',',
3540
'target: {',
3641
Template.indent(['src: url']),
3742
'}'
@@ -40,25 +45,26 @@ class LoadAsyncChunkRuntimeModule extends HelperRuntimeModule {
4045
]),
4146
Template.indent([
4247
'var doneFns = inProgress[url]',
43-
'clearTimeout(timeoutCallback)',
48+
'clearTimeout(timeout)',
4449
'delete inProgress[url]',
4550
`doneFns && doneFns.forEach(${runtimeTemplate.returningFunction(
4651
'fn(event)',
4752
'fn'
4853
)})`
4954
]),
5055
'}',
51-
`var timeoutCallback = setTimeout(callback.bind(null, 'timeout'), ${this.timeout})`,
52-
"var successCallback = callback.bind(null, 'fail');", // 错误类型和 wx 对齐
53-
"var failedCallback = callback.bind(null, 'fail')",
56+
`var timeout = setTimeout(callback.bind(null, 'timeout'), ${this.timeout})`,
5457
'var loadChunkAsyncFn = global.__mpx.config.rnConfig && global.__mpx.config.rnConfig.loadChunkAsync',
55-
'if (typeof loadChunkAsyncFn !== \'function\') {',
56-
Template.indent([
57-
'console.error("[Mpx runtime error]: please provide correct loadChunkAsync function")',
58-
'return'
59-
]),
60-
'}',
61-
'loadChunkAsyncFn(config).then(successCallback).catch(failedCallback)'
58+
'try {',
59+
Template.indent([
60+
'loadChunkAsyncFn(config).then(callback).catch(callback)'
61+
]),
62+
'} catch (e) {',
63+
Template.indent([
64+
'console.error(\'[Mpx runtime error]: please provide correct mpx.config.rnConfig.loadChunkAsync implemention!\', e)',
65+
'Promise.resolve().then(callback)'
66+
]),
67+
'}'
6268
]
6369
)}`
6470
])

packages/webpack-plugin/lib/react/processScript.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ import { getComponent, getAsyncSuspense } from ${stringifyRequest(loaderContext,
6161
jsonConfig
6262
})
6363

64-
output += buildGlobalParams({ moduleId, scriptSrcMode, loaderContext, isProduction, ctorType, jsonConfig, componentsMap, outputPath, genericsInfo, componentGenerics })
64+
output += buildGlobalParams({ moduleId, scriptSrcMode, loaderContext, isProduction, ctorType, jsonConfig, componentsMap, outputPath, genericsInfo, componentGenerics, hasApp })
6565
output += getRequireScript({ ctorType, script, loaderContext })
6666
output += `export default global.__mpxOptionsMap[${JSON.stringify(moduleId)}]\n`
6767
}

0 commit comments

Comments
 (0)