@mpxjs/webpack-plugin 2.10.6-beta.7 → 2.10.7-beta.1
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 +2 -3
- package/lib/index.js +21 -23
- package/lib/react/processJSON.js +0 -4
- package/lib/react/processScript.js +2 -2
- package/lib/react/script-helper.js +44 -22
- package/lib/runtime/components/react/AsyncContainer.tsx +217 -0
- package/lib/runtime/components/react/AsyncSuspense.tsx +51 -164
- package/lib/runtime/components/react/dist/AsyncContainer.jsx +160 -0
- package/lib/runtime/components/react/dist/AsyncSuspense.jsx +44 -113
- package/lib/runtime/components/react/dist/mpx-movable-view.jsx +13 -2
- package/lib/runtime/components/react/dist/mpx-rich-text/index.jsx +3 -0
- package/lib/runtime/components/react/dist/mpx-view.jsx +21 -7
- package/lib/runtime/components/react/dist/utils.jsx +9 -0
- package/lib/runtime/components/react/mpx-movable-view.tsx +15 -1
- package/lib/runtime/components/react/mpx-rich-text/index.tsx +3 -0
- package/lib/runtime/components/react/mpx-view.tsx +20 -7
- package/lib/runtime/components/react/utils.tsx +10 -0
- package/lib/runtime/optionProcessor.js +0 -2
- package/lib/runtime/optionProcessorReact.d.ts +0 -18
- package/lib/runtime/optionProcessorReact.js +0 -24
- package/package.json +3 -3
- package/lib/dependencies/RecordFileUrlDependency.js +0 -58
- package/lib/utils/trans-async-sub-rules.js +0 -34
package/lib/file-loader.js
CHANGED
|
@@ -35,9 +35,8 @@ module.exports = function loader (content, prevOptions) {
|
|
|
35
35
|
|
|
36
36
|
let publicPath = `__webpack_public_path__ + ${JSON.stringify(url)}`
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
38
|
+
// todo 未来添加分包处理后相对地址不一定是./开头的,需要考虑通过dependency的方式在sourceModule时通过最终的chunkName得到准确的相对路径
|
|
39
|
+
if (isRN) publicPath = `__non_webpack_require__(${JSON.stringify(`_mpx_rn_img_relative_path_/${url}`)})`
|
|
41
40
|
|
|
42
41
|
if (options.publicPath) {
|
|
43
42
|
if (typeof options.publicPath === 'function') {
|
package/lib/index.js
CHANGED
|
@@ -44,11 +44,9 @@ const FlagPluginDependency = require('./dependencies/FlagPluginDependency')
|
|
|
44
44
|
const RemoveEntryDependency = require('./dependencies/RemoveEntryDependency')
|
|
45
45
|
const RecordLoaderContentDependency = require('./dependencies/RecordLoaderContentDependency')
|
|
46
46
|
const RecordRuntimeInfoDependency = require('./dependencies/RecordRuntimeInfoDependency')
|
|
47
|
-
const RecordFileUrlDependency = require('./dependencies/RecordFileUrlDependency')
|
|
48
47
|
const SplitChunksPlugin = require('webpack/lib/optimize/SplitChunksPlugin')
|
|
49
48
|
const fixRelative = require('./utils/fix-relative')
|
|
50
49
|
const parseRequest = require('./utils/parse-request')
|
|
51
|
-
const { transAsyncSubNameRules, transAsyncSubRules } = require('./utils/trans-async-sub-rules')
|
|
52
50
|
const { matchCondition } = require('./utils/match-condition')
|
|
53
51
|
const processDefs = require('./utils/process-defs')
|
|
54
52
|
const config = require('./config')
|
|
@@ -674,9 +672,6 @@ class MpxWebpackPlugin {
|
|
|
674
672
|
compilation.dependencyFactories.set(RecordRuntimeInfoDependency, new NullFactory())
|
|
675
673
|
compilation.dependencyTemplates.set(RecordRuntimeInfoDependency, new RecordRuntimeInfoDependency.Template())
|
|
676
674
|
|
|
677
|
-
compilation.dependencyFactories.set(RecordFileUrlDependency, new NullFactory())
|
|
678
|
-
compilation.dependencyTemplates.set(RecordFileUrlDependency, new RecordFileUrlDependency.Template())
|
|
679
|
-
|
|
680
675
|
compilation.dependencyTemplates.set(ImportDependency, new ImportDependencyTemplate())
|
|
681
676
|
})
|
|
682
677
|
|
|
@@ -779,7 +774,6 @@ class MpxWebpackPlugin {
|
|
|
779
774
|
})
|
|
780
775
|
},
|
|
781
776
|
asyncSubpackageRules: this.options.asyncSubpackageRules,
|
|
782
|
-
asyncSubpackageNameMap: this.options.asyncSubpackageNameMap,
|
|
783
777
|
optimizeRenderRules: this.options.optimizeRenderRules,
|
|
784
778
|
pathHash: (resourcePath) => {
|
|
785
779
|
if (this.options.pathHashMode === 'relative' && this.options.projectRoot) {
|
|
@@ -1228,7 +1222,7 @@ class MpxWebpackPlugin {
|
|
|
1228
1222
|
if (isWeb(mpx.mode) && !hasOwn(splitChunksOptions.cacheGroups, 'main')) {
|
|
1229
1223
|
splitChunksOptions.cacheGroups.main = {
|
|
1230
1224
|
chunks: 'initial',
|
|
1231
|
-
name: '
|
|
1225
|
+
name: 'bundle/index', // web 输出 chunk 路径和 rn 输出分包格式拉齐
|
|
1232
1226
|
test: /[\\/]node_modules[\\/]/
|
|
1233
1227
|
}
|
|
1234
1228
|
needInit = true
|
|
@@ -1236,7 +1230,7 @@ class MpxWebpackPlugin {
|
|
|
1236
1230
|
if (!hasOwn(splitChunksOptions.cacheGroups, 'async')) {
|
|
1237
1231
|
splitChunksOptions.cacheGroups.async = {
|
|
1238
1232
|
chunks: 'async',
|
|
1239
|
-
name: 'async
|
|
1233
|
+
name: 'async/index',
|
|
1240
1234
|
minChunks: 2
|
|
1241
1235
|
}
|
|
1242
1236
|
needInit = true
|
|
@@ -1339,6 +1333,15 @@ class MpxWebpackPlugin {
|
|
|
1339
1333
|
compilation.hooks.processAssets.tap({
|
|
1340
1334
|
name: 'MpxWebpackPlugin'
|
|
1341
1335
|
}, (assets) => {
|
|
1336
|
+
if (isReact(mpx.mode)) {
|
|
1337
|
+
Object.keys(assets).forEach((chunkName) => {
|
|
1338
|
+
if (/\.js$/.test(chunkName)) {
|
|
1339
|
+
let val = assets[chunkName].source()
|
|
1340
|
+
val = val.replace(/_mpx_rn_img_relative_path_/g, chunkName === 'app.js' ? '.' : '..')
|
|
1341
|
+
compilation.assets[chunkName] = new RawSource(val)
|
|
1342
|
+
}
|
|
1343
|
+
})
|
|
1344
|
+
}
|
|
1342
1345
|
try {
|
|
1343
1346
|
const dynamicAssets = {}
|
|
1344
1347
|
for (const packageName in mpx.runtimeInfo) {
|
|
@@ -1384,15 +1387,6 @@ class MpxWebpackPlugin {
|
|
|
1384
1387
|
}
|
|
1385
1388
|
})
|
|
1386
1389
|
|
|
1387
|
-
parser.hooks.call.for('__mpx_rn_resolve_url_path_').tap('MpxWebpackPlugin', (expr) => {
|
|
1388
|
-
const args = expr.arguments.map((i) => i.value)
|
|
1389
|
-
args.unshift(expr.range)
|
|
1390
|
-
|
|
1391
|
-
const dep = new RecordFileUrlDependency(...args)
|
|
1392
|
-
parser.state.current.addPresentationalDependency(dep)
|
|
1393
|
-
return true
|
|
1394
|
-
})
|
|
1395
|
-
|
|
1396
1390
|
parser.hooks.call.for('__mpx_dynamic_entry__').tap('MpxWebpackPlugin', (expr) => {
|
|
1397
1391
|
const args = expr.arguments.map((i) => i.value)
|
|
1398
1392
|
args.unshift(expr.range)
|
|
@@ -1408,16 +1402,20 @@ class MpxWebpackPlugin {
|
|
|
1408
1402
|
const range = expr.arguments[0].range
|
|
1409
1403
|
const context = parser.state.module.context
|
|
1410
1404
|
const { queryObj, resourcePath } = parseRequest(request)
|
|
1411
|
-
let tarRoot =
|
|
1405
|
+
let tarRoot = queryObj.root
|
|
1406
|
+
if (!tarRoot && mpx.asyncSubpackageRules) {
|
|
1407
|
+
for (const item of mpx.asyncSubpackageRules) {
|
|
1408
|
+
if (matchCondition(resourcePath, item)) {
|
|
1409
|
+
tarRoot = item.root
|
|
1410
|
+
break
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1412
1414
|
if (tarRoot) {
|
|
1413
1415
|
// 删除root query
|
|
1414
1416
|
if (queryObj.root) request = addQuery(request, {}, false, ['root'])
|
|
1415
1417
|
// wx、ali和web平台支持require.async,其余平台使用CommonJsAsyncDependency进行模拟抹平
|
|
1416
|
-
if (
|
|
1417
|
-
// web 和 react 平台支持分包名转换,如果转换tarRoot为空,则降级到主包
|
|
1418
|
-
tarRoot = transAsyncSubNameRules(mpx.asyncSubpackageNameMap, tarRoot)
|
|
1419
|
-
}
|
|
1420
|
-
if (mpx.supportRequireAsync && tarRoot) {
|
|
1418
|
+
if (mpx.supportRequireAsync) {
|
|
1421
1419
|
if (isWeb(mpx.mode) || isReact(mpx.mode)) {
|
|
1422
1420
|
const depBlock = new AsyncDependenciesBlock(
|
|
1423
1421
|
{
|
package/lib/react/processJSON.js
CHANGED
|
@@ -8,7 +8,6 @@ const addQuery = require('../utils/add-query')
|
|
|
8
8
|
const parseComponent = require('../parser')
|
|
9
9
|
const getJSONContent = require('../utils/get-json-content')
|
|
10
10
|
const resolve = require('../utils/resolve')
|
|
11
|
-
const { transAsyncSubNameRules } = require('../utils/trans-async-sub-rules')
|
|
12
11
|
const createJSONHelper = require('../json-compiler/helper')
|
|
13
12
|
const getRulesRunner = require('../platform/index')
|
|
14
13
|
const { RESOLVE_IGNORED_ERR } = require('../utils/const')
|
|
@@ -286,8 +285,6 @@ module.exports = function (jsonContent, {
|
|
|
286
285
|
|
|
287
286
|
pagesMap[resourcePath] = outputPath
|
|
288
287
|
loaderContext._module && loaderContext._module.addPresentationalDependency(new RecordResourceMapDependency(resourcePath, 'page', outputPath))
|
|
289
|
-
// 通过asyncSubPackagesNameRules对tarRoot进行修改,仅修改tarRoot,不修改outputPath页面路径
|
|
290
|
-
tarRoot = transAsyncSubNameRules(mpx.asyncSubpackageNameMap, tarRoot)
|
|
291
288
|
localPagesMap[outputPath] = {
|
|
292
289
|
resource: addQuery(resource, { isPage: true }),
|
|
293
290
|
async: queryObj.async || tarRoot,
|
|
@@ -335,7 +332,6 @@ module.exports = function (jsonContent, {
|
|
|
335
332
|
const fillComponentsMap = (name, entry, tarRoot) => {
|
|
336
333
|
const { resource, outputPath } = entry
|
|
337
334
|
const { resourcePath, queryObj } = parseRequest(resource)
|
|
338
|
-
tarRoot = transAsyncSubNameRules(mpx.asyncSubpackageNameMap, tarRoot)
|
|
339
335
|
componentsMap[resourcePath] = outputPath
|
|
340
336
|
loaderContext._module && loaderContext._module.addPresentationalDependency(new RecordResourceMapDependency(resourcePath, 'component', outputPath))
|
|
341
337
|
localComponentsMap[name] = {
|
|
@@ -36,7 +36,7 @@ module.exports = function (script, {
|
|
|
36
36
|
output += "import { lazy, createElement, memo, forwardRef } from 'react'\n"
|
|
37
37
|
if (ctorType === 'app') {
|
|
38
38
|
output += `
|
|
39
|
-
import { getComponent
|
|
39
|
+
import { getComponent } from ${stringifyRequest(loaderContext, optionProcessorPath)}
|
|
40
40
|
\n`
|
|
41
41
|
const { pagesMap, firstPage } = buildPagesMap({
|
|
42
42
|
localPagesMap,
|
|
@@ -53,7 +53,7 @@ import { getComponent, getAsyncSuspense } from ${stringifyRequest(loaderContext,
|
|
|
53
53
|
output += getRequireScript({ ctorType, script, loaderContext })
|
|
54
54
|
output += `export default global.__mpxOptionsMap[${JSON.stringify(moduleId)}]\n`
|
|
55
55
|
} else {
|
|
56
|
-
output += `import { getComponent
|
|
56
|
+
output += `import { getComponent } from ${stringifyRequest(loaderContext, optionProcessorPath)}\n`
|
|
57
57
|
// 获取组件集合
|
|
58
58
|
const componentsMap = buildComponentsMap({
|
|
59
59
|
localComponentsMap,
|
|
@@ -4,6 +4,7 @@ const parseRequest = require('../utils/parse-request')
|
|
|
4
4
|
const shallowStringify = require('../utils/shallow-stringify')
|
|
5
5
|
const normalize = require('../utils/normalize')
|
|
6
6
|
const addQuery = require('../utils/add-query')
|
|
7
|
+
const path = require('path')
|
|
7
8
|
const { isBuildInReactTag } = require('../utils/dom-tag-config')
|
|
8
9
|
|
|
9
10
|
function stringifyRequest (loaderContext, request) {
|
|
@@ -14,6 +15,8 @@ function getMpxComponentRequest (component) {
|
|
|
14
15
|
return JSON.stringify(addQuery(`@mpxjs/webpack-plugin/lib/runtime/components/react/dist/${component}`, { isComponent: true }))
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
const mpxAsyncSuspense = getMpxComponentRequest('AsyncSuspense')
|
|
19
|
+
|
|
17
20
|
function getAsyncChunkName (chunkName) {
|
|
18
21
|
if (chunkName && typeof chunkName !== 'boolean') {
|
|
19
22
|
return `/* webpackChunkName: "${chunkName}/index" */`
|
|
@@ -21,33 +24,49 @@ function getAsyncChunkName (chunkName) {
|
|
|
21
24
|
return ''
|
|
22
25
|
}
|
|
23
26
|
|
|
24
|
-
function
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
+
})`
|
|
37
60
|
}
|
|
38
61
|
|
|
39
62
|
function buildPagesMap ({ localPagesMap, loaderContext, jsonConfig, rnConfig }) {
|
|
40
63
|
let firstPage = ''
|
|
41
64
|
const pagesMap = {}
|
|
42
|
-
const mpx = loaderContext.getMpx()
|
|
43
65
|
Object.keys(localPagesMap).forEach((pagePath) => {
|
|
44
66
|
const pageCfg = localPagesMap[pagePath]
|
|
45
67
|
const pageRequest = stringifyRequest(loaderContext, pageCfg.resource)
|
|
46
68
|
if (pageCfg.async) {
|
|
47
|
-
|
|
48
|
-
const fallback = rnConfig.asyncChunk && rnConfig.asyncChunk.fallback && JSON.stringify(addQuery(rnConfig.asyncChunk.fallback, { isComponent: true }))
|
|
49
|
-
const loading = rnConfig.asyncChunk && rnConfig.asyncChunk.loading && JSON.stringify(addQuery(rnConfig.asyncChunk.loading, { isComponent: true }))
|
|
50
|
-
pagesMap[pagePath] = getAsyncSuspense('page', moduleId, pageRequest, pageCfg.async, fallback, loading)
|
|
69
|
+
pagesMap[pagePath] = getAsyncPage(pagePath, pageRequest, pageCfg.async, rnConfig.asyncChunk && rnConfig.asyncChunk.fallback, rnConfig.asyncChunk && rnConfig.asyncChunk.loading)
|
|
51
70
|
} else {
|
|
52
71
|
// 为了保持小程序中app->page->component的js执行顺序,所有的page和component都改为require引入
|
|
53
72
|
pagesMap[pagePath] = `getComponent(require(${pageRequest}), {__mpxPageRoute: ${JSON.stringify(pagePath)}, displayName: "Page"})`
|
|
@@ -73,7 +92,6 @@ function buildComponentsMap ({ localComponentsMap, builtInComponentsMap, loaderC
|
|
|
73
92
|
const componentCfg = localComponentsMap[componentName]
|
|
74
93
|
const componentRequest = stringifyRequest(loaderContext, componentCfg.resource)
|
|
75
94
|
if (componentCfg.async) {
|
|
76
|
-
const moduleId = mpx.getModuleId(componentCfg.resource)
|
|
77
95
|
const placeholder = jsonConfig.componentPlaceholder && jsonConfig.componentPlaceholder[componentName]
|
|
78
96
|
if (placeholder) {
|
|
79
97
|
if (localComponentsMap[placeholder]) {
|
|
@@ -84,7 +102,11 @@ function buildComponentsMap ({ localComponentsMap, builtInComponentsMap, loaderC
|
|
|
84
102
|
new Error(`[json processor][${loaderContext.resource}]: componentPlaceholder ${placeholder} should not be a async component, please check!`)
|
|
85
103
|
)
|
|
86
104
|
}
|
|
87
|
-
componentsMap[componentName] =
|
|
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)
|
|
88
110
|
} else {
|
|
89
111
|
const tag = `mpx-${placeholder}`
|
|
90
112
|
if (!isBuildInReactTag(tag)) {
|
|
@@ -92,13 +114,13 @@ function buildComponentsMap ({ localComponentsMap, builtInComponentsMap, loaderC
|
|
|
92
114
|
new Error(`[json processor][${loaderContext.resource}]: componentPlaceholder ${placeholder} is not built-in component, please check!`)
|
|
93
115
|
)
|
|
94
116
|
}
|
|
95
|
-
componentsMap[componentName] =
|
|
117
|
+
componentsMap[componentName] = getAsyncComponent(componentName, componentRequest, componentCfg.async, getMpxComponentRequest(tag))
|
|
96
118
|
}
|
|
97
119
|
} else {
|
|
98
120
|
loaderContext.emitError(
|
|
99
121
|
new Error(`[json processor][${loaderContext.resource}]: ${componentName} has no componentPlaceholder, please check!`)
|
|
100
122
|
)
|
|
101
|
-
componentsMap[componentName] =
|
|
123
|
+
componentsMap[componentName] = getAsyncComponent(componentName, componentRequest, componentCfg.async)
|
|
102
124
|
}
|
|
103
125
|
} else {
|
|
104
126
|
componentsMap[componentName] = `getComponent(require(${componentRequest}), {displayName: ${JSON.stringify(componentName)}})`
|
|
@@ -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
|
+
}
|