catalyst-core 0.3.0-beta.4 → 0.3.0-canary.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.
- package/changelog.md +80 -0
- package/dist/errors/generateDocs.js +100 -0
- package/dist/errors/index.js +243 -0
- package/dist/errors/registry.js +483 -0
- package/dist/errors-index.json +506 -0
- package/dist/globals.d.ts +62 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.jsx +5 -8
- package/dist/logger.js +1 -1
- package/dist/native/androidProject/app/build.gradle.kts +142 -7
- package/dist/native/androidProject/app/src/main/AndroidManifest.xml +4 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +2 -2
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/BarcodeDetector.kt +3 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/CameraSessionManager.kt +3 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +43 -8
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/BridgeUtilsTest.kt +181 -1
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/CameraUtilsTest.kt +156 -236
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FileSizeRouterUtilsTest.kt +471 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FileUtilsTest.kt +222 -226
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FrameworkServerUtilsTest.kt +300 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/IntentUtilsTest.kt +244 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/MetricsMonitorTest.kt +359 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/NativeBridgeTest.kt +108 -63
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/NotificationUtilsTest.kt +329 -385
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/OfflineCacheServiceTest.kt +302 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/PerfEventBufferTest.kt +133 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/UtilsKtTest.kt +152 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/WebCacheManagerTest.kt +145 -324
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/BarcodeDetectorTest.kt +132 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/HoldControllerTest.kt +155 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/TorchControllerTest.kt +150 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/VideoStreamStateMachineTest.kt +120 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/VideoStreamStateTest.kt +62 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/ViewfinderMapperTest.kt +143 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/ZoomControllerTest.kt +421 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/design/DesignTokensTest.kt +207 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/plugins/PluginBridgeTest.kt +368 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/FridaDetectorTest.kt +193 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/SecurityCheckManagerTest.kt +260 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/SecurityCheckSchedulerTest.kt +166 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/DeviceInfoUtilsTest.kt +167 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/DownloadUtilsTest.kt +197 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/NetworkMonitorTest.kt +271 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/PushNotificationUtilsTest.kt +119 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/SafeAreaUtilsTest.kt +108 -0
- package/dist/native/androidProject/build.gradle.kts +1 -0
- package/dist/native/androidProject/gradle/libs.versions.toml +26 -0
- package/dist/native/androidProject/settings.gradle.kts +11 -8
- package/dist/native/androidSetup.js +2 -2
- package/dist/native/bridge/WebBridge.d.ts +98 -0
- package/dist/native/bridge/WebBridge.js +40 -6
- package/dist/native/bridge/constants/NativeInterfaces.d.ts +391 -0
- package/dist/native/bridge/errors.d.ts +67 -0
- package/dist/native/bridge/errors.js +66 -151
- package/dist/native/bridge/hooks/index.d.ts +13 -0
- package/dist/native/bridge/hooks/index.js +0 -6
- package/dist/native/bridge/hooks/useAI.d.ts +1 -0
- package/dist/native/bridge/hooks/useAI.js +3 -1
- package/dist/native/bridge/hooks/useCamera.d.ts +86 -0
- package/dist/native/bridge/hooks/useCamera.js +35 -6
- package/dist/native/bridge/hooks/useDataProtection.d.ts +17 -0
- package/dist/native/bridge/hooks/useDeviceInfo.d.ts +34 -0
- package/dist/native/bridge/hooks/useDeviceInfo.js +5 -0
- package/dist/native/bridge/hooks/useFilePicker.d.ts +53 -0
- package/dist/native/bridge/hooks/useFilePicker.js +13 -0
- package/dist/native/bridge/hooks/useGoogleSignIn.d.ts +12 -0
- package/dist/native/bridge/hooks/useHapticFeedback.d.ts +76 -0
- package/dist/native/bridge/hooks/useHapticFeedback.js +6 -1
- package/dist/native/bridge/hooks/useIntent.d.ts +33 -0
- package/dist/native/bridge/hooks/useIntent.js +2 -0
- package/dist/native/bridge/hooks/useNativeTransition.d.ts +53 -0
- package/dist/native/bridge/hooks/useNativeTransition.js +14 -7
- package/dist/native/bridge/hooks/useNetworkStatus.d.ts +22 -0
- package/dist/native/bridge/hooks/useNetworkStatus.js +7 -0
- package/dist/native/bridge/hooks/useNotification.d.ts +83 -0
- package/dist/native/bridge/hooks/useNotification.js +81 -34
- package/dist/native/bridge/hooks/useSafeArea.d.ts +12 -0
- package/dist/native/bridge/hooks/useSafeArea.js +8 -0
- package/dist/native/bridge/hooks/useVideoStream.d.ts +67 -0
- package/dist/native/bridge/hooks/useVideoStream.js +38 -52
- package/dist/native/bridge/hooks/utils.d.ts +4 -0
- package/dist/native/bridge/hooks.d.ts +1 -0
- package/dist/native/bridge/perf/collectors/ApiCollector.d.ts +30 -0
- package/dist/native/bridge/perf/collectors/ApiCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/CacheCollector.d.ts +43 -0
- package/dist/native/bridge/perf/collectors/CacheCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/HookCollector.d.ts +37 -0
- package/dist/native/bridge/perf/collectors/HookCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/InsightsCollector.d.ts +103 -0
- package/dist/native/bridge/perf/collectors/InsightsCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/InteractionCollector.d.ts +53 -0
- package/dist/native/bridge/perf/collectors/InteractionCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/KeyboardCollector.d.ts +30 -0
- package/dist/native/bridge/perf/collectors/KeyboardCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/NavigationCollector.d.ts +37 -0
- package/dist/native/bridge/perf/collectors/NavigationCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/NetworkTimingCollector.d.ts +30 -0
- package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/RenderCollector.d.ts +47 -0
- package/dist/native/bridge/perf/collectors/RenderCollector.js +8 -0
- package/dist/native/bridge/perf/collectors/ScrollCollector.d.ts +29 -0
- package/dist/native/bridge/perf/collectors/ScrollCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/ThreadCollector.d.ts +31 -0
- package/dist/native/bridge/perf/collectors/ThreadCollector.js +4 -0
- package/dist/native/bridge/perf/core/constants.d.ts +76 -0
- package/dist/native/bridge/perf/core/measure.d.ts +39 -0
- package/dist/native/bridge/perf/core/measure.js +4 -0
- package/dist/native/bridge/perf/core/session.d.ts +49 -0
- package/dist/native/bridge/perf/core/session.js +4 -0
- package/dist/native/bridge/perf/core/store.d.ts +37 -0
- package/dist/native/bridge/perf/core/store.js +4 -0
- package/dist/native/bridge/perf/core/summary.d.ts +60 -0
- package/dist/native/bridge/perf/core/traceExport.d.ts +10 -0
- package/dist/native/bridge/perf/core/waterfalls.d.ts +2 -0
- package/dist/native/bridge/perf/index.d.ts +63 -0
- package/dist/native/bridge/perf/index.js +4 -0
- package/dist/native/bridge/safeArea.d.ts +11 -0
- package/dist/native/bridge/safeArea.js +2 -0
- package/dist/native/bridge/useBaseHook.d.ts +137 -0
- package/dist/native/bridge/useBaseHook.js +34 -4
- package/dist/native/bridge/utils/CameraUtils.d.ts +94 -0
- package/dist/native/bridge/utils/CameraUtils.js +4 -0
- package/dist/native/bridge/utils/FileObjectConverter.d.ts +36 -0
- package/dist/native/bridge/utils/NativeBridge.d.ts +270 -0
- package/dist/native/bridge/utils/NativeBridge.js +10 -0
- package/dist/native/buildAndroid/index.js +1 -1
- package/dist/native/buildAppAndroid.js +9 -0
- package/dist/native/buildAppIos.js +16 -1
- package/dist/native/buildErrorFormat.js +57 -0
- package/dist/native/buildIos/config.js +15 -1
- package/dist/native/buildIos/index.js +1 -1
- package/dist/native/buildIos/plugins.js +10 -1
- package/dist/native/iosnativeWebView/CoreLogic/Package.swift +36 -0
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Camera/VideoStreamState.swift +1 -1
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Camera/VideoStreamStateMachine.swift +6 -4
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/BootTimingUtility.swift +3 -3
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/FrameworkServerUtils.swift +14 -9
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NetworkMonitor.swift +11 -9
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NotificationHandlerProvider.swift +1 -1
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/URLWhitelistManager.swift +7 -7
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/AnyCodableTests.swift +154 -0
- package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/BootTimingUtilityTests.swift +23 -5
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/CatalystConstantsTests.swift +192 -0
- package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/ConfigMappingTests.swift +1 -1
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/FrameworkServerUtilsErrorPathTests.swift +106 -0
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/FrameworkServerUtilsLoopbackTests.swift +243 -0
- package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/FrameworkServerUtilsTests.swift +1 -1
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/NetworkMonitorTests.swift +119 -0
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/NotificationHandlerProviderTests.swift +59 -0
- package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/NotificationHandlerTests.swift +1 -1
- package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/URLWhitelistManagerTests.swift +1 -1
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/VideoStreamStateMachineTests.swift +171 -0
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/VideoStreamStateTests.swift +91 -0
- package/dist/native/iosnativeWebView/Package.swift +10 -9
- package/dist/native/iosnativeWebView/Sources/CatalystNotifications/LocalNotificationHandler.swift +7 -2
- package/dist/native/iosnativeWebView/Sources/CatalystNotifications/NotificationManager.swift +1 -2
- package/dist/native/iosnativeWebView/Sources/CatalystNotifications/PushNotificationHandler.swift +5 -4
- package/dist/native/iosnativeWebView/Sources/Core/Camera/CameraSessionManager.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/HoldController.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/NativeCameraManager.swift +6 -5
- package/dist/native/iosnativeWebView/Sources/Core/Camera/ZoomController.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/Plugins/PluginBridge.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +31 -30
- package/dist/native/iosnativeWebView/Sources/Core/Utils/CameraTransportUtils.swift +2 -1
- package/dist/native/iosnativeWebView/Sources/Core/Utils/DeviceInfoUtils.swift +1 -1
- package/dist/native/iosnativeWebView/Sources/Core/Utils/FilePickerHandler.swift +22 -19
- package/dist/native/iosnativeWebView/Sources/Core/Utils/ImageHandler.swift +40 -39
- package/dist/native/iosnativeWebView/Sources/Core/Utils/PerfEventBuffer.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/ResourceURLProtocol.swift +15 -15
- package/dist/native/iosnativeWebView/Sources/Core/Utils/WebKitConfig.swift +4 -3
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeCommandHandler.swift +6 -1
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeDelegateHandler.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeFileHandler.swift +2 -1
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeJavaScriptInterface.swift +2 -1
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +3 -5
- package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +20 -13
- package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashView.swift +6 -5
- package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashViewModel.swift +14 -13
- package/dist/native/iosnativeWebView/Sources/Core/WebView/TransitionManager.swift +3 -3
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +9 -8
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewModel.swift +7 -6
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +24 -25
- package/dist/native/iosnativeWebView/iosnativeWebView/AppDelegate.swift +6 -1
- package/dist/native/iosnativeWebView/iosnativeWebView/ContentView.swift +1 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/iosnativeWebViewApp.swift +4 -0
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +2 -22
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeMessageValidatorTests.swift +1 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/CacheManagerTests.swift +1 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/FilePickerHandlerTests.swift +2 -1
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/NativeBridgeTests.swift +15 -9
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/WebViewTests.swift +1 -0
- package/dist/native/setupEmulatorIos.js +2 -2
- package/dist/native/terminalProgress.js +17 -17
- package/dist/router/ClientRouter.d.ts +8 -0
- package/dist/router/ClientRouter.js +13 -11
- package/dist/router/ServerRouter.d.ts +13 -0
- package/dist/router/ServerRouter.js +19 -7
- package/dist/scripts/build.js +14 -2
- package/dist/scripts/loadEnvironmentVariables.js +3 -2
- package/dist/scripts/plugins.js +1 -1
- package/dist/scripts/preServerInit.js +49 -3
- package/dist/scripts/preflight.js +111 -0
- package/dist/scripts/scriptUtils.js +46 -1
- package/dist/scripts/serve.js +7 -1
- package/dist/scripts/start.js +8 -1
- package/dist/server/expressServer.d.ts +1 -0
- package/dist/server/expressServer.js +247 -271
- package/dist/server/manifestCache.d.ts +2 -0
- package/dist/server/manifestCache.js +25 -33
- package/dist/server/renderer/ChunkExtractor.d.ts +44 -0
- package/dist/server/renderer/ChunkExtractor.js +113 -125
- package/dist/server/renderer/document/Body.d.ts +21 -0
- package/dist/server/renderer/document/Body.jsx +45 -45
- package/dist/server/renderer/document/FastRefresh.d.ts +3 -0
- package/dist/server/renderer/document/FastRefresh.jsx +7 -13
- package/dist/server/renderer/document/Head.d.ts +20 -0
- package/dist/server/renderer/document/Head.jsx +32 -32
- package/dist/server/renderer/document/index.d.ts +2 -0
- package/dist/server/renderer/document/index.jsx +2 -2
- package/dist/server/renderer/extract.d.ts +67 -0
- package/dist/server/renderer/extract.js +92 -89
- package/dist/server/renderer/handler.d.ts +2 -0
- package/dist/server/renderer/handler.jsx +401 -404
- package/dist/server/renderer/index.d.ts +2 -0
- package/dist/server/renderer/index.js +5 -8
- package/dist/server/renderer/render.d.ts +35 -0
- package/dist/server/renderer/render.js +33 -32
- package/dist/server/utils/botDetectionMiddleware.d.ts +1 -0
- package/dist/server/utils/botDetectionMiddleware.js +33 -23
- package/dist/server/utils/cjsRequire.d.ts +8 -0
- package/dist/server/utils/cjsRequire.js +2 -2
- package/dist/server/utils/userAgentUtil.d.ts +7 -0
- package/dist/server/utils/userAgentUtil.js +68 -71
- package/dist/server/utils/validator.d.ts +31 -0
- package/dist/server/utils/validator.js +133 -158
- package/dist/vite/inject-cache-key-plugin.js +3 -27
- package/dist/vite/moduleAliases.js +36 -0
- package/dist/vite/node-loader.mjs +2 -29
- package/dist/vite/resolveDevServerConfig.js +4 -1
- package/dist/vite/vite.config.js +13 -31
- package/dist/web-router/components/MetaTag.d.ts +4 -0
- package/dist/web-router/components/MetaTag.jsx +26 -28
- package/dist/web-router/components/MetaTag.test.jsx +100 -0
- package/dist/web-router/components/RouterDataProvider.d.ts +96 -0
- package/dist/web-router/components/RouterDataProvider.jsx +260 -259
- package/dist/web-router/components/RouterDataProvider.server.test.jsx +98 -0
- package/dist/web-router/components/RouterDataProvider.test.jsx +310 -0
- package/dist/web-router/components/Split.d.ts +74 -0
- package/dist/web-router/components/Split.jsx +147 -156
- package/dist/web-router/components/Split.server.test.jsx +52 -0
- package/dist/web-router/components/Split.test.jsx +167 -0
- package/dist/web-router/components/SplitInview.d.ts +22 -0
- package/dist/web-router/components/SplitInview.jsx +78 -89
- package/dist/web-router/components/SplitInview.test.jsx +112 -0
- package/dist/web-router/components/SsrRequestContext.d.ts +20 -0
- package/dist/web-router/components/SsrRequestContext.jsx +17 -7
- package/dist/web-router/components/SsrRequestContext.test.jsx +53 -0
- package/dist/web-router/context.d.ts +15 -0
- package/dist/web-router/context.jsx +5 -5
- package/dist/web-router/context.test.jsx +10 -0
- package/dist/web-router/utils/metaDataUtils.d.ts +24 -0
- package/dist/web-router/utils/metaDataUtils.jsx +86 -86
- package/dist/web-router/utils/metaDataUtils.test.jsx +188 -0
- package/dist/web-router/vitest.setup.js +12 -0
- package/mcp_v2/knowledge-base.json +52 -8
- package/mcp_v2/mcp.js +140 -3
- package/mcp_v2/package-lock.json +2031 -0
- package/mcp_v2/package.json +13 -2
- package/mcp_v2/scripts/copyErrorsIndex.cjs +34 -0
- package/mcp_v2/test/errors.test.ts +123 -0
- package/mcp_v2/test/github.test.ts +177 -0
- package/mcp_v2/tools/errors.d.ts +20 -0
- package/mcp_v2/tools/errors.js +63 -0
- package/mcp_v2/tools/github.d.ts +41 -0
- package/mcp_v2/tools/github.js +231 -0
- package/mcp_v2/vitest.config.mjs +22 -0
- package/package.json +60 -11
- package/test/errors/docsDrift.test.ts +113 -0
- package/test/errors/index.test.ts +200 -0
- package/test/errors/registry.test.ts +108 -0
- package/test/scripts/preflight.test.ts +121 -0
- package/test/server/ChunkExtractor.test.ts +97 -0
- package/test/server/extract.test.ts +128 -0
- package/test/server/fixtures/template/server/document.jsx +19 -0
- package/test/server/fixtures/template/server/index.js +10 -0
- package/test/server/fixtures/template/src/js/containers/App/index.jsx +33 -0
- package/test/server/fixtures/template/src/js/routes/index.jsx +72 -0
- package/test/server/fixtures/template/src/js/routes/utils.jsx +39 -0
- package/test/server/fixtures/template/src/js/store/index.js +19 -0
- package/test/server/handler.assets.test.ts +331 -0
- package/test/server/handler.test.ts +210 -0
- package/test/server/manifestCache.test.ts +71 -0
- package/test/server/render.test.ts +90 -0
- package/test/server/scriptUtils.test.ts +103 -0
- package/test/server/userAgentUtil.test.ts +48 -0
- package/test/server/validator.test.ts +228 -0
- package/test/vite/resolveDevServerConfig.test.js +4 -1
- package/tsconfig.test.json +23 -0
- package/vitest.config.ts +93 -0
- package/dist/caching.js +0 -1
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/DeviceInfoUtilsTest.kt +0 -187
- package/dist/native/bridge/standardizedHooks.js +0 -611
- package/dist/router.js +0 -1
- package/dist/scripts/validator.js +0 -149
- package/dist/web-router/hooks.jsx +0 -72
- package/dist/web-router/types/components/MetaTag.d.ts +0 -2
- package/dist/web-router/types/components/RouterDataProvider.d.ts +0 -95
- package/dist/web-router/types/context.d.ts +0 -14
- package/dist/web-router/types/hooks.d.ts +0 -1
- package/dist/web-router/types/index.d.ts +0 -3
- package/dist/web-router/types/utils/metaDataUtils.d.ts +0 -24
- /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Constants/CatalystConstants.swift +0 -0
- /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Constants/ConfigConstants.swift +0 -0
- /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Notifications/NotificationModels.swift +0 -0
- /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Protocols/NotificationHandlerProtocol.swift +0 -0
- /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NullNotificationHandler.swift +0 -0
|
@@ -1,17 +1,32 @@
|
|
|
1
|
-
import React, { Suspense, lazy, useContext, useEffect, useReducer } from "react"
|
|
2
|
-
import { SsrRequestContext } from "./SsrRequestContext.jsx"
|
|
3
|
-
import SplitInview from "./SplitInview.jsx"
|
|
1
|
+
import React, { Suspense, lazy, useContext, useEffect, useReducer } from "react";
|
|
2
|
+
import { SsrRequestContext } from "./SsrRequestContext.jsx";
|
|
3
|
+
import SplitInview from "./SplitInview.jsx";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A dynamic import of a module whose default export is the component to split.
|
|
7
|
+
* Matches the argument React's `lazy` accepts.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Options accepted by {@link split}.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The component returned by {@link split}. It renders the lazily imported
|
|
16
|
+
* component and additionally carries the route statics copied off the loaded
|
|
17
|
+
* module, plus `load()` for the fetcher contract RouterDataProvider relies on.
|
|
18
|
+
*/
|
|
4
19
|
|
|
5
20
|
// Synchronous module cache: importFn → resolved module.
|
|
6
21
|
// Populated by the eager importFn().then() calls at split() invocation time.
|
|
7
22
|
// By the time window.load fires (when hydrateRoot runs), all chunk <script>
|
|
8
23
|
// tags in the HTML have already executed, so every .then() has already
|
|
9
24
|
// resolved and the module is available here synchronously.
|
|
10
|
-
const moduleCache = new Map()
|
|
25
|
+
const moduleCache = new Map();
|
|
11
26
|
|
|
12
27
|
// Collects one promise per SSR-rendered split() call on the client.
|
|
13
28
|
// loadableReady() waits for all of them before hydration begins.
|
|
14
|
-
const prefetchPromises = []
|
|
29
|
+
const prefetchPromises = [];
|
|
15
30
|
|
|
16
31
|
/**
|
|
17
32
|
* Returns a promise that resolves once every SSR-rendered split component
|
|
@@ -24,58 +39,55 @@ const prefetchPromises = []
|
|
|
24
39
|
* hydrateRoot(document.getElementById("root"), <App />)
|
|
25
40
|
* })
|
|
26
41
|
*/
|
|
27
|
-
export const hydrationReady = () => Promise.all(prefetchPromises)
|
|
42
|
+
export const hydrationReady = () => Promise.all(prefetchPromises);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Props for the internal Split wrapper. `split()` is the public entry point;
|
|
46
|
+
* this component is an implementation detail of it.
|
|
47
|
+
*/
|
|
28
48
|
|
|
29
49
|
/**
|
|
30
50
|
* Split component that wraps React's lazy and Suspense for SSR compatibility
|
|
31
|
-
* @param {Object} props
|
|
32
|
-
* @param {boolean} props.ssr - Whether to render the component on the server
|
|
33
|
-
* @param {React.ComponentType|React.ReactElement} props.fallback - Fallback component for loading state
|
|
34
|
-
* @param {Function} props.children - Function that returns the lazy component import
|
|
35
|
-
* @param {string} props.cacheKey - Resolved path for better asset tracking
|
|
36
51
|
*/
|
|
37
52
|
const Split = ({
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
53
|
+
ssr = true,
|
|
54
|
+
fallback = null,
|
|
55
|
+
cacheKey,
|
|
56
|
+
rootOptions,
|
|
57
|
+
onVisible,
|
|
58
|
+
skipVisibility,
|
|
59
|
+
children
|
|
45
60
|
}) => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return <Suspense fallback={fallback}>{children}</Suspense>
|
|
58
|
-
} catch (error) {
|
|
59
|
-
console.warn("Error loading component for SSR:", error)
|
|
60
|
-
return fallback
|
|
61
|
-
}
|
|
62
|
-
} else {
|
|
63
|
-
// Match SplitInview's client-side wrapper so hydration doesn't mismatch.
|
|
64
|
-
// SplitInview renders <div ref>{fallback}</div> until visible; without this
|
|
65
|
-
// wrap, server outputs `fallback` and client outputs `<div>{fallback}</div>`.
|
|
66
|
-
return <div>{fallback}</div>
|
|
61
|
+
// Check if we're on the server
|
|
62
|
+
const isServer = typeof window === "undefined";
|
|
63
|
+
if (isServer) {
|
|
64
|
+
if (ssr) {
|
|
65
|
+
// On server with SSR enabled: actually load and render the component
|
|
66
|
+
try {
|
|
67
|
+
// Track this component for asset extraction
|
|
68
|
+
if (global.__CHUNK_EXTRACTOR__) {
|
|
69
|
+
global.__CHUNK_EXTRACTOR__.addComponent(cacheKey);
|
|
67
70
|
}
|
|
71
|
+
return <Suspense fallback={fallback}>{children}</Suspense>;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.warn("Error loading component for SSR:", error);
|
|
74
|
+
return fallback;
|
|
75
|
+
}
|
|
68
76
|
} else {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<SplitInview fallback={fallback} rootOptions={rootOptions} onVisible={onVisible}>
|
|
74
|
-
<Suspense fallback={fallback}>{children}</Suspense>
|
|
75
|
-
</SplitInview>
|
|
76
|
-
)
|
|
77
|
+
// Match SplitInview's client-side wrapper so hydration doesn't mismatch.
|
|
78
|
+
// SplitInview renders <div ref>{fallback}</div> until visible; without this
|
|
79
|
+
// wrap, server outputs `fallback` and client outputs `<div>{fallback}</div>`.
|
|
80
|
+
return <div>{fallback}</div>;
|
|
77
81
|
}
|
|
78
|
-
}
|
|
82
|
+
} else {
|
|
83
|
+
if (skipVisibility) {
|
|
84
|
+
return <Suspense fallback={fallback}>{children}</Suspense>;
|
|
85
|
+
}
|
|
86
|
+
return <SplitInview fallback={fallback} rootOptions={rootOptions} onVisible={onVisible}>
|
|
87
|
+
<Suspense fallback={fallback}>{children}</Suspense>
|
|
88
|
+
</SplitInview>;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
79
91
|
|
|
80
92
|
/**
|
|
81
93
|
* Like {@link split}, but forces SSR when the request is a known Google crawler (same UA rules as Head).
|
|
@@ -85,116 +97,95 @@ const Split = ({
|
|
|
85
97
|
* still hydrates without a Suspense flash.
|
|
86
98
|
*/
|
|
87
99
|
export const split = (importFn, options = {}, thirdArg, fourthArg) => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const Component = mod?.default || mod
|
|
114
|
-
for (const key of ["clientFetcher", "serverFetcher", "setMetaData"]) {
|
|
115
|
-
if (Component?.[key]) wrapper[key] = Component[key]
|
|
116
|
-
}
|
|
117
|
-
return mod
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (typeof window !== "undefined" && window.__SSR_RENDERED_COMPONENTS__?.has(cacheKey)) {
|
|
121
|
-
const prefetch = importFn().then((mod) => {
|
|
122
|
-
moduleCache.set(importFn, mod)
|
|
123
|
-
copyRouteStatics(mod)
|
|
124
|
-
})
|
|
125
|
-
prefetchPromises.push(prefetch)
|
|
100
|
+
const {
|
|
101
|
+
ssr = true,
|
|
102
|
+
fallback = null
|
|
103
|
+
} = options || {};
|
|
104
|
+
const hasThirdArg = typeof thirdArg !== "undefined";
|
|
105
|
+
const hasFourthArg = typeof fourthArg !== "undefined";
|
|
106
|
+
const cacheKey = typeof fourthArg === "string" ? fourthArg : typeof thirdArg === "string" ? thirdArg : undefined;
|
|
107
|
+
const rootOptions = hasFourthArg ? thirdArg : hasThirdArg && typeof thirdArg !== "string" ? thirdArg : undefined;
|
|
108
|
+
const LazyComponent = lazy(importFn);
|
|
109
|
+
let loadInFlight = null;
|
|
110
|
+
|
|
111
|
+
// Per-split instance subscribers. Pending wrapper instances register their
|
|
112
|
+
// forceUpdate here; notifyAll() wakes them when load() resolves or any
|
|
113
|
+
// sibling becomes visible, so they can re-render against the now-hot cache
|
|
114
|
+
// or skip their own observer setup.
|
|
115
|
+
const subscribers = new Set();
|
|
116
|
+
let anyVisible = false;
|
|
117
|
+
const notifyAll = () => {
|
|
118
|
+
anyVisible = true;
|
|
119
|
+
subscribers.forEach(fn => fn());
|
|
120
|
+
};
|
|
121
|
+
const copyRouteStatics = mod => {
|
|
122
|
+
const Component = mod?.default || mod;
|
|
123
|
+
for (const key of ["clientFetcher", "serverFetcher", "setMetaData"]) {
|
|
124
|
+
if (Component?.[key]) wrapper[key] = Component[key];
|
|
126
125
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
126
|
+
return mod;
|
|
127
|
+
};
|
|
128
|
+
if (typeof window !== "undefined" && window.__SSR_RENDERED_COMPONENTS__?.has(cacheKey)) {
|
|
129
|
+
const prefetch = importFn().then(mod => {
|
|
130
|
+
moduleCache.set(importFn, mod);
|
|
131
|
+
copyRouteStatics(mod);
|
|
132
|
+
});
|
|
133
|
+
prefetchPromises.push(prefetch);
|
|
134
|
+
}
|
|
135
|
+
const wrapper = ({
|
|
136
|
+
fallback: fallbackProp,
|
|
137
|
+
...props
|
|
138
|
+
}) => {
|
|
139
|
+
const {
|
|
140
|
+
isBot: isBotFromContext
|
|
141
|
+
} = useContext(SsrRequestContext);
|
|
142
|
+
const isBotFromWindow = typeof window !== "undefined" && window.__CATALYST_IS_BOT__ === true;
|
|
143
|
+
const isBot = Boolean(isBotFromContext || isBotFromWindow);
|
|
144
|
+
const effectiveSsr = ssr || isBot;
|
|
145
|
+
const effectiveFallback = fallbackProp !== undefined ? fallbackProp : fallback;
|
|
146
|
+
const [, forceUpdate] = useReducer(x => x + 1, 0);
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
subscribers.add(forceUpdate);
|
|
149
|
+
return () => {
|
|
150
|
+
subscribers.delete(forceUpdate);
|
|
151
|
+
};
|
|
152
|
+
}, []);
|
|
153
|
+
const mod = moduleCache.get(importFn);
|
|
154
|
+
if (mod) {
|
|
155
|
+
const Component = mod.default || mod;
|
|
156
|
+
return <Suspense fallback={effectiveFallback}>
|
|
148
157
|
<Component {...props} />
|
|
149
|
-
</Suspense
|
|
150
|
-
)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return (
|
|
154
|
-
<Split
|
|
155
|
-
ssr={effectiveSsr}
|
|
156
|
-
fallback={effectiveFallback}
|
|
157
|
-
cacheKey={cacheKey}
|
|
158
|
-
rootOptions={rootOptions}
|
|
159
|
-
onVisible={() => {
|
|
160
|
-
notifyAll()
|
|
161
|
-
props.onVisible?.()
|
|
162
|
-
}}
|
|
163
|
-
skipVisibility={effectiveSsr || anyVisible}
|
|
164
|
-
{...props}
|
|
165
|
-
isBot={isBot}
|
|
166
|
-
>
|
|
167
|
-
<LazyComponent {...props} />
|
|
168
|
-
</Split>
|
|
169
|
-
)
|
|
158
|
+
</Suspense>;
|
|
170
159
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
.catch((err) => {
|
|
190
|
-
loadInFlight = null
|
|
191
|
-
throw err
|
|
192
|
-
})
|
|
160
|
+
return <Split ssr={effectiveSsr} fallback={effectiveFallback} cacheKey={cacheKey} rootOptions={rootOptions} onVisible={() => {
|
|
161
|
+
notifyAll();
|
|
162
|
+
props.onVisible?.();
|
|
163
|
+
}} skipVisibility={effectiveSsr || anyVisible} {...props} isBot={isBot}>
|
|
164
|
+
<LazyComponent {...props} />
|
|
165
|
+
</Split>;
|
|
166
|
+
};
|
|
167
|
+
wrapper.__cacheKey = cacheKey;
|
|
168
|
+
|
|
169
|
+
/** Same contract as loadable components: RouterDataProvider awaits this before reading serverFetcher/clientFetcher. */
|
|
170
|
+
wrapper.load = () => {
|
|
171
|
+
const cached = moduleCache.get(importFn);
|
|
172
|
+
if (cached) return Promise.resolve(cached);
|
|
173
|
+
if (!loadInFlight) {
|
|
174
|
+
loadInFlight = importFn().then(mod => {
|
|
175
|
+
copyRouteStatics(mod);
|
|
176
|
+
if (typeof window !== "undefined") {
|
|
177
|
+
moduleCache.set(importFn, mod);
|
|
193
178
|
}
|
|
194
|
-
|
|
179
|
+
loadInFlight = null;
|
|
180
|
+
notifyAll();
|
|
181
|
+
return mod;
|
|
182
|
+
}).catch(err => {
|
|
183
|
+
loadInFlight = null;
|
|
184
|
+
throw err;
|
|
185
|
+
});
|
|
195
186
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
export default Split
|
|
187
|
+
return loadInFlight;
|
|
188
|
+
};
|
|
189
|
+
return wrapper;
|
|
190
|
+
};
|
|
191
|
+
export default Split;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
//
|
|
3
|
+
// Server-side Split coverage (#347/#348-style split). The jsdom
|
|
4
|
+
// Split.test.jsx cannot reach these branches -- Split checks
|
|
5
|
+
// `typeof window === "undefined"` and jsdom always defines window. Routed
|
|
6
|
+
// to the "node" project by the *.server.test.* include/exclude split in
|
|
7
|
+
// vitest.config.ts.
|
|
8
|
+
|
|
9
|
+
import React from "react"
|
|
10
|
+
import { renderToStaticMarkup } from "react-dom/server"
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
|
|
12
|
+
import Split from "./Split.jsx"
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
delete global.__CHUNK_EXTRACTOR__
|
|
16
|
+
vi.restoreAllMocks()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe("Split (server)", () => {
|
|
20
|
+
it("with ssr enabled: registers the component with the chunk extractor and renders children in Suspense", () => {
|
|
21
|
+
const addComponent = vi.fn()
|
|
22
|
+
global.__CHUNK_EXTRACTOR__ = { addComponent }
|
|
23
|
+
|
|
24
|
+
const html = renderToStaticMarkup(
|
|
25
|
+
<Split ssr cacheKey="pages/Widget" fallback={<span>fallback</span>}>
|
|
26
|
+
<p>real child</p>
|
|
27
|
+
</Split>,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
expect(addComponent).toHaveBeenCalledWith("pages/Widget")
|
|
31
|
+
expect(html).toContain("real child")
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it("with ssr enabled and no chunk extractor present: still renders children (no throw)", () => {
|
|
35
|
+
const html = renderToStaticMarkup(
|
|
36
|
+
<Split ssr cacheKey="pages/NoExtractor">
|
|
37
|
+
<p>child ok</p>
|
|
38
|
+
</Split>,
|
|
39
|
+
)
|
|
40
|
+
expect(html).toContain("child ok")
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it("with ssr disabled: renders the fallback wrapped in a <div> to match the client hydration shape", () => {
|
|
44
|
+
const html = renderToStaticMarkup(
|
|
45
|
+
<Split ssr={false} cacheKey="pages/Deferred" fallback={<span>loading…</span>}>
|
|
46
|
+
<p>should not appear</p>
|
|
47
|
+
</Split>,
|
|
48
|
+
)
|
|
49
|
+
expect(html).toBe("<div><span>loading…</span></div>")
|
|
50
|
+
expect(html).not.toContain("should not appear")
|
|
51
|
+
})
|
|
52
|
+
})
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import React, { Suspense } from "react"
|
|
2
|
+
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"
|
|
3
|
+
import { render, screen, waitFor, act } from "@testing-library/react"
|
|
4
|
+
import Split, { split } from "./Split.jsx"
|
|
5
|
+
import { SsrRequestProvider } from "./SsrRequestContext.jsx"
|
|
6
|
+
|
|
7
|
+
// jsdom does not implement IntersectionObserver; SplitInview (used by the
|
|
8
|
+
// client, non-skipVisibility path) needs one. Stub it to fire visible
|
|
9
|
+
// immediately so tests default to "already visible" unless a test wants
|
|
10
|
+
// to hold back visibility deliberately.
|
|
11
|
+
class ImmediateIntersectionObserver {
|
|
12
|
+
constructor(callback) {
|
|
13
|
+
this.callback = callback
|
|
14
|
+
}
|
|
15
|
+
observe(node) {
|
|
16
|
+
// Fire on next tick so React has committed the ref first.
|
|
17
|
+
Promise.resolve().then(() => this.callback([{ target: node, isIntersecting: true }]))
|
|
18
|
+
}
|
|
19
|
+
unobserve() {}
|
|
20
|
+
disconnect() {}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
vi.stubGlobal("IntersectionObserver", ImmediateIntersectionObserver)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
vi.unstubAllGlobals()
|
|
29
|
+
vi.restoreAllMocks()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe("Split (client-side, window defined)", () => {
|
|
33
|
+
it("renders children directly when skipVisibility is true", () => {
|
|
34
|
+
render(
|
|
35
|
+
<Split skipVisibility fallback={<span>loading</span>}>
|
|
36
|
+
<span>content</span>
|
|
37
|
+
</Split>
|
|
38
|
+
)
|
|
39
|
+
expect(screen.getByText("content")).toBeInTheDocument()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it("defers to SplitInview (shows fallback first) when skipVisibility is false", () => {
|
|
43
|
+
render(
|
|
44
|
+
<Split fallback={<span>loading</span>}>
|
|
45
|
+
<span>content</span>
|
|
46
|
+
</Split>
|
|
47
|
+
)
|
|
48
|
+
// Before the stubbed IntersectionObserver's microtask fires,
|
|
49
|
+
// SplitInview should still be showing the fallback.
|
|
50
|
+
expect(screen.getByText("loading")).toBeInTheDocument()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it("eventually renders children once SplitInview reports visibility", async () => {
|
|
54
|
+
render(
|
|
55
|
+
<Split fallback={<span>loading</span>}>
|
|
56
|
+
<span>content</span>
|
|
57
|
+
</Split>
|
|
58
|
+
)
|
|
59
|
+
await waitFor(() => expect(screen.getByText("content")).toBeInTheDocument())
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe("split()", () => {
|
|
64
|
+
it("returns a component (wrapper) rather than throwing, given a lazy-style import function", () => {
|
|
65
|
+
const importFn = () => Promise.resolve({ default: () => <span>lazy content</span> })
|
|
66
|
+
const LazyThing = split(importFn, { ssr: false })
|
|
67
|
+
expect(typeof LazyThing).toBe("function")
|
|
68
|
+
expect(typeof LazyThing.load).toBe("function")
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it("eventually renders the resolved component's output", async () => {
|
|
72
|
+
const importFn = () => Promise.resolve({ default: () => <span>lazy content</span> })
|
|
73
|
+
const LazyThing = split(importFn, { ssr: false, fallback: <span>loading</span> })
|
|
74
|
+
render(<LazyThing skipVisibility />)
|
|
75
|
+
await waitFor(() => expect(screen.getByText("lazy content")).toBeInTheDocument())
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it("copies clientFetcher/serverFetcher/setMetaData from the resolved module onto the wrapper via load()", async () => {
|
|
79
|
+
const clientFetcher = vi.fn()
|
|
80
|
+
const serverFetcher = vi.fn()
|
|
81
|
+
const setMetaData = vi.fn()
|
|
82
|
+
const importFn = () =>
|
|
83
|
+
Promise.resolve({
|
|
84
|
+
default: Object.assign(() => <span>x</span>, { clientFetcher, serverFetcher, setMetaData }),
|
|
85
|
+
})
|
|
86
|
+
const LazyThing = split(importFn, {})
|
|
87
|
+
await LazyThing.load()
|
|
88
|
+
expect(LazyThing.clientFetcher).toBe(clientFetcher)
|
|
89
|
+
expect(LazyThing.serverFetcher).toBe(serverFetcher)
|
|
90
|
+
expect(LazyThing.setMetaData).toBe(setMetaData)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it("load() only calls the import function once even when called multiple times concurrently", async () => {
|
|
94
|
+
const importFn = vi.fn().mockResolvedValue({ default: () => <span>x</span> })
|
|
95
|
+
const LazyThing = split(importFn, {})
|
|
96
|
+
const [a, b] = await Promise.all([LazyThing.load(), LazyThing.load()])
|
|
97
|
+
expect(importFn).toHaveBeenCalledTimes(1)
|
|
98
|
+
expect(a).toBe(b)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it("load() caches its result -- a second call after resolution reuses the cached module without re-importing", async () => {
|
|
102
|
+
const importFn = vi.fn().mockResolvedValue({ default: () => <span>x</span> })
|
|
103
|
+
const LazyThing = split(importFn, {})
|
|
104
|
+
await LazyThing.load()
|
|
105
|
+
await LazyThing.load()
|
|
106
|
+
expect(importFn).toHaveBeenCalledTimes(1)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it("load() clears the in-flight promise and rethrows when the import rejects", async () => {
|
|
110
|
+
const importFn = vi.fn().mockRejectedValue(new Error("chunk 404"))
|
|
111
|
+
const LazyThing = split(importFn, {})
|
|
112
|
+
await expect(LazyThing.load()).rejects.toThrow("chunk 404")
|
|
113
|
+
// in-flight was cleared -> a retry re-imports rather than
|
|
114
|
+
// returning the settled rejection.
|
|
115
|
+
importFn.mockResolvedValueOnce({ default: () => <span>recovered</span> })
|
|
116
|
+
await expect(LazyThing.load()).resolves.toBeTruthy()
|
|
117
|
+
expect(importFn).toHaveBeenCalledTimes(2)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it("copies clientFetcher / serverFetcher / setMetaData statics onto the wrapper after load()", async () => {
|
|
121
|
+
const clientFetcher = () => {}
|
|
122
|
+
const setMetaData = () => {}
|
|
123
|
+
const importFn = vi
|
|
124
|
+
.fn()
|
|
125
|
+
.mockResolvedValue({ default: Object.assign(() => <span>c</span>, { clientFetcher, setMetaData }) })
|
|
126
|
+
const LazyThing = split(importFn, {})
|
|
127
|
+
await LazyThing.load()
|
|
128
|
+
expect(LazyThing.clientFetcher).toBe(clientFetcher)
|
|
129
|
+
expect(LazyThing.setMetaData).toBe(setMetaData)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it("renders straight from the module cache (no Split/observer) once load() has resolved", async () => {
|
|
133
|
+
const importFn = vi.fn().mockResolvedValue({ default: () => <span>from cache</span> })
|
|
134
|
+
const LazyThing = split(importFn, { ssr: false })
|
|
135
|
+
await LazyThing.load()
|
|
136
|
+
render(<LazyThing />)
|
|
137
|
+
await waitFor(() => expect(screen.getByText("from cache")).toBeInTheDocument())
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it("prefetches when window.__SSR_RENDERED_COMPONENTS__ already contains the cacheKey", async () => {
|
|
141
|
+
const prev = window.__SSR_RENDERED_COMPONENTS__
|
|
142
|
+
window.__SSR_RENDERED_COMPONENTS__ = new Set(["pages/Prefetched"])
|
|
143
|
+
try {
|
|
144
|
+
const importFn = vi.fn().mockResolvedValue({ default: () => <span>pf</span> })
|
|
145
|
+
split(importFn, {}, undefined, "pages/Prefetched")
|
|
146
|
+
// the constructor-time prefetch fired
|
|
147
|
+
await waitFor(() => expect(importFn).toHaveBeenCalled())
|
|
148
|
+
} finally {
|
|
149
|
+
window.__SSR_RENDERED_COMPONENTS__ = prev
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it("treats a bot request (via SsrRequestContext) as forcing effectiveSsr, skipping visibility gating", async () => {
|
|
154
|
+
const importFn = () => Promise.resolve({ default: () => <span>bot content</span> })
|
|
155
|
+
const LazyThing = split(importFn, { ssr: false })
|
|
156
|
+
render(
|
|
157
|
+
<SsrRequestProvider value={{ isBot: true }}>
|
|
158
|
+
<LazyThing />
|
|
159
|
+
</SsrRequestProvider>
|
|
160
|
+
)
|
|
161
|
+
// effectiveSsr = ssr || isBot = false || true = true, so Split's
|
|
162
|
+
// `!isServer` branch takes skipVisibility={effectiveSsr || anyVisible}
|
|
163
|
+
// = true -- content should render without waiting on an
|
|
164
|
+
// IntersectionObserver callback at all.
|
|
165
|
+
await waitFor(() => expect(screen.getByText("bot content")).toBeInTheDocument())
|
|
166
|
+
})
|
|
167
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Props for the internal visibility-deferring wrapper. Not part of the public
|
|
3
|
+
* surface: `split()` supplies these from its own options.
|
|
4
|
+
*/
|
|
5
|
+
interface SplitInviewProps {
|
|
6
|
+
/** Shown while the component is outside the viewport */
|
|
7
|
+
fallback?: any;
|
|
8
|
+
/** Rendered once the placeholder scrolls into view */
|
|
9
|
+
children?: any;
|
|
10
|
+
/** Optional IntersectionObserver options for parity with react-loadable-visibility */
|
|
11
|
+
rootOptions?: any;
|
|
12
|
+
/** Called once when the placeholder enters the viewport */
|
|
13
|
+
onVisible?: () => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Defers rendering of children until the placeholder enters the viewport.
|
|
17
|
+
* Once visible, it stays visible.
|
|
18
|
+
*
|
|
19
|
+
* On the server or when IntersectionObserver is unavailable, children render immediately.
|
|
20
|
+
*/
|
|
21
|
+
declare const SplitInview: ({ fallback, children, rootOptions, onVisible }: SplitInviewProps) => any;
|
|
22
|
+
export default SplitInview;
|