@mpxjs/webpack-plugin 2.10.6 → 2.10.7-beta.2
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/file-loader.js +1 -1
- package/lib/index.js +41 -13
- package/lib/platform/json/wx/index.js +43 -26
- package/lib/platform/template/wx/component-config/button.js +1 -1
- package/lib/platform/template/wx/component-config/fix-component-name.js +2 -2
- package/lib/platform/template/wx/component-config/index.js +5 -1
- package/lib/platform/template/wx/component-config/input.js +1 -1
- package/lib/platform/template/wx/component-config/sticky-header.js +23 -0
- package/lib/platform/template/wx/component-config/sticky-section.js +23 -0
- package/lib/platform/template/wx/index.js +2 -1
- package/lib/react/LoadAsyncChunkModule.js +68 -0
- package/lib/react/index.js +3 -1
- package/lib/react/processJSON.js +68 -12
- package/lib/react/processScript.js +4 -3
- package/lib/react/script-helper.js +92 -18
- package/lib/runtime/components/react/AsyncContainer.tsx +217 -0
- package/lib/runtime/components/react/AsyncSuspense.tsx +81 -0
- package/lib/runtime/components/react/context.ts +12 -3
- package/lib/runtime/components/react/dist/AsyncContainer.jsx +160 -0
- package/lib/runtime/components/react/dist/AsyncSuspense.jsx +68 -0
- package/lib/runtime/components/react/dist/context.js +4 -1
- package/lib/runtime/components/react/dist/getInnerListeners.js +1 -1
- package/lib/runtime/components/react/dist/mpx-button.jsx +2 -2
- package/lib/runtime/components/react/dist/mpx-input.jsx +1 -1
- package/lib/runtime/components/react/dist/mpx-movable-area.jsx +1 -1
- package/lib/runtime/components/react/dist/mpx-movable-view.jsx +55 -40
- package/lib/runtime/components/react/dist/mpx-rich-text/index.jsx +3 -0
- package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +17 -6
- package/lib/runtime/components/react/dist/mpx-sticky-header.jsx +115 -0
- package/lib/runtime/components/react/dist/mpx-sticky-section.jsx +45 -0
- package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +2 -2
- package/lib/runtime/components/react/dist/mpx-swiper.jsx +53 -27
- package/lib/runtime/components/react/dist/mpx-view.jsx +21 -7
- package/lib/runtime/components/react/dist/mpx-web-view.jsx +16 -30
- package/lib/runtime/components/react/dist/useAnimationHooks.js +2 -87
- package/lib/runtime/components/react/dist/utils.jsx +105 -1
- package/lib/runtime/components/react/getInnerListeners.ts +1 -1
- package/lib/runtime/components/react/mpx-button.tsx +3 -2
- package/lib/runtime/components/react/mpx-input.tsx +1 -1
- package/lib/runtime/components/react/mpx-movable-area.tsx +1 -1
- package/lib/runtime/components/react/mpx-movable-view.tsx +60 -41
- package/lib/runtime/components/react/mpx-rich-text/index.tsx +3 -0
- package/lib/runtime/components/react/mpx-scroll-view.tsx +68 -50
- package/lib/runtime/components/react/mpx-sticky-header.tsx +179 -0
- package/lib/runtime/components/react/mpx-sticky-section.tsx +96 -0
- package/lib/runtime/components/react/mpx-swiper-item.tsx +2 -2
- package/lib/runtime/components/react/mpx-swiper.tsx +53 -25
- package/lib/runtime/components/react/mpx-view.tsx +20 -7
- package/lib/runtime/components/react/mpx-web-view.tsx +14 -34
- package/lib/runtime/components/react/types/global.d.ts +15 -0
- package/lib/runtime/components/react/useAnimationHooks.ts +2 -85
- package/lib/runtime/components/react/utils.tsx +93 -1
- package/lib/runtime/components/web/mpx-scroll-view.vue +21 -4
- package/lib/runtime/components/web/mpx-sticky-header.vue +91 -0
- package/lib/runtime/components/web/mpx-sticky-section.vue +15 -0
- package/lib/runtime/optionProcessor.js +0 -2
- package/lib/template-compiler/compiler.js +2 -2
- package/lib/utils/dom-tag-config.js +17 -3
- package/lib/web/script-helper.js +1 -1
- package/package.json +4 -4
- package/LICENSE +0 -433
|
@@ -3,30 +3,74 @@ const createHelpers = require('../helpers')
|
|
|
3
3
|
const parseRequest = require('../utils/parse-request')
|
|
4
4
|
const shallowStringify = require('../utils/shallow-stringify')
|
|
5
5
|
const normalize = require('../utils/normalize')
|
|
6
|
+
const addQuery = require('../utils/add-query')
|
|
7
|
+
const path = require('path')
|
|
8
|
+
const { isBuildInReactTag } = require('../utils/dom-tag-config')
|
|
6
9
|
|
|
7
10
|
function stringifyRequest (loaderContext, request) {
|
|
8
11
|
return loaderUtils.stringifyRequest(loaderContext, request)
|
|
9
12
|
}
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
function getMpxComponentRequest (component) {
|
|
15
|
+
return JSON.stringify(addQuery(`@mpxjs/webpack-plugin/lib/runtime/components/react/dist/${component}`, { isComponent: true }))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const mpxAsyncSuspense = getMpxComponentRequest('AsyncSuspense')
|
|
19
|
+
|
|
20
|
+
function getAsyncChunkName (chunkName) {
|
|
21
|
+
if (chunkName && typeof chunkName !== 'boolean') {
|
|
22
|
+
return `/* webpackChunkName: "${chunkName}/index" */`
|
|
23
|
+
}
|
|
24
|
+
return ''
|
|
25
|
+
}
|
|
17
26
|
|
|
18
|
-
function
|
|
27
|
+
function getAsyncComponent (componentName, componentRequest, chunkName, fallback) {
|
|
28
|
+
return `getComponent(memo(forwardRef(function(props, ref) {
|
|
29
|
+
return createElement(
|
|
30
|
+
getComponent(require(${mpxAsyncSuspense})),
|
|
31
|
+
{
|
|
32
|
+
type: 'component',
|
|
33
|
+
props: Object.assign({}, props, { ref }),
|
|
34
|
+
chunkName: ${JSON.stringify(chunkName)},
|
|
35
|
+
request: ${JSON.stringify(componentRequest)},
|
|
36
|
+
loading: getComponent(require(${fallback})),
|
|
37
|
+
getChildren: () => import(${getAsyncChunkName(chunkName)}${componentRequest})
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
})))`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getAsyncPage (componentName, componentRequest, chunkName, fallback, loading) {
|
|
44
|
+
fallback = fallback && `getComponent(require('${fallback}?isComponent=true'))`
|
|
45
|
+
loading = loading && `getComponent(require('${loading}?isComponent=true'))`
|
|
46
|
+
return `getComponent(function(props) {
|
|
47
|
+
return createElement(
|
|
48
|
+
getComponent(require(${mpxAsyncSuspense})),
|
|
49
|
+
{
|
|
50
|
+
type: 'page',
|
|
51
|
+
props: props,
|
|
52
|
+
chunkName: ${JSON.stringify(chunkName)},
|
|
53
|
+
request: ${JSON.stringify(componentRequest)},
|
|
54
|
+
fallback: ${fallback},
|
|
55
|
+
loading: ${loading},
|
|
56
|
+
getChildren: () => import(${getAsyncChunkName(chunkName)}${componentRequest})
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
})`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildPagesMap ({ localPagesMap, loaderContext, jsonConfig, rnConfig }) {
|
|
19
63
|
let firstPage = ''
|
|
20
64
|
const pagesMap = {}
|
|
21
65
|
Object.keys(localPagesMap).forEach((pagePath) => {
|
|
22
66
|
const pageCfg = localPagesMap[pagePath]
|
|
23
67
|
const pageRequest = stringifyRequest(loaderContext, pageCfg.resource)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
68
|
+
if (pageCfg.async) {
|
|
69
|
+
pagesMap[pagePath] = getAsyncPage(pagePath, pageRequest, pageCfg.async, rnConfig.asyncChunk && rnConfig.asyncChunk.fallback, rnConfig.asyncChunk && rnConfig.asyncChunk.loading)
|
|
70
|
+
} else {
|
|
27
71
|
// 为了保持小程序中app->page->component的js执行顺序,所有的page和component都改为require引入
|
|
28
|
-
|
|
29
|
-
|
|
72
|
+
pagesMap[pagePath] = `getComponent(require(${pageRequest}), {__mpxPageRoute: ${JSON.stringify(pagePath)}, displayName: "Page"})`
|
|
73
|
+
}
|
|
30
74
|
if (pagePath === jsonConfig.entryPagePath) {
|
|
31
75
|
firstPage = pagePath
|
|
32
76
|
}
|
|
@@ -42,16 +86,45 @@ function buildPagesMap ({ localPagesMap, loaderContext, jsonConfig }) {
|
|
|
42
86
|
|
|
43
87
|
function buildComponentsMap ({ localComponentsMap, builtInComponentsMap, loaderContext, jsonConfig }) {
|
|
44
88
|
const componentsMap = {}
|
|
89
|
+
const mpx = loaderContext.getMpx()
|
|
45
90
|
if (localComponentsMap) {
|
|
46
91
|
Object.keys(localComponentsMap).forEach((componentName) => {
|
|
47
92
|
const componentCfg = localComponentsMap[componentName]
|
|
48
93
|
const componentRequest = stringifyRequest(loaderContext, componentCfg.resource)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
94
|
+
if (componentCfg.async) {
|
|
95
|
+
const placeholder = jsonConfig.componentPlaceholder && jsonConfig.componentPlaceholder[componentName]
|
|
96
|
+
if (placeholder) {
|
|
97
|
+
if (localComponentsMap[placeholder]) {
|
|
98
|
+
const placeholderCfg = localComponentsMap[placeholder]
|
|
99
|
+
const placeholderRequest = stringifyRequest(loaderContext, placeholderCfg.resource)
|
|
100
|
+
if (placeholderCfg.async) {
|
|
101
|
+
loaderContext.emitWarning(
|
|
102
|
+
new Error(`[json processor][${loaderContext.resource}]: componentPlaceholder ${placeholder} should not be a async component, please check!`)
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
componentsMap[componentName] = getAsyncComponent(componentName, componentRequest, componentCfg.async, placeholderRequest)
|
|
106
|
+
} else if (mpx.globalComponents[placeholder]) {
|
|
107
|
+
const { queryObj, rawResourcePath } = parseRequest(mpx.globalComponents[placeholder])
|
|
108
|
+
const placeholderRequest = JSON.stringify(path.resolve(queryObj.context, rawResourcePath))
|
|
109
|
+
componentsMap[componentName] = getAsyncComponent(componentName, componentRequest, componentCfg.async, placeholderRequest)
|
|
110
|
+
} else {
|
|
111
|
+
const tag = `mpx-${placeholder}`
|
|
112
|
+
if (!isBuildInReactTag(tag)) {
|
|
113
|
+
loaderContext.emitError(
|
|
114
|
+
new Error(`[json processor][${loaderContext.resource}]: componentPlaceholder ${placeholder} is not built-in component, please check!`)
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
componentsMap[componentName] = getAsyncComponent(componentName, componentRequest, componentCfg.async, getMpxComponentRequest(tag))
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
loaderContext.emitError(
|
|
121
|
+
new Error(`[json processor][${loaderContext.resource}]: ${componentName} has no componentPlaceholder, please check!`)
|
|
122
|
+
)
|
|
123
|
+
componentsMap[componentName] = getAsyncComponent(componentName, componentRequest, componentCfg.async)
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
componentsMap[componentName] = `getComponent(require(${componentRequest}), {displayName: ${JSON.stringify(componentName)}})`
|
|
127
|
+
}
|
|
55
128
|
})
|
|
56
129
|
}
|
|
57
130
|
if (builtInComponentsMap) {
|
|
@@ -107,6 +180,7 @@ global.__mpxPageConfig = ${JSON.stringify(jsonConfig.window)}
|
|
|
107
180
|
global.__getAppComponents = function () {
|
|
108
181
|
return ${shallowStringify(componentsMap)}
|
|
109
182
|
}
|
|
183
|
+
global.__preloadRule = ${JSON.stringify(jsonConfig.preloadRule)}
|
|
110
184
|
global.currentInject.getPages = function () {
|
|
111
185
|
return ${shallowStringify(pagesMap)}
|
|
112
186
|
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { ComponentType, ReactNode, Component, Suspense } from 'react'
|
|
2
|
+
import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native'
|
|
3
|
+
import FastImage from '@d11/react-native-fast-image'
|
|
4
|
+
import Animated, { useAnimatedStyle } from 'react-native-reanimated'
|
|
5
|
+
|
|
6
|
+
type PageWrapper = {
|
|
7
|
+
children: ReactNode
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const PageWrapper = ({ children }: PageWrapper) => {
|
|
11
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
12
|
+
transform: [{ translateY: -0 }],
|
|
13
|
+
flexBasis: 'auto',
|
|
14
|
+
flex: 1
|
|
15
|
+
}))
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Animated.View
|
|
19
|
+
style={[
|
|
20
|
+
animatedStyle
|
|
21
|
+
]}
|
|
22
|
+
>
|
|
23
|
+
{children}
|
|
24
|
+
</Animated.View>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const styles = StyleSheet.create({
|
|
29
|
+
container: {
|
|
30
|
+
flex: 1,
|
|
31
|
+
padding: 20,
|
|
32
|
+
backgroundColor: '#fff'
|
|
33
|
+
},
|
|
34
|
+
loadingImage: {
|
|
35
|
+
width: 100,
|
|
36
|
+
height: 100,
|
|
37
|
+
marginTop: 220,
|
|
38
|
+
alignSelf: 'center'
|
|
39
|
+
},
|
|
40
|
+
buttonText: {
|
|
41
|
+
color: '#fff',
|
|
42
|
+
fontSize: 16,
|
|
43
|
+
fontWeight: '500',
|
|
44
|
+
textAlign: 'center'
|
|
45
|
+
},
|
|
46
|
+
errorImage: {
|
|
47
|
+
marginTop: 80,
|
|
48
|
+
width: 220,
|
|
49
|
+
aspectRatio: 1,
|
|
50
|
+
alignSelf: 'center'
|
|
51
|
+
},
|
|
52
|
+
errorText: {
|
|
53
|
+
fontSize: 16,
|
|
54
|
+
textAlign: 'center',
|
|
55
|
+
color: '#333',
|
|
56
|
+
marginBottom: 20
|
|
57
|
+
},
|
|
58
|
+
retryButton: {
|
|
59
|
+
position: 'absolute',
|
|
60
|
+
bottom: 54,
|
|
61
|
+
left: 20,
|
|
62
|
+
right: 20,
|
|
63
|
+
backgroundColor: '#fff',
|
|
64
|
+
paddingVertical: 15,
|
|
65
|
+
borderRadius: 30,
|
|
66
|
+
marginTop: 40,
|
|
67
|
+
borderWidth: 1,
|
|
68
|
+
borderColor: '#FF5F00'
|
|
69
|
+
},
|
|
70
|
+
retryButtonText: {
|
|
71
|
+
color: '#FF5F00',
|
|
72
|
+
fontSize: 16,
|
|
73
|
+
fontWeight: '500',
|
|
74
|
+
textAlign: 'center'
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
type AsyncType = 'page' | 'component'
|
|
79
|
+
|
|
80
|
+
interface PropsType<T extends AsyncType> {
|
|
81
|
+
type: T
|
|
82
|
+
props: object
|
|
83
|
+
loading: ComponentType<unknown>
|
|
84
|
+
fallback: ComponentType<unknown>
|
|
85
|
+
children: (props: any) => ReactNode | ReactNode
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface StateType {
|
|
89
|
+
hasError: boolean,
|
|
90
|
+
key: number
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface ComponentError extends Error {
|
|
94
|
+
request?: string
|
|
95
|
+
type: 'timeout' | 'fail'
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const DefaultLoading = () => {
|
|
99
|
+
return (
|
|
100
|
+
<View style={styles.container}>
|
|
101
|
+
<FastImage
|
|
102
|
+
style={styles.loadingImage}
|
|
103
|
+
source={{ uri: 'https://dpubstatic.udache.com/static/dpubimg/439jiCVOtNOnEv9F2LaDs_loading.gif' }}
|
|
104
|
+
resizeMode={FastImage.resizeMode.contain}></FastImage>
|
|
105
|
+
</View>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface DefaultFallbackProps {
|
|
110
|
+
onReload: () => void
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const DefaultFallback = ({ onReload }: DefaultFallbackProps) => {
|
|
114
|
+
return (
|
|
115
|
+
<View style={styles.container}>
|
|
116
|
+
<Image
|
|
117
|
+
source={{ uri: 'https://dpubstatic.udache.com/static/dpubimg/Vak5mZvezPpKV5ZJI6P9b_drn-fallbak.png' }}
|
|
118
|
+
style={styles.errorImage}
|
|
119
|
+
resizeMode="contain"
|
|
120
|
+
/>
|
|
121
|
+
<Text style={styles.errorText}>网络出了点问题,请查看网络环境</Text>
|
|
122
|
+
<TouchableOpacity
|
|
123
|
+
style={styles.retryButton}
|
|
124
|
+
onPress={onReload}
|
|
125
|
+
activeOpacity={0.7}
|
|
126
|
+
>
|
|
127
|
+
<Text style={styles.retryButtonText}>点击重试</Text>
|
|
128
|
+
</TouchableOpacity>
|
|
129
|
+
</View>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export default class AsyncContainer extends Component<PropsType<AsyncType>, StateType> {
|
|
134
|
+
private suspenseFallback: ReactNode
|
|
135
|
+
private errorFallback: ReactNode
|
|
136
|
+
|
|
137
|
+
constructor (props: PropsType<AsyncType>) {
|
|
138
|
+
super(props)
|
|
139
|
+
this.state = {
|
|
140
|
+
hasError: false,
|
|
141
|
+
key: 0
|
|
142
|
+
}
|
|
143
|
+
this.suspenseFallback = this.getSuspenseFallback()
|
|
144
|
+
this.errorFallback = this.getErrorFallback()
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// render 阶段收集到的错误
|
|
148
|
+
static getDerivedStateFromError (error: ComponentError): StateType | undefined {
|
|
149
|
+
if (error.name === 'ChunkLoadError') {
|
|
150
|
+
return {
|
|
151
|
+
hasError: true,
|
|
152
|
+
key: 0
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
// 被外层捕获
|
|
156
|
+
throw error
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
componentDidCatch (error: ComponentError): void {
|
|
161
|
+
if (error.name === 'ChunkLoadError' && this.props.type === 'component') {
|
|
162
|
+
const request = error.request || ''
|
|
163
|
+
const subpackage = request.split('/').filter((i: string) => !!i)[0]
|
|
164
|
+
global.onLazyLoadError({
|
|
165
|
+
type: 'subpackage',
|
|
166
|
+
subpackage: [subpackage],
|
|
167
|
+
errMsg: `loadSubpackage: ${error.type}`
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
reloadPage () {
|
|
173
|
+
this.setState((prevState) => {
|
|
174
|
+
return {
|
|
175
|
+
hasError: false,
|
|
176
|
+
key: prevState.key + 1
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
getSuspenseFallback () {
|
|
182
|
+
if (this.props.type === 'page') {
|
|
183
|
+
const Fallback = this.props.loading || DefaultLoading
|
|
184
|
+
return <Fallback />
|
|
185
|
+
} else {
|
|
186
|
+
const Fallback = this.props.loading
|
|
187
|
+
return <Fallback {...this.props.props}></Fallback>
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
getErrorFallback () {
|
|
192
|
+
if (this.props.type === 'page') {
|
|
193
|
+
const Fallback = this.props.fallback as ComponentType<DefaultFallbackProps> || DefaultFallback
|
|
194
|
+
return <Fallback onReload={this.reloadPage.bind(this)}></Fallback>
|
|
195
|
+
} else {
|
|
196
|
+
const Fallback = this.props.loading
|
|
197
|
+
return <Fallback {...this.props.props}></Fallback>
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
render () {
|
|
202
|
+
if (this.state.hasError) {
|
|
203
|
+
if (this.props.type === 'component') {
|
|
204
|
+
return this.errorFallback
|
|
205
|
+
} else {
|
|
206
|
+
return (<PageWrapper>{this.errorFallback}</PageWrapper>)
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
return (
|
|
210
|
+
<Suspense fallback={this.suspenseFallback} key={this.state.key}>
|
|
211
|
+
{typeof this.props.children === 'function' ? this.props.children(this.props.props) : this.props.children}
|
|
212
|
+
{/* {this.props.children(this.props.props)} */}
|
|
213
|
+
</Suspense>
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useState, ComponentType, useEffect, useCallback, useRef } from 'react'
|
|
2
|
+
import { DefaultFallback, DefaultLoading, PageWrapper } from './AsyncContainer'
|
|
3
|
+
import type { DefaultFallbackProps } from './AsyncContainer'
|
|
4
|
+
|
|
5
|
+
const asyncChunkMap = new Map()
|
|
6
|
+
|
|
7
|
+
interface props {
|
|
8
|
+
type: 'component' | 'page'
|
|
9
|
+
chunkName: string
|
|
10
|
+
request: string
|
|
11
|
+
props: any,
|
|
12
|
+
loading: ComponentType<unknown>
|
|
13
|
+
fallback: ComponentType<unknown>
|
|
14
|
+
getChildren: () => Promise<unknown>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const AsyncSuspense: React.FC<props> = ({ type, props, chunkName, request, loading, fallback, getChildren }) => {
|
|
18
|
+
const [status, setStatus] = useState('pending')
|
|
19
|
+
const loaded = asyncChunkMap.has(request)
|
|
20
|
+
const [, setKey] = useState(0)
|
|
21
|
+
const chunkPromise = useRef<null | Promise<unknown>>(null)
|
|
22
|
+
|
|
23
|
+
const reloadPage = useCallback(() => {
|
|
24
|
+
setKey((preV) => preV + 1)
|
|
25
|
+
console.log('[mpxAsyncSuspense]: reload page')
|
|
26
|
+
setStatus('pending')
|
|
27
|
+
}, [])
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (!loaded && status === 'pending') {
|
|
31
|
+
// todo 清楚副作用?
|
|
32
|
+
console.log('the current :', chunkPromise.current)
|
|
33
|
+
chunkPromise.current!
|
|
34
|
+
.then((m: any) => {
|
|
35
|
+
console.log('[mpxAsyncSuspense]: load sucess')
|
|
36
|
+
asyncChunkMap.set(request, m.__esModule ? m.default : m)
|
|
37
|
+
setStatus('loaded')
|
|
38
|
+
})
|
|
39
|
+
.catch((e) => {
|
|
40
|
+
if (type === 'component') {
|
|
41
|
+
console.log(11111, e)
|
|
42
|
+
global.onLazyLoadError({
|
|
43
|
+
type: 'subpackage',
|
|
44
|
+
subpackage: [chunkName],
|
|
45
|
+
errMsg: `loadSubpackage: ${e.type}`
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
console.log('[mpxAsyncSuspense]: load eror', e)
|
|
49
|
+
chunkPromise.current = null
|
|
50
|
+
setStatus('error')
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
if (loaded) {
|
|
56
|
+
const Comp = asyncChunkMap.get(request)
|
|
57
|
+
return <Comp {...props}></Comp>
|
|
58
|
+
} else if (status === 'error') {
|
|
59
|
+
console.log('the status is:', status)
|
|
60
|
+
if (type === 'page') {
|
|
61
|
+
const Fallback = fallback as ComponentType<DefaultFallbackProps> || DefaultFallback
|
|
62
|
+
return <><PageWrapper><Fallback onReload={reloadPage}></Fallback></PageWrapper></>
|
|
63
|
+
} else {
|
|
64
|
+
const Fallback = loading
|
|
65
|
+
return <Fallback {...props}></Fallback>
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
if (!chunkPromise.current) {
|
|
69
|
+
chunkPromise.current = getChildren()
|
|
70
|
+
}
|
|
71
|
+
if (type === 'page') {
|
|
72
|
+
const Fallback = loading || DefaultLoading
|
|
73
|
+
return <PageWrapper><Fallback /></PageWrapper>
|
|
74
|
+
} else {
|
|
75
|
+
const Fallback = loading
|
|
76
|
+
return <Fallback {...props}></Fallback>
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default AsyncSuspense
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createContext, Dispatch, MutableRefObject, SetStateAction } from 'react'
|
|
2
|
-
import { NativeSyntheticEvent } from 'react-native'
|
|
2
|
+
import { NativeSyntheticEvent, Animated } from 'react-native'
|
|
3
|
+
import { noop } from '@mpxjs/utils'
|
|
3
4
|
|
|
4
5
|
export type LabelContextValue = MutableRefObject<{
|
|
5
6
|
triggerChange: (evt: NativeSyntheticEvent<TouchEvent>) => void
|
|
@@ -42,7 +43,8 @@ export interface PortalContextValue {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export interface ScrollViewContextValue {
|
|
45
|
-
|
|
46
|
+
gestureRef: React.RefObject<any> | null,
|
|
47
|
+
scrollOffset: Animated.Value
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
export interface RouteContextValue {
|
|
@@ -50,6 +52,11 @@ export interface RouteContextValue {
|
|
|
50
52
|
navigation: Record<string, any>
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
export interface StickyContextValue {
|
|
56
|
+
registerStickyHeader: Function,
|
|
57
|
+
unregisterStickyHeader: Function
|
|
58
|
+
}
|
|
59
|
+
|
|
53
60
|
export const MovableAreaContext = createContext({ width: 0, height: 0 })
|
|
54
61
|
|
|
55
62
|
export const FormContext = createContext<FormContextValue | null>(null)
|
|
@@ -72,6 +79,8 @@ export const SwiperContext = createContext({})
|
|
|
72
79
|
|
|
73
80
|
export const KeyboardAvoidContext = createContext<KeyboardAvoidContextValue | null>(null)
|
|
74
81
|
|
|
75
|
-
export const ScrollViewContext = createContext<ScrollViewContextValue>({ gestureRef: null })
|
|
82
|
+
export const ScrollViewContext = createContext<ScrollViewContextValue>({ gestureRef: null, scrollOffset: new Animated.Value(0) })
|
|
76
83
|
|
|
77
84
|
export const PortalContext = createContext<PortalContextValue>(null as any)
|
|
85
|
+
|
|
86
|
+
export const StickyContext = createContext<StickyContextValue>({ registerStickyHeader: noop, unregisterStickyHeader: noop })
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { Component, Suspense } from 'react';
|
|
2
|
+
import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native';
|
|
3
|
+
import FastImage from '@d11/react-native-fast-image';
|
|
4
|
+
import Animated, { useAnimatedStyle } from 'react-native-reanimated';
|
|
5
|
+
export const PageWrapper = ({ children }) => {
|
|
6
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
7
|
+
transform: [{ translateY: -0 }],
|
|
8
|
+
flexBasis: 'auto',
|
|
9
|
+
flex: 1
|
|
10
|
+
}));
|
|
11
|
+
return (<Animated.View style={[
|
|
12
|
+
animatedStyle
|
|
13
|
+
]}>
|
|
14
|
+
{children}
|
|
15
|
+
</Animated.View>);
|
|
16
|
+
};
|
|
17
|
+
const styles = StyleSheet.create({
|
|
18
|
+
container: {
|
|
19
|
+
flex: 1,
|
|
20
|
+
padding: 20,
|
|
21
|
+
backgroundColor: '#fff'
|
|
22
|
+
},
|
|
23
|
+
loadingImage: {
|
|
24
|
+
width: 100,
|
|
25
|
+
height: 100,
|
|
26
|
+
marginTop: 220,
|
|
27
|
+
alignSelf: 'center'
|
|
28
|
+
},
|
|
29
|
+
buttonText: {
|
|
30
|
+
color: '#fff',
|
|
31
|
+
fontSize: 16,
|
|
32
|
+
fontWeight: '500',
|
|
33
|
+
textAlign: 'center'
|
|
34
|
+
},
|
|
35
|
+
errorImage: {
|
|
36
|
+
marginTop: 80,
|
|
37
|
+
width: 220,
|
|
38
|
+
aspectRatio: 1,
|
|
39
|
+
alignSelf: 'center'
|
|
40
|
+
},
|
|
41
|
+
errorText: {
|
|
42
|
+
fontSize: 16,
|
|
43
|
+
textAlign: 'center',
|
|
44
|
+
color: '#333',
|
|
45
|
+
marginBottom: 20
|
|
46
|
+
},
|
|
47
|
+
retryButton: {
|
|
48
|
+
position: 'absolute',
|
|
49
|
+
bottom: 54,
|
|
50
|
+
left: 20,
|
|
51
|
+
right: 20,
|
|
52
|
+
backgroundColor: '#fff',
|
|
53
|
+
paddingVertical: 15,
|
|
54
|
+
borderRadius: 30,
|
|
55
|
+
marginTop: 40,
|
|
56
|
+
borderWidth: 1,
|
|
57
|
+
borderColor: '#FF5F00'
|
|
58
|
+
},
|
|
59
|
+
retryButtonText: {
|
|
60
|
+
color: '#FF5F00',
|
|
61
|
+
fontSize: 16,
|
|
62
|
+
fontWeight: '500',
|
|
63
|
+
textAlign: 'center'
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
export const DefaultLoading = () => {
|
|
67
|
+
return (<View style={styles.container}>
|
|
68
|
+
<FastImage style={styles.loadingImage} source={{ uri: 'https://dpubstatic.udache.com/static/dpubimg/439jiCVOtNOnEv9F2LaDs_loading.gif' }} resizeMode={FastImage.resizeMode.contain}></FastImage>
|
|
69
|
+
</View>);
|
|
70
|
+
};
|
|
71
|
+
export const DefaultFallback = ({ onReload }) => {
|
|
72
|
+
return (<View style={styles.container}>
|
|
73
|
+
<Image source={{ uri: 'https://dpubstatic.udache.com/static/dpubimg/Vak5mZvezPpKV5ZJI6P9b_drn-fallbak.png' }} style={styles.errorImage} resizeMode="contain"/>
|
|
74
|
+
<Text style={styles.errorText}>网络出了点问题,请查看网络环境</Text>
|
|
75
|
+
<TouchableOpacity style={styles.retryButton} onPress={onReload} activeOpacity={0.7}>
|
|
76
|
+
<Text style={styles.retryButtonText}>点击重试</Text>
|
|
77
|
+
</TouchableOpacity>
|
|
78
|
+
</View>);
|
|
79
|
+
};
|
|
80
|
+
export default class AsyncContainer extends Component {
|
|
81
|
+
suspenseFallback;
|
|
82
|
+
errorFallback;
|
|
83
|
+
constructor(props) {
|
|
84
|
+
super(props);
|
|
85
|
+
this.state = {
|
|
86
|
+
hasError: false,
|
|
87
|
+
key: 0
|
|
88
|
+
};
|
|
89
|
+
this.suspenseFallback = this.getSuspenseFallback();
|
|
90
|
+
this.errorFallback = this.getErrorFallback();
|
|
91
|
+
}
|
|
92
|
+
// render 阶段收集到的错误
|
|
93
|
+
static getDerivedStateFromError(error) {
|
|
94
|
+
if (error.name === 'ChunkLoadError') {
|
|
95
|
+
return {
|
|
96
|
+
hasError: true,
|
|
97
|
+
key: 0
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
// 被外层捕获
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
componentDidCatch(error) {
|
|
106
|
+
if (error.name === 'ChunkLoadError' && this.props.type === 'component') {
|
|
107
|
+
const request = error.request || '';
|
|
108
|
+
const subpackage = request.split('/').filter((i) => !!i)[0];
|
|
109
|
+
global.onLazyLoadError({
|
|
110
|
+
type: 'subpackage',
|
|
111
|
+
subpackage: [subpackage],
|
|
112
|
+
errMsg: `loadSubpackage: ${error.type}`
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
reloadPage() {
|
|
117
|
+
this.setState((prevState) => {
|
|
118
|
+
return {
|
|
119
|
+
hasError: false,
|
|
120
|
+
key: prevState.key + 1
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
getSuspenseFallback() {
|
|
125
|
+
if (this.props.type === 'page') {
|
|
126
|
+
const Fallback = this.props.loading || DefaultLoading;
|
|
127
|
+
return <Fallback />;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const Fallback = this.props.loading;
|
|
131
|
+
return <Fallback {...this.props.props}></Fallback>;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
getErrorFallback() {
|
|
135
|
+
if (this.props.type === 'page') {
|
|
136
|
+
const Fallback = this.props.fallback || DefaultFallback;
|
|
137
|
+
return <Fallback onReload={this.reloadPage.bind(this)}></Fallback>;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
const Fallback = this.props.loading;
|
|
141
|
+
return <Fallback {...this.props.props}></Fallback>;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
render() {
|
|
145
|
+
if (this.state.hasError) {
|
|
146
|
+
if (this.props.type === 'component') {
|
|
147
|
+
return this.errorFallback;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
return (<PageWrapper>{this.errorFallback}</PageWrapper>);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
return (<Suspense fallback={this.suspenseFallback} key={this.state.key}>
|
|
155
|
+
{typeof this.props.children === 'function' ? this.props.children(this.props.props) : this.props.children}
|
|
156
|
+
{/* {this.props.children(this.props.props)} */}
|
|
157
|
+
</Suspense>);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|