@nativescript/vite 8.0.0-alpha.5 → 8.0.0-alpha.51
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/README.md +51 -11
- package/bin/cli.cjs +8 -14
- package/configuration/angular.d.ts +34 -1
- package/configuration/angular.js +376 -165
- package/configuration/angular.js.map +1 -1
- package/configuration/base.js +241 -51
- package/configuration/base.js.map +1 -1
- package/configuration/javascript.js +14 -93
- package/configuration/javascript.js.map +1 -1
- package/configuration/react.js +41 -73
- package/configuration/react.js.map +1 -1
- package/configuration/solid.js +51 -6
- package/configuration/solid.js.map +1 -1
- package/configuration/typescript.js +12 -93
- package/configuration/typescript.js.map +1 -1
- package/helpers/app-components.d.ts +2 -1
- package/helpers/app-components.js.map +1 -1
- package/helpers/app-css-state.d.ts +8 -0
- package/helpers/app-css-state.js +8 -0
- package/helpers/app-css-state.js.map +1 -0
- package/helpers/bundler-context.d.ts +11 -0
- package/helpers/bundler-context.js +71 -0
- package/helpers/bundler-context.js.map +1 -0
- package/helpers/cli-flags.d.ts +12 -0
- package/helpers/cli-flags.js +34 -0
- package/helpers/cli-flags.js.map +1 -1
- package/helpers/css-platform-plugin.d.ts +14 -0
- package/helpers/css-platform-plugin.js +43 -25
- package/helpers/css-platform-plugin.js.map +1 -1
- package/helpers/dev-host.d.ts +360 -0
- package/helpers/dev-host.js +694 -0
- package/helpers/dev-host.js.map +1 -0
- package/helpers/dynamic-import-plugin.js +1 -1
- package/helpers/dynamic-import-plugin.js.map +1 -1
- package/helpers/esbuild-platform-resolver.js +4 -1
- package/helpers/esbuild-platform-resolver.js.map +1 -1
- package/helpers/external-configs.d.ts +10 -12
- package/helpers/external-configs.js +58 -35
- package/helpers/external-configs.js.map +1 -1
- package/helpers/global-defines.d.ts +142 -0
- package/helpers/global-defines.js +213 -11
- package/helpers/global-defines.js.map +1 -1
- package/helpers/hmr-scope.d.ts +26 -0
- package/helpers/hmr-scope.js +67 -0
- package/helpers/hmr-scope.js.map +1 -0
- package/helpers/init.js +0 -18
- package/helpers/init.js.map +1 -1
- package/helpers/logging.d.ts +1 -0
- package/helpers/logging.js +65 -4
- package/helpers/logging.js.map +1 -1
- package/helpers/main-entry.d.ts +3 -1
- package/helpers/main-entry.js +301 -53
- package/helpers/main-entry.js.map +1 -1
- package/helpers/nativeclass-esbuild-plugin.d.ts +2 -1
- package/helpers/nativeclass-esbuild-plugin.js.map +1 -1
- package/helpers/nativeclass-transform.js +5 -6
- package/helpers/nativeclass-transform.js.map +1 -1
- package/helpers/nativeclass-transformer-plugin.d.ts +9 -2
- package/helpers/nativeclass-transformer-plugin.js +157 -14
- package/helpers/nativeclass-transformer-plugin.js.map +1 -1
- package/helpers/nativescript-package-resolver.js +10 -62
- package/helpers/nativescript-package-resolver.js.map +1 -1
- package/helpers/normalize-id.d.ts +42 -0
- package/helpers/normalize-id.js +60 -0
- package/helpers/normalize-id.js.map +1 -0
- package/helpers/ns-core-url.d.ts +29 -7
- package/helpers/ns-core-url.js +69 -12
- package/helpers/ns-core-url.js.map +1 -1
- package/helpers/optimize-deps.d.ts +16 -0
- package/helpers/optimize-deps.js +17 -0
- package/helpers/optimize-deps.js.map +1 -0
- package/helpers/package-platform-aliases.js +34 -49
- package/helpers/package-platform-aliases.js.map +1 -1
- package/helpers/platform-types.d.ts +2 -0
- package/helpers/platform-types.js +2 -0
- package/helpers/platform-types.js.map +1 -0
- package/helpers/postcss-platform-config.d.ts +17 -1
- package/helpers/postcss-platform-config.js +20 -37
- package/helpers/postcss-platform-config.js.map +1 -1
- package/helpers/project.d.ts +35 -0
- package/helpers/project.js +120 -2
- package/helpers/project.js.map +1 -1
- package/helpers/resolve-main-field-platform.d.ts +20 -0
- package/helpers/resolve-main-field-platform.js +49 -0
- package/helpers/resolve-main-field-platform.js.map +1 -0
- package/helpers/resolver.js +17 -2
- package/helpers/resolver.js.map +1 -1
- package/helpers/theme-core-plugins.js +1 -1
- package/helpers/theme-core-plugins.js.map +1 -1
- package/helpers/ts-config-paths.d.ts +14 -0
- package/helpers/ts-config-paths.js +90 -9
- package/helpers/ts-config-paths.js.map +1 -1
- package/helpers/typescript-check.d.ts +2 -1
- package/helpers/typescript-check.js +2 -2
- package/helpers/typescript-check.js.map +1 -1
- package/helpers/ui-registration.d.ts +21 -0
- package/helpers/ui-registration.js +156 -0
- package/helpers/ui-registration.js.map +1 -0
- package/helpers/utils.d.ts +9 -0
- package/helpers/utils.js +22 -2
- package/helpers/utils.js.map +1 -1
- package/helpers/workers.d.ts +20 -19
- package/helpers/workers.js +624 -4
- package/helpers/workers.js.map +1 -1
- package/hmr/client/css-handler.d.ts +2 -1
- package/hmr/client/css-handler.js +50 -26
- package/hmr/client/css-handler.js.map +1 -1
- package/hmr/client/css-update-overlay.d.ts +18 -0
- package/hmr/client/css-update-overlay.js +27 -0
- package/hmr/client/css-update-overlay.js.map +1 -0
- package/hmr/client/framework-client-strategy.d.ts +79 -0
- package/hmr/client/framework-client-strategy.js +19 -0
- package/hmr/client/framework-client-strategy.js.map +1 -0
- package/hmr/client/hmr-pending-overlay.d.ts +13 -0
- package/hmr/client/hmr-pending-overlay.js +60 -0
- package/hmr/client/hmr-pending-overlay.js.map +1 -0
- package/hmr/client/index.js +891 -220
- package/hmr/client/index.js.map +1 -1
- package/hmr/client/utils.d.ts +7 -1
- package/hmr/client/utils.js +207 -29
- package/hmr/client/utils.js.map +1 -1
- package/hmr/entry-runtime.d.ts +2 -1
- package/hmr/entry-runtime.js +256 -69
- package/hmr/entry-runtime.js.map +1 -1
- package/hmr/frameworks/angular/build/angular-linker.d.ts +12 -0
- package/hmr/frameworks/angular/build/angular-linker.js +109 -0
- package/hmr/frameworks/angular/build/angular-linker.js.map +1 -0
- package/hmr/frameworks/angular/build/inject-component-hmr-registration.d.ts +112 -0
- package/hmr/frameworks/angular/build/inject-component-hmr-registration.js +291 -0
- package/hmr/frameworks/angular/build/inject-component-hmr-registration.js.map +1 -0
- package/hmr/frameworks/angular/build/inject-hmr-vite-ignore.d.ts +75 -0
- package/hmr/frameworks/angular/build/inject-hmr-vite-ignore.js +221 -0
- package/hmr/frameworks/angular/build/inject-hmr-vite-ignore.js.map +1 -0
- package/{helpers/angular → hmr/frameworks/angular/build}/inline-decorator-component-templates.js +1 -170
- package/hmr/frameworks/angular/build/inline-decorator-component-templates.js.map +1 -0
- package/hmr/frameworks/angular/build/js-lexer.d.ts +4 -0
- package/{helpers/angular/synthesize-decorator-ctor-parameters.js → hmr/frameworks/angular/build/js-lexer.js} +22 -96
- package/hmr/frameworks/angular/build/js-lexer.js.map +1 -0
- package/hmr/frameworks/angular/build/shared-linker.d.ts +39 -0
- package/hmr/frameworks/angular/build/shared-linker.js +128 -0
- package/hmr/frameworks/angular/build/shared-linker.js.map +1 -0
- package/hmr/frameworks/angular/build/synthesize-decorator-ctor-parameters.js +88 -0
- package/hmr/frameworks/angular/build/synthesize-decorator-ctor-parameters.js.map +1 -0
- package/{helpers/angular → hmr/frameworks/angular/build}/synthesize-injectable-factories.js +1 -174
- package/hmr/frameworks/angular/build/synthesize-injectable-factories.js.map +1 -0
- package/{helpers/angular → hmr/frameworks/angular/build}/util.d.ts +1 -0
- package/hmr/frameworks/angular/build/util.js +155 -0
- package/hmr/frameworks/angular/build/util.js.map +1 -0
- package/hmr/frameworks/angular/client/index.d.ts +1 -0
- package/hmr/frameworks/angular/client/index.js +859 -21
- package/hmr/frameworks/angular/client/index.js.map +1 -1
- package/hmr/frameworks/angular/client/strategy.d.ts +9 -0
- package/hmr/frameworks/angular/client/strategy.js +19 -0
- package/hmr/frameworks/angular/client/strategy.js.map +1 -0
- package/hmr/frameworks/angular/server/angular-root-component.d.ts +79 -0
- package/hmr/frameworks/angular/server/angular-root-component.js +149 -0
- package/hmr/frameworks/angular/server/angular-root-component.js.map +1 -0
- package/hmr/frameworks/angular/server/linker.js +1 -4
- package/hmr/frameworks/angular/server/linker.js.map +1 -1
- package/hmr/frameworks/angular/server/strategy.js +460 -12
- package/hmr/frameworks/angular/server/strategy.js.map +1 -1
- package/hmr/{server → frameworks/angular/server}/websocket-angular-entry.js +2 -2
- package/hmr/frameworks/angular/server/websocket-angular-entry.js.map +1 -0
- package/hmr/{server → frameworks/angular/server}/websocket-angular-hot-update.d.ts +17 -11
- package/hmr/frameworks/angular/server/websocket-angular-hot-update.js +336 -0
- package/hmr/frameworks/angular/server/websocket-angular-hot-update.js.map +1 -0
- package/hmr/frameworks/react/server/strategy.d.ts +2 -0
- package/hmr/frameworks/react/server/strategy.js +150 -0
- package/hmr/frameworks/react/server/strategy.js.map +1 -0
- package/hmr/frameworks/solid/build/solid-jsx-deps.d.ts +15 -0
- package/hmr/frameworks/solid/build/solid-jsx-deps.js +178 -0
- package/hmr/frameworks/solid/build/solid-jsx-deps.js.map +1 -0
- package/hmr/frameworks/solid/client/app-runtime.d.ts +54 -0
- package/hmr/frameworks/solid/client/app-runtime.js +184 -0
- package/hmr/frameworks/solid/client/app-runtime.js.map +1 -0
- package/hmr/frameworks/solid/server/strategy.js +291 -16
- package/hmr/frameworks/solid/server/strategy.js.map +1 -1
- package/hmr/frameworks/typescript/server/strategy.js +38 -14
- package/hmr/frameworks/typescript/server/strategy.js.map +1 -1
- package/hmr/frameworks/vue/client/dep-propagation.d.ts +36 -0
- package/hmr/frameworks/vue/client/dep-propagation.js +101 -0
- package/hmr/frameworks/vue/client/dep-propagation.js.map +1 -0
- package/hmr/frameworks/vue/client/index.d.ts +8 -0
- package/hmr/frameworks/vue/client/index.js +56 -243
- package/hmr/frameworks/vue/client/index.js.map +1 -1
- package/hmr/frameworks/vue/client/strategy.d.ts +33 -0
- package/hmr/frameworks/vue/client/strategy.js +157 -0
- package/hmr/frameworks/vue/client/strategy.js.map +1 -0
- package/hmr/frameworks/vue/client/vue-sfc-update-overlay.d.ts +49 -0
- package/hmr/frameworks/vue/client/vue-sfc-update-overlay.js +142 -0
- package/hmr/frameworks/vue/client/vue-sfc-update-overlay.js.map +1 -0
- package/hmr/frameworks/vue/server/sfc-route-assemble.d.ts +7 -0
- package/hmr/frameworks/vue/server/sfc-route-assemble.js +747 -0
- package/hmr/frameworks/vue/server/sfc-route-assemble.js.map +1 -0
- package/hmr/frameworks/vue/server/sfc-route-meta.d.ts +7 -0
- package/hmr/frameworks/vue/server/sfc-route-meta.js +80 -0
- package/hmr/frameworks/vue/server/sfc-route-meta.js.map +1 -0
- package/hmr/frameworks/vue/server/sfc-route-serve.d.ts +8 -0
- package/hmr/frameworks/vue/server/sfc-route-serve.js +459 -0
- package/hmr/frameworks/vue/server/sfc-route-serve.js.map +1 -0
- package/hmr/frameworks/vue/server/sfc-route-shared.d.ts +38 -0
- package/hmr/frameworks/vue/server/sfc-route-shared.js +48 -0
- package/hmr/frameworks/vue/server/sfc-route-shared.js.map +1 -0
- package/hmr/frameworks/vue/server/strategy.d.ts +35 -0
- package/hmr/frameworks/vue/server/strategy.js +278 -1
- package/hmr/frameworks/vue/server/strategy.js.map +1 -1
- package/hmr/frameworks/vue/server/websocket-sfc.d.ts +15 -0
- package/hmr/frameworks/vue/server/websocket-sfc.js +20 -0
- package/hmr/frameworks/vue/server/websocket-sfc.js.map +1 -0
- package/hmr/helpers/ast-normalizer.d.ts +3 -1
- package/hmr/helpers/ast-normalizer.js +77 -10
- package/hmr/helpers/ast-normalizer.js.map +1 -1
- package/hmr/helpers/cjs-named-exports.d.ts +23 -0
- package/hmr/helpers/cjs-named-exports.js +152 -0
- package/hmr/helpers/cjs-named-exports.js.map +1 -0
- package/hmr/helpers/package-exports.d.ts +16 -0
- package/hmr/helpers/package-exports.js +396 -0
- package/hmr/helpers/package-exports.js.map +1 -0
- package/hmr/server/constants.js +20 -5
- package/hmr/server/constants.js.map +1 -1
- package/hmr/server/core-sanitize.d.ts +86 -7
- package/hmr/server/core-sanitize.js +170 -45
- package/hmr/server/core-sanitize.js.map +1 -1
- package/hmr/server/device-transform-helpers.d.ts +24 -0
- package/hmr/server/device-transform-helpers.js +408 -0
- package/hmr/server/device-transform-helpers.js.map +1 -0
- package/hmr/server/framework-strategy.d.ts +108 -11
- package/hmr/server/hmr-module-graph.d.ts +37 -0
- package/hmr/server/hmr-module-graph.js +214 -0
- package/hmr/server/hmr-module-graph.js.map +1 -0
- package/hmr/server/import-map.d.ts +11 -2
- package/hmr/server/import-map.js +92 -45
- package/hmr/server/import-map.js.map +1 -1
- package/hmr/server/index.js +7 -16
- package/hmr/server/index.js.map +1 -1
- package/hmr/server/ns-core-cjs-shape.d.ts +2 -4
- package/hmr/server/ns-core-cjs-shape.js +4 -6
- package/hmr/server/ns-core-cjs-shape.js.map +1 -1
- package/hmr/server/ns-rt-bridge.d.ts +51 -0
- package/hmr/server/ns-rt-bridge.js +131 -0
- package/hmr/server/ns-rt-bridge.js.map +1 -0
- package/hmr/server/ns-rt-route.d.ts +5 -0
- package/hmr/server/ns-rt-route.js +38 -0
- package/hmr/server/ns-rt-route.js.map +1 -0
- package/hmr/server/perf-instrumentation.d.ts +114 -0
- package/hmr/server/perf-instrumentation.js +197 -0
- package/hmr/server/perf-instrumentation.js.map +1 -0
- package/hmr/server/process-code-for-device.d.ts +14 -0
- package/hmr/server/process-code-for-device.js +699 -0
- package/hmr/server/process-code-for-device.js.map +1 -0
- package/hmr/server/require-guard.d.ts +1 -0
- package/hmr/server/require-guard.js +12 -0
- package/hmr/server/require-guard.js.map +1 -0
- package/hmr/server/rewrite-imports.d.ts +2 -0
- package/hmr/server/rewrite-imports.js +630 -0
- package/hmr/server/rewrite-imports.js.map +1 -0
- package/hmr/server/route-helpers.d.ts +7 -0
- package/hmr/server/route-helpers.js +13 -0
- package/hmr/server/route-helpers.js.map +1 -0
- package/hmr/server/server-origin.d.ts +2 -0
- package/hmr/server/server-origin.js +83 -0
- package/hmr/server/server-origin.js.map +1 -0
- package/hmr/server/shared-transform-request.js +13 -7
- package/hmr/server/shared-transform-request.js.map +1 -1
- package/hmr/server/transform-cache-invalidation.d.ts +37 -0
- package/hmr/server/transform-cache-invalidation.js +156 -0
- package/hmr/server/transform-cache-invalidation.js.map +1 -0
- package/hmr/server/vendor-bare-module-shims.d.ts +4 -0
- package/hmr/server/vendor-bare-module-shims.js +80 -0
- package/hmr/server/vendor-bare-module-shims.js.map +1 -0
- package/hmr/server/vite-plugin.js +78 -42
- package/hmr/server/vite-plugin.js.map +1 -1
- package/hmr/server/websocket-core-bridge.d.ts +45 -4
- package/hmr/server/websocket-core-bridge.js +50 -48
- package/hmr/server/websocket-core-bridge.js.map +1 -1
- package/hmr/server/websocket-css-hot-update.d.ts +33 -0
- package/hmr/server/websocket-css-hot-update.js +65 -0
- package/hmr/server/websocket-css-hot-update.js.map +1 -0
- package/hmr/server/websocket-device-transform.d.ts +3 -0
- package/hmr/server/websocket-device-transform.js +7 -0
- package/hmr/server/websocket-device-transform.js.map +1 -0
- package/hmr/server/websocket-graph-upsert.d.ts +15 -0
- package/hmr/server/websocket-graph-upsert.js +20 -0
- package/hmr/server/websocket-graph-upsert.js.map +1 -1
- package/hmr/server/websocket-hmr-pending.d.ts +37 -0
- package/hmr/server/websocket-hmr-pending.js +55 -0
- package/hmr/server/websocket-hmr-pending.js.map +1 -0
- package/hmr/server/websocket-hot-update.d.ts +77 -0
- package/hmr/server/websocket-hot-update.js +360 -0
- package/hmr/server/websocket-hot-update.js.map +1 -0
- package/hmr/server/websocket-import-map-route.d.ts +15 -0
- package/hmr/server/websocket-import-map-route.js +50 -0
- package/hmr/server/websocket-import-map-route.js.map +1 -0
- package/hmr/server/websocket-module-bindings.js +3 -3
- package/hmr/server/websocket-module-bindings.js.map +1 -1
- package/hmr/server/websocket-module-specifiers.d.ts +66 -2
- package/hmr/server/websocket-module-specifiers.js +191 -20
- package/hmr/server/websocket-module-specifiers.js.map +1 -1
- package/hmr/server/websocket-ns-core.d.ts +21 -0
- package/hmr/server/websocket-ns-core.js +311 -0
- package/hmr/server/websocket-ns-core.js.map +1 -0
- package/hmr/server/websocket-ns-entry.d.ts +21 -0
- package/hmr/server/websocket-ns-entry.js +164 -0
- package/hmr/server/websocket-ns-entry.js.map +1 -0
- package/hmr/server/websocket-ns-m-paths.d.ts +1 -1
- package/hmr/server/websocket-ns-m-paths.js +58 -13
- package/hmr/server/websocket-ns-m-paths.js.map +1 -1
- package/hmr/server/websocket-ns-m-request.d.ts +11 -1
- package/hmr/server/websocket-ns-m-request.js +18 -25
- package/hmr/server/websocket-ns-m-request.js.map +1 -1
- package/hmr/server/websocket-ns-m.d.ts +33 -0
- package/hmr/server/websocket-ns-m.js +752 -0
- package/hmr/server/websocket-ns-m.js.map +1 -0
- package/hmr/server/websocket-served-module-helpers.d.ts +49 -3
- package/hmr/server/websocket-served-module-helpers.js +403 -87
- package/hmr/server/websocket-served-module-helpers.js.map +1 -1
- package/hmr/server/websocket-txn.js +2 -8
- package/hmr/server/websocket-txn.js.map +1 -1
- package/hmr/server/websocket-vendor-unifier.d.ts +0 -1
- package/hmr/server/websocket-vendor-unifier.js +4 -9
- package/hmr/server/websocket-vendor-unifier.js.map +1 -1
- package/hmr/server/websocket.d.ts +8 -39
- package/hmr/server/websocket.js +608 -6300
- package/hmr/server/websocket.js.map +1 -1
- package/hmr/shared/ns-globals.d.ts +118 -0
- package/hmr/shared/ns-globals.js +29 -0
- package/hmr/shared/ns-globals.js.map +1 -0
- package/hmr/shared/protocol.d.ts +145 -0
- package/hmr/shared/protocol.js +28 -0
- package/hmr/shared/protocol.js.map +1 -0
- package/hmr/shared/runtime/boot-placeholder-ui.d.ts +69 -0
- package/hmr/shared/runtime/boot-placeholder-ui.js +101 -0
- package/hmr/shared/runtime/boot-placeholder-ui.js.map +1 -0
- package/hmr/shared/runtime/boot-progress.d.ts +44 -0
- package/hmr/shared/runtime/boot-progress.js +133 -0
- package/hmr/shared/runtime/boot-progress.js.map +1 -0
- package/hmr/shared/runtime/boot-timeline.d.ts +18 -0
- package/hmr/shared/runtime/boot-timeline.js +42 -0
- package/hmr/shared/runtime/boot-timeline.js.map +1 -0
- package/hmr/shared/runtime/browser-runtime-contract.js.map +1 -1
- package/hmr/shared/runtime/dev-overlay-snapshots.d.ts +31 -0
- package/hmr/shared/runtime/dev-overlay-snapshots.js +324 -0
- package/hmr/shared/runtime/dev-overlay-snapshots.js.map +1 -0
- package/hmr/shared/runtime/dev-overlay.d.ts +119 -26
- package/hmr/shared/runtime/dev-overlay.js +1196 -262
- package/hmr/shared/runtime/dev-overlay.js.map +1 -1
- package/hmr/shared/runtime/global-scope.d.ts +18 -0
- package/hmr/shared/runtime/global-scope.js +21 -0
- package/hmr/shared/runtime/global-scope.js.map +1 -0
- package/hmr/shared/runtime/hooks.js +2 -1
- package/hmr/shared/runtime/hooks.js.map +1 -1
- package/hmr/shared/runtime/http-only-boot.js +7 -6
- package/hmr/shared/runtime/http-only-boot.js.map +1 -1
- package/hmr/shared/runtime/module-provenance.js +4 -6
- package/hmr/shared/runtime/module-provenance.js.map +1 -1
- package/hmr/shared/runtime/root-placeholder-view.d.ts +19 -0
- package/hmr/shared/runtime/root-placeholder-view.js +311 -0
- package/hmr/shared/runtime/root-placeholder-view.js.map +1 -0
- package/hmr/shared/runtime/root-placeholder.js +393 -200
- package/hmr/shared/runtime/root-placeholder.js.map +1 -1
- package/hmr/shared/runtime/session-bootstrap.js +168 -4
- package/hmr/shared/runtime/session-bootstrap.js.map +1 -1
- package/hmr/shared/runtime/vendor-bootstrap.js +3 -10
- package/hmr/shared/runtime/vendor-bootstrap.js.map +1 -1
- package/hmr/shared/vendor/manifest-collect.d.ts +4 -0
- package/hmr/shared/vendor/manifest-collect.js +549 -0
- package/hmr/shared/vendor/manifest-collect.js.map +1 -0
- package/hmr/shared/vendor/manifest-loader.d.ts +2 -1
- package/hmr/shared/vendor/manifest-loader.js +5 -3
- package/hmr/shared/vendor/manifest-loader.js.map +1 -1
- package/hmr/shared/vendor/manifest.d.ts +1 -7
- package/hmr/shared/vendor/manifest.js +84 -819
- package/hmr/shared/vendor/manifest.js.map +1 -1
- package/hmr/shared/vendor/vendor-device-shim.d.ts +1 -0
- package/hmr/shared/vendor/vendor-device-shim.js +208 -0
- package/hmr/shared/vendor/vendor-device-shim.js.map +1 -0
- package/hmr/shared/vendor/vendor-esbuild-plugins.d.ts +38 -0
- package/hmr/shared/vendor/vendor-esbuild-plugins.js +296 -0
- package/hmr/shared/vendor/vendor-esbuild-plugins.js.map +1 -0
- package/hmr/vendor-bootstrap.d.ts +1 -3
- package/hmr/vendor-bootstrap.js +4 -6
- package/hmr/vendor-bootstrap.js.map +1 -1
- package/index.d.ts +1 -0
- package/index.js +5 -0
- package/index.js.map +1 -1
- package/package.json +61 -11
- package/runtime/core-aliases-early.js +25 -55
- package/runtime/core-aliases-early.js.map +1 -1
- package/shims/react-jsx-runtime.d.ts +4 -0
- package/shims/react-jsx-runtime.js +61 -0
- package/shims/react-jsx-runtime.js.map +1 -0
- package/helpers/angular/angular-linker.d.ts +0 -13
- package/helpers/angular/angular-linker.js +0 -194
- package/helpers/angular/angular-linker.js.map +0 -1
- package/helpers/angular/inline-decorator-component-templates.js.map +0 -1
- package/helpers/angular/shared-linker.d.ts +0 -11
- package/helpers/angular/shared-linker.js +0 -75
- package/helpers/angular/shared-linker.js.map +0 -1
- package/helpers/angular/synthesize-decorator-ctor-parameters.js.map +0 -1
- package/helpers/angular/synthesize-injectable-factories.js.map +0 -1
- package/helpers/angular/util.js +0 -67
- package/helpers/angular/util.js.map +0 -1
- package/helpers/prelink-angular.d.ts +0 -2
- package/helpers/prelink-angular.js +0 -117
- package/helpers/prelink-angular.js.map +0 -1
- package/hmr/server/websocket-angular-entry.js.map +0 -1
- package/hmr/server/websocket-angular-hot-update.js +0 -239
- package/hmr/server/websocket-angular-hot-update.js.map +0 -1
- package/hmr/server/websocket-ns-m-finalize.d.ts +0 -22
- package/hmr/server/websocket-ns-m-finalize.js +0 -88
- package/hmr/server/websocket-ns-m-finalize.js.map +0 -1
- package/hmr/server/websocket-runtime-compat.d.ts +0 -19
- package/hmr/server/websocket-runtime-compat.js +0 -286
- package/hmr/server/websocket-runtime-compat.js.map +0 -1
- package/hmr/server/websocket-vue-sfc.d.ts +0 -27
- package/hmr/server/websocket-vue-sfc.js +0 -1117
- package/hmr/server/websocket-vue-sfc.js.map +0 -1
- package/transformers/NativeClass/index.d.ts +0 -2
- package/transformers/NativeClass/index.js +0 -222
- package/transformers/NativeClass/index.js.map +0 -1
- /package/{helpers/angular → hmr/frameworks/angular/build}/inline-decorator-component-templates.d.ts +0 -0
- /package/{helpers/angular → hmr/frameworks/angular/build}/synthesize-decorator-ctor-parameters.d.ts +0 -0
- /package/{helpers/angular → hmr/frameworks/angular/build}/synthesize-injectable-factories.d.ts +0 -0
- /package/hmr/{server → frameworks/angular/server}/websocket-angular-entry.d.ts +0 -0
package/hmr/client/index.js
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
* Always resolve core classes and Application from the vendor realm or globalThis at runtime.
|
|
6
6
|
* The HMR client is evaluated via HTTP ESM on device; static imports would create secondary instances.
|
|
7
7
|
*/
|
|
8
|
-
import { setHMRWsUrl, getHMRWsUrl, pendingModuleFetches, deriveHttpOrigin, setHttpOriginForVite, moduleFetchCache, requestModuleFromServer, getHttpOriginForVite, normalizeSpec, hmrMetrics, graph, setGraphVersion, getGraphVersion, getCurrentApp, getRootFrame, setCurrentApp, setRootFrame, getCore } from './utils.js';
|
|
8
|
+
import { setHMRWsUrl, getHMRWsUrl, pendingModuleFetches, deriveHttpOrigin, setHttpOriginForVite, moduleFetchCache, requestModuleFromServer, getHttpOriginForVite, normalizeSpec, hmrMetrics, graph, setGraphVersion, getGraphVersion, getCurrentApp, getRootFrame, setCurrentApp, setRootFrame, getCore, hasExplicitEviction, invalidateModulesByUrls, buildEvictionUrls, emitHmrModeBannerOnce, ENV_VERBOSE } from './utils.js';
|
|
9
9
|
import { handleCssUpdates } from './css-handler.js';
|
|
10
|
-
|
|
10
|
+
import { buildCssApplyingDetail, buildCssAppliedDetail } from './css-update-overlay.js';
|
|
11
|
+
import { getGlobalScope } from '../shared/runtime/global-scope.js';
|
|
12
|
+
const VERBOSE = ENV_VERBOSE;
|
|
11
13
|
function resolveTargetFlavor() {
|
|
12
14
|
try {
|
|
13
15
|
if (typeof __NS_TARGET_FLAVOR__ !== 'undefined' && __NS_TARGET_FLAVOR__) {
|
|
@@ -16,7 +18,7 @@ function resolveTargetFlavor() {
|
|
|
16
18
|
}
|
|
17
19
|
catch { }
|
|
18
20
|
try {
|
|
19
|
-
const g =
|
|
21
|
+
const g = getGlobalScope();
|
|
20
22
|
if (typeof g.__NS_TARGET_FLAVOR__ === 'string' && g.__NS_TARGET_FLAVOR__) {
|
|
21
23
|
return g.__NS_TARGET_FLAVOR__;
|
|
22
24
|
}
|
|
@@ -34,13 +36,24 @@ function resolveTargetFlavor() {
|
|
|
34
36
|
return undefined;
|
|
35
37
|
}
|
|
36
38
|
const TARGET_FLAVOR = resolveTargetFlavor();
|
|
39
|
+
// React reuses the generic TypeScript HMR path on BOTH server and client: it has
|
|
40
|
+
// no Fast Refresh, so a module edit drives a plain module reload / root reset
|
|
41
|
+
// (the React tree re-renders), exactly like the `typescript` flavor. The server
|
|
42
|
+
// strategy is `{ ...typescriptServerStrategy, flavor: 'react' }`; this mirrors that
|
|
43
|
+
// on the client so the `typescript`-gated update branches also run for React
|
|
44
|
+
// (otherwise a React edit is received but never applied — the overlay sticks).
|
|
45
|
+
const TS_LIKE_FLAVOR = TARGET_FLAVOR === 'typescript' || TARGET_FLAVOR === 'react';
|
|
37
46
|
try {
|
|
38
47
|
if (TARGET_FLAVOR && !globalThis.__NS_TARGET_FLAVOR__) {
|
|
39
48
|
globalThis.__NS_TARGET_FLAVOR__ = TARGET_FLAVOR;
|
|
40
49
|
}
|
|
41
50
|
}
|
|
42
51
|
catch { }
|
|
43
|
-
|
|
52
|
+
// Define substitution does NOT reach this file (served raw from node_modules),
|
|
53
|
+
// so prefer the globalThis seed planted by the entry's defines-seed module —
|
|
54
|
+
// the '/src' literal is a last-resort default and is WRONG for 'app/'-rooted
|
|
55
|
+
// projects.
|
|
56
|
+
const APP_ROOT_VIRTUAL = (typeof __NS_APP_ROOT_VIRTUAL__ === 'string' && __NS_APP_ROOT_VIRTUAL__) || (typeof getGlobalScope().__NS_APP_ROOT_VIRTUAL__ === 'string' && getGlobalScope().__NS_APP_ROOT_VIRTUAL__) || '/src';
|
|
44
57
|
const APP_VIRTUAL_WITH_SLASH = APP_ROOT_VIRTUAL.endsWith('/') ? APP_ROOT_VIRTUAL : `${APP_ROOT_VIRTUAL}/`;
|
|
45
58
|
const APP_MAIN_ENTRY_SPEC = `${APP_VIRTUAL_WITH_SLASH}app.ts`;
|
|
46
59
|
// Policy: by default, let the app's own main entry mount initially; HMR client handles updates/remounts only.
|
|
@@ -101,12 +114,101 @@ function hideConnectionOverlay() {
|
|
|
101
114
|
}
|
|
102
115
|
catch { }
|
|
103
116
|
}
|
|
117
|
+
function setUpdateOverlayStage(stage, info) {
|
|
118
|
+
try {
|
|
119
|
+
const api = getHmrOverlayApi();
|
|
120
|
+
if (api && typeof api.setUpdateStage === 'function') {
|
|
121
|
+
api.setUpdateStage(stage, info);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch { }
|
|
125
|
+
}
|
|
126
|
+
// Store the listener registry on globalThis (rather than in a module-private
|
|
127
|
+
// closure) because in NativeScript the HMR client module and the user app
|
|
128
|
+
// modules can resolve to different module instances depending on how the
|
|
129
|
+
// dev runtime loads them (HTTP client URL vs. the bundled vendor realm).
|
|
130
|
+
// A module-local Set would not be shared across instances; the global one
|
|
131
|
+
// is.
|
|
132
|
+
function getNsSolidHmrListenerSet() {
|
|
133
|
+
const g = getGlobalScope();
|
|
134
|
+
let set = g.__ns_solid_hmr_listener_set;
|
|
135
|
+
if (!set) {
|
|
136
|
+
set = new Set();
|
|
137
|
+
g.__ns_solid_hmr_listener_set = set;
|
|
138
|
+
}
|
|
139
|
+
return set;
|
|
140
|
+
}
|
|
141
|
+
function nsSolidHmrSubscribe(fn) {
|
|
142
|
+
const listeners = getNsSolidHmrListenerSet();
|
|
143
|
+
listeners.add(fn);
|
|
144
|
+
if (VERBOSE)
|
|
145
|
+
console.log('[hmr][solid] subscribe — listeners=', listeners.size);
|
|
146
|
+
return () => listeners.delete(fn);
|
|
147
|
+
}
|
|
148
|
+
function nsSolidHmrEmit(ev) {
|
|
149
|
+
const listeners = getNsSolidHmrListenerSet();
|
|
150
|
+
if (VERBOSE)
|
|
151
|
+
console.log('[hmr][solid] emit listeners=', listeners.size, 'changedFiles=', ev.changedFiles);
|
|
152
|
+
for (const fn of Array.from(listeners)) {
|
|
153
|
+
try {
|
|
154
|
+
fn(ev);
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
if (VERBOSE)
|
|
158
|
+
console.warn('[hmr][solid] listener threw', err);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
const g = getGlobalScope();
|
|
164
|
+
g.__ns_solid_hmr_subscribe = nsSolidHmrSubscribe;
|
|
165
|
+
// Eagerly create the listener set so the global exists at module load time.
|
|
166
|
+
getNsSolidHmrListenerSet();
|
|
167
|
+
if (VERBOSE)
|
|
168
|
+
console.log('[hmr][solid] HMR client loaded. global set=', typeof g.__ns_solid_hmr_subscribe, 'listenerSet=', typeof g.__ns_solid_hmr_listener_set);
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
console.warn('[hmr][solid] could not install global __ns_solid_hmr_subscribe', err);
|
|
172
|
+
}
|
|
173
|
+
// Eagerly drive the HMR-applying overlay's 'received' frame as soon
|
|
174
|
+
// as the server emits `ns:hmr-pending`, BEFORE the framework-specific
|
|
175
|
+
// (`ns:angular-update` / `ns:css-updates`) payload arrives. The
|
|
176
|
+
// flavor-specific handler later walks through 'evicting' →
|
|
177
|
+
// 'reimporting' → 'rebooting' → 'complete'. Calling 'received' twice
|
|
178
|
+
// in the same cycle is safe: the overlay preserves
|
|
179
|
+
// `updateCycleStartedAt` when a 'received' frame replaces an existing
|
|
180
|
+
// 'received' frame so the minimum-visible window is still timed
|
|
181
|
+
// against the FIRST frame.
|
|
182
|
+
//
|
|
183
|
+
// Soft-fails when the overlay isn't installed (production builds,
|
|
184
|
+
// vitest, etc.) or when the user opted out via
|
|
185
|
+
// `__NS_HMR_PROGRESS_OVERLAY_ENABLED__ === false`.
|
|
186
|
+
import { applyHmrPendingFrame } from './hmr-pending-overlay.js';
|
|
187
|
+
function setHmrPendingOverlay(filePath) {
|
|
188
|
+
applyHmrPendingFrame(filePath, { getOverlay: getHmrOverlayApi });
|
|
189
|
+
}
|
|
104
190
|
let connectionOverlayTimer = null;
|
|
105
191
|
let connectionOverlayVisible = false;
|
|
106
192
|
let hasOpenedHmrSocket = false;
|
|
107
193
|
let awaitingHealthyHmrMessage = false;
|
|
108
194
|
let pendingConnectionOverlayStage = 'connecting';
|
|
109
195
|
let pendingConnectionOverlayDetail = '';
|
|
196
|
+
// The stage currently PAINTED on screen (set only when we actually show
|
|
197
|
+
// the overlay, not when we merely schedule it). Used to suppress the
|
|
198
|
+
// sub-perceptible offline↔reconnecting flicker during the reconnect
|
|
199
|
+
// retry loop — see the deferral in `connectHmr`/`tryNext`.
|
|
200
|
+
let shownConnectionOverlayStage = null;
|
|
201
|
+
// While the terminal 'offline' frame is showing, a fresh reconnect
|
|
202
|
+
// attempt that is refused near-instantly (the norm when the dev server
|
|
203
|
+
// is down — especially on the Android emulator, where connection-refused
|
|
204
|
+
// returns in ~1ms) would flip the overlay to 'reconnecting' and straight
|
|
205
|
+
// back to 'offline', producing a jarring 1-frame flicker. We defer the
|
|
206
|
+
// 'reconnecting' frame by this much so an instant failure (→ back to
|
|
207
|
+
// 'offline') or a success (→ 'synchronizing') cancels it before it ever
|
|
208
|
+
// paints; only a genuinely in-flight attempt surfaces 'reconnecting'.
|
|
209
|
+
// On iOS real connect attempts exceed this threshold, so the behaviour
|
|
210
|
+
// there is unchanged.
|
|
211
|
+
const RECONNECTING_OVER_OFFLINE_DELAY_MS = 500;
|
|
110
212
|
function clearConnectionOverlayTimer() {
|
|
111
213
|
if (connectionOverlayTimer) {
|
|
112
214
|
clearTimeout(connectionOverlayTimer);
|
|
@@ -114,9 +216,14 @@ function clearConnectionOverlayTimer() {
|
|
|
114
216
|
}
|
|
115
217
|
}
|
|
116
218
|
function showConnectionOverlayNow(stage, detail) {
|
|
219
|
+
// Painting a stage now supersedes any scheduled (deferred) stage, so
|
|
220
|
+
// cancel the pending timer — otherwise a deferred 'reconnecting' could
|
|
221
|
+
// fire moments after we settle back on 'offline' and revive the flicker.
|
|
222
|
+
clearConnectionOverlayTimer();
|
|
117
223
|
pendingConnectionOverlayStage = stage;
|
|
118
224
|
pendingConnectionOverlayDetail = detail || '';
|
|
119
225
|
connectionOverlayVisible = true;
|
|
226
|
+
shownConnectionOverlayStage = stage;
|
|
120
227
|
setConnectionOverlayStage(stage, detail);
|
|
121
228
|
}
|
|
122
229
|
function scheduleConnectionOverlay(stage, detail, delayMs = 1200) {
|
|
@@ -137,24 +244,25 @@ function updateConnectionOverlay(stage, detail) {
|
|
|
137
244
|
function markHmrConnectionHealthy() {
|
|
138
245
|
awaitingHealthyHmrMessage = false;
|
|
139
246
|
clearConnectionOverlayTimer();
|
|
247
|
+
shownConnectionOverlayStage = null;
|
|
140
248
|
if (connectionOverlayVisible) {
|
|
141
249
|
connectionOverlayVisible = false;
|
|
142
250
|
hideConnectionOverlay();
|
|
143
251
|
}
|
|
144
252
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
253
|
+
let CLIENT_STRATEGY;
|
|
254
|
+
const CLIENT_STRATEGY_READY = TARGET_FLAVOR === 'vue' || TARGET_FLAVOR === 'angular'
|
|
255
|
+
? import(`../frameworks/${TARGET_FLAVOR}/client/strategy.js`)
|
|
256
|
+
.then((mod) => {
|
|
257
|
+
CLIENT_STRATEGY = mod && mod[`${TARGET_FLAVOR}ClientStrategy`];
|
|
258
|
+
if (VERBOSE)
|
|
259
|
+
console.log('[hmr-client] client strategy loaded for flavor:', TARGET_FLAVOR);
|
|
260
|
+
CLIENT_STRATEGY?.install();
|
|
261
|
+
})
|
|
262
|
+
.catch((err) => {
|
|
263
|
+
console.warn('[hmr-client] failed to load client strategy for', TARGET_FLAVOR, err);
|
|
264
|
+
})
|
|
265
|
+
: Promise.resolve();
|
|
158
266
|
// Track whether we've mounted an initial app root yet in HTTP-only boot
|
|
159
267
|
let initialMounted = !!globalThis.__NS_HMR_BOOT_COMPLETE__;
|
|
160
268
|
// Prevent duplicate initial-mount scheduling across rapid full-graph broadcasts and re-evaluations
|
|
@@ -172,7 +280,7 @@ let processingPromise = null;
|
|
|
172
280
|
// Detect whether the early placeholder root is still active on screen
|
|
173
281
|
function isPlaceholderActive() {
|
|
174
282
|
try {
|
|
175
|
-
const g =
|
|
283
|
+
const g = getGlobalScope();
|
|
176
284
|
if (g.__NS_DEV_PLACEHOLDER_ROOT_VIEW__)
|
|
177
285
|
return true;
|
|
178
286
|
if (g.__NS_DEV_PLACEHOLDER_ROOT_EARLY__)
|
|
@@ -202,9 +310,9 @@ function applyFullGraph(payload) {
|
|
|
202
310
|
// causes a double-mount race (rescue fires at 450ms, then main.ts fires ~1s later,
|
|
203
311
|
// causing a visual flash and leaving the app in an inconsistent state).
|
|
204
312
|
try {
|
|
205
|
-
const g =
|
|
313
|
+
const g = getGlobalScope();
|
|
206
314
|
const bootDone = !!g.__NS_HMR_BOOT_COMPLETE__;
|
|
207
|
-
if (!bootDone && !initialMounted && !initialMounting && !g.__NS_HMR_RESCUE_SCHEDULED__ &&
|
|
315
|
+
if (!bootDone && !initialMounted && !initialMounting && !g.__NS_HMR_RESCUE_SCHEDULED__ && !TS_LIKE_FLAVOR) {
|
|
208
316
|
// simple snapshot helpers
|
|
209
317
|
const getTopmost = () => {
|
|
210
318
|
try {
|
|
@@ -277,7 +385,7 @@ function applyFullGraph(payload) {
|
|
|
277
385
|
if (VERBOSE)
|
|
278
386
|
console.log('[hmr][init] placeholder persists after delay; evaluating rescue policy');
|
|
279
387
|
// Flavor-specific rescue handling
|
|
280
|
-
if (
|
|
388
|
+
if (TS_LIKE_FLAVOR) {
|
|
281
389
|
// For TS apps, perform a one-time resetRootView to the conventional
|
|
282
390
|
// app root module. This mimics what Application.run would do and
|
|
283
391
|
// replaces the placeholder with the real UI without trying to
|
|
@@ -317,39 +425,7 @@ function applyFullGraph(payload) {
|
|
|
317
425
|
}
|
|
318
426
|
return;
|
|
319
427
|
}
|
|
320
|
-
|
|
321
|
-
switch (TARGET_FLAVOR) {
|
|
322
|
-
case 'vue': {
|
|
323
|
-
const appEntry = graph.get(APP_MAIN_ENTRY_SPEC);
|
|
324
|
-
if (appEntry && Array.isArray(appEntry.deps)) {
|
|
325
|
-
const vueDep = appEntry.deps.find((d) => typeof d === 'string' && /\.vue$/i.test(d));
|
|
326
|
-
if (vueDep)
|
|
327
|
-
candidate = vueDep;
|
|
328
|
-
}
|
|
329
|
-
if (!candidate) {
|
|
330
|
-
for (const id of graph.keys()) {
|
|
331
|
-
if (/\.vue$/i.test(id)) {
|
|
332
|
-
candidate = id;
|
|
333
|
-
break;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
// Fallback: when the module graph is empty (Vite 7+ may not populate it
|
|
338
|
-
// before the first full-graph broadcast), check the SFC artifact registry
|
|
339
|
-
// which is populated from the ns:vue-sfc-registry message.
|
|
340
|
-
if (!candidate && sfcArtifactMap.size > 0) {
|
|
341
|
-
for (const id of sfcArtifactMap.keys()) {
|
|
342
|
-
if (/\.vue$/i.test(id)) {
|
|
343
|
-
candidate = id;
|
|
344
|
-
if (VERBOSE)
|
|
345
|
-
console.log('[hmr][init] rescue candidate from SFC registry:', id);
|
|
346
|
-
break;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
break;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
428
|
+
const candidate = CLIENT_STRATEGY?.selectMountCandidate?.({ graph, appMainEntrySpec: APP_MAIN_ENTRY_SPEC }) ?? null;
|
|
353
429
|
if (!candidate)
|
|
354
430
|
return;
|
|
355
431
|
initialMounting = true;
|
|
@@ -360,10 +436,8 @@ function applyFullGraph(payload) {
|
|
|
360
436
|
(async () => {
|
|
361
437
|
try {
|
|
362
438
|
let comp = null;
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
comp = await loadSfcComponent(candidate, 'initial_mount_rescue');
|
|
366
|
-
break;
|
|
439
|
+
if (CLIENT_STRATEGY?.loadComponentForMount) {
|
|
440
|
+
comp = await CLIENT_STRATEGY.loadComponentForMount(candidate, 'initial_mount_rescue');
|
|
367
441
|
}
|
|
368
442
|
if (!comp)
|
|
369
443
|
return;
|
|
@@ -404,41 +478,11 @@ function applyFullGraph(payload) {
|
|
|
404
478
|
// Only allow initial mount when explicitly enabled. Rely on the app's own main entry start() for the first mount
|
|
405
479
|
// to avoid double-mount races that can cause duplicate navigation logs.
|
|
406
480
|
if (ALLOW_INITIAL_MOUNT && !initialMounted && !bootDone && !bootInProgress && !getCurrentApp() && !getRootFrame()) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
const appEntry = graph.get(APP_MAIN_ENTRY_SPEC);
|
|
411
|
-
if (appEntry && Array.isArray(appEntry.deps)) {
|
|
412
|
-
const vueDep = appEntry.deps.find((d) => typeof d === 'string' && /\.vue$/i.test(d));
|
|
413
|
-
if (vueDep)
|
|
414
|
-
candidate = vueDep;
|
|
415
|
-
}
|
|
416
|
-
if (!candidate) {
|
|
417
|
-
for (const id of graph.keys()) {
|
|
418
|
-
if (/\.vue$/i.test(id)) {
|
|
419
|
-
candidate = id;
|
|
420
|
-
break;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
// Fallback: SFC registry (same as rescue mount above)
|
|
425
|
-
if (!candidate && sfcArtifactMap.size > 0) {
|
|
426
|
-
for (const id of sfcArtifactMap.keys()) {
|
|
427
|
-
if (/\.vue$/i.test(id)) {
|
|
428
|
-
candidate = id;
|
|
429
|
-
if (VERBOSE)
|
|
430
|
-
console.log('[hmr][init] initial mount candidate from SFC registry:', id);
|
|
431
|
-
break;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
break;
|
|
436
|
-
}
|
|
437
|
-
case 'typescript': {
|
|
438
|
-
// For TS flavor, do not perform client-driven initial mount; rely on Application.run.
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
481
|
+
if (TS_LIKE_FLAVOR) {
|
|
482
|
+
// For TS flavor, do not perform client-driven initial mount; rely on Application.run.
|
|
483
|
+
return;
|
|
441
484
|
}
|
|
485
|
+
const candidate = CLIENT_STRATEGY?.selectMountCandidate?.({ graph, appMainEntrySpec: APP_MAIN_ENTRY_SPEC }) ?? null;
|
|
442
486
|
if (candidate) {
|
|
443
487
|
// Mark initial-mount in progress (both module-local and global) BEFORE scheduling async work
|
|
444
488
|
initialMounting = true;
|
|
@@ -452,7 +496,7 @@ function applyFullGraph(payload) {
|
|
|
452
496
|
console.log('[hmr][init] mounting initial root from', candidate, 'flavor=', TARGET_FLAVOR);
|
|
453
497
|
// Android-only: avoid racing entry-runtime reset and Activity bring-up
|
|
454
498
|
try {
|
|
455
|
-
const g =
|
|
499
|
+
const g = getGlobalScope();
|
|
456
500
|
const App = getCore('Application') || g.Application;
|
|
457
501
|
const isAndroid = !!(App && App.android !== undefined);
|
|
458
502
|
if (isAndroid) {
|
|
@@ -483,21 +527,19 @@ function applyFullGraph(payload) {
|
|
|
483
527
|
}
|
|
484
528
|
catch { }
|
|
485
529
|
let comp = null;
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
}
|
|
500
|
-
break;
|
|
530
|
+
if (TS_LIKE_FLAVOR) {
|
|
531
|
+
try {
|
|
532
|
+
const url = await requestModuleFromServer(candidate);
|
|
533
|
+
const mod = await import(/* @vite-ignore */ url);
|
|
534
|
+
comp = mod && (mod.default || mod);
|
|
535
|
+
}
|
|
536
|
+
catch (e) {
|
|
537
|
+
if (VERBOSE)
|
|
538
|
+
console.warn('[hmr][init] TS initial mount failed to import', candidate, e);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
else if (CLIENT_STRATEGY?.loadComponentForMount) {
|
|
542
|
+
comp = await CLIENT_STRATEGY.loadComponentForMount(candidate, 'initial_mount');
|
|
501
543
|
}
|
|
502
544
|
if (comp) {
|
|
503
545
|
const ok = await performResetRoot(comp);
|
|
@@ -558,11 +600,7 @@ function applyDelta(payload) {
|
|
|
558
600
|
setGraphVersion(payload.newVersion);
|
|
559
601
|
}
|
|
560
602
|
const changed = payload.changed || [];
|
|
561
|
-
|
|
562
|
-
case 'vue':
|
|
563
|
-
recordVuePayloadChanges(changed, getGraphVersion());
|
|
564
|
-
break;
|
|
565
|
-
}
|
|
603
|
+
CLIENT_STRATEGY?.recordPayloadChanges?.(changed, getGraphVersion());
|
|
566
604
|
(payload.changed || []).forEach((m) => {
|
|
567
605
|
if (!m || !m.id)
|
|
568
606
|
return;
|
|
@@ -629,7 +667,7 @@ function applyDelta(payload) {
|
|
|
629
667
|
}
|
|
630
668
|
if (isAppMainEntryId(id)) {
|
|
631
669
|
try {
|
|
632
|
-
const exists =
|
|
670
|
+
const exists = getGlobalScope().require?.(id) || globalThis.__nsGetModuleExports?.(id);
|
|
633
671
|
if (!exists && VERBOSE)
|
|
634
672
|
console.log(`[hmr][delta] skipping unresolved ${APP_MAIN_ENTRY_SPEC} change`);
|
|
635
673
|
if (!exists)
|
|
@@ -645,14 +683,10 @@ function applyDelta(payload) {
|
|
|
645
683
|
processQueue();
|
|
646
684
|
}
|
|
647
685
|
}
|
|
648
|
-
// Deterministic navigation using the current Vue app instance rather than vendor-held rootApp
|
|
686
|
+
// Deterministic navigation using the current Vue app instance rather than vendor-held rootApp.
|
|
649
687
|
function __nsNavigateUsingApp(comp, opts = {}) {
|
|
650
|
-
const g =
|
|
651
|
-
|
|
652
|
-
case 'vue':
|
|
653
|
-
ensureVueGlobals();
|
|
654
|
-
break;
|
|
655
|
-
}
|
|
688
|
+
const g = getGlobalScope();
|
|
689
|
+
CLIENT_STRATEGY?.beforeNavigateBuild?.();
|
|
656
690
|
const AppFactory = g.createApp;
|
|
657
691
|
const RootCtor = g.NSVRoot;
|
|
658
692
|
if (typeof AppFactory !== 'function' || typeof RootCtor !== 'function') {
|
|
@@ -675,12 +709,14 @@ function __nsNavigateUsingApp(comp, opts = {}) {
|
|
|
675
709
|
const buildTarget = () => {
|
|
676
710
|
const existingApp = getCurrentApp();
|
|
677
711
|
const baseProvides = (existingApp && existingApp._context && existingApp._context.provides) || {};
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
712
|
+
// Forward `opts.props` as Vue's rootProps so `$navigateTo(Comp, { props: { … } })`
|
|
713
|
+
// reaches the destination component. nativescript-vue's stock `$navigateTo`
|
|
714
|
+
// does the same via `createNativeView(target, options?.props, …)` →
|
|
715
|
+
// `renderer.createApp(component, props)`. Dropping props here would surface
|
|
716
|
+
// at the destination as `[Vue warn]: Missing required prop` and any
|
|
717
|
+
// required-prop component would render with `undefined` bindings.
|
|
718
|
+
const app = AppFactory(normalizeComponent(comp, comp && (comp.__name || comp.name)), opts && opts.props);
|
|
719
|
+
CLIENT_STRATEGY?.onNavAppCreated?.(app);
|
|
684
720
|
try {
|
|
685
721
|
const registry = g.__nsVendorRegistry;
|
|
686
722
|
const req = registry?.get ? g.__nsVendorRequire || g.__nsRequire || g.require : g.__nsRequire || g.require;
|
|
@@ -775,6 +811,184 @@ try {
|
|
|
775
811
|
globalThis.__nsNavigateUsingApp = __nsNavigateUsingApp;
|
|
776
812
|
}
|
|
777
813
|
catch { }
|
|
814
|
+
const openModalRecords = [];
|
|
815
|
+
let modalTrackingInstalled = false;
|
|
816
|
+
/**
|
|
817
|
+
* Map a served/graph module id (e.g. `/app/modal-page.xml`) to its app-root
|
|
818
|
+
* relative path (`modal-page.xml`). Single mapping point — the raw-asset
|
|
819
|
+
* re-registration, page-navigation targets, and modal matching all derive
|
|
820
|
+
* from this; keep them in sync by construction.
|
|
821
|
+
*/
|
|
822
|
+
function toAppRelativePath(id) {
|
|
823
|
+
try {
|
|
824
|
+
const spec = normalizeSpec(id);
|
|
825
|
+
const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
|
|
826
|
+
let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
|
|
827
|
+
if (relPath.startsWith(appVirtual))
|
|
828
|
+
relPath = relPath.slice(appVirtual.length);
|
|
829
|
+
return relPath || null;
|
|
830
|
+
}
|
|
831
|
+
catch {
|
|
832
|
+
return null;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
/** App-root relative module name (no extension) for page-shaped files, else null. */
|
|
836
|
+
function toAppModuleName(id) {
|
|
837
|
+
const relPath = toAppRelativePath(id);
|
|
838
|
+
if (!relPath || !/\.(xml|ts|js)$/i.test(relPath))
|
|
839
|
+
return null;
|
|
840
|
+
return relPath.replace(/\.(xml|ts|js)$/i, '');
|
|
841
|
+
}
|
|
842
|
+
function ensureModalTracking() {
|
|
843
|
+
if (modalTrackingInstalled)
|
|
844
|
+
return;
|
|
845
|
+
try {
|
|
846
|
+
const View = getCore('View') || getGlobalScope().View;
|
|
847
|
+
const proto = View?.prototype;
|
|
848
|
+
if (!proto || typeof proto.showModal !== 'function')
|
|
849
|
+
return;
|
|
850
|
+
const orig = proto.showModal;
|
|
851
|
+
if (orig.__nsHmrModalTracked) {
|
|
852
|
+
modalTrackingInstalled = true;
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
const wrapped = function (...args) {
|
|
856
|
+
const result = orig.apply(this, args);
|
|
857
|
+
try {
|
|
858
|
+
if (typeof args[0] === 'string' && result) {
|
|
859
|
+
// Mirror core's getModalOptions arg shapes: (moduleName, options)
|
|
860
|
+
// or the deprecated positional form.
|
|
861
|
+
const options = args.length === 2 && args[1] && typeof args[1] === 'object' ? args[1] : { context: args[1], closeCallback: args[2], fullscreen: args[3], animated: args[4], stretched: args[5] };
|
|
862
|
+
const moduleName = String(args[0])
|
|
863
|
+
.replace(/^\.\//, '')
|
|
864
|
+
.replace(/\.(xml|ts|js)$/i, '');
|
|
865
|
+
openModalRecords.push({ moduleName, options, parent: this, modal: result });
|
|
866
|
+
if (VERBOSE)
|
|
867
|
+
console.log('[hmr][modal] tracked open modal', moduleName);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
catch { }
|
|
871
|
+
return result;
|
|
872
|
+
};
|
|
873
|
+
wrapped.__nsHmrModalTracked = true;
|
|
874
|
+
proto.showModal = wrapped;
|
|
875
|
+
modalTrackingInstalled = true;
|
|
876
|
+
}
|
|
877
|
+
catch (e) {
|
|
878
|
+
if (VERBOSE)
|
|
879
|
+
console.warn('[hmr][modal] tracking install failed', e);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Enumerate the modals that are currently presented AND were opened by module
|
|
884
|
+
* name, with everything needed to re-present them.
|
|
885
|
+
*
|
|
886
|
+
* Source of truth is core's live modal stack (`_getRootModalViews()`):
|
|
887
|
+
* - `modal._moduleName` — set by the Builder on every createViewFromEntry
|
|
888
|
+
* view (longstanding, used by livesync), so available on any core.
|
|
889
|
+
* - `modal._modalOptions` — the original ShowModalOptions, stored by core's
|
|
890
|
+
* `_showNativeModalView` (newer cores). For older cores the showModal
|
|
891
|
+
* wrap's records (see ensureModalTracking) fill the gap.
|
|
892
|
+
* - `modal._modalParent` — the presenting view.
|
|
893
|
+
* Stale wrap records are pruned against the live stack while we're here.
|
|
894
|
+
*/
|
|
895
|
+
function getOpenStringModuleModals() {
|
|
896
|
+
const out = [];
|
|
897
|
+
try {
|
|
898
|
+
const App = getCore('Application');
|
|
899
|
+
const root = App?.getRootView?.() || App?._rootView;
|
|
900
|
+
const stack = root?._getRootModalViews?.() || [];
|
|
901
|
+
// Prune wrap records whose modal is gone (keeps the fallback list small).
|
|
902
|
+
for (let i = openModalRecords.length - 1; i >= 0; i--) {
|
|
903
|
+
if (!stack.includes(openModalRecords[i].modal)) {
|
|
904
|
+
openModalRecords.splice(i, 1);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
for (const modal of stack) {
|
|
908
|
+
const record = openModalRecords.find((r) => r.modal === modal);
|
|
909
|
+
const rawModuleName = typeof modal?._moduleName === 'string' && modal._moduleName ? modal._moduleName : record?.moduleName;
|
|
910
|
+
const parent = modal?._modalParent || record?.parent;
|
|
911
|
+
const options = modal?._modalOptions || record?.options;
|
|
912
|
+
if (!rawModuleName || !parent)
|
|
913
|
+
continue;
|
|
914
|
+
const moduleName = String(rawModuleName)
|
|
915
|
+
.replace(/^\.\//, '')
|
|
916
|
+
.replace(/\.(xml|ts|js)$/i, '');
|
|
917
|
+
out.push({ moduleName, options, parent, modal });
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
catch (e) {
|
|
921
|
+
if (VERBOSE)
|
|
922
|
+
console.warn('[hmr][modal] open-modal enumeration failed', e);
|
|
923
|
+
}
|
|
924
|
+
return out;
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Close and re-present an open modal so it rebuilds from the freshly
|
|
928
|
+
* re-registered XML/code-behind. Core clears the modal stack synchronously on
|
|
929
|
+
* close but the NATIVE dismissal completes asynchronously; iOS refuses a
|
|
930
|
+
* present while a dismissal is in flight. Newer cores fire `closedModally` on
|
|
931
|
+
* the modal at exactly that completion point — preferred signal. Older cores
|
|
932
|
+
* fall back to polling `isLoaded` (flipped by `_tearDownUI` in the same
|
|
933
|
+
* completion callback).
|
|
934
|
+
*/
|
|
935
|
+
async function reshowOpenModal(record) {
|
|
936
|
+
const { parent, modal, moduleName, options } = record;
|
|
937
|
+
await new Promise((resolve) => {
|
|
938
|
+
let settled = false;
|
|
939
|
+
const finish = () => {
|
|
940
|
+
if (!settled) {
|
|
941
|
+
settled = true;
|
|
942
|
+
resolve();
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
let eventArmed = false;
|
|
946
|
+
try {
|
|
947
|
+
if (typeof modal.once === 'function') {
|
|
948
|
+
modal.once('closedModally', finish);
|
|
949
|
+
eventArmed = true;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
catch { }
|
|
953
|
+
// Poll fallback (also the safety net if the event never fires —
|
|
954
|
+
// e.g. an interactive-dismiss cancellation).
|
|
955
|
+
const deadline = Date.now() + 2000;
|
|
956
|
+
const poll = () => {
|
|
957
|
+
if (settled)
|
|
958
|
+
return;
|
|
959
|
+
let stillLoaded = false;
|
|
960
|
+
try {
|
|
961
|
+
stillLoaded = !!modal.isLoaded;
|
|
962
|
+
}
|
|
963
|
+
catch { }
|
|
964
|
+
if ((!eventArmed && !stillLoaded) || Date.now() > deadline) {
|
|
965
|
+
finish();
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
setTimeout(poll, 50);
|
|
969
|
+
};
|
|
970
|
+
setTimeout(poll, 50);
|
|
971
|
+
try {
|
|
972
|
+
modal.closeModal();
|
|
973
|
+
}
|
|
974
|
+
catch (e) {
|
|
975
|
+
if (VERBOSE)
|
|
976
|
+
console.warn('[hmr][modal] close failed for', moduleName, e);
|
|
977
|
+
finish();
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
// One settle beat so the platform finishes releasing the presentation
|
|
981
|
+
// before the new present begins.
|
|
982
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
983
|
+
try {
|
|
984
|
+
parent.showModal(moduleName, { ...(options || {}), animated: false });
|
|
985
|
+
if (VERBOSE)
|
|
986
|
+
console.log('[hmr][modal] re-presented', moduleName);
|
|
987
|
+
}
|
|
988
|
+
catch (e) {
|
|
989
|
+
console.warn('[hmr][modal] re-present failed for', moduleName, e);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
778
992
|
async function processQueue() {
|
|
779
993
|
if (!globalThis.__NS_HMR_BOOT_COMPLETE__) {
|
|
780
994
|
if (VERBOSE)
|
|
@@ -810,7 +1024,43 @@ async function processQueue() {
|
|
|
810
1024
|
return;
|
|
811
1025
|
if (VERBOSE)
|
|
812
1026
|
console.log('[hmr][queue] processing changed ids', drained);
|
|
1027
|
+
// Track wall-clock so the 'complete' frame can show a meaningful
|
|
1028
|
+
// total. Only the Solid + TypeScript flavors drive the overlay
|
|
1029
|
+
// from here; Angular has its own flow inside
|
|
1030
|
+
// `frameworks/angular/client/index.ts`.
|
|
1031
|
+
const tQueueStart = Date.now();
|
|
1032
|
+
const driveSolidOverlay = TARGET_FLAVOR === 'solid';
|
|
1033
|
+
// Explicit eviction step.
|
|
1034
|
+
//
|
|
1035
|
+
// On modern runtimes the URL canonicalizer collapses any
|
|
1036
|
+
// `__ns_hmr__/<tag>/` segment back to a stable cache key, so
|
|
1037
|
+
// without explicit eviction the upcoming `import(url)` would
|
|
1038
|
+
// resolve via V8's `g_moduleRegistry` and return the cached
|
|
1039
|
+
// stale module — making the queue drain a silent no-op for
|
|
1040
|
+
// every save after the first.
|
|
1041
|
+
//
|
|
1042
|
+
// We hand the canonical eviction URLs to the runtime first;
|
|
1043
|
+
// `invalidateModulesByUrls` is a no-op on older runtimes and
|
|
1044
|
+
// `requestModuleFromServer` automatically falls back to the
|
|
1045
|
+
// legacy `/ns/m/__ns_hmr__/v<N>/` URL versioning path in that
|
|
1046
|
+
// case. node_modules and virtual specs are filtered out by
|
|
1047
|
+
// `buildEvictionUrls` so vendor modules stay hot.
|
|
1048
|
+
if (driveSolidOverlay) {
|
|
1049
|
+
setUpdateOverlayStage('evicting', {
|
|
1050
|
+
detail: drained.length === 1 ? `Invalidating ${drained[0]}` : `Invalidating ${drained.length} modules`,
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
const evictUrls = buildEvictionUrls(drained);
|
|
1054
|
+
const evicted = invalidateModulesByUrls(evictUrls);
|
|
1055
|
+
if (VERBOSE)
|
|
1056
|
+
console.log(`[hmr][queue] eviction count=${evictUrls.length} ok=${evicted}`);
|
|
813
1057
|
// Evaluate changed modules best-effort; failures shouldn't completely break HMR.
|
|
1058
|
+
if (driveSolidOverlay) {
|
|
1059
|
+
setUpdateOverlayStage('reimporting', {
|
|
1060
|
+
detail: drained.length === 1 ? `Re-importing ${drained[0]}` : `Re-importing ${drained.length} modules`,
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
let reimportFailures = 0;
|
|
814
1064
|
for (const id of drained) {
|
|
815
1065
|
try {
|
|
816
1066
|
const spec = normalizeSpec(id);
|
|
@@ -820,18 +1070,71 @@ async function processQueue() {
|
|
|
820
1070
|
if (VERBOSE)
|
|
821
1071
|
console.log('[hmr][queue] re-import', { id, spec, url });
|
|
822
1072
|
const mod = await import(/* @vite-ignore */ url);
|
|
1073
|
+
// TS/XML flavor: refresh the bundler module registry with the fresh
|
|
1074
|
+
// exports so Builder.createViewFromEntry / loadModule('<page>')
|
|
1075
|
+
// resolves the NEW code-behind (tap handlers, page events) instead
|
|
1076
|
+
// of the stale module captured in the boot bundle. Without this,
|
|
1077
|
+
// XML re-renders pick up new markup but keep old behavior.
|
|
1078
|
+
if (TS_LIKE_FLAVOR && mod && /\.(ts|js)$/i.test(id)) {
|
|
1079
|
+
try {
|
|
1080
|
+
const g = getGlobalScope();
|
|
1081
|
+
const moduleName = toAppModuleName(id);
|
|
1082
|
+
if (moduleName && typeof g.registerModule === 'function') {
|
|
1083
|
+
g.registerModule(moduleName, () => mod);
|
|
1084
|
+
g.registerModule('./' + moduleName, () => mod);
|
|
1085
|
+
if (VERBOSE)
|
|
1086
|
+
console.log('[hmr][queue] re-registered code-behind', moduleName);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
catch (e) {
|
|
1090
|
+
if (VERBOSE)
|
|
1091
|
+
console.warn('[hmr][queue] code-behind re-register failed for', id, e);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
823
1094
|
}
|
|
824
1095
|
catch (e) {
|
|
825
|
-
|
|
826
|
-
|
|
1096
|
+
// Never silent: a failed re-import means the device may keep
|
|
1097
|
+
// running the previous module body, so always surface it (not
|
|
1098
|
+
// verbose-gated) — otherwise the overlay reports success while
|
|
1099
|
+
// the app runs stale code.
|
|
1100
|
+
reimportFailures++;
|
|
1101
|
+
console.warn('[hmr][queue] re-import FAILED for', id, '-', e?.message ?? e);
|
|
827
1102
|
}
|
|
828
1103
|
}
|
|
1104
|
+
if (reimportFailures > 0) {
|
|
1105
|
+
console.warn(`[hmr][queue] ${reimportFailures}/${drained.length} module(s) failed to re-import; the applied update may be incomplete.`);
|
|
1106
|
+
}
|
|
829
1107
|
// After evaluating the batch, perform flavor-specific UI refresh.
|
|
830
1108
|
switch (TARGET_FLAVOR) {
|
|
831
1109
|
case 'vue':
|
|
832
|
-
//
|
|
1110
|
+
// graph + performResetRoot + getOverlay let the Vue strategy
|
|
1111
|
+
// propagate non-SFC dep changes to the nearest `.vue` boundary
|
|
1112
|
+
// and remount it (see `propagateDepChangeToSfcBoundary`).
|
|
1113
|
+
await CLIENT_STRATEGY?.refreshAfterBatch?.(drained, { setUpdateOverlayStage, startedAt: tQueueStart, graph, performResetRoot, getOverlay: getHmrOverlayApi });
|
|
1114
|
+
break;
|
|
1115
|
+
case 'react': {
|
|
1116
|
+
// React has no Fast Refresh, and (like Solid) mounts a dominative
|
|
1117
|
+
// document root via `startReactApp` rather than an `app-root`
|
|
1118
|
+
// module — so the TypeScript `resetRootView({moduleName:'app-root'})`
|
|
1119
|
+
// path doesn't apply. The changed modules were already re-imported
|
|
1120
|
+
// into the device cache above; the per-page remount is driven by the
|
|
1121
|
+
// app's `__NS_HMR_ON_UPDATE__` hook (see
|
|
1122
|
+
// `@nativescript/tanstack-router/react`'s `subscribeReactHmrRemount`),
|
|
1123
|
+
// which fires right after this queue drains. Here we only mark the
|
|
1124
|
+
// overlay complete so it doesn't stick at 'received' (5%).
|
|
1125
|
+
setUpdateOverlayStage('complete', {
|
|
1126
|
+
detail: `Total ${Math.max(0, Date.now() - tQueueStart)}ms`,
|
|
1127
|
+
});
|
|
833
1128
|
break;
|
|
1129
|
+
}
|
|
834
1130
|
case 'solid': {
|
|
1131
|
+
// Boundaries discovered in this HMR cycle (tsx files reachable
|
|
1132
|
+
// via the reverse import graph from any changed file, plus route
|
|
1133
|
+
// files reachable from any tsx start point). Declared at the top
|
|
1134
|
+
// of the case block so the emit step below can include the
|
|
1135
|
+
// complete set in the listener event — framework integrations
|
|
1136
|
+
// use it to map route boundaries → fresh component references.
|
|
1137
|
+
const boundaries = new Set();
|
|
835
1138
|
// Solid .tsx components are self-accepting via solid-refresh's inline
|
|
836
1139
|
// patchRegistry — re-importing them is sufficient. For non-component
|
|
837
1140
|
// .ts utility modules, we must propagate up the import graph to find
|
|
@@ -850,8 +1153,10 @@ async function processQueue() {
|
|
|
850
1153
|
arr.push(id);
|
|
851
1154
|
}
|
|
852
1155
|
}
|
|
853
|
-
// BFS from each non-tsx changed module up to tsx/jsx
|
|
854
|
-
|
|
1156
|
+
// Pass 1: BFS from each non-tsx changed module up to tsx/jsx
|
|
1157
|
+
// boundaries. These get re-imported below so solid-refresh's
|
|
1158
|
+
// inline patchRegistry runs and (best-effort) swaps the proxy
|
|
1159
|
+
// signals for any components defined in those tsx boundaries.
|
|
855
1160
|
for (const id of drained) {
|
|
856
1161
|
if (/\.(tsx|jsx)$/i.test(id))
|
|
857
1162
|
continue; // already self-accepting
|
|
@@ -875,6 +1180,51 @@ async function processQueue() {
|
|
|
875
1180
|
}
|
|
876
1181
|
}
|
|
877
1182
|
}
|
|
1183
|
+
// Pass 2: walk further from any tsx starting point (a tsx file
|
|
1184
|
+
// in `drained` OR a tsx boundary discovered in pass 1) to find
|
|
1185
|
+
// route files (`/src/routes/*.{tsx,jsx}`) that transitively
|
|
1186
|
+
// import them. Re-importing a route file refreshes its
|
|
1187
|
+
// `Route.options.component` to the freshly-imported reference
|
|
1188
|
+
// and the existing boundary loop below patches the live router
|
|
1189
|
+
// with that fresh reference.
|
|
1190
|
+
//
|
|
1191
|
+
// This is the key fix for "edit home.tsx → save → no visual
|
|
1192
|
+
// update": the old BFS skipped tsx files in `drained` (assuming
|
|
1193
|
+
// solid-refresh's in-place proxy patch was sufficient), but in
|
|
1194
|
+
// the universal-renderer + nested-context configuration that
|
|
1195
|
+
// patch does not always propagate to the visible page tree.
|
|
1196
|
+
// Adding the route file as a boundary lets us patch
|
|
1197
|
+
// `route.options.component` directly to a fresh module export,
|
|
1198
|
+
// which the framework subscriber then passes through to the
|
|
1199
|
+
// page remount — making the cycle robust to the proxy patch
|
|
1200
|
+
// silently failing.
|
|
1201
|
+
const tsxStarts = new Set();
|
|
1202
|
+
for (const id of drained) {
|
|
1203
|
+
if (/\.(tsx|jsx)$/i.test(id))
|
|
1204
|
+
tsxStarts.add(id);
|
|
1205
|
+
}
|
|
1206
|
+
for (const b of boundaries)
|
|
1207
|
+
tsxStarts.add(b);
|
|
1208
|
+
const ROUTE_FILE_RE = /\/src\/routes\/.+\.(tsx|jsx)$/i;
|
|
1209
|
+
for (const start of tsxStarts) {
|
|
1210
|
+
const visited = new Set();
|
|
1211
|
+
const queue = [start];
|
|
1212
|
+
while (queue.length) {
|
|
1213
|
+
const cur = queue.shift();
|
|
1214
|
+
if (visited.has(cur))
|
|
1215
|
+
continue;
|
|
1216
|
+
visited.add(cur);
|
|
1217
|
+
if (cur !== start && ROUTE_FILE_RE.test(cur)) {
|
|
1218
|
+
boundaries.add(cur);
|
|
1219
|
+
}
|
|
1220
|
+
const importers = reverseIndex.get(cur);
|
|
1221
|
+
if (!importers)
|
|
1222
|
+
continue;
|
|
1223
|
+
for (const imp of importers) {
|
|
1224
|
+
queue.push(imp);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
878
1228
|
// Re-import each boundary so solid-refresh patchRegistry fires.
|
|
879
1229
|
// For route files (TanStack Router), capture the new Route export
|
|
880
1230
|
// and patch the router's existing route with the fresh loader.
|
|
@@ -885,7 +1235,7 @@ async function processQueue() {
|
|
|
885
1235
|
const findRouter = () => {
|
|
886
1236
|
if (discoveredRouter)
|
|
887
1237
|
return discoveredRouter;
|
|
888
|
-
const g =
|
|
1238
|
+
const g = getGlobalScope();
|
|
889
1239
|
if (g.__ns_router?.routesById)
|
|
890
1240
|
return (discoveredRouter = g.__ns_router);
|
|
891
1241
|
// Fallback: scan common global keys for router
|
|
@@ -913,44 +1263,100 @@ async function processQueue() {
|
|
|
913
1263
|
p = p.replace(/(^|\/)-([\w])/g, '$1$2');
|
|
914
1264
|
return '/' + p;
|
|
915
1265
|
};
|
|
916
|
-
// Find existing route by fullPath (since new Route has no id yet)
|
|
1266
|
+
// Find existing route by fullPath (since new Route has no id yet).
|
|
1267
|
+
// The root route (`__root__`) shares fullPath '/' with the index
|
|
1268
|
+
// route, so prefer the non-root (leaf) match — otherwise editing
|
|
1269
|
+
// `index.tsx` would clobber the root layout's component instead of
|
|
1270
|
+
// the page's. @nativescript/tanstack-router's remount uses the same
|
|
1271
|
+
// preference so patch + read target the same route.
|
|
917
1272
|
const findRouteByFullPath = (router, fp) => {
|
|
918
1273
|
if (!router?.routesById)
|
|
919
1274
|
return null;
|
|
1275
|
+
let rootMatch = null;
|
|
920
1276
|
for (const rid of Object.keys(router.routesById)) {
|
|
921
1277
|
const r = router.routesById[rid];
|
|
922
|
-
if (r?.fullPath
|
|
923
|
-
|
|
1278
|
+
if (r?.fullPath !== fp)
|
|
1279
|
+
continue;
|
|
1280
|
+
if (rid === '__root__') {
|
|
1281
|
+
rootMatch = rootMatch || r;
|
|
1282
|
+
continue;
|
|
1283
|
+
}
|
|
1284
|
+
return r;
|
|
924
1285
|
}
|
|
925
|
-
return
|
|
1286
|
+
return rootMatch;
|
|
926
1287
|
};
|
|
1288
|
+
// A changed route file is itself a boundary to re-import + patch — Pass 2
|
|
1289
|
+
// only adds OTHER route files that import a changed module, never the
|
|
1290
|
+
// changed route file itself. Without this, editing `about.tsx` while on
|
|
1291
|
+
// /about never refreshes the About component.
|
|
1292
|
+
for (const id of drained) {
|
|
1293
|
+
if (ROUTE_FILE_RE.test(id))
|
|
1294
|
+
boundaries.add(id);
|
|
1295
|
+
}
|
|
1296
|
+
// Evict ONLY the boundary set (changed component + route files) — NOT the
|
|
1297
|
+
// router / route-tree / app chain. Keeping those cached preserves the live
|
|
1298
|
+
// router instance and the user's current route across the re-mount; fresh
|
|
1299
|
+
// route components reach the screen via the in-place `route.options.component`
|
|
1300
|
+
// patch below, not a router rebuild (which would reset to the initial route).
|
|
1301
|
+
const boundaryIds = Array.from(boundaries);
|
|
1302
|
+
const solidEvictUrls = buildEvictionUrls(boundaryIds);
|
|
1303
|
+
const solidEvicted = invalidateModulesByUrls(solidEvictUrls);
|
|
1304
|
+
if (VERBOSE)
|
|
1305
|
+
console.log(`[hmr][solid] eviction count=${solidEvictUrls.length} ok=${solidEvicted}`);
|
|
927
1306
|
for (const id of boundaries) {
|
|
928
|
-
|
|
1307
|
+
// Skip already-drained modules — EXCEPT route files. The main
|
|
1308
|
+
// drain loop above re-imports drained modules but does NOT patch
|
|
1309
|
+
// `route.options.component`; a changed ROUTE file (which is in
|
|
1310
|
+
// `drained`/`seen`) must still flow through this loop so its route
|
|
1311
|
+
// gets patched with the fresh component, otherwise the router's
|
|
1312
|
+
// per-page HMR remount re-renders the stale (one-edit-behind) one.
|
|
1313
|
+
if (seen.has(id) && !/\/src\/routes\/.+\.(?:tsx|jsx)$/i.test(id))
|
|
1314
|
+
continue;
|
|
1315
|
+
// Skip the root layout (`__root`): it commonly imports a global
|
|
1316
|
+
// stylesheet (`./styles.css?url`) with no ESM `default` export on
|
|
1317
|
+
// device, so re-importing it throws and aborts the cycle. The root
|
|
1318
|
+
// layout rarely needs a component hot-swap anyway.
|
|
1319
|
+
if (/\/routes\/__root\.(?:tsx|jsx|ts|js)$/i.test(id))
|
|
929
1320
|
continue;
|
|
930
1321
|
try {
|
|
931
1322
|
const spec = normalizeSpec(id);
|
|
932
|
-
|
|
1323
|
+
// Re-import via an entry-tagged URL so the dev server serves a
|
|
1324
|
+
// freshly-transformed module rather than a cached (one-edit-behind)
|
|
1325
|
+
// transform. This is what `route.options.component` is patched
|
|
1326
|
+
// from below, and what @nativescript/tanstack-router's per-page
|
|
1327
|
+
// HMR remount reads — it MUST be the just-saved code, not the
|
|
1328
|
+
// previous save. `requestModuleFromServer` (used elsewhere) can
|
|
1329
|
+
// return the prior transform within the cache window.
|
|
1330
|
+
const ftOrigin = getHttpOriginForVite() || deriveHttpOrigin(getHMRWsUrl());
|
|
1331
|
+
const ftNonce = `${getGraphVersion()}_${Date.now()}`;
|
|
1332
|
+
const url = ftOrigin ? ftOrigin + '/ns/m/__ns_hmr__/entry-' + ftNonce + (spec.startsWith('/') ? spec : '/' + spec) + '?t=' + ftNonce : await requestModuleFromServer(spec);
|
|
933
1333
|
if (!url)
|
|
934
1334
|
continue;
|
|
935
1335
|
if (VERBOSE)
|
|
936
1336
|
console.log('[hmr][solid] propagated to boundary', { id, url });
|
|
937
1337
|
const mod = await import(/* @vite-ignore */ url);
|
|
938
|
-
// Patch TanStack Router route
|
|
1338
|
+
// Patch TanStack Router route options for any module
|
|
1339
|
+
// that exports a `Route`. We patch BOTH the component
|
|
1340
|
+
// and the loader (when present); components-only routes
|
|
1341
|
+
// were previously skipped because the gate required a
|
|
1342
|
+
// loader, which left their `options.component` pointing
|
|
1343
|
+
// at the stale module's exports after HMR.
|
|
939
1344
|
try {
|
|
940
1345
|
const newRoute = mod?.Route;
|
|
941
|
-
if (newRoute?.options
|
|
1346
|
+
if (newRoute?.options) {
|
|
942
1347
|
const router = findRouter();
|
|
943
1348
|
const fullPath = boundaryToFullPath(id);
|
|
944
1349
|
if (VERBOSE)
|
|
945
|
-
console.log('[hmr][solid][diag] route patch attempt', { id, fullPath, hasRouter: !!router,
|
|
1350
|
+
console.log('[hmr][solid][diag] route patch attempt', { id, fullPath, hasRouter: !!router, hasLoader: !!newRoute.options.loader, hasComponent: !!newRoute.options.component });
|
|
946
1351
|
const existingRoute = fullPath && router ? findRouteByFullPath(router, fullPath) : null;
|
|
947
1352
|
if (existingRoute?.options) {
|
|
948
|
-
|
|
1353
|
+
if (newRoute.options.loader)
|
|
1354
|
+
existingRoute.options.loader = newRoute.options.loader;
|
|
949
1355
|
if (newRoute.options.component)
|
|
950
1356
|
existingRoute.options.component = newRoute.options.component;
|
|
951
1357
|
routesPatchCount++;
|
|
952
1358
|
if (VERBOSE)
|
|
953
|
-
console.log('[hmr][solid] patched route
|
|
1359
|
+
console.log('[hmr][solid] patched route', existingRoute.id, 'fullPath=', fullPath);
|
|
954
1360
|
}
|
|
955
1361
|
else if (VERBOSE) {
|
|
956
1362
|
console.log('[hmr][solid] no matching route for fullPath', fullPath);
|
|
@@ -986,6 +1392,44 @@ async function processQueue() {
|
|
|
986
1392
|
if (VERBOSE)
|
|
987
1393
|
console.warn('[hmr][solid] propagation failed', e);
|
|
988
1394
|
}
|
|
1395
|
+
// Notify any framework integrations (e.g.
|
|
1396
|
+
// `@nativescript/tanstack-router`) that a Solid HMR
|
|
1397
|
+
// cycle has completed. They use this signal to perform
|
|
1398
|
+
// framework-specific UI refresh (e.g. remount the active
|
|
1399
|
+
// router page) when solid-refresh's own reactive
|
|
1400
|
+
// propagation does not reach the visible tree under
|
|
1401
|
+
// the current renderer/context configuration.
|
|
1402
|
+
//
|
|
1403
|
+
// Boundaries include both the directly-changed tsx files
|
|
1404
|
+
// AND every tsx ancestor reachable via the reverse import
|
|
1405
|
+
// graph (route files in particular). The framework
|
|
1406
|
+
// listener uses the route-file boundaries to look up the
|
|
1407
|
+
// freshly-patched `route.options.component` and pass it
|
|
1408
|
+
// through to the page remount.
|
|
1409
|
+
try {
|
|
1410
|
+
const tsxChangedInDrained = drained.filter((id) => /\.(tsx|jsx)$/i.test(id));
|
|
1411
|
+
const allBoundaries = Array.from(new Set([...tsxChangedInDrained, ...boundaries]));
|
|
1412
|
+
nsSolidHmrEmit({
|
|
1413
|
+
kind: 'solid',
|
|
1414
|
+
changedFiles: drained.slice(),
|
|
1415
|
+
boundaries: allBoundaries,
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
catch (err) {
|
|
1419
|
+
if (VERBOSE)
|
|
1420
|
+
console.warn('[hmr][solid] emit failed', err);
|
|
1421
|
+
}
|
|
1422
|
+
// Tell the overlay the cycle is done. solid-refresh's
|
|
1423
|
+
// inline patchRegistry has already flushed the new
|
|
1424
|
+
// component bodies into the live tree (the `case
|
|
1425
|
+
// 'solid'` block above re-imports each .tsx
|
|
1426
|
+
// boundary), so by the time we get here the user is
|
|
1427
|
+
// already looking at the new render. The 'complete'
|
|
1428
|
+
// frame surfaces the wall-clock total and triggers
|
|
1429
|
+
// the overlay's auto-hide.
|
|
1430
|
+
setUpdateOverlayStage('complete', {
|
|
1431
|
+
detail: `Total ${Math.max(0, Date.now() - tQueueStart)}ms`,
|
|
1432
|
+
});
|
|
989
1433
|
break;
|
|
990
1434
|
}
|
|
991
1435
|
case 'typescript': {
|
|
@@ -993,7 +1437,7 @@ async function processQueue() {
|
|
|
993
1437
|
// This preserves the shell (Frame, ActionBar, etc.) that the app's
|
|
994
1438
|
// own bootstrapping wires up via `Application.run`.
|
|
995
1439
|
try {
|
|
996
|
-
const g =
|
|
1440
|
+
const g = getGlobalScope();
|
|
997
1441
|
const App = getCore('Application') || g.Application;
|
|
998
1442
|
if (!App || typeof App.resetRootView !== 'function') {
|
|
999
1443
|
if (VERBOSE)
|
|
@@ -1019,10 +1463,9 @@ async function processQueue() {
|
|
|
1019
1463
|
const rawContent = await resp.text();
|
|
1020
1464
|
// Register under all nickname variants the module registry uses.
|
|
1021
1465
|
// The bundler context registers XML as e.g., './main-page.xml' and 'main-page.xml'
|
|
1022
|
-
const
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
relPath = relPath.slice(appVirtual.length);
|
|
1466
|
+
const relPath = toAppRelativePath(id);
|
|
1467
|
+
if (!relPath)
|
|
1468
|
+
continue;
|
|
1026
1469
|
const nicknames = ['./' + relPath, relPath];
|
|
1027
1470
|
// Also add without extension for CSS
|
|
1028
1471
|
const extIdx = relPath.lastIndexOf('.');
|
|
@@ -1048,21 +1491,24 @@ async function processQueue() {
|
|
|
1048
1491
|
}
|
|
1049
1492
|
}
|
|
1050
1493
|
}
|
|
1494
|
+
// Modal-aware refresh: pages currently PRESENTED AS MODALS must be
|
|
1495
|
+
// closed + re-presented in place — navigating the top frame to a
|
|
1496
|
+
// modal's page would push it as a frame page, and resetRootView
|
|
1497
|
+
// would dismiss the modal entirely. State comes from core's live
|
|
1498
|
+
// modal stack (_moduleName/_modalOptions/_modalParent); the
|
|
1499
|
+
// showModal wrap only backfills options on older cores.
|
|
1500
|
+
ensureModalTracking();
|
|
1501
|
+
const openModals = getOpenStringModuleModals();
|
|
1502
|
+
const changedModuleNames = new Set(drained.map(toAppModuleName).filter(Boolean));
|
|
1503
|
+
const modalsToReshow = openModals.filter((record) => changedModuleNames.has(record.moduleName));
|
|
1504
|
+
const reshowModuleNames = new Set(modalsToReshow.map((record) => record.moduleName));
|
|
1051
1505
|
// Determine if we can navigate in-place to a changed page
|
|
1052
1506
|
// instead of resetting all the way back to app-root.
|
|
1053
1507
|
// This keeps the user on the page they're editing for faster iteration.
|
|
1054
1508
|
const changedXmlPages = drained
|
|
1055
1509
|
.filter((id) => /\.xml$/i.test(id))
|
|
1056
|
-
.map((id) =>
|
|
1057
|
-
|
|
1058
|
-
const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
|
|
1059
|
-
let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
|
|
1060
|
-
if (relPath.startsWith(appVirtual))
|
|
1061
|
-
relPath = relPath.slice(appVirtual.length);
|
|
1062
|
-
// Strip .xml extension to get the moduleName (e.g., 'pages/status-bar')
|
|
1063
|
-
return relPath.replace(/\.xml$/i, '');
|
|
1064
|
-
})
|
|
1065
|
-
.filter((m) => m && m !== 'app-root');
|
|
1510
|
+
.map((id) => toAppModuleName(id))
|
|
1511
|
+
.filter((m) => m && m !== 'app-root' && !reshowModuleNames.has(m));
|
|
1066
1512
|
// Resolve the topmost Frame from the bundled realm.
|
|
1067
1513
|
// Frame.topmost() relies on an internal frameStack array, so we must
|
|
1068
1514
|
// call it on the bundled-realm class. Multiple strategies to find it:
|
|
@@ -1098,7 +1544,7 @@ async function processQueue() {
|
|
|
1098
1544
|
catch { }
|
|
1099
1545
|
}
|
|
1100
1546
|
if (VERBOSE)
|
|
1101
|
-
console.log('[hmr][queue] TS: changedXmlPages=', changedXmlPages, 'topFrame=', !!topFrame);
|
|
1547
|
+
console.log('[hmr][queue] TS: changedXmlPages=', changedXmlPages, 'topFrame=', !!topFrame, 'modalsToReshow=', modalsToReshow.length);
|
|
1102
1548
|
if (changedXmlPages.length > 0 && topFrame) {
|
|
1103
1549
|
// Navigate the current frame to the changed page directly.
|
|
1104
1550
|
// Use the last changed XML page (most specific).
|
|
@@ -1113,15 +1559,32 @@ async function processQueue() {
|
|
|
1113
1559
|
App.resetRootView({ moduleName: 'app-root' });
|
|
1114
1560
|
}
|
|
1115
1561
|
}
|
|
1116
|
-
else {
|
|
1562
|
+
else if (modalsToReshow.length === 0) {
|
|
1563
|
+
// No frame page to refresh and no open modal owns the change —
|
|
1564
|
+
// fall back to a full root reset. (Skipped when an open modal is
|
|
1565
|
+
// being re-presented below: resetRootView would dismiss it.)
|
|
1117
1566
|
if (VERBOSE)
|
|
1118
1567
|
console.log('[hmr][queue] TS flavor: resetRootView(app-root) after changes');
|
|
1119
1568
|
App.resetRootView({ moduleName: 'app-root' });
|
|
1120
1569
|
}
|
|
1570
|
+
// Re-present any open modals whose XML/code-behind changed. The
|
|
1571
|
+
// modules were already re-registered above (raw XML assets + fresh
|
|
1572
|
+
// code-behind exports), so the re-presented modal rebuilds from
|
|
1573
|
+
// the new content while the page beneath it stays put.
|
|
1574
|
+
for (const record of modalsToReshow) {
|
|
1575
|
+
await reshowOpenModal(record);
|
|
1576
|
+
}
|
|
1121
1577
|
}
|
|
1122
1578
|
catch (e) {
|
|
1123
1579
|
console.warn('[hmr][queue] TS flavor: resetRootView(app-root) failed', e);
|
|
1124
1580
|
}
|
|
1581
|
+
// Tell the overlay the cycle is done — same as the solid path
|
|
1582
|
+
// above. Without this the applying overlay sticks at
|
|
1583
|
+
// 'received' (5%) forever even though the in-place navigate /
|
|
1584
|
+
// resetRootView already applied the update.
|
|
1585
|
+
setUpdateOverlayStage('complete', {
|
|
1586
|
+
detail: `Total ${Math.max(0, Date.now() - tQueueStart)}ms`,
|
|
1587
|
+
});
|
|
1125
1588
|
break;
|
|
1126
1589
|
}
|
|
1127
1590
|
}
|
|
@@ -1129,11 +1592,37 @@ async function processQueue() {
|
|
|
1129
1592
|
finally {
|
|
1130
1593
|
processingQueue = false;
|
|
1131
1594
|
processingPromise = null;
|
|
1595
|
+
// If a delta arrived mid-cycle (pushed onto changedQueue while
|
|
1596
|
+
// processingQueue was still true, so its processQueue() call early-
|
|
1597
|
+
// returned), re-drain — otherwise that save is stranded until the next
|
|
1598
|
+
// delta. Deferred via setTimeout to avoid synchronous re-entrancy as
|
|
1599
|
+
// this promise settles.
|
|
1600
|
+
if (changedQueue.length) {
|
|
1601
|
+
setTimeout(() => {
|
|
1602
|
+
try {
|
|
1603
|
+
processQueue();
|
|
1604
|
+
}
|
|
1605
|
+
catch { }
|
|
1606
|
+
}, 0);
|
|
1607
|
+
}
|
|
1132
1608
|
}
|
|
1133
1609
|
})();
|
|
1134
1610
|
return processingPromise;
|
|
1135
1611
|
}
|
|
1136
1612
|
let hmrSocket = null;
|
|
1613
|
+
// Single reconnect timer. Overlapping close/timeout events used to each schedule
|
|
1614
|
+
// their own `setTimeout(connectHmr, …)`, stacking multiple pending reconnects
|
|
1615
|
+
// that could spawn (and leak) duplicate sockets/listeners. Route all reconnect
|
|
1616
|
+
// scheduling through here so only one is ever pending.
|
|
1617
|
+
let reconnectTimer = null;
|
|
1618
|
+
function scheduleReconnect(delayMs) {
|
|
1619
|
+
if (reconnectTimer)
|
|
1620
|
+
clearTimeout(reconnectTimer);
|
|
1621
|
+
reconnectTimer = setTimeout(() => {
|
|
1622
|
+
reconnectTimer = null;
|
|
1623
|
+
connectHmr();
|
|
1624
|
+
}, delayMs);
|
|
1625
|
+
}
|
|
1137
1626
|
// Track server-announced batches for each version so we can import in-order client-side
|
|
1138
1627
|
const txnClientBatches = new Map();
|
|
1139
1628
|
// Public hook for NativeScript runtime to call from ImportModuleDynamicallyCallback later.
|
|
@@ -1161,10 +1650,16 @@ function connectHmr() {
|
|
|
1161
1650
|
if (hmrSocket?.readyState === WebSocket.OPEN)
|
|
1162
1651
|
return;
|
|
1163
1652
|
if (hmrSocket?.readyState === WebSocket.CONNECTING) {
|
|
1164
|
-
if (
|
|
1653
|
+
if (VERBOSE)
|
|
1165
1654
|
console.log('[hmr-client] Already connecting to HMR WebSocket, skipping');
|
|
1166
1655
|
return;
|
|
1167
1656
|
}
|
|
1657
|
+
// A reconnect fired (or a manual connect raced one) — cancel any other pending
|
|
1658
|
+
// reconnect so we don't end up with overlapping connect attempts.
|
|
1659
|
+
if (reconnectTimer) {
|
|
1660
|
+
clearTimeout(reconnectTimer);
|
|
1661
|
+
reconnectTimer = null;
|
|
1662
|
+
}
|
|
1168
1663
|
try {
|
|
1169
1664
|
globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = false;
|
|
1170
1665
|
}
|
|
@@ -1172,7 +1667,7 @@ function connectHmr() {
|
|
|
1172
1667
|
const overlayStage = hasOpenedHmrSocket ? 'reconnecting' : 'connecting';
|
|
1173
1668
|
const baseUrl = getHMRWsUrl() || 'ws://localhost:5173/ns-hmr';
|
|
1174
1669
|
const buildCandidates = (url) => {
|
|
1175
|
-
|
|
1670
|
+
const candidates = [];
|
|
1176
1671
|
try {
|
|
1177
1672
|
const u = new URL(url);
|
|
1178
1673
|
const proto = u.protocol === 'wss:' ? ['wss'] : ['ws'];
|
|
@@ -1180,7 +1675,7 @@ function connectHmr() {
|
|
|
1180
1675
|
// Build ordered host candidates with preference to the active HTTP origin
|
|
1181
1676
|
const orderedHosts = [];
|
|
1182
1677
|
try {
|
|
1183
|
-
const g =
|
|
1678
|
+
const g = getGlobalScope();
|
|
1184
1679
|
const httpOrigin = g && typeof g.__NS_HTTP_ORIGIN__ === 'string' ? g.__NS_HTTP_ORIGIN__ : undefined;
|
|
1185
1680
|
if (httpOrigin) {
|
|
1186
1681
|
try {
|
|
@@ -1231,19 +1726,29 @@ function connectHmr() {
|
|
|
1231
1726
|
if (idx >= candidates.length) {
|
|
1232
1727
|
showConnectionOverlayNow('offline', 'Waiting for the Vite websocket to come back.');
|
|
1233
1728
|
console.warn('[hmr-client] All WS candidates failed:', candidates.join(', '));
|
|
1234
|
-
|
|
1729
|
+
scheduleReconnect(1500);
|
|
1235
1730
|
return;
|
|
1236
1731
|
}
|
|
1237
1732
|
const url = candidates[idx++];
|
|
1238
1733
|
const connectionDetail = `${overlayStage === 'reconnecting' ? 'Retrying' : 'Opening'} ${url}`;
|
|
1239
|
-
if (
|
|
1734
|
+
if (overlayStage === 'reconnecting' && shownConnectionOverlayStage === 'offline') {
|
|
1735
|
+
// Don't flip the visible 'offline' frame to 'reconnecting' for an
|
|
1736
|
+
// attempt that may fail instantly — defer it so only a genuinely
|
|
1737
|
+
// in-flight attempt surfaces. The deferred show is cancelled by
|
|
1738
|
+
// showConnectionOverlayNow (offline re-show / synchronizing) the
|
|
1739
|
+
// moment this attempt resolves. Keeps 'offline' stable instead of
|
|
1740
|
+
// flickering once per retry. (Checking the PAINTED stage, not the
|
|
1741
|
+
// pending one, so every candidate in the loop keeps deferring.)
|
|
1742
|
+
scheduleConnectionOverlay(overlayStage, connectionDetail, RECONNECTING_OVER_OFFLINE_DELAY_MS);
|
|
1743
|
+
}
|
|
1744
|
+
else if (connectionOverlayVisible) {
|
|
1240
1745
|
updateConnectionOverlay(overlayStage, connectionDetail);
|
|
1241
1746
|
}
|
|
1242
1747
|
else {
|
|
1243
1748
|
scheduleConnectionOverlay(overlayStage, connectionDetail);
|
|
1244
1749
|
}
|
|
1245
1750
|
try {
|
|
1246
|
-
if (
|
|
1751
|
+
if (VERBOSE)
|
|
1247
1752
|
console.log('[hmr-client] Connecting to HMR WebSocket:', url);
|
|
1248
1753
|
const sock = new WebSocket(url);
|
|
1249
1754
|
hmrSocket = sock;
|
|
@@ -1273,7 +1778,16 @@ function connectHmr() {
|
|
|
1273
1778
|
if (connectionOverlayVisible) {
|
|
1274
1779
|
showConnectionOverlayNow('synchronizing', 'Connected. Synchronizing the HMR graph.');
|
|
1275
1780
|
}
|
|
1276
|
-
|
|
1781
|
+
if (VERBOSE)
|
|
1782
|
+
console.log('[hmr-client] Connected to HMR WebSocket');
|
|
1783
|
+
// Print the active module reload mode once on first
|
|
1784
|
+
// successful connect so the user can correlate HMR latency
|
|
1785
|
+
// with runtime capability without grepping for protocol
|
|
1786
|
+
// details. The banner is verbose-gated.
|
|
1787
|
+
try {
|
|
1788
|
+
emitHmrModeBannerOnce();
|
|
1789
|
+
}
|
|
1790
|
+
catch { }
|
|
1277
1791
|
};
|
|
1278
1792
|
sock.onmessage = handleHmrMessage;
|
|
1279
1793
|
sock.onerror = (error) => {
|
|
@@ -1297,7 +1811,7 @@ function connectHmr() {
|
|
|
1297
1811
|
console.log('[hmr-client] WebSocket closed (code', ev?.code, '), will reconnect…');
|
|
1298
1812
|
scheduleConnectionOverlay('reconnecting', 'The websocket closed. Waiting to reconnect.', 700);
|
|
1299
1813
|
// try to reconnect with full candidate list again
|
|
1300
|
-
|
|
1814
|
+
scheduleReconnect(1000);
|
|
1301
1815
|
}
|
|
1302
1816
|
};
|
|
1303
1817
|
}
|
|
@@ -1333,10 +1847,26 @@ async function handleHmrMessage(ev) {
|
|
|
1333
1847
|
catch { }
|
|
1334
1848
|
}
|
|
1335
1849
|
if (msg) {
|
|
1850
|
+
// `ns:hmr-pending` is a fire-and-forget UX hint emitted by the
|
|
1851
|
+
// server at the START of handleHotUpdate. We drive the
|
|
1852
|
+
// HMR-applying overlay's 'received' frame here (synchronously),
|
|
1853
|
+
// well before the authoritative payload (`ns:angular-update` /
|
|
1854
|
+
// `ns:css-updates`) lands. Skip running any other handlers —
|
|
1855
|
+
// the pending message has no module payload and intentionally
|
|
1856
|
+
// does not bump the graph version.
|
|
1857
|
+
if (msg.type === 'ns:hmr-pending' && typeof msg.path === 'string') {
|
|
1858
|
+
setHmrPendingOverlay(msg.path);
|
|
1859
|
+
return;
|
|
1860
|
+
}
|
|
1861
|
+
// The per-flavor client strategy is loaded by a dynamic import(); make
|
|
1862
|
+
// sure it has resolved (and `install()` has run) before any handler that
|
|
1863
|
+
// delegates through it. After the first message this is an already-settled
|
|
1864
|
+
// promise (microtask only); for Solid/TypeScript it is `Promise.resolve()`.
|
|
1865
|
+
await CLIENT_STRATEGY_READY;
|
|
1336
1866
|
if (msg.type === 'ns:hmr-full-graph') {
|
|
1337
1867
|
// Bump a monotonic nonce so HTTP ESM imports can always be cache-busted per update.
|
|
1338
1868
|
try {
|
|
1339
|
-
const g =
|
|
1869
|
+
const g = getGlobalScope();
|
|
1340
1870
|
g.__NS_HMR_IMPORT_NONCE__ = (typeof g.__NS_HMR_IMPORT_NONCE__ === 'number' ? g.__NS_HMR_IMPORT_NONCE__ : 0) + 1;
|
|
1341
1871
|
}
|
|
1342
1872
|
catch { }
|
|
@@ -1402,6 +1932,14 @@ async function handleHmrMessage(ev) {
|
|
|
1402
1932
|
});
|
|
1403
1933
|
if (toReimport.length && VERBOSE)
|
|
1404
1934
|
console.log('[hmr][full-graph] inferred changed modules; re-importing', toReimport);
|
|
1935
|
+
// Evict the inferred changed set before re-importing.
|
|
1936
|
+
// See `processQueue` for the architectural rationale; the
|
|
1937
|
+
// full-graph code path is the resync fallback (server chose
|
|
1938
|
+
// not to send a delta) and shares the same V8 cache pitfall.
|
|
1939
|
+
const fgEvictUrls = buildEvictionUrls(toReimport);
|
|
1940
|
+
const fgEvicted = invalidateModulesByUrls(fgEvictUrls);
|
|
1941
|
+
if (VERBOSE)
|
|
1942
|
+
console.log(`[hmr][full-graph] eviction count=${fgEvictUrls.length} ok=${fgEvicted}`);
|
|
1405
1943
|
for (const id of toReimport) {
|
|
1406
1944
|
try {
|
|
1407
1945
|
const spec = normalizeSpec(id);
|
|
@@ -1453,7 +1991,7 @@ async function handleHmrMessage(ev) {
|
|
|
1453
1991
|
if (msg.type === 'ns:hmr-delta') {
|
|
1454
1992
|
// Bump a monotonic nonce so HTTP ESM imports can always be cache-busted per update.
|
|
1455
1993
|
try {
|
|
1456
|
-
const g =
|
|
1994
|
+
const g = getGlobalScope();
|
|
1457
1995
|
g.__NS_HMR_IMPORT_NONCE__ = (typeof g.__NS_HMR_IMPORT_NONCE__ === 'number' ? g.__NS_HMR_IMPORT_NONCE__ : 0) + 1;
|
|
1458
1996
|
}
|
|
1459
1997
|
catch { }
|
|
@@ -1475,10 +2013,108 @@ async function handleHmrMessage(ev) {
|
|
|
1475
2013
|
return;
|
|
1476
2014
|
}
|
|
1477
2015
|
else {
|
|
2016
|
+
// Vite custom-event dispatch.
|
|
2017
|
+
//
|
|
2018
|
+
// `server.ws.send('event-name', payload)` from any Vite plugin lands
|
|
2019
|
+
// on the wire as `{ type: 'custom', event: 'event-name', data: payload }`.
|
|
2020
|
+
// On the web, Vite's stock client owns a `customListenersMap` that
|
|
2021
|
+
// fires every `import.meta.hot.on('event-name', cb)` callback. We
|
|
2022
|
+
// don't run Vite's stock client on device — the iOS runtime owns
|
|
2023
|
+
// the listener registry via `__NS_DISPATCH_HOT_EVENT__` (the
|
|
2024
|
+
// counterpart to `import.meta.hot.on` populated by user code +
|
|
2025
|
+
// compiled Angular components). Forwarding `type: 'custom'` here
|
|
2026
|
+
// is the only thing standing between server-emitted events and
|
|
2027
|
+
// the listeners they were meant for.
|
|
2028
|
+
//
|
|
2029
|
+
// `angular:component-update` is the canonical example. Analog's
|
|
2030
|
+
// plugin sends it on `.html` / component-style edits; the
|
|
2031
|
+
// compiled component `.mjs` registered a listener that
|
|
2032
|
+
// dynamic-imports `/@ng/component?c=<id>&t=<ts>` and calls
|
|
2033
|
+
// `ɵɵreplaceMetadata` on the live class — swapping the template
|
|
2034
|
+
// definition AND walking live `LView`s to recreate matching views
|
|
2035
|
+
// in-place. The page stays mounted and only the changed bits
|
|
2036
|
+
// re-render. We MUST `return` after dispatch so the reboot path
|
|
2037
|
+
// (`handleAngularHotUpdateMessage` → `__reboot_ng_modules__`)
|
|
2038
|
+
// never runs for these updates — that's the whole point of the
|
|
2039
|
+
// component-replacement pipeline.
|
|
2040
|
+
//
|
|
2041
|
+
// All other custom events are forwarded but NOT short-circuited
|
|
2042
|
+
// (Vite spec: custom events are additive — they don't replace
|
|
2043
|
+
// any framework-specific handling). The reboot path falls through
|
|
2044
|
+
// for `ns:angular-update` (the legacy/`.ts`-edit broadcast) and
|
|
2045
|
+
// for any framework not yet using the in-place replacement path.
|
|
2046
|
+
if (msg.type === 'custom' && typeof msg.event === 'string') {
|
|
2047
|
+
// Dispatch every Vite "custom" event through the runtime's
|
|
2048
|
+
// `__NS_DISPATCH_HOT_EVENT__` bridge so `import.meta.hot.on(event, cb)`
|
|
2049
|
+
// callbacks fire on the device. Critical contract: this is the
|
|
2050
|
+
// ONLY route by which Analog's `angular:component-update` reaches
|
|
2051
|
+
// the compiled component's `(d) => d.id === id && Component_HmrLoad(...)`
|
|
2052
|
+
// listener — without it, server-side broadcasts log green
|
|
2053
|
+
// (`(client) hmr update`) while the device sees nothing happen.
|
|
2054
|
+
//
|
|
2055
|
+
// Diagnostic policy: log "no dispatcher" loud (boot-time rt-bridge
|
|
2056
|
+
// failure), and listener exceptions loud (compiled HmrLoad
|
|
2057
|
+
// fetch/parse error). Successful dispatches are silent — the
|
|
2058
|
+
// runtime's `[import.meta.hot] dispatch summary` line carries
|
|
2059
|
+
// the per-event match-count diagnostic.
|
|
2060
|
+
try {
|
|
2061
|
+
const dispatch = globalThis.__NS_DISPATCH_HOT_EVENT__;
|
|
2062
|
+
if (typeof dispatch === 'function') {
|
|
2063
|
+
dispatch(msg.event, msg.data);
|
|
2064
|
+
}
|
|
2065
|
+
else {
|
|
2066
|
+
console.warn(`[hmr-client][custom] no __NS_DISPATCH_HOT_EVENT__ available for '${msg.event}'`);
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
catch (err) {
|
|
2070
|
+
console.warn('[hmr-client][custom] dispatch threw for', msg.event, err);
|
|
2071
|
+
}
|
|
2072
|
+
if (msg.event === 'angular:component-update') {
|
|
2073
|
+
if (VERBOSE)
|
|
2074
|
+
console.log('[hmr-client][custom] dispatched angular:component-update — skipping reboot path');
|
|
2075
|
+
// Walk the apply-progress overlay through its
|
|
2076
|
+
// remaining stages for the in-place template-swap
|
|
2077
|
+
// path. The full reboot path
|
|
2078
|
+
// (`handleAngularHotUpdateMessage`) drives the
|
|
2079
|
+
// overlay itself ('received' → 'evicting' →
|
|
2080
|
+
// 'reimporting' → 'rebooting' → 'complete'); the
|
|
2081
|
+
// in-place path bypasses that handler entirely
|
|
2082
|
+
// because the work happens inside Angular's
|
|
2083
|
+
// `ɵɵreplaceMetadata` after the runtime forwards the
|
|
2084
|
+
// `angular:component-update` event to the compiled
|
|
2085
|
+
// component's listener. Without this update the
|
|
2086
|
+
// overlay would freeze at 5% ('received') even
|
|
2087
|
+
// though the visual swap completes a few frames
|
|
2088
|
+
// later — exactly the "Preparing update (5%)" stuck
|
|
2089
|
+
// frame we have been chasing.
|
|
2090
|
+
//
|
|
2091
|
+
// We transition straight to 'reimporting' to
|
|
2092
|
+
// communicate that metadata is being fetched (the
|
|
2093
|
+
// runtime listener fires `__ns_import('/@ng/component?c=...&t=...')`),
|
|
2094
|
+
// then schedule 'complete' on the next macrotask so
|
|
2095
|
+
// the auto-hide timer kicks in. The actual
|
|
2096
|
+
// template swap is fire-and-forget from this point;
|
|
2097
|
+
// the user sees the overlay close at the same time
|
|
2098
|
+
// as Angular re-renders the bound text/structure.
|
|
2099
|
+
try {
|
|
2100
|
+
const filePath = typeof msg.data?.id === 'string' ? decodeURIComponent(msg.data.id).split('@')[0] : undefined;
|
|
2101
|
+
const detail = filePath ? `Applying template update to ${filePath}` : 'Applying template update';
|
|
2102
|
+
setUpdateOverlayStage('reimporting', { detail });
|
|
2103
|
+
setTimeout(() => {
|
|
2104
|
+
try {
|
|
2105
|
+
setUpdateOverlayStage('complete', { detail: filePath ? `Updated ${filePath}` : 'Update applied' });
|
|
2106
|
+
}
|
|
2107
|
+
catch { }
|
|
2108
|
+
}, 16);
|
|
2109
|
+
}
|
|
2110
|
+
catch { }
|
|
2111
|
+
return;
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
1478
2114
|
if (msg.type === 'ns:angular-update' && typeof msg.version === 'number') {
|
|
1479
2115
|
setGraphVersion(Number(msg.version || getGraphVersion() || 0));
|
|
1480
2116
|
}
|
|
1481
|
-
if (await
|
|
2117
|
+
if (CLIENT_STRATEGY?.handleHotUpdateMessage && (await CLIENT_STRATEGY.handleHotUpdateMessage(msg, { getCore, verbose: VERBOSE, performResetRoot, getOverlay: getHmrOverlayApi }))) {
|
|
1482
2118
|
return;
|
|
1483
2119
|
}
|
|
1484
2120
|
}
|
|
@@ -1512,27 +2148,47 @@ async function handleHmrMessage(ev) {
|
|
|
1512
2148
|
return;
|
|
1513
2149
|
}
|
|
1514
2150
|
if (msg.type === 'ns:css-updates' && Array.isArray(msg.updates)) {
|
|
2151
|
+
// Drive the HMR-applying overlay past the 'received' (5%) frame
|
|
2152
|
+
// that `ns:hmr-pending` set earlier in the cycle. Without this
|
|
2153
|
+
// the overlay sticks at "Preparing update" forever for CSS-only
|
|
2154
|
+
// edits because `handleCssUpdates` is a leaf — there's no
|
|
2155
|
+
// downstream module-evaluation path that would hit the queue's
|
|
2156
|
+
// 'complete' transition.
|
|
2157
|
+
const cssCount = msg.updates.length;
|
|
2158
|
+
try {
|
|
2159
|
+
setUpdateOverlayStage('reimporting', { detail: buildCssApplyingDetail(cssCount) });
|
|
2160
|
+
}
|
|
2161
|
+
catch { }
|
|
1515
2162
|
try {
|
|
1516
2163
|
const origin = msg.origin || getHttpOriginForVite() || deriveHttpOrigin(getHMRWsUrl());
|
|
1517
2164
|
await handleCssUpdates(msg.updates, origin);
|
|
2165
|
+
try {
|
|
2166
|
+
setUpdateOverlayStage('complete', { detail: buildCssAppliedDetail(cssCount) });
|
|
2167
|
+
}
|
|
2168
|
+
catch { }
|
|
1518
2169
|
return;
|
|
1519
2170
|
}
|
|
1520
2171
|
catch (e) {
|
|
1521
2172
|
console.warn('[hmr-client] CSS updates handling failed:', e);
|
|
2173
|
+
try {
|
|
2174
|
+
setUpdateOverlayStage('complete', { detail: 'CSS update failed' });
|
|
2175
|
+
}
|
|
2176
|
+
catch { }
|
|
1522
2177
|
return;
|
|
1523
2178
|
}
|
|
1524
2179
|
}
|
|
1525
2180
|
if (msg.type === 'ns:vue-sfc-registry') {
|
|
1526
|
-
|
|
2181
|
+
CLIENT_STRATEGY?.handleSfcRegistry?.(msg);
|
|
1527
2182
|
return;
|
|
1528
2183
|
}
|
|
1529
2184
|
if (msg.type === 'ns:vue-sfc-registry-update') {
|
|
1530
2185
|
if (typeof msg.version === 'number')
|
|
1531
2186
|
setGraphVersion(msg.version);
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
2187
|
+
// `ns:hmr-pending` already set the overlay to 'received' (5%). The Vue
|
|
2188
|
+
// strategy walks 'evicting' → 'reimporting' → 'rebooting' → 'complete'
|
|
2189
|
+
// around the SFC load + reset so the toast always lands on 'complete'
|
|
2190
|
+
// (or a failure detail) and the auto-hide timer can dismiss it.
|
|
2191
|
+
await CLIENT_STRATEGY?.handleSfcRegistryUpdate?.(msg, getGraphVersion(), { getCore, verbose: VERBOSE, performResetRoot, getOverlay: getHmrOverlayApi });
|
|
1536
2192
|
return;
|
|
1537
2193
|
}
|
|
1538
2194
|
}
|
|
@@ -1552,9 +2208,9 @@ function normalizeComponent(input, nameHint) {
|
|
|
1552
2208
|
}
|
|
1553
2209
|
// If provided a render function, wrap with defineComponent
|
|
1554
2210
|
if (typeof input === 'function') {
|
|
1555
|
-
|
|
1556
|
-
const comp =
|
|
1557
|
-
?
|
|
2211
|
+
CLIENT_STRATEGY?.beforeNavigateBuild?.();
|
|
2212
|
+
const comp = getGlobalScope().defineComponent
|
|
2213
|
+
? getGlobalScope().defineComponent({
|
|
1558
2214
|
name: nameHint || input.name || 'AnonymousSFC',
|
|
1559
2215
|
render: input,
|
|
1560
2216
|
})
|
|
@@ -1563,9 +2219,9 @@ function normalizeComponent(input, nameHint) {
|
|
|
1563
2219
|
}
|
|
1564
2220
|
// If object has a render function property
|
|
1565
2221
|
if (input?.render && typeof input.render === 'function') {
|
|
1566
|
-
|
|
1567
|
-
const comp =
|
|
1568
|
-
?
|
|
2222
|
+
CLIENT_STRATEGY?.beforeNavigateBuild?.();
|
|
2223
|
+
const comp = getGlobalScope().defineComponent
|
|
2224
|
+
? getGlobalScope().defineComponent({
|
|
1569
2225
|
name: nameHint || input.name || 'AnonymousSFC',
|
|
1570
2226
|
render: input.render,
|
|
1571
2227
|
})
|
|
@@ -1621,35 +2277,32 @@ async function performResetRoot(newComponent) {
|
|
|
1621
2277
|
if (cachedRoot)
|
|
1622
2278
|
return cachedRoot;
|
|
1623
2279
|
try {
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
root = newComponent();
|
|
1634
|
-
}
|
|
1635
|
-
else {
|
|
1636
|
-
root = newComponent;
|
|
1637
|
-
}
|
|
2280
|
+
if (CLIENT_STRATEGY?.createRoot) {
|
|
2281
|
+
cachedRoot = CLIENT_STRATEGY.createRoot(newComponent, state);
|
|
2282
|
+
}
|
|
2283
|
+
else if (TS_LIKE_FLAVOR) {
|
|
2284
|
+
// For TS flavor, treat the component as a factory or direct NS view.
|
|
2285
|
+
let root = null;
|
|
2286
|
+
try {
|
|
2287
|
+
if (typeof newComponent === 'function') {
|
|
2288
|
+
root = newComponent();
|
|
1638
2289
|
}
|
|
1639
|
-
|
|
1640
|
-
|
|
2290
|
+
else {
|
|
2291
|
+
root = newComponent;
|
|
1641
2292
|
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
2293
|
+
}
|
|
2294
|
+
catch (e) {
|
|
2295
|
+
console.warn('[hmr-client][ts] root factory invocation failed', e);
|
|
2296
|
+
}
|
|
2297
|
+
cachedRoot = root || {};
|
|
2298
|
+
// Heuristic: if the root "looks" like a Frame, prefer frame semantics
|
|
2299
|
+
try {
|
|
2300
|
+
const name = String(cachedRoot?.constructor?.name || '').replace(/^_+/, '');
|
|
2301
|
+
if (/^Frame(\$\d+)?$/.test(name)) {
|
|
2302
|
+
rootKind = 'frame';
|
|
1649
2303
|
}
|
|
1650
|
-
catch { }
|
|
1651
|
-
break;
|
|
1652
2304
|
}
|
|
2305
|
+
catch { }
|
|
1653
2306
|
}
|
|
1654
2307
|
return cachedRoot;
|
|
1655
2308
|
}
|
|
@@ -1667,7 +2320,7 @@ async function performResetRoot(newComponent) {
|
|
|
1667
2320
|
return factory;
|
|
1668
2321
|
}
|
|
1669
2322
|
// Android readiness before any root changes
|
|
1670
|
-
const App = getCore('Application') ||
|
|
2323
|
+
const App = getCore('Application') || getGlobalScope().Application;
|
|
1671
2324
|
const isAndroid = !!(App && App.android !== undefined);
|
|
1672
2325
|
if (isAndroid) {
|
|
1673
2326
|
const isReady = () => {
|
|
@@ -1769,7 +2422,7 @@ async function performResetRoot(newComponent) {
|
|
|
1769
2422
|
}
|
|
1770
2423
|
catch { }
|
|
1771
2424
|
try {
|
|
1772
|
-
const AppAny = getCore('Application') ||
|
|
2425
|
+
const AppAny = getCore('Application') || getGlobalScope().Application;
|
|
1773
2426
|
isIOS = !!(AppAny && AppAny.ios !== undefined);
|
|
1774
2427
|
}
|
|
1775
2428
|
catch { }
|
|
@@ -1779,7 +2432,7 @@ async function performResetRoot(newComponent) {
|
|
|
1779
2432
|
// - Otherwise (subsequent HMR updates with an authoritative Frame already in place), re-use the
|
|
1780
2433
|
// current app Frame and navigate to the new Page. This avoids a brief flash that can occur
|
|
1781
2434
|
// when swapping the entire root view on Android. The placeholder is never involved here.
|
|
1782
|
-
const gAnyForPolicy =
|
|
2435
|
+
const gAnyForPolicy = getGlobalScope();
|
|
1783
2436
|
const placeholderFrame = (() => {
|
|
1784
2437
|
try {
|
|
1785
2438
|
return gAnyForPolicy.__NS_DEV_PLACEHOLDER_ROOT_VIEW__ || null;
|
|
@@ -1794,7 +2447,14 @@ async function performResetRoot(newComponent) {
|
|
|
1794
2447
|
}
|
|
1795
2448
|
catch { }
|
|
1796
2449
|
const isAuthoritativeFrame = !!existingAppFrame && existingAppFrame !== placeholderFrame;
|
|
1797
|
-
|
|
2450
|
+
// Vue: skip the in-place navigate path. After `app.mount(NSVRoot)` in getRootForVue the
|
|
2451
|
+
// new Page already has a parent (the freshly-constructed NSVRoot), so an attempt to navigate
|
|
2452
|
+
// the existing app Frame to that same Page completes silently without ever rebinding the
|
|
2453
|
+
// page to the Frame — the screen keeps showing the previous render. resetRootView with a
|
|
2454
|
+
// fresh Frame correctly reparents the Page and is the proven path that produces visible
|
|
2455
|
+
// in-place updates for SFC HMR cycles. Non-Vue flavors keep the legacy navigate fast path.
|
|
2456
|
+
const allowNavigateFastPath = CLIENT_STRATEGY?.allowNavigateFastPath ?? true;
|
|
2457
|
+
if (allowNavigateFastPath && !hadPlaceholder && !isFrameRoot && isAuthoritativeFrame && typeof existingAppFrame.navigate === 'function') {
|
|
1798
2458
|
try {
|
|
1799
2459
|
const navEntry = {
|
|
1800
2460
|
create: () => preparedRoot,
|
|
@@ -1813,7 +2473,7 @@ async function performResetRoot(newComponent) {
|
|
|
1813
2473
|
console.log('[hmr-client] full root replacement via resetRootView (placeholder will be discarded)', { isFrameRoot, isIOS, hadPlaceholder });
|
|
1814
2474
|
// Fallback or preferred path: resetRootView with a creator that builds a fresh Frame and navigates to the new Page
|
|
1815
2475
|
try {
|
|
1816
|
-
const App2 = getCore('Application') ||
|
|
2476
|
+
const App2 = getCore('Application') || getGlobalScope().Application;
|
|
1817
2477
|
if (!App2 || typeof App2.resetRootView !== 'function') {
|
|
1818
2478
|
console.warn('[hmr-client] Application.resetRootView unavailable');
|
|
1819
2479
|
return false;
|
|
@@ -1832,7 +2492,7 @@ async function performResetRoot(newComponent) {
|
|
|
1832
2492
|
if (VERBOSE)
|
|
1833
2493
|
console.warn('[hmr-client] iOS Application.window is boolean false; attempting to clear cached window');
|
|
1834
2494
|
try {
|
|
1835
|
-
const g =
|
|
2495
|
+
const g = getGlobalScope();
|
|
1836
2496
|
const reg = g.__nsVendorRegistry;
|
|
1837
2497
|
const req = reg?.get ? g.__nsVendorRequire || g.__nsRequire || g.require : g.__nsRequire || g.require;
|
|
1838
2498
|
let helpers = null;
|
|
@@ -1936,6 +2596,20 @@ export function initHmrClient(opts) {
|
|
|
1936
2596
|
}
|
|
1937
2597
|
g.__NS_HMR_CLIENT_ACTIVE__ = true;
|
|
1938
2598
|
ensureCoreAliasesOnGlobalThis();
|
|
2599
|
+
// XML flavor: record string-module modals from the moment the client is up
|
|
2600
|
+
// so an already-open modal can be re-presented when its files change.
|
|
2601
|
+
// Installed at init (not first-update time) because the wrap can only
|
|
2602
|
+
// observe showModal calls made AFTER it lands. Retried briefly because
|
|
2603
|
+
// getCore('View') may not resolve until the vendor realm finishes booting.
|
|
2604
|
+
if (TS_LIKE_FLAVOR) {
|
|
2605
|
+
const tryInstallModalTracking = (attempts) => {
|
|
2606
|
+
ensureModalTracking();
|
|
2607
|
+
if (!modalTrackingInstalled && attempts > 0) {
|
|
2608
|
+
setTimeout(() => tryInstallModalTracking(attempts - 1), 250);
|
|
2609
|
+
}
|
|
2610
|
+
};
|
|
2611
|
+
tryInstallModalTracking(40);
|
|
2612
|
+
}
|
|
1939
2613
|
// Defer WebSocket connection until boot completes to avoid native V8 crashes
|
|
1940
2614
|
// caused by concurrent WebSocket message handling + HTTP fetch during early startup.
|
|
1941
2615
|
// The WebSocket is only needed for HMR updates, not the initial boot sequence.
|
|
@@ -1957,12 +2631,9 @@ export function initHmrClient(opts) {
|
|
|
1957
2631
|
console.log('[hmr-client] deferring WebSocket connection until boot completes');
|
|
1958
2632
|
setTimeout(waitForBoot, 100);
|
|
1959
2633
|
}
|
|
1960
|
-
// Best-effort: install back wrapper even before first remount; original root may be captured later
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
ensureBackWrapperInstalled(performResetRoot, getCore);
|
|
1964
|
-
break;
|
|
1965
|
-
}
|
|
2634
|
+
// Best-effort: install back wrapper even before first remount; original root may be captured later.
|
|
2635
|
+
// Deferred until the dynamically-imported strategy resolves.
|
|
2636
|
+
void CLIENT_STRATEGY_READY.then(() => CLIENT_STRATEGY?.installBackWrapper?.(performResetRoot, getCore));
|
|
1966
2637
|
}
|
|
1967
2638
|
export default function startViteHMR(opts) {
|
|
1968
2639
|
if (VERBOSE)
|