react-native-windows 0.82.3 โ†’ 0.83.0-preview.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 (223) hide show
  1. package/.flowconfig +2 -7
  2. package/Libraries/Animated/components/AnimatedFlatList.js +5 -3
  3. package/Libraries/Animated/components/AnimatedImage.js +4 -3
  4. package/Libraries/Animated/components/AnimatedSectionList.js +2 -2
  5. package/Libraries/Animated/components/AnimatedText.js +7 -3
  6. package/Libraries/Animated/components/AnimatedView.js +3 -2
  7. package/Libraries/Animated/createAnimatedComponent.js +24 -12
  8. package/Libraries/Animated/nodes/AnimatedColor.js +26 -10
  9. package/Libraries/Components/AccessibilityInfo/AccessibilityInfo.js +43 -15
  10. package/Libraries/Components/AccessibilityInfo/AccessibilityInfo.windows.js +43 -15
  11. package/Libraries/Components/ActivityIndicator/ActivityIndicator.d.ts +2 -2
  12. package/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.d.ts +2 -2
  13. package/Libraries/Components/Glyph/Glyph.js +4 -1
  14. package/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.d.ts +2 -2
  15. package/Libraries/Components/RefreshControl/RefreshControl.d.ts +3 -3
  16. package/Libraries/Components/SafeAreaView/SafeAreaView.d.ts +2 -2
  17. package/Libraries/Components/ScrollView/ScrollView.js +1 -0
  18. package/Libraries/Components/ScrollView/ScrollView.windows.js +1 -0
  19. package/Libraries/Components/ScrollView/ScrollViewStickyHeader.js +6 -6
  20. package/Libraries/Components/Switch/Switch.d.ts +2 -2
  21. package/Libraries/Components/Switch/Switch.windows.js +1 -1
  22. package/Libraries/Components/TextInput/TextInput.d.ts +2 -5
  23. package/Libraries/Components/TextInput/TextInput.js +6 -0
  24. package/Libraries/Components/TextInput/TextInput.windows.js +6 -0
  25. package/Libraries/Components/Touchable/TouchableBounce.js +7 -7
  26. package/Libraries/Components/Touchable/TouchableBounce.windows.js +7 -7
  27. package/Libraries/Components/Touchable/TouchableWithoutFeedback.windows.js +1 -1
  28. package/Libraries/Components/View/ReactNativeStyleAttributes.js +19 -1
  29. package/Libraries/Components/View/View.d.ts +2 -2
  30. package/Libraries/Components/View/View.windows.js +0 -1
  31. package/Libraries/Components/View/ViewNativeComponent.js +13 -1
  32. package/Libraries/Core/ReactNativeVersion.js +3 -3
  33. package/Libraries/Core/setUpPerformance.js +2 -0
  34. package/Libraries/Debugging/DebuggingOverlay.js +14 -14
  35. package/Libraries/Debugging/DebuggingOverlayRegistry.js +8 -2
  36. package/Libraries/EventEmitter/RCTDeviceEventEmitter.js +5 -2
  37. package/Libraries/Image/Image.d.ts +3 -3
  38. package/Libraries/Image/ImageInjection.js +3 -6
  39. package/Libraries/Image/ImageTypes.flow.js +3 -7
  40. package/Libraries/Lists/FlatList.js +8 -8
  41. package/Libraries/Lists/SectionList.d.ts +5 -1
  42. package/Libraries/Lists/ViewabilityHelper.js +1 -1
  43. package/Libraries/Lists/VirtualizedList.js +1 -0
  44. package/Libraries/LogBox/UI/AnsiHighlight.js +4 -1
  45. package/Libraries/NativeComponent/BaseViewConfig.android.js +11 -2
  46. package/Libraries/NativeComponent/NativeComponentRegistry.d.ts +98 -0
  47. package/Libraries/NativeComponent/NativeComponentRegistry.js +2 -0
  48. package/Libraries/NativeComponent/NativeComponentRegistryUnstable.js +3 -1
  49. package/Libraries/NativeComponent/ViewConfigIgnore.windows.js +45 -0
  50. package/Libraries/Network/RCTNetworking.android.js +3 -1
  51. package/Libraries/Network/RCTNetworking.ios.js +3 -0
  52. package/Libraries/Network/RCTNetworking.windows.js +3 -0
  53. package/Libraries/Network/XMLHttpRequest.js +1 -41
  54. package/Libraries/Pressability/usePressability.js +14 -3
  55. package/Libraries/ReactNative/PaperUIManager.windows.js +3 -3
  56. package/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance.js +20 -82
  57. package/Libraries/ReactNative/getNativeComponentAttributes.js +12 -0
  58. package/Libraries/Renderer/implementations/ReactFabric-dev.js +6759 -4478
  59. package/Libraries/Renderer/implementations/ReactFabric-prod.js +3169 -3119
  60. package/Libraries/Renderer/implementations/ReactFabric-profiling.js +4732 -3535
  61. package/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js +6646 -4070
  62. package/Libraries/Renderer/implementations/ReactNativeRenderer-prod.js +3136 -2825
  63. package/Libraries/Renderer/implementations/ReactNativeRenderer-profiling.js +4761 -3312
  64. package/Libraries/Renderer/shims/ReactNative.js +3 -1
  65. package/Libraries/Renderer/shims/ReactNativeTypes.js +2 -1
  66. package/Libraries/Renderer/shims/ReactNativeTypes.windows.js +2 -1
  67. package/Libraries/StyleSheet/StyleSheetTypes.d.ts +1 -1
  68. package/Libraries/StyleSheet/StyleSheetTypes.js +44 -0
  69. package/Libraries/StyleSheet/processBackgroundPosition.js +284 -0
  70. package/Libraries/StyleSheet/processBackgroundRepeat.js +105 -0
  71. package/Libraries/StyleSheet/processBackgroundSize.js +104 -0
  72. package/Libraries/Text/Text.d.ts +2 -2
  73. package/Libraries/Text/TextNativeComponent.js +10 -0
  74. package/Libraries/TurboModule/TurboModuleRegistry.js +3 -9
  75. package/Libraries/Utilities/DevLoadingView.js +14 -6
  76. package/Libraries/Utilities/HMRClient.js +13 -5
  77. package/Microsoft.ReactNative/CompositionComponentView.idl +2 -0
  78. package/Microsoft.ReactNative/CompositionHwndHost.idl +1 -0
  79. package/Microsoft.ReactNative/Fabric/ComponentView.cpp +1 -1
  80. package/Microsoft.ReactNative/Fabric/ComponentView.h +1 -1
  81. package/Microsoft.ReactNative/Fabric/Composition/CompositionHwndHost.cpp +10 -40
  82. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +3 -80
  83. package/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp +45 -12
  84. package/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h +8 -0
  85. package/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp +61 -74
  86. package/Microsoft.ReactNative/Fabric/Composition/ParagraphComponentView.h +4 -0
  87. package/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.h +1 -0
  88. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.cpp +4 -3
  89. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.h +2 -1
  90. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeWindow.cpp +245 -0
  91. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeWindow.h +80 -0
  92. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentDescriptor.h +20 -36
  93. package/Microsoft.ReactNative/Fabric/Composition/UriImageManager.cpp +5 -0
  94. package/Microsoft.ReactNative/IReactCompositionViewComponentBuilder.idl +1 -0
  95. package/Microsoft.ReactNative/Modules/ImageViewManagerModule.cpp +1 -1
  96. package/Microsoft.ReactNative/Modules/LogBoxModule.cpp +20 -94
  97. package/Microsoft.ReactNative/Modules/LogBoxModule.h +1 -3
  98. package/Microsoft.ReactNative/ReactNativeAppBuilder.cpp +0 -41
  99. package/Microsoft.ReactNative/ReactNativeAppBuilder.idl +0 -11
  100. package/Microsoft.ReactNative/ReactNativeIsland.idl +2 -3
  101. package/Microsoft.ReactNative/ReactNativeWin32App.cpp +31 -101
  102. package/Microsoft.ReactNative/ReactNativeWin32App.h +2 -13
  103. package/Microsoft.ReactNative/ReactNativeWindow.idl +44 -0
  104. package/Microsoft.ReactNative.Cxx/AutoDraw.h +9 -1
  105. package/Microsoft.ReactNative.Cxx/ReactCommon/CallInvoker.h +13 -16
  106. package/Microsoft.ReactNative.Cxx/ReactCommon/TurboModule.h +24 -36
  107. package/Microsoft.ReactNative.Cxx/ReactCommon/TurboModuleUtils.h +5 -8
  108. package/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/AString.h +8 -6
  109. package/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Array.h +32 -49
  110. package/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Base.h +27 -76
  111. package/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Bool.h +4 -2
  112. package/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/CallbackWrapper.h +19 -18
  113. package/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Class.h +25 -48
  114. package/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Convert.h +38 -31
  115. package/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Error.h +11 -6
  116. package/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/EventEmitter.h +47 -45
  117. package/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Function.h +69 -89
  118. package/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/HighResTimeStamp.h +8 -8
  119. package/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/LongLivedObject.h +6 -6
  120. package/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Number.h +16 -8
  121. package/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Object.h +17 -24
  122. package/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Promise.h +17 -17
  123. package/Microsoft.ReactNative.Cxx/ReactCommon/react/bridging/Value.h +20 -29
  124. package/Microsoft.ReactNative.Cxx/ReactCommon/react/debug/react_native_assert.h +2 -7
  125. package/Microsoft.ReactNative.Cxx/ReactCommon/react/timing/primitives.h +127 -115
  126. package/PropertySheets/Generated/PackageVersion.g.props +4 -4
  127. package/PropertySheets/Warnings.props +2 -1
  128. package/ReactCommon/ReactCommon.vcxproj +11 -2
  129. package/ReactCommon/TEMP_UntilReactCommonUpdate/cxxreact/CxxNativeModule.cpp +2 -2
  130. package/ReactCommon/TEMP_UntilReactCommonUpdate/cxxreact/Instance.cpp +4 -2
  131. package/ReactCommon/TEMP_UntilReactCommonUpdate/cxxreact/JSExecutor.cpp +4 -2
  132. package/ReactCommon/TEMP_UntilReactCommonUpdate/cxxreact/JSIndexedRAMBundle.cpp +4 -2
  133. package/ReactCommon/TEMP_UntilReactCommonUpdate/cxxreact/MethodCall.cpp +4 -2
  134. package/ReactCommon/TEMP_UntilReactCommonUpdate/cxxreact/ModuleRegistry.cpp +5 -3
  135. package/ReactCommon/TEMP_UntilReactCommonUpdate/cxxreact/NativeToJsBridge.cpp +4 -4
  136. package/ReactCommon/TEMP_UntilReactCommonUpdate/cxxreact/RAMBundleRegistry.cpp +6 -4
  137. package/ReactCommon/TEMP_UntilReactCommonUpdate/cxxreact/ReactMarker.cpp +2 -0
  138. package/ReactCommon/TEMP_UntilReactCommonUpdate/cxxreact/TraceSection.h +25 -35
  139. package/ReactCommon/TEMP_UntilReactCommonUpdate/jsi/jsi/test/testlib.cpp +86 -67
  140. package/ReactCommon/TEMP_UntilReactCommonUpdate/jsiexecutor/jsireact/JSIExecutor.cpp +15 -8
  141. package/ReactCommon/TEMP_UntilReactCommonUpdate/jsiexecutor/jsireact/JSINativeModules.cpp +4 -2
  142. package/ReactCommon/TEMP_UntilReactCommonUpdate/jsinspector-modern/NetworkIOAgent.cpp +84 -68
  143. package/ReactCommon/TEMP_UntilReactCommonUpdate/jsinspector-modern/NetworkIOAgent.h +31 -35
  144. package/ReactCommon/TEMP_UntilReactCommonUpdate/jsinspector-modern/Utf8.h +4 -5
  145. package/ReactCommon/TEMP_UntilReactCommonUpdate/jsinspector-modern/network/HttpUtils.cpp +2 -1
  146. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/nativemodule/core/ReactCommon/TurboModule.h +24 -36
  147. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/nativemodule/dom/NativeDOM.h +28 -66
  148. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/nativemodule/webperformance/NativePerformance.cpp +5 -0
  149. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/componentregistry/ComponentDescriptorRegistry.cpp +3 -3
  150. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/components/image/conversions.h +8 -4
  151. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/components/text/ParagraphShadowNode.cpp +19 -16
  152. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/components/text/platform/android/react/renderer/components/text/ParagraphState.h +8 -9
  153. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/components/view/AccessibilityPrimitives.h +25 -95
  154. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/components/view/BaseViewProps.cpp +36 -0
  155. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/components/view/accessibilityPropsConversions.h +85 -42
  156. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/core/EventDispatcher.cpp +2 -0
  157. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/core/EventQueueProcessor.cpp +2 -0
  158. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/imagemanager/primitives.h +25 -31
  159. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/uimanager/UIManager.cpp +19 -5
  160. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/runtime/ReactInstance.cpp +16 -1
  161. package/Scripts/Tfs/Layout-Desktop-Headers.ps1 +1 -15
  162. package/Scripts/creaternwapp.cmd +1 -1
  163. package/Scripts/perf/compare-results.js +357 -0
  164. package/Scripts/perf/create-perf-test.js +343 -0
  165. package/Scripts/perf/post-pr-comment.js +210 -0
  166. package/Shared/Hermes/HermesRuntimeTargetDelegate.cpp +8 -0
  167. package/Shared/Hermes/HermesRuntimeTargetDelegate.h +3 -0
  168. package/Shared/Shared.vcxitems +15 -3
  169. package/Shared/Shared.vcxitems.filters +11 -3
  170. package/codegen/NativeIntersectionObserverSpec.g.h +2 -0
  171. package/codegen/NativeNetworkingIOSSpec.g.h +2 -0
  172. package/codegen/NativePerformanceSpec.g.h +6 -0
  173. package/codegen/NativeReactNativeFeatureFlagsSpec.g.h +229 -139
  174. package/codegen/react/components/rnwcore/ActivityIndicatorView.g.h +2 -1
  175. package/codegen/react/components/rnwcore/AndroidDrawerLayout.g.h +42 -25
  176. package/codegen/react/components/rnwcore/AndroidHorizontalScrollContentView.g.h +2 -1
  177. package/codegen/react/components/rnwcore/AndroidProgressBar.g.h +2 -1
  178. package/codegen/react/components/rnwcore/AndroidSwipeRefreshLayout.g.h +11 -6
  179. package/codegen/react/components/rnwcore/AndroidSwitch.g.h +11 -6
  180. package/codegen/react/components/rnwcore/DebuggingOverlay.g.h +1 -0
  181. package/codegen/react/components/rnwcore/InputAccessory.g.h +2 -1
  182. package/codegen/react/components/rnwcore/ModalHostView.g.h +40 -23
  183. package/codegen/react/components/rnwcore/Props.cpp +6 -1
  184. package/codegen/react/components/rnwcore/Props.h +1 -0
  185. package/codegen/react/components/rnwcore/PullToRefreshView.g.h +11 -6
  186. package/codegen/react/components/rnwcore/SafeAreaView.g.h +1 -0
  187. package/codegen/react/components/rnwcore/Switch.g.h +11 -6
  188. package/codegen/react/components/rnwcore/UnimplementedNativeView.g.h +2 -1
  189. package/codegen/react/components/rnwcore/VirtualView.g.h +41 -8
  190. package/codegen/react/components/rnwcore/VirtualViewExperimental.g.h +45 -8
  191. package/codegen/rnwcoreJSI.h +3973 -6059
  192. package/index.js +6 -0
  193. package/index.windows.js +6 -0
  194. package/jest/mockComponent.js +6 -6
  195. package/jest/setup.js +15 -10
  196. package/package.json +27 -27
  197. package/src/private/components/virtualview/VirtualView.js +22 -27
  198. package/src/private/components/virtualview/VirtualViewExperimentalNativeComponent.js +6 -0
  199. package/src/private/featureflags/ReactNativeFeatureFlags.js +100 -19
  200. package/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +18 -3
  201. package/src/private/setup/setUpDefaultReactNativeEnvironment.js +6 -0
  202. package/src/private/specs_DEPRECATED/components/SwitchNativeComponent.js +1 -1
  203. package/src/private/specs_DEPRECATED/modules/NativeNetworkingIOS.js +1 -0
  204. package/src/private/webapis/dom/nodes/ReactNativeElement.js +12 -2
  205. package/src/private/webapis/intersectionobserver/IntersectionObserver.js +76 -15
  206. package/src/private/webapis/intersectionobserver/internals/IntersectionObserverManager.js +1 -0
  207. package/src/private/webapis/intersectionobserver/specs/NativeIntersectionObserver.js +1 -0
  208. package/src/private/webapis/performance/ResourceTiming.js +31 -4
  209. package/src/private/webapis/performance/internals/RawPerformanceEntry.js +4 -1
  210. package/src/private/webapis/performance/specs/NativePerformance.js +3 -0
  211. package/stubs/double-conversion/double-conversion.h +5 -0
  212. package/templates/cpp-app/template.config.js +1 -1
  213. package/templates/cpp-app/windows/MyApp/MyApp.vcxproj +1 -1
  214. package/templates/cpp-lib/template.config.js +1 -1
  215. package/templates/cpp-lib/windows/MyLib/MyLib.vcxproj +1 -1
  216. package/types/index.d.ts +1 -0
  217. package/types/public/ReactNativeTypes.d.ts +115 -2
  218. package/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricHostComponent.js +0 -152
  219. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/components/view/conversions.h +0 -1574
  220. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/core/ShadowNode.cpp +0 -528
  221. package/Scripts/OpenSSL.nuspec +0 -39
  222. package/Scripts/OpenSSL.targets +0 -36
  223. package/codegen/rnwcoreJSI-generated.cpp +0 -3470
@@ -0,0 +1,357 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ * Licensed under the MIT License.
4
+ *
5
+ * Compare perf test results from the current (head) run against
6
+ * committed baselines from the base branch. Produces a markdown
7
+ * comparison report and exits with non-zero if regressions detected.
8
+ *
9
+ * Usage:
10
+ * node vnext/Scripts/perf/compare-results.js [options]
11
+ *
12
+ * Options:
13
+ * --results <path> Path to CI results JSON (default: .perf-results/results.json)
14
+ * --baselines <dir> Path to base branch perf snapshots directory
15
+ * --output <path> Path to write markdown report (default: .perf-results/report.md)
16
+ * --fail-on-regression Exit 1 if regressions found (default: true in CI)
17
+ *
18
+ * @format
19
+ */
20
+
21
+ 'use strict';
22
+
23
+ const fs = require('fs');
24
+ const path = require('path');
25
+
26
+ function parseArgs() {
27
+ const args = process.argv.slice(2);
28
+ const opts = {
29
+ results: '.perf-results/results.json',
30
+ baselines: null, // auto-detect from test file paths
31
+ output: '.perf-results/report.md',
32
+ failOnRegression: !!process.env.CI,
33
+ };
34
+
35
+ for (let i = 0; i < args.length; i++) {
36
+ switch (args[i]) {
37
+ case '--results':
38
+ opts.results = args[++i];
39
+ break;
40
+ case '--baselines':
41
+ opts.baselines = args[++i];
42
+ break;
43
+ case '--output':
44
+ opts.output = args[++i];
45
+ break;
46
+ case '--fail-on-regression':
47
+ opts.failOnRegression = true;
48
+ break;
49
+ case '--no-fail-on-regression':
50
+ opts.failOnRegression = false;
51
+ break;
52
+ }
53
+ }
54
+ return opts;
55
+ }
56
+
57
+ function getSnapshotPath(testFilePath) {
58
+ const dir = path.dirname(testFilePath);
59
+ const basename = path.basename(testFilePath);
60
+ return path.join(dir, '__perf_snapshots__', `${basename}.perf-baseline.json`);
61
+ }
62
+
63
+ function loadSnapshot(filePath) {
64
+ if (fs.existsSync(filePath)) {
65
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
66
+ }
67
+ return {};
68
+ }
69
+
70
+ const DEFAULT_THRESHOLD = {
71
+ maxDurationIncrease: 10,
72
+ maxDuration: Infinity,
73
+ minAbsoluteDelta: 3,
74
+ maxRenderCount: 5,
75
+ minRuns: 10,
76
+ };
77
+
78
+ function resolveThreshold(threshold) {
79
+ return {
80
+ ...DEFAULT_THRESHOLD,
81
+ ...threshold,
82
+ // JSON.stringify(Infinity) === 'null', so restore it
83
+ maxDuration:
84
+ threshold.maxDuration === null || threshold.maxDuration === undefined
85
+ ? Infinity
86
+ : threshold.maxDuration,
87
+ };
88
+ }
89
+
90
+ function compareEntry(head, base, threshold) {
91
+ // median for comparison
92
+ const percentChange =
93
+ base.medianDuration > 0
94
+ ? ((head.medianDuration - base.medianDuration) / base.medianDuration) *
95
+ 100
96
+ : head.medianDuration > 0
97
+ ? Infinity
98
+ : 0;
99
+
100
+ const errors = [];
101
+
102
+ const absoluteDelta = head.medianDuration - base.medianDuration;
103
+ const minAbsoluteDelta =
104
+ threshold.minAbsoluteDelta ?? DEFAULT_THRESHOLD.minAbsoluteDelta;
105
+ if (
106
+ percentChange > threshold.maxDurationIncrease &&
107
+ absoluteDelta > minAbsoluteDelta
108
+ ) {
109
+ errors.push(
110
+ `Duration increased by ${percentChange.toFixed(1)}% / +${absoluteDelta.toFixed(2)}ms (threshold: ${threshold.maxDurationIncrease}% & ${minAbsoluteDelta}ms)`,
111
+ );
112
+ }
113
+ if (head.medianDuration > threshold.maxDuration) {
114
+ errors.push(
115
+ `Duration ${head.medianDuration.toFixed(2)}ms exceeds max ${threshold.maxDuration}ms`,
116
+ );
117
+ }
118
+ if (head.renderCount > threshold.maxRenderCount) {
119
+ errors.push(
120
+ `Render count ${head.renderCount} exceeds max ${threshold.maxRenderCount}`,
121
+ );
122
+ }
123
+
124
+ return {
125
+ name: head.name,
126
+ head,
127
+ base,
128
+ percentChange,
129
+ passed: errors.length === 0,
130
+ errors,
131
+ };
132
+ }
133
+
134
+ function compareSuite(suiteName, headSnaps, baseSnaps) {
135
+ const results = [];
136
+
137
+ for (const [key, headEntry] of Object.entries(headSnaps)) {
138
+ const baseEntry = baseSnaps[key];
139
+ const threshold = resolveThreshold({
140
+ ...DEFAULT_THRESHOLD,
141
+ ...(headEntry.threshold || {}),
142
+ });
143
+
144
+ if (!baseEntry) {
145
+ results.push({
146
+ name: headEntry.metrics.name,
147
+ head: headEntry.metrics,
148
+ base: null,
149
+ percentChange: null,
150
+ passed: true,
151
+ errors: [],
152
+ isNew: true,
153
+ });
154
+ continue;
155
+ }
156
+
157
+ results.push(compareEntry(headEntry.metrics, baseEntry.metrics, threshold));
158
+ }
159
+
160
+ // Detect removed scenarios
161
+ for (const key of Object.keys(baseSnaps)) {
162
+ if (!headSnaps[key]) {
163
+ results.push({
164
+ name: `[REMOVED] ${baseSnaps[key].metrics.name}`,
165
+ head: null,
166
+ base: baseSnaps[key].metrics,
167
+ percentChange: null,
168
+ passed: true,
169
+ errors: [],
170
+ isRemoved: true,
171
+ });
172
+ }
173
+ }
174
+
175
+ return {suiteName, results, hasRegressions: results.some(r => !r.passed)};
176
+ }
177
+
178
+ function generateMarkdown(suiteComparisons, ciResults) {
179
+ let md = '## Performance Test Results\n\n';
180
+
181
+ md += `**Branch:** \`${ciResults.branch}\` \n`;
182
+ md += `**Commit:** \`${ciResults.commitSha.slice(0, 8)}\` \n`;
183
+ md += `**Time:** ${ciResults.timestamp} \n`;
184
+ md += `**Tests:** ${ciResults.summary.passed}/${ciResults.summary.totalTests} passed \n\n`;
185
+
186
+ const allRegressions = suiteComparisons.filter(s => s.hasRegressions);
187
+ if (allRegressions.length > 0) {
188
+ md += '### โŒ Regressions Detected\n\n';
189
+ for (const suite of allRegressions) {
190
+ md += `#### ${suite.suiteName}\n\n`;
191
+ md += '| Scenario | Baseline | Current | Change | Status |\n';
192
+ md += '|----------|----------|---------|--------|--------|\n';
193
+ for (const r of suite.results.filter(x => !x.passed)) {
194
+ const baseline = r.base ? `${r.base.meanDuration.toFixed(2)}ms` : 'N/A';
195
+ const current = r.head ? `${r.head.meanDuration.toFixed(2)}ms` : 'N/A';
196
+ const change =
197
+ r.percentChange != null ? `+${r.percentChange.toFixed(1)}%` : 'N/A';
198
+ md += `| ${r.name} | ${baseline} | ${current} | ${change} | โŒ |\n`;
199
+ }
200
+ md += '\n';
201
+ for (const r of suite.results.filter(x => !x.passed)) {
202
+ if (r.errors.length > 0) {
203
+ md += `> **${r.name}:** ${r.errors.join('; ')} \n`;
204
+ }
205
+ }
206
+ md += '\n';
207
+ }
208
+ }
209
+
210
+ // Passed suites
211
+ const passedSuites = suiteComparisons.filter(s => !s.hasRegressions);
212
+ if (passedSuites.length > 0) {
213
+ md += '### โœ… Passed\n\n';
214
+ md += '<details>\n<summary>';
215
+ const totalPassed = passedSuites.reduce(
216
+ (acc, s) => acc + s.results.length,
217
+ 0,
218
+ );
219
+ md += `${totalPassed} scenario(s) across ${passedSuites.length} suite(s) โ€” no regressions`;
220
+ md += '</summary>\n\n';
221
+
222
+ for (const suite of passedSuites) {
223
+ md += `#### ${suite.suiteName}\n\n`;
224
+ md += '| Scenario | Mean | Median | StdDev | Renders | vs Baseline |\n';
225
+ md += '|----------|------|--------|--------|---------|-------------|\n';
226
+ for (const r of suite.results) {
227
+ if (!r.head) continue;
228
+ const change =
229
+ r.percentChange != null
230
+ ? `${r.percentChange >= 0 ? '+' : ''}${r.percentChange.toFixed(1)}%`
231
+ : 'new';
232
+ md +=
233
+ `| ${r.name}` +
234
+ ` | ${r.head.meanDuration.toFixed(2)}ms` +
235
+ ` | ${r.head.medianDuration.toFixed(2)}ms` +
236
+ ` | ยฑ${r.head.stdDev.toFixed(2)}ms` +
237
+ ` | ${r.head.renderCount}` +
238
+ ` | ${change} |\n`;
239
+ }
240
+ md += '\n';
241
+ }
242
+ md += '</details>\n';
243
+ }
244
+
245
+ // New scenarios
246
+ const allNew = suiteComparisons.flatMap(s => s.results.filter(r => r.isNew));
247
+ if (allNew.length > 0) {
248
+ md += '\n### ๐Ÿ†• New Scenarios\n\n';
249
+ md += `${allNew.length} new scenario(s) added (no baseline comparison). \n`;
250
+ md +=
251
+ 'These will become baselines after merge. Run `yarn perf:update` to capture locally.\n';
252
+ }
253
+
254
+ return md;
255
+ }
256
+
257
+ function main() {
258
+ const opts = parseArgs();
259
+
260
+ // 1. Load CI results JSON
261
+ if (!fs.existsSync(opts.results)) {
262
+ console.error(`โŒ Results file not found: ${opts.results}`);
263
+ console.error('Run perf tests with CI=true first: CI=true yarn perf:ci');
264
+ process.exit(1);
265
+ }
266
+
267
+ const ciResults = JSON.parse(fs.readFileSync(opts.results, 'utf-8'));
268
+ console.log(
269
+ `๐Ÿ“Š Loaded ${ciResults.suites.length} suite(s) from ${opts.results}`,
270
+ );
271
+
272
+ // 2. Compare each suite against its committed baseline
273
+ const suiteComparisons = [];
274
+
275
+ for (const suite of ciResults.suites) {
276
+ // Head snapshots come from the CI run
277
+ const headSnaps = suite.snapshots || {};
278
+
279
+ // Base snapshots come from the committed .perf.snap files
280
+ // In CI, the base branch is checked out into a separate dir,
281
+ // or we read the committed snapshots from the test file path
282
+ let baseSnapPath;
283
+ if (opts.baselines) {
284
+ // Explicit baseline directory โ€” look for matching snap file
285
+ const relPath = path.relative(process.cwd(), suite.testFilePath);
286
+ baseSnapPath = path.join(
287
+ opts.baselines,
288
+ path.dirname(relPath),
289
+ '__perf_snapshots__',
290
+ `${path.basename(suite.testFilePath)}.perf.snap`,
291
+ );
292
+ } else {
293
+ // Default: baselines live adjacent to test files (committed in repo)
294
+ baseSnapPath = getSnapshotPath(suite.testFilePath);
295
+ }
296
+
297
+ const baseSnaps = loadSnapshot(baseSnapPath);
298
+ console.log(
299
+ ` ๐Ÿ“‹ ${suite.suiteName}: ${Object.keys(headSnaps).length} head / ${Object.keys(baseSnaps).length} base snapshots`,
300
+ );
301
+
302
+ suiteComparisons.push(compareSuite(suite.suiteName, headSnaps, baseSnaps));
303
+ }
304
+
305
+ // 3. Generate markdown report
306
+ const markdown = generateMarkdown(suiteComparisons, ciResults);
307
+
308
+ const outputDir = path.dirname(opts.output);
309
+ if (!fs.existsSync(outputDir)) {
310
+ fs.mkdirSync(outputDir, {recursive: true});
311
+ }
312
+ fs.writeFileSync(opts.output, markdown, 'utf-8');
313
+ console.log(`\n๐Ÿ“ Report written to ${opts.output}`);
314
+
315
+ // 4. Also write a machine-readable summary
316
+ const summaryPath = opts.output.replace(/\.md$/, '.json');
317
+ const summaryData = {
318
+ hasRegressions: suiteComparisons.some(s => s.hasRegressions),
319
+ totalSuites: suiteComparisons.length,
320
+ totalScenarios: suiteComparisons.reduce(
321
+ (acc, s) => acc + s.results.length,
322
+ 0,
323
+ ),
324
+ regressions: suiteComparisons.flatMap(s =>
325
+ s.results
326
+ .filter(r => !r.passed)
327
+ .map(r => ({
328
+ suite: s.suiteName,
329
+ scenario: r.name,
330
+ percentChange: r.percentChange,
331
+ errors: r.errors,
332
+ })),
333
+ ),
334
+ };
335
+ fs.writeFileSync(summaryPath, JSON.stringify(summaryData, null, 2) + '\n');
336
+
337
+ // 5. Print summary
338
+ const hasRegressions = suiteComparisons.some(s => s.hasRegressions);
339
+ if (hasRegressions) {
340
+ const count = summaryData.regressions.length;
341
+ console.error(`\nโŒ ${count} regression(s) detected!`);
342
+ for (const reg of summaryData.regressions) {
343
+ console.error(
344
+ ` - ${reg.suite} / ${reg.scenario}: ${reg.errors.join('; ')}`,
345
+ );
346
+ }
347
+ if (opts.failOnRegression) {
348
+ process.exit(1);
349
+ }
350
+ } else {
351
+ console.log(
352
+ `\nโœ… All ${summaryData.totalScenarios} scenarios passed โ€” no regressions.`,
353
+ );
354
+ }
355
+ }
356
+
357
+ main();
@@ -0,0 +1,343 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Copyright (c) Microsoft Corporation.
4
+ * Licensed under the MIT License.
5
+ *
6
+ * Interactive CLI to scaffold a new component performance test.
7
+ *
8
+ * Usage:
9
+ * node vnext/Scripts/perf/create-perf-test.js
10
+ * yarn perf:create (from e2e-test-app-fabric)
11
+ *
12
+ * Generates a `.perf-test.tsx` file with:
13
+ * - Class extending ComponentPerfTestBase
14
+ * - Standard mount / unmount / rerender tests
15
+ * - Placeholder for custom scenarios
16
+ * - Correct imports, test ID, and category
17
+ *
18
+ * @format
19
+ */
20
+
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+ const readline = require('readline');
24
+
25
+ function ask(rl, question, defaultValue) {
26
+ const suffix = defaultValue ? ` (${defaultValue})` : '';
27
+ return new Promise(resolve => {
28
+ rl.question(`${question}${suffix}: `, answer => {
29
+ resolve(answer.trim() || defaultValue || '');
30
+ });
31
+ });
32
+ }
33
+
34
+ function choose(rl, question, options, defaultIndex = 0) {
35
+ return new Promise(resolve => {
36
+ console.log(`\n${question}`);
37
+ options.forEach((opt, i) => {
38
+ const marker = i === defaultIndex ? ' (default)' : '';
39
+ console.log(` ${i + 1}. ${opt}${marker}`);
40
+ });
41
+ rl.question(`Choice [${defaultIndex + 1}]: `, answer => {
42
+ const idx = parseInt(answer, 10) - 1;
43
+ resolve(
44
+ idx >= 0 && idx < options.length ? options[idx] : options[defaultIndex],
45
+ );
46
+ });
47
+ });
48
+ }
49
+
50
+ function toPascalCase(str) {
51
+ return str
52
+ .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
53
+ .replace(/^(.)/, (_, c) => c.toUpperCase());
54
+ }
55
+
56
+ function toKebabCase(str) {
57
+ return str
58
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
59
+ .replace(/[\s_]+/g, '-')
60
+ .toLowerCase();
61
+ }
62
+
63
+ /**
64
+ * Split a props string into individual prop tokens,
65
+ * correctly handling nested braces like onPress={() => {}}.
66
+ */
67
+ function splitProps(propsStr) {
68
+ const props = [];
69
+ let current = '';
70
+ let braceDepth = 0;
71
+ let inString = null; // '"' or "'"
72
+
73
+ for (let i = 0; i < propsStr.length; i++) {
74
+ const ch = propsStr[i];
75
+
76
+ // Track string boundaries
77
+ if ((ch === '"' || ch === "'") && !inString) {
78
+ inString = ch;
79
+ } else if (ch === inString) {
80
+ inString = null;
81
+ }
82
+
83
+ if (!inString) {
84
+ if (ch === '{') braceDepth++;
85
+ if (ch === '}') braceDepth--;
86
+ }
87
+
88
+ // Split on whitespace only when outside braces/strings
89
+ if (ch === ' ' && braceDepth === 0 && !inString) {
90
+ if (current.trim()) props.push(current.trim());
91
+ current = '';
92
+ } else {
93
+ current += ch;
94
+ }
95
+ }
96
+ if (current.trim()) props.push(current.trim());
97
+ return props;
98
+ }
99
+
100
+ const CATEGORIES = ['core', 'extended', 'interactive', 'list', 'community'];
101
+
102
+ const PERF_TEST_ROOT = path.resolve(
103
+ __dirname,
104
+ '../../..',
105
+ 'packages/e2e-test-app-fabric/test/__perf__',
106
+ );
107
+
108
+ // Known required props for common RN components.
109
+ // These are auto-applied so the generated test works out of the box.
110
+ const KNOWN_REQUIRED_PROPS = {
111
+ Button: 'title="Perf Test" onPress={() => {}}',
112
+ Switch: 'value={false}',
113
+ Image: 'source={{uri: "https://example.com/img.png"}}',
114
+ FlatList: 'data={[]} renderItem={() => null}',
115
+ SectionList: 'sections={[]} renderItem={() => null}',
116
+ Modal: 'visible={true}',
117
+ TouchableHighlight: 'onPress={() => {}}',
118
+ TouchableOpacity: 'onPress={() => {}}',
119
+ Pressable: 'onPress={() => {}}',
120
+ };
121
+
122
+ function generateTestFile({
123
+ componentName,
124
+ pascalName,
125
+ kebabName,
126
+ importPath,
127
+ category,
128
+ hasChildren,
129
+ childrenText,
130
+ requiredProps,
131
+ }) {
132
+ const testId = `perf-test-${kebabName}`;
133
+ const instanceName = `${pascalName.charAt(0).toLowerCase() + pascalName.slice(1)}PerfTest`;
134
+
135
+ const allProps = [`testID={this.testId}`];
136
+ if (requiredProps) {
137
+ // Parse individual JSX props, handling nested braces like {() => {}}
138
+ allProps.push(...splitProps(requiredProps));
139
+ }
140
+ allProps.push('style={styles.default}', '{...props}');
141
+
142
+ let createComponentBody;
143
+ if (hasChildren) {
144
+ const propLines = allProps.map(p => ` ${p}`).join('\n');
145
+ createComponentBody = ` return (\n <${componentName}\n${propLines}>\n ${childrenText}\n </${componentName}>\n );`;
146
+ } else if (allProps.length > 3) {
147
+ const propLines = allProps.map(p => ` ${p}`).join('\n');
148
+ createComponentBody = ` return (\n <${componentName}\n${propLines}\n />\n );`;
149
+ } else {
150
+ createComponentBody = ` return <${componentName} ${allProps.join(' ')} />;`;
151
+ }
152
+
153
+ return `/**
154
+ * Copyright (c) Microsoft Corporation.
155
+ * Licensed under the MIT License.
156
+ *
157
+ * Performance tests for ${componentName} component.
158
+ *
159
+ * @format
160
+ */
161
+
162
+ import * as React from 'react';
163
+ import {${componentName}, StyleSheet} from '${importPath}';
164
+ import {
165
+ ComponentPerfTestBase,
166
+ measurePerf,
167
+ } from '@react-native-windows/perf-testing';
168
+ import type {IScenario, PerfMetrics} from '@react-native-windows/perf-testing';
169
+
170
+ class ${pascalName}PerfTest extends ComponentPerfTestBase {
171
+ readonly componentName = '${componentName}';
172
+ readonly category = '${category}' as const;
173
+ readonly testId = '${testId}';
174
+
175
+ createComponent(props?: Record<string, unknown>): React.ReactElement {
176
+ ${createComponentBody}
177
+ }
178
+
179
+ getCustomScenarios(): IScenario[] {
180
+ return [];
181
+ }
182
+ }
183
+
184
+ const ${instanceName} = new ${pascalName}PerfTest();
185
+
186
+ describe('${componentName} Performance', () => {
187
+ test('mount time', async () => {
188
+ const perf = await ${instanceName}.measureMount();
189
+ expect(perf).toMatchPerfSnapshot();
190
+ });
191
+
192
+ test('unmount time', async () => {
193
+ const perf = await ${instanceName}.measureUnmount();
194
+ expect(perf).toMatchPerfSnapshot();
195
+ });
196
+
197
+ test('rerender time', async () => {
198
+ const perf = await ${instanceName}.measureRerender();
199
+ expect(perf).toMatchPerfSnapshot();
200
+ });
201
+ });
202
+
203
+ const styles = StyleSheet.create({
204
+ default: {
205
+ });
206
+ `;
207
+ }
208
+
209
+ async function main() {
210
+ const rl = readline.createInterface({
211
+ input: process.stdin,
212
+ output: process.stdout,
213
+ });
214
+
215
+ console.log('\n๐Ÿงช React Native Windows โ€” Performance Test Generator\n');
216
+ console.log('This will create a new .perf-test.tsx file with mount,');
217
+ console.log('unmount, and rerender tests for your component.\n');
218
+ console.log('Examples:');
219
+ console.log(
220
+ ' Component: ScrollView | Import: react-native | Category: core | Children: yes',
221
+ );
222
+ console.log(
223
+ ' Component: Button | Import: react-native | Category: core | Children: no',
224
+ );
225
+ console.log(
226
+ ' Component: Image | Import: react-native | Category: core | Children: no',
227
+ );
228
+ console.log(
229
+ ' Component: Pressable | Import: react-native | Category: interactive | Children: yes',
230
+ );
231
+ console.log(
232
+ ' Component: Slider | Import: @react-native-community/slider | Category: community | Children: no',
233
+ );
234
+ console.log('');
235
+
236
+ const rawName = await ask(
237
+ rl,
238
+ 'Component name (e.g. ScrollView, Image, Pressable)',
239
+ );
240
+ if (!rawName) {
241
+ console.error('โŒ Component name is required.');
242
+ rl.close();
243
+ process.exit(1);
244
+ }
245
+
246
+ const pascalName = toPascalCase(rawName);
247
+ const kebabName = toKebabCase(rawName);
248
+
249
+ const importPath = await ask(
250
+ rl,
251
+ 'Import path for the component',
252
+ 'react-native',
253
+ );
254
+
255
+ const category = await choose(
256
+ rl,
257
+ 'Select component category:',
258
+ CATEGORIES,
259
+ 0,
260
+ );
261
+
262
+ const hasChildrenAnswer = await ask(
263
+ rl,
264
+ 'Does this component render children? (y/N)',
265
+ 'N',
266
+ );
267
+ const hasChildren = hasChildrenAnswer.toLowerCase() === 'y';
268
+ let childrenText = 'Sample Content';
269
+ if (hasChildren) {
270
+ const customChildren = await ask(
271
+ rl,
272
+ 'Children text/content',
273
+ 'Sample Content',
274
+ );
275
+ childrenText = customChildren;
276
+ }
277
+
278
+ let requiredProps = KNOWN_REQUIRED_PROPS[rawName] || '';
279
+ if (requiredProps) {
280
+ console.log(`\n Auto-detected required props: ${requiredProps}`);
281
+ const override = await ask(
282
+ rl,
283
+ 'Required props (Enter to accept, or type custom)',
284
+ requiredProps,
285
+ );
286
+ requiredProps = override;
287
+ } else {
288
+ const customProps = await ask(
289
+ rl,
290
+ 'Required props (e.g. title="Test" onPress={() => {}}) โ€“ leave blank if none',
291
+ '',
292
+ );
293
+ requiredProps = customProps;
294
+ }
295
+
296
+ rl.close();
297
+
298
+ const categoryDir = path.join(PERF_TEST_ROOT, category);
299
+ if (!fs.existsSync(categoryDir)) {
300
+ fs.mkdirSync(categoryDir, {recursive: true});
301
+ console.log(
302
+ `\n๐Ÿ“ Created directory: ${path.relative(process.cwd(), categoryDir)}`,
303
+ );
304
+ }
305
+
306
+ const outputFile = path.join(categoryDir, `${pascalName}.perf-test.tsx`);
307
+ if (fs.existsSync(outputFile)) {
308
+ console.error(
309
+ `\nโŒ File already exists: ${path.relative(process.cwd(), outputFile)}`,
310
+ );
311
+ console.error(' Delete it first or choose a different name.');
312
+ process.exit(1);
313
+ }
314
+
315
+ const content = generateTestFile({
316
+ componentName: rawName,
317
+ pascalName,
318
+ kebabName,
319
+ importPath,
320
+ category,
321
+ hasChildren,
322
+ childrenText,
323
+ requiredProps,
324
+ });
325
+
326
+ fs.writeFileSync(outputFile, content, 'utf8');
327
+
328
+ const relPath = path.relative(process.cwd(), outputFile);
329
+ console.log(`\nโœ… Created: ${relPath}`);
330
+ console.log(`\nNext steps:`);
331
+ console.log(` 1. Review and customize the generated test file`);
332
+ console.log(` 2. Add any component-specific custom scenarios`);
333
+ console.log(` 3. Run: yarn perf --testPathPattern=${pascalName}`);
334
+ console.log(
335
+ ` 4. Generate baseline: yarn perf:update --testPathPattern=${pascalName}`,
336
+ );
337
+ console.log('');
338
+ }
339
+
340
+ main().catch(err => {
341
+ console.error('Error:', err);
342
+ process.exit(1);
343
+ });