@mpxjs/webpack-plugin 2.10.6-beta.4 → 2.10.6-beta.6
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/dependencies/RecordFileUrlDependency.js +58 -0
- package/lib/file-loader.js +3 -2
- package/lib/index.js +19 -20
- package/lib/react/processJSON.js +4 -0
- package/lib/react/processScript.js +2 -2
- package/lib/react/script-helper.js +22 -44
- package/lib/runtime/components/react/AsyncSuspense.tsx +164 -51
- package/lib/runtime/components/react/dist/AsyncSuspense.jsx +113 -44
- package/lib/runtime/optionProcessorReact.d.ts +18 -0
- package/lib/runtime/optionProcessorReact.js +24 -0
- package/lib/utils/trans-async-sub-rules.js +34 -0
- package/package.json +1 -1
- package/lib/runtime/components/react/AsyncContainer.tsx +0 -217
- package/lib/runtime/components/react/dist/AsyncContainer.jsx +0 -160
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const NullDependency = require('webpack/lib/dependencies/NullDependency')
|
|
3
|
+
const makeSerializable = require('webpack/lib/util/makeSerializable')
|
|
4
|
+
|
|
5
|
+
class RecordFileUrlDependency extends NullDependency {
|
|
6
|
+
constructor (range, url) {
|
|
7
|
+
super()
|
|
8
|
+
this.range = range
|
|
9
|
+
this.url = url
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get type () {
|
|
13
|
+
return 'mpx record file url'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
mpxAction (module, compilation, callback) {
|
|
17
|
+
return callback()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
updateHash (hash, context) {
|
|
21
|
+
hash.update('' + (+new Date()) + Math.random())
|
|
22
|
+
super.updateHash(hash, context)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
serialize (context) {
|
|
26
|
+
const { write } = context
|
|
27
|
+
write(this.url)
|
|
28
|
+
super.serialize(context)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
deserialize (context) {
|
|
32
|
+
const { read } = context
|
|
33
|
+
this.url = read()
|
|
34
|
+
super.deserialize(context)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
RecordFileUrlDependency.Template = class RecordFileUrlDependencyTemplate {
|
|
39
|
+
apply (dependency, source, { module, chunkGraph, runtimeTemplate }) {
|
|
40
|
+
const { range } = dependency
|
|
41
|
+
const compliation = runtimeTemplate.compilation
|
|
42
|
+
const publicPath = compliation.outputOptions.publicPath
|
|
43
|
+
const chunks = chunkGraph.getModuleChunks(module)
|
|
44
|
+
const chunk = chunks[0]
|
|
45
|
+
const chunkPath = path.dirname(publicPath + chunk.name)
|
|
46
|
+
const imgPath = publicPath + dependency.url
|
|
47
|
+
let relativePath = path.relative(chunkPath, imgPath)
|
|
48
|
+
if (!relativePath.startsWith('.')) {
|
|
49
|
+
relativePath = './' + relativePath
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
source.replace(range[0], range[1] - 1, JSON.stringify(relativePath))
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
makeSerializable(RecordFileUrlDependency, '@mpxjs/webpack-plugin/lib/dependencies/RecordFileUrlDependency')
|
|
57
|
+
|
|
58
|
+
module.exports = RecordFileUrlDependency
|
package/lib/file-loader.js
CHANGED
|
@@ -35,8 +35,9 @@ module.exports = function loader (content, prevOptions) {
|
|
|
35
35
|
|
|
36
36
|
let publicPath = `__webpack_public_path__ + ${JSON.stringify(url)}`
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
if (isRN) {
|
|
39
|
+
publicPath = `__non_webpack_require__(__mpx_rn_resolve_url_path_(${JSON.stringify(url)}))`
|
|
40
|
+
}
|
|
40
41
|
|
|
41
42
|
if (options.publicPath) {
|
|
42
43
|
if (typeof options.publicPath === 'function') {
|
package/lib/index.js
CHANGED
|
@@ -44,9 +44,11 @@ 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')
|
|
47
48
|
const SplitChunksPlugin = require('webpack/lib/optimize/SplitChunksPlugin')
|
|
48
49
|
const fixRelative = require('./utils/fix-relative')
|
|
49
50
|
const parseRequest = require('./utils/parse-request')
|
|
51
|
+
const { transAsyncSubNameRules, transAsyncSubRules } = require('./utils/trans-async-sub-rules')
|
|
50
52
|
const { matchCondition } = require('./utils/match-condition')
|
|
51
53
|
const processDefs = require('./utils/process-defs')
|
|
52
54
|
const config = require('./config')
|
|
@@ -672,6 +674,9 @@ class MpxWebpackPlugin {
|
|
|
672
674
|
compilation.dependencyFactories.set(RecordRuntimeInfoDependency, new NullFactory())
|
|
673
675
|
compilation.dependencyTemplates.set(RecordRuntimeInfoDependency, new RecordRuntimeInfoDependency.Template())
|
|
674
676
|
|
|
677
|
+
compilation.dependencyFactories.set(RecordFileUrlDependency, new NullFactory())
|
|
678
|
+
compilation.dependencyTemplates.set(RecordFileUrlDependency, new RecordFileUrlDependency.Template())
|
|
679
|
+
|
|
675
680
|
compilation.dependencyTemplates.set(ImportDependency, new ImportDependencyTemplate())
|
|
676
681
|
})
|
|
677
682
|
|
|
@@ -774,6 +779,7 @@ class MpxWebpackPlugin {
|
|
|
774
779
|
})
|
|
775
780
|
},
|
|
776
781
|
asyncSubpackageRules: this.options.asyncSubpackageRules,
|
|
782
|
+
asyncSubpackageNameMap: this.options.asyncSubpackageNameMap,
|
|
777
783
|
optimizeRenderRules: this.options.optimizeRenderRules,
|
|
778
784
|
pathHash: (resourcePath) => {
|
|
779
785
|
if (this.options.pathHashMode === 'relative' && this.options.projectRoot) {
|
|
@@ -1222,7 +1228,7 @@ class MpxWebpackPlugin {
|
|
|
1222
1228
|
if (isWeb(mpx.mode) && !hasOwn(splitChunksOptions.cacheGroups, 'main')) {
|
|
1223
1229
|
splitChunksOptions.cacheGroups.main = {
|
|
1224
1230
|
chunks: 'initial',
|
|
1225
|
-
name: '
|
|
1231
|
+
name: 'lib/index', // web 输出 chunk 路径和 rn 输出分包格式拉齐
|
|
1226
1232
|
test: /[\\/]node_modules[\\/]/
|
|
1227
1233
|
}
|
|
1228
1234
|
needInit = true
|
|
@@ -1230,7 +1236,7 @@ class MpxWebpackPlugin {
|
|
|
1230
1236
|
if (!hasOwn(splitChunksOptions.cacheGroups, 'async')) {
|
|
1231
1237
|
splitChunksOptions.cacheGroups.async = {
|
|
1232
1238
|
chunks: 'async',
|
|
1233
|
-
name: 'async/index',
|
|
1239
|
+
name: 'async-common/index',
|
|
1234
1240
|
minChunks: 2
|
|
1235
1241
|
}
|
|
1236
1242
|
needInit = true
|
|
@@ -1333,15 +1339,6 @@ class MpxWebpackPlugin {
|
|
|
1333
1339
|
compilation.hooks.processAssets.tap({
|
|
1334
1340
|
name: 'MpxWebpackPlugin'
|
|
1335
1341
|
}, (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
|
-
}
|
|
1345
1342
|
try {
|
|
1346
1343
|
const dynamicAssets = {}
|
|
1347
1344
|
for (const packageName in mpx.runtimeInfo) {
|
|
@@ -1387,6 +1384,15 @@ class MpxWebpackPlugin {
|
|
|
1387
1384
|
}
|
|
1388
1385
|
})
|
|
1389
1386
|
|
|
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
|
+
|
|
1390
1396
|
parser.hooks.call.for('__mpx_dynamic_entry__').tap('MpxWebpackPlugin', (expr) => {
|
|
1391
1397
|
const args = expr.arguments.map((i) => i.value)
|
|
1392
1398
|
args.unshift(expr.range)
|
|
@@ -1402,21 +1408,14 @@ class MpxWebpackPlugin {
|
|
|
1402
1408
|
const range = expr.arguments[0].range
|
|
1403
1409
|
const context = parser.state.module.context
|
|
1404
1410
|
const { queryObj, resourcePath } = parseRequest(request)
|
|
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
|
-
}
|
|
1411
|
+
let tarRoot = transAsyncSubRules(resourcePath, mpx.asyncSubpackageRules, queryObj.root)
|
|
1414
1412
|
if (tarRoot) {
|
|
1415
1413
|
// 删除root query
|
|
1416
1414
|
if (queryObj.root) request = addQuery(request, {}, false, ['root'])
|
|
1417
1415
|
// wx、ali和web平台支持require.async,其余平台使用CommonJsAsyncDependency进行模拟抹平
|
|
1418
1416
|
if (mpx.supportRequireAsync) {
|
|
1419
1417
|
if (isWeb(mpx.mode) || isReact(mpx.mode)) {
|
|
1418
|
+
tarRoot = transAsyncSubNameRules(mpx.asyncSubpackageNameMap, tarRoot)
|
|
1420
1419
|
const depBlock = new AsyncDependenciesBlock(
|
|
1421
1420
|
{
|
|
1422
1421
|
name: tarRoot + '/index'
|
package/lib/react/processJSON.js
CHANGED
|
@@ -8,6 +8,7 @@ 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')
|
|
11
12
|
const createJSONHelper = require('../json-compiler/helper')
|
|
12
13
|
const getRulesRunner = require('../platform/index')
|
|
13
14
|
const { RESOLVE_IGNORED_ERR } = require('../utils/const')
|
|
@@ -285,6 +286,8 @@ module.exports = function (jsonContent, {
|
|
|
285
286
|
|
|
286
287
|
pagesMap[resourcePath] = outputPath
|
|
287
288
|
loaderContext._module && loaderContext._module.addPresentationalDependency(new RecordResourceMapDependency(resourcePath, 'page', outputPath))
|
|
289
|
+
// 通过asyncSubPackagesNameRules对tarRoot进行修改,仅修改tarRoot,不修改outputPath页面路径
|
|
290
|
+
tarRoot = transAsyncSubNameRules(mpx.asyncSubpackageNameMap, tarRoot)
|
|
288
291
|
localPagesMap[outputPath] = {
|
|
289
292
|
resource: addQuery(resource, { isPage: true }),
|
|
290
293
|
async: queryObj.async || tarRoot,
|
|
@@ -332,6 +335,7 @@ module.exports = function (jsonContent, {
|
|
|
332
335
|
const fillComponentsMap = (name, entry, tarRoot) => {
|
|
333
336
|
const { resource, outputPath } = entry
|
|
334
337
|
const { resourcePath, queryObj } = parseRequest(resource)
|
|
338
|
+
tarRoot = transAsyncSubNameRules(mpx.asyncSubpackageNameMap, tarRoot)
|
|
335
339
|
componentsMap[resourcePath] = outputPath
|
|
336
340
|
loaderContext._module && loaderContext._module.addPresentationalDependency(new RecordResourceMapDependency(resourcePath, 'component', outputPath))
|
|
337
341
|
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 } from ${stringifyRequest(loaderContext, optionProcessorPath)}
|
|
39
|
+
import { getComponent, getAsyncSuspense } from ${stringifyRequest(loaderContext, optionProcessorPath)}
|
|
40
40
|
\n`
|
|
41
41
|
const { pagesMap, firstPage } = buildPagesMap({
|
|
42
42
|
localPagesMap,
|
|
@@ -53,7 +53,7 @@ import { getComponent } from ${stringifyRequest(loaderContext, optionProcessorPa
|
|
|
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 } from ${stringifyRequest(loaderContext, optionProcessorPath)}\n`
|
|
56
|
+
output += `import { getComponent, getAsyncSuspense } from ${stringifyRequest(loaderContext, optionProcessorPath)}\n`
|
|
57
57
|
// 获取组件集合
|
|
58
58
|
const componentsMap = buildComponentsMap({
|
|
59
59
|
localComponentsMap,
|
|
@@ -4,7 +4,6 @@ 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')
|
|
8
7
|
const { isBuildInReactTag } = require('../utils/dom-tag-config')
|
|
9
8
|
|
|
10
9
|
function stringifyRequest (loaderContext, request) {
|
|
@@ -15,8 +14,6 @@ function getMpxComponentRequest (component) {
|
|
|
15
14
|
return JSON.stringify(addQuery(`@mpxjs/webpack-plugin/lib/runtime/components/react/dist/${component}`, { isComponent: true }))
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
const mpxAsyncSuspense = getMpxComponentRequest('AsyncSuspense')
|
|
19
|
-
|
|
20
17
|
function getAsyncChunkName (chunkName) {
|
|
21
18
|
if (chunkName && typeof chunkName !== 'boolean') {
|
|
22
19
|
return `/* webpackChunkName: "${chunkName}/index" */`
|
|
@@ -24,49 +21,33 @@ function getAsyncChunkName (chunkName) {
|
|
|
24
21
|
return ''
|
|
25
22
|
}
|
|
26
23
|
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
})`
|
|
24
|
+
function getAsyncSuspense (type, moduleId, componentRequest, chunkName, fallback, loading) {
|
|
25
|
+
fallback = fallback && `getComponent(require(${fallback}))`
|
|
26
|
+
loading = loading && `getComponent(require(${loading}))`
|
|
27
|
+
return `
|
|
28
|
+
getAsyncSuspense({
|
|
29
|
+
type: ${JSON.stringify(type)},
|
|
30
|
+
moduleId: ${JSON.stringify(moduleId)},
|
|
31
|
+
chunkName: ${JSON.stringify(chunkName)},
|
|
32
|
+
loading: ${loading},
|
|
33
|
+
fallback: ${fallback},
|
|
34
|
+
getChildren: () => import(${getAsyncChunkName(chunkName)}${componentRequest})
|
|
35
|
+
})
|
|
36
|
+
`
|
|
60
37
|
}
|
|
61
38
|
|
|
62
39
|
function buildPagesMap ({ localPagesMap, loaderContext, jsonConfig, rnConfig }) {
|
|
63
40
|
let firstPage = ''
|
|
64
41
|
const pagesMap = {}
|
|
42
|
+
const mpx = loaderContext.getMpx()
|
|
65
43
|
Object.keys(localPagesMap).forEach((pagePath) => {
|
|
66
44
|
const pageCfg = localPagesMap[pagePath]
|
|
67
45
|
const pageRequest = stringifyRequest(loaderContext, pageCfg.resource)
|
|
68
46
|
if (pageCfg.async) {
|
|
69
|
-
|
|
47
|
+
const moduleId = mpx.getModuleId(pageCfg.resource)
|
|
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)
|
|
70
51
|
} else {
|
|
71
52
|
// 为了保持小程序中app->page->component的js执行顺序,所有的page和component都改为require引入
|
|
72
53
|
pagesMap[pagePath] = `getComponent(require(${pageRequest}), {__mpxPageRoute: ${JSON.stringify(pagePath)}, displayName: "Page"})`
|
|
@@ -92,6 +73,7 @@ function buildComponentsMap ({ localComponentsMap, builtInComponentsMap, loaderC
|
|
|
92
73
|
const componentCfg = localComponentsMap[componentName]
|
|
93
74
|
const componentRequest = stringifyRequest(loaderContext, componentCfg.resource)
|
|
94
75
|
if (componentCfg.async) {
|
|
76
|
+
const moduleId = mpx.getModuleId(componentCfg.resource)
|
|
95
77
|
const placeholder = jsonConfig.componentPlaceholder && jsonConfig.componentPlaceholder[componentName]
|
|
96
78
|
if (placeholder) {
|
|
97
79
|
if (localComponentsMap[placeholder]) {
|
|
@@ -102,11 +84,7 @@ function buildComponentsMap ({ localComponentsMap, builtInComponentsMap, loaderC
|
|
|
102
84
|
new Error(`[json processor][${loaderContext.resource}]: componentPlaceholder ${placeholder} should not be a async component, please check!`)
|
|
103
85
|
)
|
|
104
86
|
}
|
|
105
|
-
componentsMap[componentName] =
|
|
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)
|
|
87
|
+
componentsMap[componentName] = getAsyncSuspense('component', moduleId, componentRequest, componentCfg.async, placeholderRequest)
|
|
110
88
|
} else {
|
|
111
89
|
const tag = `mpx-${placeholder}`
|
|
112
90
|
if (!isBuildInReactTag(tag)) {
|
|
@@ -114,13 +92,13 @@ function buildComponentsMap ({ localComponentsMap, builtInComponentsMap, loaderC
|
|
|
114
92
|
new Error(`[json processor][${loaderContext.resource}]: componentPlaceholder ${placeholder} is not built-in component, please check!`)
|
|
115
93
|
)
|
|
116
94
|
}
|
|
117
|
-
componentsMap[componentName] =
|
|
95
|
+
componentsMap[componentName] = getAsyncSuspense('component', moduleId, componentRequest, componentCfg.async, getMpxComponentRequest(tag))
|
|
118
96
|
}
|
|
119
97
|
} else {
|
|
120
98
|
loaderContext.emitError(
|
|
121
99
|
new Error(`[json processor][${loaderContext.resource}]: ${componentName} has no componentPlaceholder, please check!`)
|
|
122
100
|
)
|
|
123
|
-
componentsMap[componentName] =
|
|
101
|
+
componentsMap[componentName] = getAsyncSuspense('component', moduleId, componentRequest, componentCfg.async)
|
|
124
102
|
}
|
|
125
103
|
} else {
|
|
126
104
|
componentsMap[componentName] = `getComponent(require(${componentRequest}), {displayName: ${JSON.stringify(componentName)}})`
|
|
@@ -1,79 +1,192 @@
|
|
|
1
|
-
import { useState, ComponentType, useEffect, useCallback, useRef } from 'react'
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
1
|
+
import { useState, ComponentType, useEffect, useCallback, useRef, ReactNode, createElement } from 'react'
|
|
2
|
+
import { View, Image, StyleSheet, Text, TouchableOpacity } from 'react-native'
|
|
3
|
+
import FastImage from '@d11/react-native-fast-image'
|
|
4
4
|
|
|
5
5
|
const asyncChunkMap = new Map()
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
const styles = StyleSheet.create({
|
|
8
|
+
container: {
|
|
9
|
+
flex: 1,
|
|
10
|
+
padding: 20,
|
|
11
|
+
backgroundColor: '#fff'
|
|
12
|
+
},
|
|
13
|
+
loadingImage: {
|
|
14
|
+
width: 100,
|
|
15
|
+
height: 100,
|
|
16
|
+
marginTop: 220,
|
|
17
|
+
alignSelf: 'center'
|
|
18
|
+
},
|
|
19
|
+
buttonText: {
|
|
20
|
+
color: '#fff',
|
|
21
|
+
fontSize: 16,
|
|
22
|
+
fontWeight: '500',
|
|
23
|
+
textAlign: 'center'
|
|
24
|
+
},
|
|
25
|
+
errorImage: {
|
|
26
|
+
marginTop: 80,
|
|
27
|
+
width: 220,
|
|
28
|
+
aspectRatio: 1,
|
|
29
|
+
alignSelf: 'center'
|
|
30
|
+
},
|
|
31
|
+
errorText: {
|
|
32
|
+
fontSize: 16,
|
|
33
|
+
textAlign: 'center',
|
|
34
|
+
color: '#333',
|
|
35
|
+
marginBottom: 20
|
|
36
|
+
},
|
|
37
|
+
retryButton: {
|
|
38
|
+
position: 'absolute',
|
|
39
|
+
bottom: 54,
|
|
40
|
+
left: 20,
|
|
41
|
+
right: 20,
|
|
42
|
+
backgroundColor: '#fff',
|
|
43
|
+
paddingVertical: 15,
|
|
44
|
+
borderRadius: 30,
|
|
45
|
+
marginTop: 40,
|
|
46
|
+
borderWidth: 1,
|
|
47
|
+
borderColor: '#FF5F00'
|
|
48
|
+
},
|
|
49
|
+
retryButtonText: {
|
|
50
|
+
color: '#FF5F00',
|
|
51
|
+
fontSize: 16,
|
|
52
|
+
fontWeight: '500',
|
|
53
|
+
textAlign: 'center'
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
interface LayoutViewProps {
|
|
58
|
+
children: ReactNode
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface AsyncModule {
|
|
62
|
+
__esModule: boolean
|
|
63
|
+
default: ReactNode
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface DefaultFallbackProps {
|
|
67
|
+
onReload: () => void
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const DefaultFallback = ({ onReload }: DefaultFallbackProps) => {
|
|
71
|
+
return (
|
|
72
|
+
<View style={styles.container}>
|
|
73
|
+
<Image
|
|
74
|
+
source={{
|
|
75
|
+
uri: 'https://dpubstatic.udache.com/static/dpubimg/Vak5mZvezPpKV5ZJI6P9b_drn-fallbak.png'
|
|
76
|
+
}}
|
|
77
|
+
style={styles.errorImage}
|
|
78
|
+
resizeMode="contain"
|
|
79
|
+
/>
|
|
80
|
+
<Text style={styles.errorText}>网络出了点问题,请查看网络环境</Text>
|
|
81
|
+
<TouchableOpacity
|
|
82
|
+
style={styles.retryButton}
|
|
83
|
+
onPress={onReload}
|
|
84
|
+
activeOpacity={0.7}
|
|
85
|
+
>
|
|
86
|
+
<Text style={styles.retryButtonText}>点击重试</Text>
|
|
87
|
+
</TouchableOpacity>
|
|
88
|
+
</View>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const LayoutView = (props: LayoutViewProps) => {
|
|
93
|
+
return (
|
|
94
|
+
<View style={{ flex: 1 }} collapsable={false}>{props.children}</View>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const DefaultLoading = () => {
|
|
99
|
+
return (
|
|
100
|
+
<View style={styles.container}>
|
|
101
|
+
<FastImage
|
|
102
|
+
style={styles.loadingImage}
|
|
103
|
+
source={{
|
|
104
|
+
uri: 'https://dpubstatic.udache.com/static/dpubimg/439jiCVOtNOnEv9F2LaDs_loading.gif'
|
|
105
|
+
}}
|
|
106
|
+
resizeMode={FastImage.resizeMode.contain}
|
|
107
|
+
></FastImage>
|
|
108
|
+
</View>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface AsyncSuspenseProps {
|
|
8
113
|
type: 'component' | 'page'
|
|
9
114
|
chunkName: string
|
|
10
|
-
|
|
11
|
-
|
|
115
|
+
moduleId: string
|
|
116
|
+
innerProps: any,
|
|
12
117
|
loading: ComponentType<unknown>
|
|
13
118
|
fallback: ComponentType<unknown>
|
|
14
|
-
getChildren: () => Promise<
|
|
119
|
+
getChildren: () => Promise<AsyncModule>
|
|
15
120
|
}
|
|
16
121
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
122
|
+
type ComponentStauts = 'pending' | 'error' | 'loaded'
|
|
123
|
+
|
|
124
|
+
const AsyncSuspense: React.FC<AsyncSuspenseProps> = ({
|
|
125
|
+
type,
|
|
126
|
+
innerProps,
|
|
127
|
+
chunkName,
|
|
128
|
+
moduleId,
|
|
129
|
+
loading,
|
|
130
|
+
fallback,
|
|
131
|
+
getChildren
|
|
132
|
+
}) => {
|
|
133
|
+
const [status, setStatus] = useState<ComponentStauts>('pending')
|
|
134
|
+
const chunkLoaded = asyncChunkMap.has(moduleId)
|
|
135
|
+
const loadChunkPromise = useRef<null | Promise<AsyncModule>>(null)
|
|
22
136
|
|
|
23
137
|
const reloadPage = useCallback(() => {
|
|
24
|
-
setKey((preV) => preV + 1)
|
|
25
|
-
console.log('[mpxAsyncSuspense]: reload page')
|
|
26
138
|
setStatus('pending')
|
|
27
139
|
}, [])
|
|
28
140
|
|
|
29
141
|
useEffect(() => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
142
|
+
let cancelled = false
|
|
143
|
+
if (!chunkLoaded && status === 'pending') {
|
|
144
|
+
if (loadChunkPromise.current) {
|
|
145
|
+
loadChunkPromise
|
|
146
|
+
.current.then((m: AsyncModule) => {
|
|
147
|
+
if (cancelled) return
|
|
148
|
+
asyncChunkMap.set(moduleId, m.__esModule ? m.default : m)
|
|
149
|
+
setStatus('loaded')
|
|
150
|
+
})
|
|
151
|
+
.catch((e) => {
|
|
152
|
+
if (cancelled) return
|
|
153
|
+
if (type === 'component') {
|
|
154
|
+
global.onLazyLoadError({
|
|
155
|
+
type: 'subpackage',
|
|
156
|
+
subpackage: [chunkName],
|
|
157
|
+
errMsg: `loadSubpackage: ${e.type}`
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
loadChunkPromise.current = null
|
|
161
|
+
setStatus('error')
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return () => {
|
|
167
|
+
cancelled = true
|
|
52
168
|
}
|
|
53
|
-
})
|
|
169
|
+
}, [status])
|
|
54
170
|
|
|
55
|
-
if (
|
|
56
|
-
const Comp = asyncChunkMap.get(
|
|
57
|
-
return
|
|
171
|
+
if (chunkLoaded) {
|
|
172
|
+
const Comp = asyncChunkMap.get(moduleId)
|
|
173
|
+
return createElement(Comp, innerProps)
|
|
58
174
|
} else if (status === 'error') {
|
|
59
|
-
console.log('the status is:', status)
|
|
60
175
|
if (type === 'page') {
|
|
61
|
-
const Fallback =
|
|
62
|
-
|
|
176
|
+
const Fallback =
|
|
177
|
+
(fallback as ComponentType<DefaultFallbackProps>) || DefaultFallback
|
|
178
|
+
return createElement(LayoutView, null, createElement(Fallback, { onReload: reloadPage }))
|
|
63
179
|
} else {
|
|
64
|
-
|
|
65
|
-
return <Fallback {...props}></Fallback>
|
|
180
|
+
return createElement(fallback, innerProps)
|
|
66
181
|
}
|
|
67
182
|
} else {
|
|
68
|
-
if (!
|
|
69
|
-
|
|
183
|
+
if (!loadChunkPromise.current) {
|
|
184
|
+
loadChunkPromise.current = getChildren()
|
|
70
185
|
}
|
|
71
186
|
if (type === 'page') {
|
|
72
|
-
|
|
73
|
-
return <PageWrapper><Fallback /></PageWrapper>
|
|
187
|
+
return createElement(LayoutView, null, createElement(loading || DefaultLoading))
|
|
74
188
|
} else {
|
|
75
|
-
|
|
76
|
-
return <Fallback {...props}></Fallback>
|
|
189
|
+
return createElement(fallback, innerProps)
|
|
77
190
|
}
|
|
78
191
|
}
|
|
79
192
|
}
|
|
@@ -1,67 +1,136 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
2
|
-
import {
|
|
1
|
+
import { useState, useEffect, useCallback, useRef, createElement } from 'react';
|
|
2
|
+
import { View, Image, StyleSheet, Text, TouchableOpacity } from 'react-native';
|
|
3
|
+
import FastImage from '@d11/react-native-fast-image';
|
|
3
4
|
const asyncChunkMap = new Map();
|
|
4
|
-
const
|
|
5
|
+
const styles = StyleSheet.create({
|
|
6
|
+
container: {
|
|
7
|
+
flex: 1,
|
|
8
|
+
padding: 20,
|
|
9
|
+
backgroundColor: '#fff'
|
|
10
|
+
},
|
|
11
|
+
loadingImage: {
|
|
12
|
+
width: 100,
|
|
13
|
+
height: 100,
|
|
14
|
+
marginTop: 220,
|
|
15
|
+
alignSelf: 'center'
|
|
16
|
+
},
|
|
17
|
+
buttonText: {
|
|
18
|
+
color: '#fff',
|
|
19
|
+
fontSize: 16,
|
|
20
|
+
fontWeight: '500',
|
|
21
|
+
textAlign: 'center'
|
|
22
|
+
},
|
|
23
|
+
errorImage: {
|
|
24
|
+
marginTop: 80,
|
|
25
|
+
width: 220,
|
|
26
|
+
aspectRatio: 1,
|
|
27
|
+
alignSelf: 'center'
|
|
28
|
+
},
|
|
29
|
+
errorText: {
|
|
30
|
+
fontSize: 16,
|
|
31
|
+
textAlign: 'center',
|
|
32
|
+
color: '#333',
|
|
33
|
+
marginBottom: 20
|
|
34
|
+
},
|
|
35
|
+
retryButton: {
|
|
36
|
+
position: 'absolute',
|
|
37
|
+
bottom: 54,
|
|
38
|
+
left: 20,
|
|
39
|
+
right: 20,
|
|
40
|
+
backgroundColor: '#fff',
|
|
41
|
+
paddingVertical: 15,
|
|
42
|
+
borderRadius: 30,
|
|
43
|
+
marginTop: 40,
|
|
44
|
+
borderWidth: 1,
|
|
45
|
+
borderColor: '#FF5F00'
|
|
46
|
+
},
|
|
47
|
+
retryButtonText: {
|
|
48
|
+
color: '#FF5F00',
|
|
49
|
+
fontSize: 16,
|
|
50
|
+
fontWeight: '500',
|
|
51
|
+
textAlign: 'center'
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
const DefaultFallback = ({ onReload }) => {
|
|
55
|
+
return (<View style={styles.container}>
|
|
56
|
+
<Image source={{
|
|
57
|
+
uri: 'https://dpubstatic.udache.com/static/dpubimg/Vak5mZvezPpKV5ZJI6P9b_drn-fallbak.png'
|
|
58
|
+
}} style={styles.errorImage} resizeMode="contain"/>
|
|
59
|
+
<Text style={styles.errorText}>网络出了点问题,请查看网络环境</Text>
|
|
60
|
+
<TouchableOpacity style={styles.retryButton} onPress={onReload} activeOpacity={0.7}>
|
|
61
|
+
<Text style={styles.retryButtonText}>点击重试</Text>
|
|
62
|
+
</TouchableOpacity>
|
|
63
|
+
</View>);
|
|
64
|
+
};
|
|
65
|
+
const LayoutView = (props) => {
|
|
66
|
+
return (<View style={{ flex: 1 }} collapsable={false}>{props.children}</View>);
|
|
67
|
+
};
|
|
68
|
+
const DefaultLoading = () => {
|
|
69
|
+
return (<View style={styles.container}>
|
|
70
|
+
<FastImage style={styles.loadingImage} source={{
|
|
71
|
+
uri: 'https://dpubstatic.udache.com/static/dpubimg/439jiCVOtNOnEv9F2LaDs_loading.gif'
|
|
72
|
+
}} resizeMode={FastImage.resizeMode.contain}></FastImage>
|
|
73
|
+
</View>);
|
|
74
|
+
};
|
|
75
|
+
const AsyncSuspense = ({ type, innerProps, chunkName, moduleId, loading, fallback, getChildren }) => {
|
|
5
76
|
const [status, setStatus] = useState('pending');
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const chunkPromise = useRef(null);
|
|
77
|
+
const chunkLoaded = asyncChunkMap.has(moduleId);
|
|
78
|
+
const loadChunkPromise = useRef(null);
|
|
9
79
|
const reloadPage = useCallback(() => {
|
|
10
|
-
setKey((preV) => preV + 1);
|
|
11
|
-
console.log('[mpxAsyncSuspense]: reload page');
|
|
12
80
|
setStatus('pending');
|
|
13
81
|
}, []);
|
|
14
82
|
useEffect(() => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
83
|
+
let cancelled = false;
|
|
84
|
+
if (!chunkLoaded && status === 'pending') {
|
|
85
|
+
if (loadChunkPromise.current) {
|
|
86
|
+
loadChunkPromise
|
|
87
|
+
.current.then((m) => {
|
|
88
|
+
if (cancelled)
|
|
89
|
+
return;
|
|
90
|
+
asyncChunkMap.set(moduleId, m.__esModule ? m.default : m);
|
|
91
|
+
setStatus('loaded');
|
|
92
|
+
})
|
|
93
|
+
.catch((e) => {
|
|
94
|
+
if (cancelled)
|
|
95
|
+
return;
|
|
96
|
+
if (type === 'component') {
|
|
97
|
+
global.onLazyLoadError({
|
|
98
|
+
type: 'subpackage',
|
|
99
|
+
subpackage: [chunkName],
|
|
100
|
+
errMsg: `loadSubpackage: ${e.type}`
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
loadChunkPromise.current = null;
|
|
104
|
+
setStatus('error');
|
|
105
|
+
});
|
|
106
|
+
}
|
|
37
107
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
108
|
+
return () => {
|
|
109
|
+
cancelled = true;
|
|
110
|
+
};
|
|
111
|
+
}, [status]);
|
|
112
|
+
if (chunkLoaded) {
|
|
113
|
+
const Comp = asyncChunkMap.get(moduleId);
|
|
114
|
+
return createElement(Comp, innerProps);
|
|
42
115
|
}
|
|
43
116
|
else if (status === 'error') {
|
|
44
|
-
console.log('the status is:', status);
|
|
45
117
|
if (type === 'page') {
|
|
46
118
|
const Fallback = fallback || DefaultFallback;
|
|
47
|
-
return
|
|
119
|
+
return createElement(LayoutView, null, createElement(Fallback, { onReload: reloadPage }));
|
|
48
120
|
}
|
|
49
121
|
else {
|
|
50
|
-
|
|
51
|
-
return <Fallback {...props}></Fallback>;
|
|
122
|
+
return createElement(fallback, innerProps);
|
|
52
123
|
}
|
|
53
124
|
}
|
|
54
125
|
else {
|
|
55
|
-
if (!
|
|
56
|
-
|
|
126
|
+
if (!loadChunkPromise.current) {
|
|
127
|
+
loadChunkPromise.current = getChildren();
|
|
57
128
|
}
|
|
58
129
|
if (type === 'page') {
|
|
59
|
-
|
|
60
|
-
return <PageWrapper><Fallback /></PageWrapper>;
|
|
130
|
+
return createElement(LayoutView, null, createElement(loading || DefaultLoading));
|
|
61
131
|
}
|
|
62
132
|
else {
|
|
63
|
-
|
|
64
|
-
return <Fallback {...props}></Fallback>;
|
|
133
|
+
return createElement(fallback, innerProps);
|
|
65
134
|
}
|
|
66
135
|
}
|
|
67
136
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ReactNode, ComponentType } from 'react'
|
|
1
2
|
declare global {
|
|
2
3
|
namespace NodeJS {
|
|
3
4
|
interface Global {
|
|
@@ -7,3 +8,20 @@ declare global {
|
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export function getComponent (...args: any): object
|
|
11
|
+
|
|
12
|
+
interface AsyncModule {
|
|
13
|
+
__esModule: boolean
|
|
14
|
+
default: ReactNode
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface AsyncSuspenseProps {
|
|
18
|
+
type: 'component' | 'page'
|
|
19
|
+
chunkName: string
|
|
20
|
+
moduleId: string
|
|
21
|
+
innerProps: any
|
|
22
|
+
loading: ComponentType<unknown>
|
|
23
|
+
fallback: ComponentType<unknown>
|
|
24
|
+
getChildren: () => Promise<AsyncModule>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getAsyncSuspense(props: AsyncSuspenseProps): ReactNode
|
|
@@ -1,6 +1,30 @@
|
|
|
1
|
+
import AsyncSuspense from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/AsyncSuspense'
|
|
2
|
+
import { memo, forwardRef, createElement } from 'react'
|
|
3
|
+
import { extend } from './utils'
|
|
4
|
+
|
|
1
5
|
export function getComponent (component, extendOptions) {
|
|
2
6
|
component = component.__esModule ? component.default : component
|
|
3
7
|
// eslint-disable-next-line
|
|
4
8
|
if (extendOptions) Object.assign(component, extendOptions)
|
|
5
9
|
return component
|
|
6
10
|
}
|
|
11
|
+
|
|
12
|
+
export function getAsyncSuspense (commonProps) {
|
|
13
|
+
if (commonProps.type === 'component') {
|
|
14
|
+
return memo(forwardRef(function (props, ref) {
|
|
15
|
+
return createElement(AsyncSuspense,
|
|
16
|
+
extend(commonProps, {
|
|
17
|
+
innerProps: Object.assign({}, props, { ref })
|
|
18
|
+
})
|
|
19
|
+
)
|
|
20
|
+
}))
|
|
21
|
+
} else {
|
|
22
|
+
return function (props) {
|
|
23
|
+
return createElement(AsyncSuspense,
|
|
24
|
+
extend(commonProps, {
|
|
25
|
+
innerProps: props
|
|
26
|
+
})
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const { matchCondition } = require('./match-condition')
|
|
2
|
+
|
|
3
|
+
function transAsyncSubNameRules (asyncSubpackageNameRules, tarRoot) {
|
|
4
|
+
// 如果没有tarRoot,则无需进行tarRoot的修改,因此
|
|
5
|
+
if (tarRoot && Array.isArray(asyncSubpackageNameRules) && asyncSubpackageNameRules.length >= 1) {
|
|
6
|
+
for (const item of asyncSubpackageNameRules) {
|
|
7
|
+
if (item?.from) {
|
|
8
|
+
const fromPaths = Array.isArray(item.from) ? item.from : [item.from];
|
|
9
|
+
if (fromPaths.includes(tarRoot)) {
|
|
10
|
+
tarRoot = item.to
|
|
11
|
+
break
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return tarRoot
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function transAsyncSubRules (resourcePath, asyncSubpackageRules, tarRoot) {
|
|
20
|
+
if (!tarRoot && Array.isArray(asyncSubpackageRules) && asyncSubpackageRules.length >= 1) {
|
|
21
|
+
for (const item of asyncSubpackageRules) {
|
|
22
|
+
if (matchCondition(resourcePath, item)) {
|
|
23
|
+
tarRoot = item.root
|
|
24
|
+
break
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return tarRoot
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = {
|
|
32
|
+
transAsyncSubNameRules,
|
|
33
|
+
transAsyncSubRules
|
|
34
|
+
}
|
package/package.json
CHANGED
|
@@ -1,217 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,160 +0,0 @@
|
|
|
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
|
-
}
|