@mpxjs/webpack-plugin 2.10.7-beta.9 → 2.10.8-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.
Files changed (46) hide show
  1. package/lib/dependencies/RequireExternalDependency.js +61 -0
  2. package/lib/file-loader.js +3 -2
  3. package/lib/index.js +60 -15
  4. package/lib/json-compiler/index.js +1 -0
  5. package/lib/parser.js +1 -1
  6. package/lib/platform/json/wx/index.js +43 -25
  7. package/lib/platform/template/wx/component-config/fix-component-name.js +2 -2
  8. package/lib/platform/template/wx/component-config/index.js +2 -0
  9. package/lib/platform/template/wx/component-config/movable-view.js +10 -1
  10. package/lib/platform/template/wx/component-config/page-container.js +19 -0
  11. package/lib/platform/template/wx/component-config/unsupported.js +1 -1
  12. package/lib/platform/template/wx/index.js +2 -1
  13. package/lib/react/LoadAsyncChunkModule.js +74 -0
  14. package/lib/react/index.js +3 -1
  15. package/lib/react/processJSON.js +74 -13
  16. package/lib/react/processScript.js +6 -6
  17. package/lib/react/script-helper.js +100 -41
  18. package/lib/runtime/components/react/context.ts +2 -12
  19. package/lib/runtime/components/react/dist/context.js +1 -1
  20. package/lib/runtime/components/react/dist/getInnerListeners.js +1 -1
  21. package/lib/runtime/components/react/dist/mpx-async-suspense.jsx +135 -0
  22. package/lib/runtime/components/react/dist/mpx-movable-area.jsx +9 -63
  23. package/lib/runtime/components/react/dist/mpx-movable-view.jsx +58 -301
  24. package/lib/runtime/components/react/dist/mpx-page-container.jsx +255 -0
  25. package/lib/runtime/components/react/dist/mpx-swiper.jsx +30 -55
  26. package/lib/runtime/components/react/dist/mpx-web-view.jsx +14 -28
  27. package/lib/runtime/components/react/dist/useAnimationHooks.js +2 -87
  28. package/lib/runtime/components/react/getInnerListeners.ts +1 -1
  29. package/lib/runtime/components/react/mpx-async-suspense.tsx +180 -0
  30. package/lib/runtime/components/react/mpx-movable-area.tsx +11 -98
  31. package/lib/runtime/components/react/mpx-movable-view.tsx +60 -350
  32. package/lib/runtime/components/react/mpx-page-container.tsx +394 -0
  33. package/lib/runtime/components/react/mpx-swiper.tsx +29 -55
  34. package/lib/runtime/components/react/mpx-web-view.tsx +13 -33
  35. package/lib/runtime/components/react/types/global.d.ts +15 -0
  36. package/lib/runtime/components/react/useAnimationHooks.ts +2 -85
  37. package/lib/runtime/optionProcessorReact.d.ts +18 -0
  38. package/lib/runtime/optionProcessorReact.js +30 -0
  39. package/lib/script-setup-compiler/index.js +27 -5
  40. package/lib/template-compiler/compiler.js +27 -6
  41. package/lib/utils/dom-tag-config.js +18 -4
  42. package/lib/utils/trans-async-sub-rules.js +19 -0
  43. package/lib/web/script-helper.js +1 -1
  44. package/package.json +3 -3
  45. package/lib/runtime/components/react/AsyncContainer.tsx +0 -189
  46. package/lib/runtime/components/react/dist/AsyncContainer.jsx +0 -141
@@ -42,6 +42,21 @@ declare let global: {
42
42
 
43
43
  declare module '@react-navigation/native' {
44
44
  export function useNavigation (): Record<string, any>
45
+ export function usePreventRemove(
46
+ enabled: boolean,
47
+ callback: (e: { data: { action: any } }) => void
48
+ ): void;
49
+ export interface PreventRemoveEvent {
50
+ data: {
51
+ action: NavigationAction;
52
+ route: {
53
+ key: string;
54
+ name: string;
55
+ params?: unknown;
56
+ };
57
+ };
58
+ preventDefault(): void;
59
+ }
45
60
  }
46
61
 
47
62
  declare module '*.png'
@@ -84,88 +84,6 @@ const InitialValue: ExtendedViewStyle = Object.assign({
84
84
  const TransformOrigin = 'transformOrigin'
85
85
  // transform
86
86
  const isTransform = (key: string) => Object.keys(TransformInitial).includes(key)
87
- // 多value解析
88
- const parseValues = (str: string, char = ' ') => {
89
- let stack = 0
90
- let temp = ''
91
- const result = []
92
- for (let i = 0; i < str.length; i++) {
93
- if (str[i] === '(') {
94
- stack++
95
- } else if (str[i] === ')') {
96
- stack--
97
- }
98
- // 非括号内 或者 非分隔字符且非空
99
- if (stack !== 0 || (str[i] !== char && str[i] !== ' ')) {
100
- temp += str[i]
101
- }
102
- if ((stack === 0 && str[i] === char) || i === str.length - 1) {
103
- result.push(temp)
104
- temp = ''
105
- }
106
- }
107
- return result
108
- }
109
- // parse string transform, eg: transform: 'rotateX(45deg) rotateZ(0.785398rad)'
110
- const parseTransform = (transformStr: string) => {
111
- const values = parseValues(transformStr)
112
- const transform: {[propName: string]: string|number|number[]}[] = []
113
- values.forEach(item => {
114
- const match = item.match(/([/\w]+)\((.+)\)/)
115
- if (match && match.length >= 3) {
116
- let key = match[1]
117
- const val = match[2]
118
- switch (key) {
119
- case 'translateX':
120
- case 'translateY':
121
- case 'scaleX':
122
- case 'scaleY':
123
- case 'rotateX':
124
- case 'rotateY':
125
- case 'rotateZ':
126
- case 'rotate':
127
- case 'skewX':
128
- case 'skewY':
129
- case 'perspective':
130
- // rotate 处理成 rotateZ
131
- key = key === 'rotate' ? 'rotateZ' : key
132
- // 单个值处理
133
- transform.push({ [key]: global.__formatValue(val) })
134
- break
135
- case 'matrix':
136
- transform.push({ [key]: parseValues(val, ',').map(val => +val) })
137
- break
138
- case 'translate':
139
- case 'scale':
140
- case 'skew':
141
- case 'translate3d': // x y 支持 z不支持
142
- case 'scale3d': // x y 支持 z不支持
143
- {
144
- // 2 个以上的值处理
145
- key = key.replace('3d', '')
146
- const vals = parseValues(val, ',').splice(0, 3)
147
- // scale(.5) === scaleX(.5) scaleY(.5)
148
- if (vals.length === 1 && key === 'scale') {
149
- vals.push(vals[0])
150
- }
151
- const xyz = ['X', 'Y', 'Z']
152
- transform.push(...vals.map((v, index) => {
153
- return { [`${key}${xyz[index] || ''}`]: global.__formatValue(v.trim()) }
154
- }))
155
- break
156
- }
157
- }
158
- }
159
- })
160
- return transform
161
- }
162
- // format style
163
- const formatStyle = (style: ExtendedViewStyle): ExtendedViewStyle => {
164
- if (!style.transform || Array.isArray(style.transform)) return style
165
- return Object.assign({}, style, {
166
- transform: parseTransform(style.transform)
167
- })
168
- }
169
87
 
170
88
  // transform 数组转对象
171
89
  function getTransformObj (transforms: { [propName: string]: string | number }[]) {
@@ -176,7 +94,7 @@ function getTransformObj (transforms: { [propName: string]: string | number }[])
176
94
  }
177
95
 
178
96
  export default function useAnimationHooks<T, P> (props: _ViewProps & { enableAnimation?: boolean, layoutRef: MutableRefObject<any>, transitionend?: (event: NativeSyntheticEvent<TouchEvent> | unknown) => void }) {
179
- const { style = {}, animation, enableAnimation, transitionend, layoutRef } = props
97
+ const { style: originalStyle = {}, animation, enableAnimation, transitionend, layoutRef } = props
180
98
  const enableStyleAnimation = enableAnimation || !!animation
181
99
  const enableAnimationRef = useRef(enableStyleAnimation)
182
100
  if (enableAnimationRef.current !== enableStyleAnimation) {
@@ -185,7 +103,6 @@ export default function useAnimationHooks<T, P> (props: _ViewProps & { enableAni
185
103
 
186
104
  if (!enableAnimationRef.current) return { enableStyleAnimation: false }
187
105
 
188
- const originalStyle = formatStyle(style)
189
106
  // id 标识
190
107
  const id = animation?.id || -1
191
108
  // 有动画样式的 style key
@@ -214,7 +131,7 @@ export default function useAnimationHooks<T, P> (props: _ViewProps & { enableAni
214
131
  useEffect(() => {
215
132
  // style 更新后同步更新 lastStyleRef & shareValMap
216
133
  updateStyleVal()
217
- }, [style])
134
+ }, [originalStyle])
218
135
  // ** 获取动画样式prop & 驱动动画
219
136
  // eslint-disable-next-line react-hooks/rules-of-hooks
220
137
  useEffect(() => {
@@ -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,36 @@
1
+ import AsyncSuspense from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/mpx-async-suspense'
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
+ }
31
+
32
+ export function getLazyPage (getComponent) {
33
+ return function (props) {
34
+ return createElement(getComponent(), props)
35
+ }
36
+ }
@@ -1,5 +1,6 @@
1
1
  const babylon = require('@babel/parser')
2
2
  const MagicString = require('magic-string')
3
+ const { SourceMapConsumer, SourceMapGenerator } = require('source-map')
3
4
  const traverse = require('@babel/traverse').default
4
5
  const t = require('@babel/types')
5
6
  const formatCodeFrame = require('@babel/code-frame')
@@ -625,7 +626,12 @@ function compileScriptSetup (
625
626
  _s.appendRight(endOffset, '})')
626
627
 
627
628
  return {
628
- content: _s.toString()
629
+ content: _s.toString(),
630
+ map: _s.generateMap({
631
+ source: filePath,
632
+ hires: true,
633
+ includeContent: true
634
+ })
629
635
  }
630
636
  }
631
637
 
@@ -1165,14 +1171,30 @@ function getCtor (ctorType) {
1165
1171
  return ctor
1166
1172
  }
1167
1173
 
1168
- module.exports = function (content) {
1174
+ module.exports = async function (content, sourceMap) {
1169
1175
  const { queryObj } = parseRequest(this.resource)
1170
1176
  const { ctorType, lang } = queryObj
1171
1177
  const filePath = this.resourcePath
1172
- const { content: callbackContent } = compileScriptSetup({
1178
+ const callback = this.async()
1179
+ let finalSourceMap = null
1180
+ const {
1181
+ content: callbackContent,
1182
+ map
1183
+ } = compileScriptSetup({
1173
1184
  content,
1174
1185
  lang
1175
1186
  }, ctorType, filePath)
1176
-
1177
- this.callback(null, callbackContent)
1187
+ finalSourceMap = map
1188
+ if (sourceMap) {
1189
+ const compiledMapConsumer = await new SourceMapConsumer(map)
1190
+ const compiledMapGenerator = SourceMapGenerator.fromSourceMap(compiledMapConsumer)
1191
+
1192
+ const originalConsumer = await new SourceMapConsumer(sourceMap)
1193
+ compiledMapGenerator.applySourceMap(
1194
+ originalConsumer,
1195
+ filePath // 需要确保与原始映射的source路径一致
1196
+ )
1197
+ finalSourceMap = compiledMapGenerator.toJSON()
1198
+ }
1199
+ callback(null, callbackContent, finalSourceMap)
1178
1200
  }
@@ -38,7 +38,7 @@ const endTag = new RegExp(('^<\\/' + qnameCapture + '[^>]*>'))
38
38
  const doctype = /^<!DOCTYPE [^>]+>/i
39
39
  const comment = /^<!--/
40
40
  const conditionalComment = /^<!\[/
41
- const specialClassReg = /^mpx-((cover-)?view|button|navigator|picker-view|input|textarea)$/
41
+ const specialClassReg = /^mpx-((cover-)?view|button|navigator|picker-view|input|textarea|page-container)$/
42
42
  let IS_REGEX_CAPTURING_BROKEN = false
43
43
  'x'.replace(/x(.)?/g, function (m, g) {
44
44
  IS_REGEX_CAPTURING_BROKEN = g === ''
@@ -180,7 +180,7 @@ const i18nModuleName = '_i'
180
180
  const stringifyWxsPath = '~' + normalize.lib('runtime/stringify.wxs')
181
181
  const stringifyModuleName = '_s'
182
182
  const optionalChainWxsPath = '~' + normalize.lib('runtime/oc.wxs')
183
- const optionalChainWxsName = '_o'
183
+ const optionalChainWxsName = '_oc' // 改成_oc解决web下_o重名问题
184
184
 
185
185
  const tagRES = /(\{\{(?:.|\n|\r)+?\}\})(?!})/
186
186
  const tagRE = /\{\{((?:.|\n|\r)+?)\}\}(?!})/
@@ -581,7 +581,8 @@ function parseComponent (content, options) {
581
581
  let text = content.slice(currentBlock.start, currentBlock.end)
582
582
  // pad content so that linters and pre-processors can output correct
583
583
  // line numbers in errors and warnings
584
- if (options.pad) {
584
+ // stylus编译遇到大量空行时会出现栈溢出,故针对stylus不走pad
585
+ if (options.pad && !(currentBlock.tag === 'style' && currentBlock.lang === 'stylus')) {
585
586
  text = padContent(currentBlock, options.pad) + text
586
587
  }
587
588
  currentBlock.content = text
@@ -1110,8 +1111,28 @@ function processStyleReact (el, options) {
1110
1111
  }])
1111
1112
  }
1112
1113
 
1113
- if (specialClassReg.test(el.tag)) {
1114
- const staticClassNames = ['hover', 'indicator', 'mask', 'placeholder']
1114
+ // 原生组件支持 xx-class 与 xx-style(xx-class 将会被合并到 xx-style 中)
1115
+ const match = el.tag.match(specialClassReg)
1116
+ if (match) {
1117
+ let staticClassNames
1118
+ switch (el.tag) {
1119
+ case 'mpx-view':
1120
+ case 'mpx-cover-view':
1121
+ case 'mpx-button':
1122
+ case 'mpx-navigator':
1123
+ staticClassNames = ['hover']
1124
+ break
1125
+ case 'mpx-page-container':
1126
+ staticClassNames = ['custom', 'overlay']
1127
+ break
1128
+ case 'mpx-input':
1129
+ case 'mpx-textarea':
1130
+ staticClassNames = ['placeholder']
1131
+ break
1132
+ case 'mpx-picker-view':
1133
+ staticClassNames = ['mask', 'indicator']
1134
+ break
1135
+ }
1115
1136
  staticClassNames.forEach((className) => {
1116
1137
  let staticClass = el.attrsMap[className + '-class'] || ''
1117
1138
  let staticStyle = getAndRemoveAttr(el, className + '-style').val || ''
@@ -2002,7 +2023,7 @@ function postProcessFor (el) {
2002
2023
  function postProcessForReact (el) {
2003
2024
  if (el.for) {
2004
2025
  if (el.for.key) {
2005
- addExp(el, `this.__getWxKey(${el.for.item || 'item'}, ${stringify(el.for.key)}, ${el.for.index || 'index'})`, false, 'key')
2026
+ addExp(el, `this.__getWxKey(${el.for.item || 'item'}, ${stringify(el.for.key === '_' ? 'index' : el.for.key)}, ${el.for.index || 'index'})`, false, 'key')
2006
2027
  addAttrs(el, [{
2007
2028
  name: 'key',
2008
2029
  value: el.for.key
@@ -72,13 +72,26 @@ const isNativeMiniTag = makeMap(
72
72
  * 是否为mpx内置组件
73
73
  * collected from packages/webpack-plugin/lib/runtime/components/web/
74
74
  */
75
- const isBuildInTag = makeMap(
75
+ const isBuildInWebTag = makeMap(
76
76
  'mpx-image,mpx-picker-view,mpx-slider,mpx-textarea,mpx-input,mpx-picker,' +
77
77
  'mpx-swiper-item,mpx-video,mpx-button,mpx-keep-alive,mpx-progress,' +
78
78
  'mpx-swiper,mpx-view,mpx-checkbox-group,mpx-movable-area,mpx-radio-group,' +
79
79
  'mpx-switch,mpx-web-view,mpx-checkbox,mpx-movable-view,mpx-radio,' +
80
80
  'mpx-tab-bar-container,mpx-form,mpx-navigator,mpx-rich-text,mpx-tab-bar,' +
81
- 'mpx-icon,mpx-picker-view-column,mpx-scroll-view,mpx-text'
81
+ 'mpx-icon,mpx-picker-view-column,mpx-scroll-view,mpx-text,mpx-page-container'
82
+ )
83
+
84
+ /**
85
+ * 是否为mpx2rn内置组件
86
+ */
87
+ const isBuildInReactTag = makeMap(
88
+ 'mpx-web-view,mpx-view,mpx-video,mpx-textarea,mpx-text,mpx-switch,' +
89
+ 'mpx-swiper,mpx-swiper-item,mpx-simple-view,mpx-simple-text,mpx-scroll-view,' +
90
+ 'mpx-root-portal,mpx-radio,mpx-radio-group,mpx-navigator,mpx-movable-view,' +
91
+ 'mpx-movable-area,mpx-label,mpx-keyboard-avoiding-view,mpx-input,mpx-inline-text,' +
92
+ 'mpx-image,mpx-form,mpx-checkbox,mpx-checkbox-group,mpx-button,' +
93
+ 'mpx-rich-text,mpx-portal,mpx-popup,mpx-picker-view-column,mpx-picker-view,mpx-picker,' +
94
+ 'mpx-icon,mpx-canvas'
82
95
  )
83
96
 
84
97
  const isSpace = makeMap('ensp,emsp,nbsp')
@@ -105,11 +118,12 @@ module.exports = {
105
118
  isVoidTag,
106
119
  isNonPhrasingTag,
107
120
  isRichTextTag,
108
- isBuildInTag,
121
+ isBuildInWebTag,
109
122
  isUnaryTag,
110
123
  isSpace,
111
124
  isContWidth,
112
125
  isContHeight,
113
126
  isNativeMiniTag,
114
- isContConRow
127
+ isContConRow,
128
+ isBuildInReactTag
115
129
  }
@@ -0,0 +1,19 @@
1
+ function transSubpackage (asyncSubpackageNameRules, tarRoot) {
2
+ // 如果没有tarRoot,则无需进行tarRoot的修改,因此
3
+ if (tarRoot && Array.isArray(asyncSubpackageNameRules) && asyncSubpackageNameRules.length >= 1) {
4
+ for (const item of asyncSubpackageNameRules) {
5
+ if (item?.from) {
6
+ const fromPaths = Array.isArray(item.from) ? item.from : [item.from]
7
+ if (fromPaths.includes(tarRoot)) {
8
+ tarRoot = item.to
9
+ break
10
+ }
11
+ }
12
+ }
13
+ }
14
+ return tarRoot
15
+ }
16
+
17
+ module.exports = {
18
+ transSubpackage
19
+ }
@@ -13,7 +13,7 @@ function stringifyRequest (loaderContext, request) {
13
13
 
14
14
  function getAsyncChunkName (chunkName) {
15
15
  if (chunkName && typeof chunkName !== 'boolean') {
16
- return `/* webpackChunkName: "${chunkName}" */`
16
+ return `/* webpackChunkName: "${chunkName}/index" */`
17
17
  }
18
18
  return ''
19
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpxjs/webpack-plugin",
3
- "version": "2.10.7-beta.9",
3
+ "version": "2.10.8-beta.1",
4
4
  "description": "mpx compile core",
5
5
  "keywords": [
6
6
  "mpx"
@@ -28,7 +28,7 @@
28
28
  "@better-scroll/wheel": "^2.5.1",
29
29
  "@better-scroll/zoom": "^2.5.1",
30
30
  "@mpxjs/template-engine": "^2.8.7",
31
- "@mpxjs/utils": "^2.10.6 | ^2.10.6-beta.1",
31
+ "@mpxjs/utils": "^2.10.8",
32
32
  "acorn": "^8.11.3",
33
33
  "acorn-walk": "^7.2.0",
34
34
  "async": "^2.6.0",
@@ -83,7 +83,7 @@
83
83
  },
84
84
  "devDependencies": {
85
85
  "@d11/react-native-fast-image": "^8.6.12",
86
- "@mpxjs/api-proxy": "^2.10.7 | ^2.10.7-beta.1",
86
+ "@mpxjs/api-proxy": "^2.10.8",
87
87
  "@types/babel-traverse": "^6.25.4",
88
88
  "@types/babel-types": "^7.0.4",
89
89
  "@types/react": "^18.2.79",
@@ -1,189 +0,0 @@
1
- import { ComponentType, ReactNode, Component, Fragment, Suspense } from 'react'
2
- import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native'
3
- import FastImage from '@d11/react-native-fast-image'
4
-
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
-
55
- type AsyncType = 'page' | 'component'
56
-
57
- interface PropsType<T extends AsyncType> {
58
- type: T
59
- props: object
60
- loading: ComponentType<unknown>
61
- fallback: ComponentType<unknown>
62
- children: (props: unknown) => ReactNode
63
- }
64
-
65
- interface StateType {
66
- hasError: boolean,
67
- key: number
68
- }
69
-
70
- interface ComponentError extends Error {
71
- request?: string
72
- type: 'timeout' | 'fail'
73
- }
74
-
75
- const DefaultLoading = () => {
76
- return (
77
- <View style={styles.container}>
78
- <FastImage
79
- style={styles.loadingImage}
80
- source={{ uri: 'https://dpubstatic.udache.com/static/dpubimg/439jiCVOtNOnEv9F2LaDs_loading.gif' }}
81
- resizeMode={FastImage.resizeMode.contain}></FastImage>
82
- </View>
83
- )
84
- }
85
-
86
- interface DefaultFallbackProps {
87
- onReload: () => void
88
- }
89
-
90
- const DefaultFallback = ({ onReload }: DefaultFallbackProps) => {
91
- return (
92
- <View style={styles.container}>
93
- <Image
94
- source={{ uri: 'https://dpubstatic.udache.com/static/dpubimg/Vak5mZvezPpKV5ZJI6P9b_drn-fallbak.png' }}
95
- style={styles.errorImage}
96
- resizeMode="contain"
97
- />
98
- <Text style={styles.errorText}>网络出了点问题,请查看网络环境</Text>
99
- <TouchableOpacity
100
- style={styles.retryButton}
101
- onPress={onReload}
102
- activeOpacity={0.7}
103
- >
104
- <Text style={styles.retryButtonText}>点击重试</Text>
105
- </TouchableOpacity>
106
- </View>
107
- )
108
- }
109
-
110
- export default class AsyncContainer extends Component<PropsType<AsyncType>, StateType> {
111
- private suspenseFallback: ReactNode
112
- private errorFallback: ReactNode
113
-
114
- constructor (props: PropsType<AsyncType>) {
115
- super(props)
116
- this.state = {
117
- hasError: false,
118
- key: 0
119
- }
120
- this.suspenseFallback = this.getSuspenseFallback()
121
- this.errorFallback = this.getErrorFallback()
122
- }
123
-
124
- // render 阶段收集到的错误
125
- static getDerivedStateFromError (error: ComponentError): StateType | undefined {
126
- if (error.name === 'ChunkLoadError') {
127
- return {
128
- hasError: true,
129
- key: 0
130
- }
131
- } else {
132
- // 被外层捕获
133
- throw error
134
- }
135
- }
136
-
137
- componentDidCatch (error: ComponentError): void {
138
- if (error.name === 'ChunkLoadError' && this.props.type === 'component') {
139
- const request = error.request || ''
140
- const subpackage = request.split('/').filter((i: string) => !!i)[0]
141
- global.onLazyLoadError({
142
- type: 'subpackage',
143
- subpackage: [subpackage],
144
- errMsg: `loadSubpackage: ${error.type}`
145
- })
146
- }
147
- }
148
-
149
- reloadPage () {
150
- this.setState((prevState) => {
151
- return {
152
- hasError: false,
153
- key: prevState.key + 1
154
- }
155
- })
156
- }
157
-
158
- getSuspenseFallback () {
159
- if (this.props.type === 'page') {
160
- const Fallback = this.props.loading || DefaultLoading
161
- return <Fallback />
162
- } else {
163
- const Fallback = this.props.loading
164
- return <Fallback {...this.props.props}></Fallback>
165
- }
166
- }
167
-
168
- getErrorFallback () {
169
- if (this.props.type === 'page') {
170
- const Fallback = this.props.fallback as ComponentType<DefaultFallbackProps> || DefaultFallback
171
- return <Fallback onReload={this.reloadPage.bind(this)}></Fallback>
172
- } else {
173
- const Fallback = this.props.loading
174
- return <Fallback {...this.props.props}></Fallback>
175
- }
176
- }
177
-
178
- render () {
179
- if (this.state.hasError) {
180
- return this.errorFallback
181
- } else {
182
- return (
183
- <Suspense fallback={this.suspenseFallback} key={this.state.key}>
184
- {this.props.children(this.props.props)}
185
- </Suspense>
186
- )
187
- }
188
- }
189
- }