@mpxjs/webpack-plugin 2.9.17 → 2.9.19-react.0

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 (66) hide show
  1. package/lib/config.js +59 -97
  2. package/lib/dependencies/ResolveDependency.js +2 -2
  3. package/lib/helpers.js +5 -1
  4. package/lib/index.js +27 -19
  5. package/lib/loader.js +56 -118
  6. package/lib/native-loader.js +43 -20
  7. package/lib/platform/index.js +3 -0
  8. package/lib/platform/style/wx/index.js +413 -0
  9. package/lib/platform/template/wx/component-config/button.js +36 -0
  10. package/lib/platform/template/wx/component-config/cover-view.js +1 -1
  11. package/lib/platform/template/wx/component-config/image.js +15 -0
  12. package/lib/platform/template/wx/component-config/input.js +36 -0
  13. package/lib/platform/template/wx/component-config/scroll-view.js +27 -1
  14. package/lib/platform/template/wx/component-config/swiper-item.js +13 -1
  15. package/lib/platform/template/wx/component-config/swiper.js +25 -1
  16. package/lib/platform/template/wx/component-config/text.js +17 -1
  17. package/lib/platform/template/wx/component-config/textarea.js +39 -0
  18. package/lib/platform/template/wx/component-config/unsupported.js +18 -0
  19. package/lib/platform/template/wx/component-config/view.js +15 -1
  20. package/lib/platform/template/wx/index.js +89 -7
  21. package/lib/react/index.js +92 -0
  22. package/lib/react/processJSON.js +362 -0
  23. package/lib/react/processScript.js +40 -0
  24. package/lib/react/processStyles.js +63 -0
  25. package/lib/react/processTemplate.js +151 -0
  26. package/lib/react/script-helper.js +79 -0
  27. package/lib/react/style-helper.js +91 -0
  28. package/lib/runtime/components/react/event.config.ts +32 -0
  29. package/lib/runtime/components/react/getInnerListeners.ts +289 -0
  30. package/lib/runtime/components/react/getInnerListeners.type.ts +68 -0
  31. package/lib/runtime/components/react/mpx-button.tsx +402 -0
  32. package/lib/runtime/components/react/mpx-image/index.tsx +351 -0
  33. package/lib/runtime/components/react/mpx-image/svg.tsx +21 -0
  34. package/lib/runtime/components/react/mpx-input.tsx +389 -0
  35. package/lib/runtime/components/react/mpx-scroll-view.tsx +412 -0
  36. package/lib/runtime/components/react/mpx-swiper/carouse.tsx +407 -0
  37. package/lib/runtime/components/react/mpx-swiper/index.tsx +68 -0
  38. package/lib/runtime/components/react/mpx-swiper/type.ts +69 -0
  39. package/lib/runtime/components/react/mpx-swiper-item.tsx +42 -0
  40. package/lib/runtime/components/react/mpx-text.tsx +106 -0
  41. package/lib/runtime/components/react/mpx-textarea.tsx +46 -0
  42. package/lib/runtime/components/react/mpx-view.tsx +397 -0
  43. package/lib/runtime/components/react/utils.ts +92 -0
  44. package/lib/runtime/components/web/event.js +100 -0
  45. package/lib/runtime/components/web/getInnerListeners.js +0 -78
  46. package/lib/runtime/components/web/mpx-button.vue +1 -1
  47. package/lib/runtime/components/web/mpx-navigator.vue +1 -1
  48. package/lib/runtime/components/web/mpx-scroll-view.vue +113 -37
  49. package/lib/runtime/oc.wxs +16 -0
  50. package/lib/runtime/optionProcessor.js +1 -1
  51. package/lib/runtime/stringify.wxs +3 -7
  52. package/lib/runtime/useNodesRef.ts +39 -0
  53. package/lib/style-compiler/index.js +2 -1
  54. package/lib/template-compiler/compiler.js +544 -121
  55. package/lib/template-compiler/gen-node-react.js +95 -0
  56. package/lib/template-compiler/index.js +19 -31
  57. package/lib/utils/env.js +17 -0
  58. package/lib/utils/make-map.js +1 -1
  59. package/lib/utils/shallow-stringify.js +17 -0
  60. package/lib/utils/ts-loader-watch-run-loader-filter.js +4 -1
  61. package/lib/web/index.js +122 -0
  62. package/lib/web/processMainScript.js +6 -4
  63. package/lib/web/processScript.js +9 -5
  64. package/lib/web/processTemplate.js +14 -14
  65. package/lib/web/script-helper.js +11 -19
  66. package/package.json +7 -3
@@ -12,6 +12,8 @@ const transDynamicClassExpr = require('./trans-dynamic-class-expr')
12
12
  const dash2hump = require('../utils/hump-dash').dash2hump
13
13
  const makeMap = require('../utils/make-map')
14
14
  const { isNonPhrasingTag } = require('../utils/dom-tag-config')
15
+ const shallowStringify = require('../utils/shallow-stringify')
16
+ const { isReact } = require('../utils/env')
15
17
 
16
18
  const no = function () {
17
19
  return false
@@ -34,7 +36,7 @@ const endTag = new RegExp(('^<\\/' + qnameCapture + '[^>]*>'))
34
36
  const doctype = /^<!DOCTYPE [^>]+>/i
35
37
  const comment = /^<!--/
36
38
  const conditionalComment = /^<!\[/
37
-
39
+ const hoverClassReg = /^mpx-((cover-)?view|button|navigator)$/
38
40
  let IS_REGEX_CAPTURING_BROKEN = false
39
41
  'x'.replace(/x(.)?/g, function (m, g) {
40
42
  IS_REGEX_CAPTURING_BROKEN = g === ''
@@ -62,7 +64,7 @@ function makeAttrsMap (attrs) {
62
64
  return map
63
65
  }
64
66
 
65
- function createASTElement (tag, attrs, parent) {
67
+ function createASTElement (tag, attrs = [], parent = null) {
66
68
  return {
67
69
  type: 1,
68
70
  tag: tag,
@@ -88,24 +90,31 @@ function isForbiddenTag (el) {
88
90
  let warn$1
89
91
  let error$1
90
92
  let mode
93
+ let env
91
94
  let defs
92
95
  let i18n
93
96
  let srcMode
94
- let processingTemplate
97
+ let ctorType
98
+ let moduleId
95
99
  let isNative
100
+ let hasScoped
101
+ let hasVirtualHost
96
102
  let rulesRunner
97
103
  let currentEl
98
104
  let injectNodes = []
99
105
  let forScopes = []
100
106
  let forScopesMap = {}
101
- let hasI18n = false
102
- let i18nInjectableComputed = []
103
- let env
104
107
  let platformGetTagNamespace
105
108
  let filePath
106
109
  let refId
110
+ let hasI18n = false
111
+ let i18nInjectableComputed = []
112
+ let hasOptionalChaining = false
113
+ let processingTemplate = false
114
+ const rulesResultMap = new Map()
107
115
 
108
116
  function updateForScopesMap () {
117
+ forScopesMap = {}
109
118
  forScopes.forEach((scope) => {
110
119
  forScopesMap[scope.index] = 'index'
111
120
  forScopesMap[scope.item] = 'item'
@@ -125,7 +134,6 @@ function popForScopes () {
125
134
  return scope
126
135
  }
127
136
 
128
- const rulesResultMap = new Map()
129
137
  const deleteErrorInResultMap = (node) => {
130
138
  rulesResultMap.delete(node)
131
139
  Array.isArray(node.children) && node.children.forEach(item => deleteErrorInResultMap(item))
@@ -164,6 +172,8 @@ const i18nWxsRequest = '~' + i18nWxsLoaderPath + '!' + i18nWxsPath
164
172
  const i18nModuleName = '__i18n__'
165
173
  const stringifyWxsPath = '~' + normalize.lib('runtime/stringify.wxs')
166
174
  const stringifyModuleName = '__stringify__'
175
+ const optionsChainWxsPath = '~' + normalize.lib('runtime/oc.wxs')
176
+ const optionsChainWxsName = '__oc__'
167
177
 
168
178
  const tagRES = /(\{\{(?:.|\n|\r)+?\}\})(?!})/
169
179
  const tagRE = /\{\{((?:.|\n|\r)+?)\}\}(?!})/
@@ -592,10 +602,30 @@ function parseComponent (content, options) {
592
602
  }
593
603
 
594
604
  function parse (template, options) {
595
- rulesResultMap.clear()
605
+ // global var init
596
606
  warn$1 = options.warn || baseWarn
597
607
  error$1 = options.error || baseError
608
+ mode = options.mode || 'wx'
609
+ env = options.env
610
+ defs = options.defs || {}
611
+ srcMode = options.srcMode || mode
612
+ ctorType = options.ctorType
613
+ moduleId = options.moduleId
614
+ isNative = options.isNative
615
+ hasScoped = options.hasScoped
616
+ hasVirtualHost = options.hasVirtualHost
617
+ filePath = options.filePath
618
+ i18n = options.i18n
619
+ platformGetTagNamespace = options.getTagNamespace || no
620
+ refId = 0
621
+ injectNodes = []
622
+ forScopes = []
623
+ forScopesMap = {}
624
+ hasI18n = false
598
625
  i18nInjectableComputed = []
626
+ hasOptionalChaining = false
627
+ processingTemplate = false
628
+ rulesResultMap.clear()
599
629
 
600
630
  const _warn = content => {
601
631
  const currentElementRuleResult = rulesResultMap.get(currentEl) || rulesResultMap.set(currentEl, {
@@ -612,15 +642,6 @@ function parse (template, options) {
612
642
  currentElementRuleResult.errorArray.push(content)
613
643
  }
614
644
 
615
- mode = options.mode || 'wx'
616
- env = options.env
617
- defs = options.defs || {}
618
- srcMode = options.srcMode || mode
619
- isNative = options.isNative
620
- filePath = options.filePath
621
- i18n = options.i18n
622
- refId = 0
623
-
624
645
  rulesRunner = getRulesRunner({
625
646
  mode,
626
647
  srcMode,
@@ -633,13 +654,6 @@ function parse (template, options) {
633
654
  error: _error
634
655
  })
635
656
 
636
- injectNodes = []
637
- forScopes = []
638
- forScopesMap = {}
639
- hasI18n = false
640
-
641
- platformGetTagNamespace = options.getTagNamespace || no
642
-
643
657
  const stack = []
644
658
  let root
645
659
  const meta = {}
@@ -732,7 +746,7 @@ function parse (template, options) {
732
746
  parent: currentParent
733
747
  }
734
748
  children.push(el)
735
- processText(el)
749
+ processText(el, meta, options)
736
750
  }
737
751
  }
738
752
  },
@@ -761,6 +775,10 @@ function parse (template, options) {
761
775
  }
762
776
  }
763
777
 
778
+ if (hasOptionalChaining) {
779
+ injectWxs(meta, optionsChainWxsName, optionsChainWxsPath)
780
+ }
781
+
764
782
  injectNodes.forEach((node) => {
765
783
  addChild(root, node, true)
766
784
  })
@@ -777,7 +795,7 @@ function parse (template, options) {
777
795
  arr.push(item)
778
796
  }
779
797
  })
780
- arr.length && warn$1(`\n ${options.filePath} \n 组件 ${arr.join(' | ')} 注册了,但是未被对应的模板引用,建议删除!`)
798
+ arr.length && warn$1(`\n ${filePath} \n 组件 ${arr.join(' | ')} 注册了,但是未被对应的模板引用,建议删除!`)
781
799
  }
782
800
 
783
801
  return {
@@ -787,7 +805,7 @@ function parse (template, options) {
787
805
  }
788
806
 
789
807
  function getTempNode () {
790
- return createASTElement('temp-node', [])
808
+ return createASTElement('temp-node')
791
809
  }
792
810
 
793
811
  function addChild (parent, newChild, before) {
@@ -840,16 +858,23 @@ function modifyAttr (el, name, val) {
840
858
  }
841
859
  }
842
860
 
843
- function moveBaseDirective (target, from, isDelete = true) {
844
- target.for = from.for
845
- target.if = from.if
846
- target.elseif = from.elseif
847
- target.else = from.else
861
+ function postMoveBaseDirective (target, source, isDelete = true) {
862
+ target.for = source.for
863
+ target.if = source.if
864
+ target.elseif = source.elseif
865
+ target.else = source.else
866
+ if (isReact(mode)) {
867
+ postProcessForReact(target)
868
+ postProcessIfReact(target)
869
+ } else {
870
+ postProcessFor(target)
871
+ postProcessIf(target)
872
+ }
848
873
  if (isDelete) {
849
- delete from.for
850
- delete from.if
851
- delete from.elseif
852
- delete from.else
874
+ delete source.for
875
+ delete source.if
876
+ delete source.elseif
877
+ delete source.else
853
878
  }
854
879
  }
855
880
 
@@ -887,7 +912,7 @@ function stringify (str) {
887
912
 
888
913
  const genericRE = /^generic:(.+)$/
889
914
 
890
- function processComponentGenericsForWeb (el, options, meta) {
915
+ function processComponentGenericsWeb (el, options, meta) {
891
916
  if (options.componentGenerics && options.componentGenerics[el.tag]) {
892
917
  const generic = dash2hump(el.tag)
893
918
  el.tag = 'component'
@@ -899,7 +924,7 @@ function processComponentGenericsForWeb (el, options, meta) {
899
924
 
900
925
  let hasGeneric = false
901
926
 
902
- const genericHash = options.moduleId
927
+ const genericHash = moduleId
903
928
 
904
929
  el.attrsList.forEach((attr) => {
905
930
  if (genericRE.test(attr.name)) {
@@ -936,10 +961,10 @@ function processComponentIs (el, options) {
936
961
  return
937
962
  }
938
963
 
939
- options = options || {}
940
- el.components = options.usingComponents
941
- if (!el.components) {
942
- warn$1('Component in which <component> tag is used must have a nonblank usingComponents field')
964
+ const isInRange = makeMap(getAndRemoveAttr(el, 'range').val || '')
965
+ el.components = (options.usingComponents || []).filter(i => isInRange(i))
966
+ if (!el.components.length) {
967
+ warn$1('Component in which <component> tag is used must have a non blank usingComponents field')
943
968
  }
944
969
 
945
970
  const is = getAndRemoveAttr(el, 'is').val
@@ -952,7 +977,7 @@ function processComponentIs (el, options) {
952
977
 
953
978
  const eventIdentifier = '__mpx_event__'
954
979
 
955
- function parseFuncStr2 (str) {
980
+ function parseFuncStr (str) {
956
981
  const funcRE = /^([^()]+)(\((.*)\))?/
957
982
  const match = funcRE.exec(str)
958
983
  if (match) {
@@ -1015,23 +1040,144 @@ function stringifyWithResolveComputed (modelValue) {
1015
1040
  return result.join('+')
1016
1041
  }
1017
1042
 
1018
- function processBindEvent (el, options) {
1043
+ function processStyleReact (el) {
1044
+ // process class/wx:class/style/wx:style/wx:show for react native
1045
+ const dynamicClass = getAndRemoveAttr(el, config[mode].directive.dynamicClass).val
1046
+ let staticClass = getAndRemoveAttr(el, 'class').val || ''
1047
+ staticClass = staticClass.replace(/\s+/g, ' ')
1048
+
1049
+ let staticHoverClass = ''
1050
+ if (hoverClassReg.test(el.tag)) {
1051
+ staticHoverClass = el.attrsMap['hover-class'] || ''
1052
+ staticHoverClass = staticHoverClass.replace(/\s+/g, ' ')
1053
+ }
1054
+
1055
+ const dynamicStyle = getAndRemoveAttr(el, config[mode].directive.dynamicStyle).val
1056
+ let staticStyle = getAndRemoveAttr(el, 'style').val || ''
1057
+ staticStyle = staticStyle.replace(/\s+/g, ' ')
1058
+
1059
+ const show = getAndRemoveAttr(el, config[mode].directive.show).val
1060
+
1061
+ if (dynamicClass || staticClass || dynamicStyle || staticStyle || show) {
1062
+ const staticClassExp = parseMustacheWithContext(staticClass).result
1063
+ const dynamicClassExp = parseMustacheWithContext(dynamicClass).result
1064
+ const staticStyleExp = parseMustacheWithContext(staticStyle).result
1065
+ const dynamicStyleExp = parseMustacheWithContext(dynamicStyle).result
1066
+ const showExp = parseMustacheWithContext(show).result
1067
+
1068
+ addAttrs(el, [{
1069
+ name: 'style',
1070
+ // runtime helper
1071
+ value: `{{this.__getStyle(${staticClassExp}, ${dynamicClassExp}, ${staticStyleExp}, ${dynamicStyleExp}, ${showExp})}}`
1072
+ }])
1073
+ }
1074
+
1075
+ if (staticHoverClass && staticHoverClass !== 'none') {
1076
+ const staticClassExp = parseMustacheWithContext(staticHoverClass).result
1077
+ addAttrs(el, [{
1078
+ name: 'hoverStyle',
1079
+ // runtime helper
1080
+ value: `{{this.__getStyle(${staticClassExp})}}`
1081
+ }])
1082
+ }
1083
+ }
1084
+
1085
+ function processEventReact (el, options, meta) {
1086
+ const eventConfigMap = {}
1087
+ el.attrsList.forEach(function ({ name, value }) {
1088
+ const parsedEvent = config[mode].event.parseEvent(name)
1089
+ if (parsedEvent) {
1090
+ const type = parsedEvent.eventName
1091
+ const parsedFunc = parseFuncStr(value)
1092
+ if (parsedFunc) {
1093
+ if (!eventConfigMap[type]) {
1094
+ eventConfigMap[type] = {
1095
+ configs: []
1096
+ }
1097
+ }
1098
+ eventConfigMap[type].configs.push(Object.assign({ name }, parsedFunc))
1099
+ }
1100
+ }
1101
+ })
1102
+
1103
+ let wrapper
1104
+
1105
+ for (const type in eventConfigMap) {
1106
+ let { configs } = eventConfigMap[type]
1107
+
1108
+ let resultName
1109
+ configs.forEach(({ name }) => {
1110
+ if (name) {
1111
+ // 清空原始事件绑定
1112
+ let has
1113
+ do {
1114
+ has = getAndRemoveAttr(el, name).has
1115
+ } while (has)
1116
+
1117
+ if (!resultName) {
1118
+ // 清除修饰符
1119
+ resultName = name.replace(/\..*/, '')
1120
+ }
1121
+ }
1122
+ })
1123
+ configs = configs.map((item) => {
1124
+ return item.expStr
1125
+ })
1126
+ const name = resultName || config[mode].event.getEvent(type)
1127
+ const value = `{{(e)=>this.__invoke(e, [${configs}])}}`
1128
+ addAttrs(el, [
1129
+ {
1130
+ name,
1131
+ value
1132
+ }
1133
+ ])
1134
+ // 非button的情况下,press/longPress时间需要包裹TouchableWithoutFeedback进行响应,后续可支持配置
1135
+ // if ((type === 'press' || type === 'longPress') && el.tag !== 'mpx-button') {
1136
+ // if (!wrapper) {
1137
+ // wrapper = createASTElement('TouchableWithoutFeedback')
1138
+ // wrapper.isBuiltIn = true
1139
+ // processBuiltInComponents(wrapper, meta)
1140
+ // }
1141
+ // addAttrs(el, [
1142
+ // {
1143
+ // name,
1144
+ // value
1145
+ // }
1146
+ // ])
1147
+ // } else {
1148
+ // addAttrs(el, [
1149
+ // {
1150
+ // name,
1151
+ // value
1152
+ // }
1153
+ // ])
1154
+ // }
1155
+ }
1156
+
1157
+ if (wrapper) {
1158
+ replaceNode(el, wrapper, true)
1159
+ addChild(wrapper, el)
1160
+ processAttrs(wrapper, options)
1161
+ postMoveBaseDirective(wrapper, el)
1162
+ }
1163
+ }
1164
+
1165
+ function processEvent (el, options) {
1019
1166
  const eventConfigMap = {}
1020
- el.attrsList.forEach(function (attr) {
1021
- const parsedEvent = config[mode].event.parseEvent(attr.name)
1167
+ el.attrsList.forEach(function ({ name, value }) {
1168
+ const parsedEvent = config[mode].event.parseEvent(name)
1022
1169
 
1023
1170
  if (parsedEvent) {
1024
1171
  const type = parsedEvent.eventName
1025
1172
  const modifiers = (parsedEvent.modifier || '').split('.')
1026
- const parsedFunc = parseFuncStr2(attr.value)
1173
+ const parsedFunc = parseFuncStr(value)
1027
1174
  if (parsedFunc) {
1028
1175
  if (!eventConfigMap[type]) {
1029
1176
  eventConfigMap[type] = {
1030
- rawName: attr.name,
1031
1177
  configs: []
1032
1178
  }
1033
1179
  }
1034
- eventConfigMap[type].configs.push(parsedFunc)
1180
+ eventConfigMap[type].configs.push(Object.assign({ name }, parsedFunc))
1035
1181
  if (modifiers.indexOf('proxy') > -1 || options.forceProxyEvent) {
1036
1182
  eventConfigMap[type].proxy = true
1037
1183
  }
@@ -1090,7 +1236,7 @@ function processBindEvent (el, options) {
1090
1236
 
1091
1237
  for (const type in eventConfigMap) {
1092
1238
  let needBind = false
1093
- let { configs, rawName, proxy } = eventConfigMap[type]
1239
+ const { configs, proxy } = eventConfigMap[type]
1094
1240
  delete eventConfigMap[type]
1095
1241
  if (proxy) {
1096
1242
  needBind = true
@@ -1108,19 +1254,25 @@ function processBindEvent (el, options) {
1108
1254
  }
1109
1255
 
1110
1256
  if (needBind) {
1111
- if (rawName) {
1112
- // 清空原始事件绑定
1113
- let has
1114
- do {
1115
- has = getAndRemoveAttr(el, rawName).has
1116
- } while (has)
1117
- // 清除修饰符
1118
- rawName = rawName.replace(/\..*/, '')
1119
- }
1257
+ let resultName
1258
+ configs.forEach(({ name }) => {
1259
+ if (name) {
1260
+ // 清空原始事件绑定
1261
+ let has
1262
+ do {
1263
+ has = getAndRemoveAttr(el, name).has
1264
+ } while (has)
1265
+
1266
+ if (!resultName) {
1267
+ // 清除修饰符
1268
+ resultName = name.replace(/\..*/, '')
1269
+ }
1270
+ }
1271
+ })
1120
1272
 
1121
1273
  addAttrs(el, [
1122
1274
  {
1123
- name: rawName || config[mode].event.getEvent(type),
1275
+ name: resultName || config[mode].event.getEvent(type),
1124
1276
  value: '__invoke'
1125
1277
  }
1126
1278
  ])
@@ -1133,15 +1285,181 @@ function processBindEvent (el, options) {
1133
1285
  if (!isEmptyObject(eventConfigMap)) {
1134
1286
  addAttrs(el, [{
1135
1287
  name: 'data-eventconfigs',
1136
- value: `{{${config[mode].event.shallowStringify(eventConfigMap)}}}`
1288
+ value: `{{${shallowStringify(eventConfigMap, true)}}}`
1137
1289
  }])
1138
1290
  }
1139
1291
  }
1140
1292
 
1293
+ function processSlotReact (el) {
1294
+ if (el.tag === 'slot') {
1295
+ el.slot = {
1296
+ name: getAndRemoveAttr(el, 'name').val
1297
+ }
1298
+ }
1299
+ }
1300
+
1141
1301
  function wrapMustache (val) {
1142
1302
  return val && !tagRE.test(val) ? `{{${val}}}` : val
1143
1303
  }
1144
1304
 
1305
+ function parseOptionalChaining (str) {
1306
+ const wxsName = `${optionsChainWxsName}.g`
1307
+ let optionsRes
1308
+ while (optionsRes = /\?\./.exec(str)) {
1309
+ const strLength = str.length
1310
+ const grammarMap = {
1311
+ init () {
1312
+ const initKey = [
1313
+ {
1314
+ mapKey: '[]',
1315
+ mapValue: [
1316
+ {
1317
+ key: '[',
1318
+ value: 1
1319
+ },
1320
+ {
1321
+ key: ']',
1322
+ value: -1
1323
+ }
1324
+ ]
1325
+ },
1326
+ {
1327
+ mapKey: '()',
1328
+ mapValue: [
1329
+ {
1330
+ key: '(',
1331
+ value: 1
1332
+ },
1333
+ {
1334
+ key: ')',
1335
+ value: -1
1336
+ }
1337
+ ]
1338
+ }
1339
+ ]
1340
+ this.count = {}
1341
+ initKey.forEach(({ mapKey, mapValue }) => {
1342
+ mapValue.forEach(({ key, value }) => {
1343
+ this[key] = this.changeState(mapKey, value)
1344
+ })
1345
+ })
1346
+ },
1347
+ changeState (key, value) {
1348
+ if (!this.count[key]) {
1349
+ this.count[key] = 0
1350
+ }
1351
+ return () => {
1352
+ this.count[key] = this.count[key] + value
1353
+ return this.count[key]
1354
+ }
1355
+ },
1356
+ checkState () {
1357
+ return Object.values(this.count).find(i => i)
1358
+ }
1359
+ }
1360
+ let leftIndex = optionsRes.index
1361
+ let rightIndex = leftIndex + 2
1362
+ let haveNotGetValue = true
1363
+ let chainValue = ''
1364
+ let chainKey = ''
1365
+ let notCheck = false
1366
+ grammarMap.init()
1367
+ // 查 ?. 左边值
1368
+ while (haveNotGetValue && leftIndex > 0) {
1369
+ const left = str[leftIndex - 1]
1370
+ const grammar = grammarMap[left]
1371
+ if (notCheck) {
1372
+ // 处于表达式内
1373
+ chainValue = left + chainValue
1374
+ if (grammar) {
1375
+ grammar()
1376
+ if (!grammarMap.checkState()) {
1377
+ // 表达式结束
1378
+ notCheck = false
1379
+ }
1380
+ }
1381
+ } else if (~[']', ')'].indexOf(left)) {
1382
+ // 命中表达式,开始记录表达式
1383
+ chainValue = left + chainValue
1384
+ notCheck = true
1385
+ grammar()
1386
+ } else if (left !== ' ') {
1387
+ if (!/[A-Za-z0-9_$.]/.test(left)) {
1388
+ // 结束
1389
+ haveNotGetValue = false
1390
+ leftIndex++
1391
+ } else {
1392
+ // 正常语法
1393
+ chainValue = left + chainValue
1394
+ }
1395
+ }
1396
+ leftIndex--
1397
+ }
1398
+ if (grammarMap.checkState() && haveNotGetValue) {
1399
+ // 值查找结束但是语法未闭合或者处理到边界还未结束,抛异常
1400
+ throw new Error('[optionChain] option value illegal!!!')
1401
+ }
1402
+ haveNotGetValue = true
1403
+ let keyValue = ''
1404
+ // 查 ?. 右边key
1405
+ while (haveNotGetValue && rightIndex < strLength) {
1406
+ const right = str[rightIndex]
1407
+ const grammar = grammarMap[right]
1408
+ if (notCheck) {
1409
+ // 处于表达式内
1410
+ if (grammar) {
1411
+ grammar()
1412
+ if (grammarMap.checkState()) {
1413
+ keyValue += right
1414
+ } else {
1415
+ // 表达式结束
1416
+ notCheck = false
1417
+ chainKey += `,${keyValue}`
1418
+ keyValue = ''
1419
+ }
1420
+ } else {
1421
+ keyValue += right
1422
+ }
1423
+ } else if (~['[', '('].indexOf(right)) {
1424
+ // 命中表达式,开始记录表达式
1425
+ grammar()
1426
+ if (keyValue) {
1427
+ chainKey += `,'${keyValue}'`
1428
+ keyValue = ''
1429
+ }
1430
+ notCheck = true
1431
+ } else if (!/[A-Za-z0-9_$.?]/.test(right)) {
1432
+ // 结束
1433
+ haveNotGetValue = false
1434
+ rightIndex--
1435
+ } else if (right !== '?') {
1436
+ // 正常语法
1437
+ if (right === '.') {
1438
+ if (keyValue) {
1439
+ chainKey += `,'${keyValue}'`
1440
+ }
1441
+ keyValue = ''
1442
+ } else {
1443
+ keyValue += right
1444
+ }
1445
+ }
1446
+ rightIndex++
1447
+ }
1448
+ if (grammarMap.checkState() && haveNotGetValue) {
1449
+ // key值查找结束但是语法未闭合或者处理到边界还未结束,抛异常
1450
+ throw new Error('[optionChain] option key illegal!!!')
1451
+ }
1452
+ if (keyValue) {
1453
+ chainKey += `,'${keyValue}'`
1454
+ }
1455
+ str = str.slice(0, leftIndex) + `${wxsName}(${chainValue},[${chainKey.slice(1)}])` + str.slice(rightIndex)
1456
+ if (!hasOptionalChaining) {
1457
+ hasOptionalChaining = true
1458
+ }
1459
+ }
1460
+ return str
1461
+ }
1462
+
1145
1463
  function parseMustacheWithContext (raw = '') {
1146
1464
  return parseMustache(raw, (exp) => {
1147
1465
  if (defs) {
@@ -1155,6 +1473,8 @@ function parseMustacheWithContext (raw = '') {
1155
1473
  }
1156
1474
  })
1157
1475
  }
1476
+ // 处理可选链表达式
1477
+ exp = parseOptionalChaining(exp)
1158
1478
 
1159
1479
  if (i18n) {
1160
1480
  for (const i18nFuncName of i18nFuncNames) {
@@ -1235,12 +1555,12 @@ function parseMustache (raw = '', expHandler = exp => exp, strHandler = str => s
1235
1555
  }
1236
1556
  }
1237
1557
 
1238
- function addExp (el, exp, isProps) {
1558
+ function addExp (el, exp, isProps, attrName) {
1239
1559
  if (exp) {
1240
1560
  if (!el.exps) {
1241
1561
  el.exps = []
1242
1562
  }
1243
- el.exps.push({ exp, isProps })
1563
+ el.exps.push({ exp, isProps, attrName })
1244
1564
  }
1245
1565
  }
1246
1566
 
@@ -1265,7 +1585,7 @@ function processIf (el) {
1265
1585
  }
1266
1586
  }
1267
1587
 
1268
- function processIfForWeb (el) {
1588
+ function processIfWeb (el) {
1269
1589
  let val = getAndRemoveAttr(el, config[mode].directive.if).val
1270
1590
  if (val) {
1271
1591
  el.if = {
@@ -1319,6 +1639,27 @@ function processFor (el) {
1319
1639
  }
1320
1640
  }
1321
1641
 
1642
+ function processRefReact (el, options, meta) {
1643
+ const val = getAndRemoveAttr(el, config[mode].directive.ref).val
1644
+ const type = isComponentNode(el, options) ? 'component' : 'node'
1645
+ if (val) {
1646
+ if (!meta.refs) {
1647
+ meta.refs = []
1648
+ }
1649
+ const all = !!forScopes.length
1650
+ meta.refs.push({
1651
+ key: val,
1652
+ all,
1653
+ type
1654
+ })
1655
+
1656
+ addAttrs(el, [{
1657
+ name: 'ref',
1658
+ value: `{{ this.__getRefVal('${val}') }}`
1659
+ }])
1660
+ }
1661
+ }
1662
+
1322
1663
  function processRef (el, options, meta) {
1323
1664
  const val = getAndRemoveAttr(el, config[mode].directive.ref).val
1324
1665
  const type = isComponentNode(el, options) ? 'component' : 'node'
@@ -1413,7 +1754,7 @@ function processAttrs (el, options) {
1413
1754
  if (parsed.hasBinding) {
1414
1755
  // 该属性判断用于提供给运行时对于计算属性作为props传递时提出警告
1415
1756
  const isProps = isComponentNode(el, options) && !(attr.name === 'class' || attr.name === 'style')
1416
- addExp(el, parsed.result, isProps)
1757
+ addExp(el, parsed.result, isProps, attr.name)
1417
1758
  if (parsed.replaced) {
1418
1759
  modifyAttr(el, attr.name, needWrap ? parsed.val.slice(1, -1) : parsed.val)
1419
1760
  }
@@ -1428,7 +1769,7 @@ function postProcessFor (el) {
1428
1769
  这个操作主要是因为百度小程序不支持这两个directive在同级使用
1429
1770
  */
1430
1771
  if (el.if && mode === 'swan') {
1431
- const block = createASTElement('block', [])
1772
+ const block = createASTElement('block')
1432
1773
  replaceNode(el, block, true)
1433
1774
  block.for = el.for
1434
1775
  delete el.for
@@ -1468,6 +1809,19 @@ function postProcessFor (el) {
1468
1809
  }
1469
1810
  }
1470
1811
 
1812
+ function postProcessForReact (el) {
1813
+ if (el.for) {
1814
+ if (el.for.key) {
1815
+ addExp(el, `this.__getWxKey(${el.for.item || 'item'}, ${stringify(el.for.key)})`, false, 'key')
1816
+ addAttrs(el, [{
1817
+ name: 'key',
1818
+ value: el.for.key
1819
+ }])
1820
+ }
1821
+ popForScopes()
1822
+ }
1823
+ }
1824
+
1471
1825
  function evalExp (exp) {
1472
1826
  let result = { success: false }
1473
1827
  try {
@@ -1544,7 +1898,35 @@ function postProcessIf (el) {
1544
1898
  }
1545
1899
  }
1546
1900
 
1547
- function processText (el) {
1901
+ function addIfCondition (el, condition) {
1902
+ if (!el.ifConditions) {
1903
+ el.ifConditions = []
1904
+ }
1905
+ el.ifConditions.push(condition)
1906
+ }
1907
+
1908
+ function postProcessIfReact (el) {
1909
+ let prevNode
1910
+ if (el.if) {
1911
+ addIfCondition(el, {
1912
+ exp: el.if.exp,
1913
+ block: el
1914
+ })
1915
+ } else if (el.elseif || el.else) {
1916
+ prevNode = findPrevNode(el)
1917
+ if (prevNode && prevNode.if) {
1918
+ addIfCondition(prevNode, {
1919
+ exp: el.elseif && el.elseif.exp,
1920
+ block: el
1921
+ })
1922
+ removeNode(el, true)
1923
+ } else {
1924
+ warn$1(`wx:${el.elseif ? `elif="${el.elseif.raw}"` : 'else'} used on element [${el.tag}] without corresponding wx:if.`)
1925
+ }
1926
+ }
1927
+ }
1928
+
1929
+ function processText (el, meta) {
1548
1930
  if (el.type !== 3 || el.isComment) {
1549
1931
  return
1550
1932
  }
@@ -1553,6 +1935,19 @@ function processText (el) {
1553
1935
  addExp(el, parsed.result)
1554
1936
  }
1555
1937
  el.text = parsed.val
1938
+ if (isReact(mode)) {
1939
+ processWrapTextReact(el, meta)
1940
+ }
1941
+ }
1942
+
1943
+ // RN中文字需被Text包裹
1944
+ function processWrapTextReact (el, meta) {
1945
+ const parentTag = el.parent.tag
1946
+ if (parentTag !== 'mpx-text' && parentTag !== 'Text') {
1947
+ const wrapper = createASTElement('Text')
1948
+ replaceNode(el, wrapper, true)
1949
+ addChild(wrapper, el)
1950
+ }
1556
1951
  }
1557
1952
 
1558
1953
  // function injectComputed (el, meta, type, body) {
@@ -1675,13 +2070,13 @@ function processAliExternalClassesHack (el, options) {
1675
2070
  }
1676
2071
  })
1677
2072
 
1678
- if (options.hasScoped && isComponent) {
2073
+ if (hasScoped && isComponent) {
1679
2074
  options.externalClasses.forEach((className) => {
1680
2075
  const externalClass = getAndRemoveAttr(el, className).val
1681
2076
  if (externalClass) {
1682
2077
  addAttrs(el, [{
1683
2078
  name: className,
1684
- value: `${externalClass} ${options.moduleId}`
2079
+ value: `${externalClass} ${moduleId}`
1685
2080
  }])
1686
2081
  }
1687
2082
  })
@@ -1749,10 +2144,9 @@ function processWebExternalClassesHack (el, options) {
1749
2144
  }
1750
2145
  }
1751
2146
 
1752
- function processScoped (el, options) {
1753
- if (options.hasScoped && isRealNode(el)) {
1754
- const moduleId = options.moduleId
1755
- const rootModuleId = options.isComponent ? '' : MPX_APP_MODULE_ID // 处理app全局样式对页面的影响
2147
+ function processScoped (el) {
2148
+ if (hasScoped && isRealNode(el)) {
2149
+ const rootModuleId = ctorType === 'component' ? '' : MPX_APP_MODULE_ID // 处理app全局样式对页面的影响
1756
2150
  const staticClass = getAndRemoveAttr(el, 'class').val
1757
2151
  addAttrs(el, [{
1758
2152
  name: 'class',
@@ -1770,12 +2164,16 @@ function processBuiltInComponents (el, meta) {
1770
2164
  }
1771
2165
  const tag = el.tag
1772
2166
  if (!meta.builtInComponentsMap[tag]) {
1773
- meta.builtInComponentsMap[tag] = `${builtInComponentsPrefix}/${mode}/${tag}.vue`
2167
+ if (isReact(mode)) {
2168
+ meta.builtInComponentsMap[tag] = `${builtInComponentsPrefix}/react/${tag}`
2169
+ } else {
2170
+ meta.builtInComponentsMap[tag] = `${builtInComponentsPrefix}/${mode}/${tag}`
2171
+ }
1774
2172
  }
1775
2173
  }
1776
2174
  }
1777
2175
 
1778
- function processAliAddComponentRootView (el, options) {
2176
+ function postProcessAliComponentRootView (el, options) {
1779
2177
  const processAttrsConditions = [
1780
2178
  { condition: /^(on|catch)Tap$/, action: 'clone' },
1781
2179
  { condition: /^(on|catch)TouchStart$/, action: 'clone' },
@@ -1789,25 +2187,25 @@ function processAliAddComponentRootView (el, options) {
1789
2187
  { condition: /^slot$/, action: 'move' }
1790
2188
  ]
1791
2189
  const processAppendAttrsRules = [
1792
- { name: 'class', value: `${MPX_ROOT_VIEW} host-${options.moduleId}` }
2190
+ { name: 'class', value: `${MPX_ROOT_VIEW} host-${moduleId}` }
1793
2191
  ]
1794
- const newElAttrs = []
2192
+ const newAttrs = []
1795
2193
  const allAttrs = cloneAttrsList(el.attrsList)
1796
2194
 
1797
2195
  function processClone (attr) {
1798
- newElAttrs.push(attr)
2196
+ newAttrs.push(attr)
1799
2197
  }
1800
2198
 
1801
2199
  function processMove (attr) {
1802
2200
  getAndRemoveAttr(el, attr.name)
1803
- newElAttrs.push(attr)
2201
+ newAttrs.push(attr)
1804
2202
  }
1805
2203
 
1806
2204
  function processAppendRules (el) {
1807
2205
  processAppendAttrsRules.forEach((rule) => {
1808
2206
  const getNeedAppendAttrValue = el.attrsMap[rule.name]
1809
2207
  const value = getNeedAppendAttrValue ? getNeedAppendAttrValue + ' ' + rule.value : rule.value
1810
- newElAttrs.push({
2208
+ newAttrs.push({
1811
2209
  name: rule.name,
1812
2210
  value
1813
2211
  })
@@ -1828,48 +2226,51 @@ function processAliAddComponentRootView (el, options) {
1828
2226
  })
1829
2227
 
1830
2228
  processAppendRules(el)
1831
- const componentWrapView = createASTElement('view', newElAttrs)
1832
- moveBaseDirective(componentWrapView, el)
1833
- if (el.is && el.components) {
1834
- el = postProcessComponentIs(el)
1835
- }
1836
-
2229
+ const componentWrapView = createASTElement('view', newAttrs)
1837
2230
  replaceNode(el, componentWrapView, true)
1838
2231
  addChild(componentWrapView, el)
1839
- return componentWrapView
2232
+ postMoveBaseDirective(componentWrapView, el)
1840
2233
  }
1841
2234
 
1842
2235
  // 有virtualHost情况wx组件注入virtualHost。无virtualHost阿里组件注入root-view。其他跳过。
1843
2236
  function getVirtualHostRoot (options, meta) {
1844
2237
  if (srcMode === 'wx') {
1845
- if (options.isComponent) {
1846
- if ((mode === 'wx') && options.hasVirtualHost) {
2238
+ if (ctorType === 'component') {
2239
+ if (mode === 'wx' && hasVirtualHost) {
1847
2240
  // wx组件注入virtualHost配置
1848
2241
  !meta.options && (meta.options = {})
1849
2242
  meta.options.virtualHost = true
1850
2243
  }
1851
- if ((mode === 'web') && !options.hasVirtualHost) {
2244
+ if (mode === 'web' && !hasVirtualHost) {
1852
2245
  // ali组件根节点实体化
1853
2246
  const rootView = createASTElement('view', [
1854
2247
  {
1855
2248
  name: 'class',
1856
- value: `${MPX_ROOT_VIEW} host-${options.moduleId}`
2249
+ value: `${MPX_ROOT_VIEW} host-${moduleId}`
1857
2250
  },
1858
2251
  {
1859
2252
  name: 'v-on',
1860
2253
  value: '$listeners'
1861
2254
  }
1862
2255
  ])
1863
- rootView.hasEvent = true
1864
2256
  processElement(rootView, rootView, options, meta)
1865
2257
  return rootView
1866
2258
  }
1867
- }
1868
- if (options.isPage) {
1869
- if (mode === 'web') {
1870
- return createASTElement('page', [])
2259
+ if (isReact(mode) && !hasVirtualHost) {
2260
+ const rootView = createASTElement('view', [
2261
+ {
2262
+ name: 'class',
2263
+ value: `${MPX_ROOT_VIEW} host-${moduleId}`
2264
+ }
2265
+ ])
2266
+ rootView.isRoot = true
2267
+ processElement(rootView, rootView, options, meta)
2268
+ return rootView
1871
2269
  }
1872
2270
  }
2271
+ if (mode === 'web' && ctorType === 'page') {
2272
+ return createASTElement('page')
2273
+ }
1873
2274
  }
1874
2275
  return getTempNode()
1875
2276
  }
@@ -1880,8 +2281,8 @@ function processShow (el, options, root) {
1880
2281
  let show = getAndRemoveAttr(el, config[mode].directive.show).val
1881
2282
  if (mode === 'swan') show = wrapMustache(show)
1882
2283
 
1883
- if (options.hasVirtualHost) {
1884
- if (options.isComponent && el.parent === root && isRealNode(el)) {
2284
+ if (hasVirtualHost) {
2285
+ if (ctorType === 'component' && el.parent === root && isRealNode(el)) {
1885
2286
  if (show !== undefined) {
1886
2287
  show = `{{${parseMustacheWithContext(show).result}&&mpxShow}}`
1887
2288
  } else {
@@ -1931,7 +2332,7 @@ function postProcessTemplate (el) {
1931
2332
  }
1932
2333
  }
1933
2334
 
1934
- const isValidMode = makeMap('wx,ali,swan,tt,qq,web,qa,jd,dd,tenon,noMode')
2335
+ const isValidMode = makeMap('wx,ali,swan,tt,qq,web,qa,jd,dd,tenon,ios,android,noMode')
1935
2336
 
1936
2337
  function isValidModeP (i) {
1937
2338
  return isValidMode(i[0] === '_' ? i.slice(1) : i)
@@ -2059,6 +2460,12 @@ function processMpxTagName (el) {
2059
2460
  }
2060
2461
  }
2061
2462
 
2463
+ function postProcessComponent (el, options) {
2464
+ if (isComponentNode(el, options)) {
2465
+ el.isComponent = true
2466
+ }
2467
+ }
2468
+
2062
2469
  function processElement (el, root, options, meta) {
2063
2470
  processAtMode(el)
2064
2471
  // 如果已经标记了这个元素要被清除,直接return跳过后续处理步骤
@@ -2085,9 +2492,24 @@ function processElement (el, root, options, meta) {
2085
2492
  // 收集内建组件
2086
2493
  processBuiltInComponents(el, meta)
2087
2494
  // 预处理代码维度条件编译
2088
- processIfForWeb(el)
2495
+ processIfWeb(el)
2089
2496
  processWebExternalClassesHack(el, options)
2090
- processComponentGenericsForWeb(el, options, meta)
2497
+ processComponentGenericsWeb(el, options, meta)
2498
+ return
2499
+ }
2500
+
2501
+ if (isReact(mode)) {
2502
+ // 收集内建组件
2503
+ processBuiltInComponents(el, meta)
2504
+ // 预处理代码维度条件编译
2505
+ processIf(el)
2506
+ processFor(el)
2507
+ processRefReact(el, options, meta)
2508
+ processStyleReact(el)
2509
+ processEventReact(el, options, meta)
2510
+ processComponentIs(el, options)
2511
+ processSlotReact(el)
2512
+ processAttrs(el, options)
2091
2513
  return
2092
2514
  }
2093
2515
 
@@ -2095,7 +2517,7 @@ function processElement (el, root, options, meta) {
2095
2517
 
2096
2518
  // 仅ali平台需要scoped模拟样式隔离
2097
2519
  if (mode === 'ali') {
2098
- processScoped(el, options)
2520
+ processScoped(el)
2099
2521
  }
2100
2522
 
2101
2523
  if (transAli) {
@@ -2113,7 +2535,7 @@ function processElement (el, root, options, meta) {
2113
2535
  }
2114
2536
 
2115
2537
  if (!pass) {
2116
- processBindEvent(el, options)
2538
+ processEvent(el, options)
2117
2539
  processComponentIs(el, options)
2118
2540
  }
2119
2541
 
@@ -2128,15 +2550,20 @@ function closeElement (el, meta, options) {
2128
2550
  postProcessIf(el)
2129
2551
  return
2130
2552
  }
2553
+ if (isReact(mode)) {
2554
+ postProcessForReact(el)
2555
+ postProcessIfReact(el)
2556
+ // flag component for react
2557
+ postProcessComponent(el, options)
2558
+ return
2559
+ }
2131
2560
  const pass = isNative || postProcessTemplate(el) || processingTemplate
2132
2561
  postProcessWxs(el, meta)
2133
-
2134
2562
  if (!pass) {
2135
- if (isComponentNode(el, options) && !options.hasVirtualHost && mode === 'ali') {
2136
- el = processAliAddComponentRootView(el, options)
2137
- } else {
2138
- el = postProcessComponentIs(el)
2563
+ if (isComponentNode(el, options) && !hasVirtualHost && mode === 'ali') {
2564
+ postProcessAliComponentRootView(el, options)
2139
2565
  }
2566
+ postProcessComponentIs(el)
2140
2567
  }
2141
2568
  postProcessFor(el)
2142
2569
  postProcessIf(el)
@@ -2174,17 +2601,14 @@ function postProcessComponentIs (el) {
2174
2601
  if (el.is && el.components) {
2175
2602
  let tempNode
2176
2603
  if (el.for || el.if || el.elseif || el.else) {
2177
- tempNode = createASTElement('block', [])
2178
- moveBaseDirective(tempNode, el)
2604
+ tempNode = createASTElement('block')
2179
2605
  } else {
2180
2606
  tempNode = getTempNode()
2181
2607
  }
2182
- let range = []
2183
- if (el.attrsMap.range) {
2184
- range = getAndRemoveAttr(el, 'range').val.split(',')
2185
- }
2608
+ replaceNode(el, tempNode, true)
2609
+ postMoveBaseDirective(tempNode, el)
2610
+
2186
2611
  el.components.forEach(function (component) {
2187
- if (range.length > 0 && !range.includes(component)) return
2188
2612
  const newChild = createASTElement(component, cloneAttrsList(el.attrsList), tempNode)
2189
2613
  newChild.if = {
2190
2614
  raw: `{{${el.is} === ${stringify(component)}}}`,
@@ -2201,10 +2625,9 @@ function postProcessComponentIs (el) {
2201
2625
  if (!el.parent) {
2202
2626
  error$1('Dynamic component can not be the template root, considering wrapping it with <view> or <text> tag!')
2203
2627
  } else {
2204
- el = replaceNode(el, tempNode, true) || el
2628
+ replaceNode(el, tempNode, true)
2205
2629
  }
2206
2630
  }
2207
- return el
2208
2631
  }
2209
2632
 
2210
2633
  function stringifyAttr (val) {
@@ -2284,8 +2707,8 @@ function findPrevNode (node) {
2284
2707
  }
2285
2708
  }
2286
2709
 
2287
- function replaceNode (node, newNode, reserveNode) {
2288
- if (!reserveNode) deleteErrorInResultMap(node)
2710
+ function replaceNode (node, newNode, reserveError) {
2711
+ if (!reserveError) deleteErrorInResultMap(node)
2289
2712
  const parent = node.parent
2290
2713
  if (parent) {
2291
2714
  const index = parent.children.indexOf(node)
@@ -2297,8 +2720,8 @@ function replaceNode (node, newNode, reserveNode) {
2297
2720
  }
2298
2721
  }
2299
2722
 
2300
- function removeNode (node, reserveNode) {
2301
- if (!reserveNode) deleteErrorInResultMap(node)
2723
+ function removeNode (node, reserveError) {
2724
+ if (!reserveError) deleteErrorInResultMap(node)
2302
2725
  const parent = node.parent
2303
2726
  if (parent) {
2304
2727
  const index = parent.children.indexOf(node)