@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.
@@ -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
@@ -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
- // todo 未来添加分包处理后相对地址不一定是./开头的,需要考虑通过dependency的方式在sourceModule时通过最终的chunkName得到准确的相对路径
39
- if (isRN) publicPath = `__non_webpack_require__(${JSON.stringify(`_mpx_rn_img_relative_path_/${url}`)})`
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: 'bundle/index', // web 输出 chunk 路径和 rn 输出分包格式拉齐
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'
@@ -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 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
- })`
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
- pagesMap[pagePath] = getAsyncPage(pagePath, pageRequest, pageCfg.async, rnConfig.asyncChunk && rnConfig.asyncChunk.fallback, rnConfig.asyncChunk && rnConfig.asyncChunk.loading)
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] = 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)
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] = getAsyncComponent(componentName, componentRequest, componentCfg.async, getMpxComponentRequest(tag))
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] = getAsyncComponent(componentName, componentRequest, componentCfg.async)
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 { DefaultFallback, DefaultLoading, PageWrapper } from './AsyncContainer'
3
- import type { DefaultFallbackProps } from './AsyncContainer'
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
- interface props {
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
- request: string
11
- props: any,
115
+ moduleId: string
116
+ innerProps: any,
12
117
  loading: ComponentType<unknown>
13
118
  fallback: ComponentType<unknown>
14
- getChildren: () => Promise<unknown>
119
+ getChildren: () => Promise<AsyncModule>
15
120
  }
16
121
 
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)
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
- 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
- })
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 (loaded) {
56
- const Comp = asyncChunkMap.get(request)
57
- return <Comp {...props}></Comp>
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 = fallback as ComponentType<DefaultFallbackProps> || DefaultFallback
62
- return <><PageWrapper><Fallback onReload={reloadPage}></Fallback></PageWrapper></>
176
+ const Fallback =
177
+ (fallback as ComponentType<DefaultFallbackProps>) || DefaultFallback
178
+ return createElement(LayoutView, null, createElement(Fallback, { onReload: reloadPage }))
63
179
  } else {
64
- const Fallback = loading
65
- return <Fallback {...props}></Fallback>
180
+ return createElement(fallback, innerProps)
66
181
  }
67
182
  } else {
68
- if (!chunkPromise.current) {
69
- chunkPromise.current = getChildren()
183
+ if (!loadChunkPromise.current) {
184
+ loadChunkPromise.current = getChildren()
70
185
  }
71
186
  if (type === 'page') {
72
- const Fallback = loading || DefaultLoading
73
- return <PageWrapper><Fallback /></PageWrapper>
187
+ return createElement(LayoutView, null, createElement(loading || DefaultLoading))
74
188
  } else {
75
- const Fallback = loading
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 { DefaultFallback, DefaultLoading, PageWrapper } from './AsyncContainer';
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 AsyncSuspense = ({ type, props, chunkName, request, loading, fallback, getChildren }) => {
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 loaded = asyncChunkMap.has(request);
7
- const [, setKey] = useState(0);
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
- if (!loaded && status === 'pending') {
16
- // todo 清楚副作用?
17
- console.log('the current :', chunkPromise.current);
18
- chunkPromise.current
19
- .then((m) => {
20
- console.log('[mpxAsyncSuspense]: load sucess');
21
- asyncChunkMap.set(request, m.__esModule ? m.default : m);
22
- setStatus('loaded');
23
- })
24
- .catch((e) => {
25
- if (type === 'component') {
26
- console.log(11111, e);
27
- global.onLazyLoadError({
28
- type: 'subpackage',
29
- subpackage: [chunkName],
30
- errMsg: `loadSubpackage: ${e.type}`
31
- });
32
- }
33
- console.log('[mpxAsyncSuspense]: load eror', e);
34
- chunkPromise.current = null;
35
- setStatus('error');
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
- if (loaded) {
40
- const Comp = asyncChunkMap.get(request);
41
- return <Comp {...props}></Comp>;
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 <><PageWrapper><Fallback onReload={reloadPage}></Fallback></PageWrapper></>;
119
+ return createElement(LayoutView, null, createElement(Fallback, { onReload: reloadPage }));
48
120
  }
49
121
  else {
50
- const Fallback = loading;
51
- return <Fallback {...props}></Fallback>;
122
+ return createElement(fallback, innerProps);
52
123
  }
53
124
  }
54
125
  else {
55
- if (!chunkPromise.current) {
56
- chunkPromise.current = getChildren();
126
+ if (!loadChunkPromise.current) {
127
+ loadChunkPromise.current = getChildren();
57
128
  }
58
129
  if (type === 'page') {
59
- const Fallback = loading || DefaultLoading;
60
- return <PageWrapper><Fallback /></PageWrapper>;
130
+ return createElement(LayoutView, null, createElement(loading || DefaultLoading));
61
131
  }
62
132
  else {
63
- const Fallback = loading;
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpxjs/webpack-plugin",
3
- "version": "2.10.6-beta.4",
3
+ "version": "2.10.6-beta.6",
4
4
  "description": "mpx compile core",
5
5
  "keywords": [
6
6
  "mpx"
@@ -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
- }