@nativescript/vite 8.0.0-alpha.6 → 8.0.0-alpha.60
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 +244 -53
- 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 +4 -2
- 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 +100 -13
- package/helpers/global-defines.js +149 -28
- 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.js +29 -1
- package/helpers/logging.js.map +1 -1
- package/helpers/main-entry.d.ts +4 -1
- package/helpers/main-entry.js +368 -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 +32 -0
- package/helpers/ts-config-paths.js +156 -27
- 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/vite-package-version.d.ts +7 -0
- package/helpers/vite-package-version.js +44 -0
- package/helpers/vite-package-version.js.map +1 -0
- 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 +0 -14
- package/hmr/client/hmr-pending-overlay.js +14 -4
- package/hmr/client/hmr-pending-overlay.js.map +1 -1
- package/hmr/client/hot-context.d.ts +67 -0
- package/hmr/client/hot-context.js +284 -0
- package/hmr/client/hot-context.js.map +1 -0
- package/hmr/client/index.js +922 -273
- package/hmr/client/index.js.map +1 -1
- package/hmr/client/utils.d.ts +2 -1
- package/hmr/client/utils.js +111 -99
- package/hmr/client/utils.js.map +1 -1
- package/hmr/entry-runtime.d.ts +2 -1
- package/hmr/entry-runtime.js +271 -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.js +542 -175
- 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 +458 -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 +14 -11
- package/hmr/{server → frameworks/angular/server}/websocket-angular-hot-update.js +108 -111
- 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 +230 -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 +68 -0
- package/hmr/frameworks/solid/client/app-runtime.js +246 -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 +44 -0
- package/hmr/frameworks/vue/client/dep-propagation.js +148 -0
- package/hmr/frameworks/vue/client/dep-propagation.js.map +1 -0
- package/hmr/frameworks/vue/client/index.d.ts +9 -0
- package/hmr/frameworks/vue/client/index.js +251 -247
- 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/compiler.d.ts +3 -1
- package/hmr/frameworks/vue/server/compiler.js +3 -1
- package/hmr/frameworks/vue/server/compiler.js.map +1 -1
- package/hmr/frameworks/vue/server/sfc-route-assemble.d.ts +7 -0
- package/hmr/frameworks/vue/server/sfc-route-assemble.js +823 -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 +462 -0
- package/hmr/frameworks/vue/server/sfc-route-serve.js.map +1 -0
- package/hmr/frameworks/vue/server/sfc-route-shared.d.ts +45 -0
- package/hmr/frameworks/vue/server/sfc-route-shared.js +105 -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 +286 -7
- 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/helpers/vendor-rewrite.d.ts +1 -1
- package/hmr/helpers/vendor-rewrite.js +5 -3
- package/hmr/helpers/vendor-rewrite.js.map +1 -1
- package/hmr/server/boot-archive-route.d.ts +40 -0
- package/hmr/server/boot-archive-route.js +154 -0
- package/hmr/server/boot-archive-route.js.map +1 -0
- package/hmr/server/boot-closure-route.d.ts +49 -0
- package/hmr/server/boot-closure-route.js +144 -0
- package/hmr/server/boot-closure-route.js.map +1 -0
- package/hmr/server/boot-recording.d.ts +58 -0
- package/hmr/server/boot-recording.js +228 -0
- package/hmr/server/boot-recording.js.map +1 -0
- package/hmr/server/constants.js +20 -5
- package/hmr/server/constants.js.map +1 -1
- package/hmr/server/core-bundle.d.ts +140 -0
- package/hmr/server/core-bundle.js +614 -0
- package/hmr/server/core-bundle.js.map +1 -0
- 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/css-device-module.d.ts +28 -0
- package/hmr/server/css-device-module.js +44 -0
- package/hmr/server/css-device-module.js.map +1 -0
- package/hmr/server/deps-bundle.d.ts +218 -0
- package/hmr/server/deps-bundle.js +984 -0
- package/hmr/server/deps-bundle.js.map +1 -0
- 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 +113 -11
- package/hmr/server/hmr-module-graph.d.ts +37 -0
- package/hmr/server/hmr-module-graph.js +215 -0
- package/hmr/server/hmr-module-graph.js.map +1 -0
- package/hmr/server/import-map.d.ts +13 -4
- package/hmr/server/import-map.js +94 -47
- 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-m-response-memo.d.ts +10 -0
- package/hmr/server/ns-m-response-memo.js +33 -0
- package/hmr/server/ns-m-response-memo.js.map +1 -0
- 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 +39 -0
- package/hmr/server/ns-rt-route.js.map +1 -0
- package/hmr/server/perf-instrumentation.d.ts +6 -10
- package/hmr/server/perf-instrumentation.js +5 -6
- package/hmr/server/perf-instrumentation.js.map +1 -1
- package/hmr/server/process-code-for-device.d.ts +30 -0
- package/hmr/server/process-code-for-device.js +791 -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 +1 -2
- 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.d.ts +1 -0
- package/hmr/server/vite-plugin.js +180 -54
- package/hmr/server/vite-plugin.js.map +1 -1
- package/hmr/server/websocket-core-bridge.d.ts +51 -4
- package/hmr/server/websocket-core-bridge.js +62 -54
- 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-hmr-pending.d.ts +3 -9
- package/hmr/server/websocket-hmr-pending.js +1 -1
- package/hmr/server/websocket-hmr-pending.js.map +1 -1
- 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 +34 -0
- package/hmr/server/websocket-ns-core.js +414 -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 -3
- package/hmr/server/websocket-ns-m-paths.js +25 -82
- 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 +17 -9
- package/hmr/server/websocket-ns-m-request.js.map +1 -1
- package/hmr/server/websocket-ns-m.d.ts +52 -0
- package/hmr/server/websocket-ns-m.js +824 -0
- package/hmr/server/websocket-ns-m.js.map +1 -0
- package/hmr/server/websocket-served-module-helpers.d.ts +50 -4
- package/hmr/server/websocket-served-module-helpers.js +384 -86
- package/hmr/server/websocket-served-module-helpers.js.map +1 -1
- package/hmr/server/websocket-txn.js +6 -9
- 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 +7 -39
- package/hmr/server/websocket.js +643 -6716
- package/hmr/server/websocket.js.map +1 -1
- package/hmr/shared/ns-globals.d.ts +108 -0
- package/hmr/shared/ns-globals.js +28 -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-archive-format.d.ts +16 -0
- package/hmr/shared/runtime/boot-archive-format.js +31 -0
- package/hmr/shared/runtime/boot-archive-format.js.map +1 -0
- package/hmr/shared/runtime/boot-complete.d.ts +14 -0
- package/hmr/shared/runtime/boot-complete.js +32 -0
- package/hmr/shared/runtime/boot-complete.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 +134 -0
- package/hmr/shared/runtime/boot-progress.js.map +1 -0
- package/hmr/shared/runtime/boot-timeline.d.ts +2 -1
- package/hmr/shared/runtime/boot-timeline.js +9 -22
- package/hmr/shared/runtime/boot-timeline.js.map +1 -1
- package/hmr/shared/runtime/browser-runtime-contract.d.ts +53 -22
- package/hmr/shared/runtime/browser-runtime-contract.js +6 -9
- 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 +76 -30
- package/hmr/shared/runtime/dev-overlay.js +767 -408
- 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 +398 -204
- package/hmr/shared/runtime/root-placeholder.js.map +1 -1
- package/hmr/shared/runtime/session-bootstrap.js +255 -50
- 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 +125 -825
- package/hmr/shared/vendor/manifest.js.map +1 -1
- package/hmr/shared/vendor/registry.d.ts +2 -0
- package/hmr/shared/vendor/registry.js +14 -0
- package/hmr/shared/vendor/registry.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 +65 -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.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 -1114
- 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,13 @@
|
|
|
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, hasExplicitEviction, invalidateModulesByUrls, buildEvictionUrls, emitHmrModeBannerOnce } from './utils.js';
|
|
9
|
-
import { handleCssUpdates } from './css-handler.js';
|
|
10
|
-
|
|
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
|
+
import { applyCssText, handleCssUpdates } from './css-handler.js';
|
|
10
|
+
import { buildCssApplyingDetail, buildCssAppliedDetail } from './css-update-overlay.js';
|
|
11
|
+
import { getGlobalScope } from '../shared/runtime/global-scope.js';
|
|
12
|
+
import { markDevBootComplete } from '../shared/runtime/boot-complete.js';
|
|
13
|
+
import { getNsHotRegistry } from './hot-context.js';
|
|
14
|
+
const VERBOSE = ENV_VERBOSE;
|
|
11
15
|
function resolveTargetFlavor() {
|
|
12
16
|
try {
|
|
13
17
|
if (typeof __NS_TARGET_FLAVOR__ !== 'undefined' && __NS_TARGET_FLAVOR__) {
|
|
@@ -16,7 +20,7 @@ function resolveTargetFlavor() {
|
|
|
16
20
|
}
|
|
17
21
|
catch { }
|
|
18
22
|
try {
|
|
19
|
-
const g =
|
|
23
|
+
const g = getGlobalScope();
|
|
20
24
|
if (typeof g.__NS_TARGET_FLAVOR__ === 'string' && g.__NS_TARGET_FLAVOR__) {
|
|
21
25
|
return g.__NS_TARGET_FLAVOR__;
|
|
22
26
|
}
|
|
@@ -34,13 +38,24 @@ function resolveTargetFlavor() {
|
|
|
34
38
|
return undefined;
|
|
35
39
|
}
|
|
36
40
|
const TARGET_FLAVOR = resolveTargetFlavor();
|
|
41
|
+
// React reuses the generic TypeScript HMR path on BOTH server and client: it has
|
|
42
|
+
// no Fast Refresh, so a module edit drives a plain module reload / root reset
|
|
43
|
+
// (the React tree re-renders), exactly like the `typescript` flavor. The server
|
|
44
|
+
// strategy is `{ ...typescriptServerStrategy, flavor: 'react' }`; this mirrors that
|
|
45
|
+
// on the client so the `typescript`-gated update branches also run for React
|
|
46
|
+
// (otherwise a React edit is received but never applied — the overlay sticks).
|
|
47
|
+
const TS_LIKE_FLAVOR = TARGET_FLAVOR === 'typescript' || TARGET_FLAVOR === 'react';
|
|
37
48
|
try {
|
|
38
49
|
if (TARGET_FLAVOR && !globalThis.__NS_TARGET_FLAVOR__) {
|
|
39
50
|
globalThis.__NS_TARGET_FLAVOR__ = TARGET_FLAVOR;
|
|
40
51
|
}
|
|
41
52
|
}
|
|
42
53
|
catch { }
|
|
43
|
-
|
|
54
|
+
// Define substitution does NOT reach this file (served raw from node_modules),
|
|
55
|
+
// so prefer the globalThis seed planted by the entry's defines-seed module —
|
|
56
|
+
// the '/src' literal is a last-resort default and is WRONG for 'app/'-rooted
|
|
57
|
+
// projects.
|
|
58
|
+
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
59
|
const APP_VIRTUAL_WITH_SLASH = APP_ROOT_VIRTUAL.endsWith('/') ? APP_ROOT_VIRTUAL : `${APP_ROOT_VIRTUAL}/`;
|
|
45
60
|
const APP_MAIN_ENTRY_SPEC = `${APP_VIRTUAL_WITH_SLASH}app.ts`;
|
|
46
61
|
// Policy: by default, let the app's own main entry mount initially; HMR client handles updates/remounts only.
|
|
@@ -76,6 +91,41 @@ function ensureCoreAliasesOnGlobalThis() {
|
|
|
76
91
|
}
|
|
77
92
|
// Apply once on module evaluation
|
|
78
93
|
ensureCoreAliasesOnGlobalThis();
|
|
94
|
+
// Install the device CSS applier. CSS-bearing modules (a Vue SFC `<style>` via
|
|
95
|
+
// `/ns/asm`, an imported `.css` via `/ns/m`) call `__NS_REGISTER_CSS__(tag, css)`,
|
|
96
|
+
// or queue in `__NS_PENDING_CSS__` if they ran first; the drain below covers that.
|
|
97
|
+
// Routes through the shared `applyCssText` (tagged remove+add + root restyle),
|
|
98
|
+
// the same path app.css and live `.css` edits use.
|
|
99
|
+
function installCssRegister() {
|
|
100
|
+
const g = getGlobalScope();
|
|
101
|
+
if (typeof g.__NS_REGISTER_CSS__ === 'function') {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const apply = (tag, cssText) => {
|
|
105
|
+
if (typeof cssText !== 'string' || !cssText.length) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
applyCssText(cssText, tag);
|
|
110
|
+
}
|
|
111
|
+
catch (cssErr) {
|
|
112
|
+
if (VERBOSE)
|
|
113
|
+
console.warn('[ns-hmr] CSS register/apply failed for', tag, cssErr?.message || cssErr);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
g.__NS_REGISTER_CSS__ = apply;
|
|
117
|
+
try {
|
|
118
|
+
const pending = g.__NS_PENDING_CSS__;
|
|
119
|
+
if (pending && typeof pending === 'object') {
|
|
120
|
+
for (const tag of Object.keys(pending)) {
|
|
121
|
+
apply(tag, pending[tag]);
|
|
122
|
+
}
|
|
123
|
+
g.__NS_PENDING_CSS__ = null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch { }
|
|
127
|
+
}
|
|
128
|
+
installCssRegister();
|
|
79
129
|
function getHmrOverlayApi() {
|
|
80
130
|
try {
|
|
81
131
|
return globalThis.__NS_HMR_DEV_OVERLAY__ || null;
|
|
@@ -101,15 +151,71 @@ function hideConnectionOverlay() {
|
|
|
101
151
|
}
|
|
102
152
|
catch { }
|
|
103
153
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
154
|
+
function setUpdateOverlayStage(stage, info) {
|
|
155
|
+
try {
|
|
156
|
+
const api = getHmrOverlayApi();
|
|
157
|
+
if (api && typeof api.setUpdateStage === 'function') {
|
|
158
|
+
api.setUpdateStage(stage, info);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch { }
|
|
162
|
+
}
|
|
163
|
+
// Store the listener registry on globalThis (rather than in a module-private
|
|
164
|
+
// closure) because in NativeScript the HMR client module and the user app
|
|
165
|
+
// modules can resolve to different module instances depending on how the
|
|
166
|
+
// dev runtime loads them (HTTP client URL vs. the bundled vendor realm).
|
|
167
|
+
// A module-local Set would not be shared across instances; the global one
|
|
168
|
+
// is.
|
|
169
|
+
function getNsSolidHmrListenerSet() {
|
|
170
|
+
const g = getGlobalScope();
|
|
171
|
+
let set = g.__ns_solid_hmr_listener_set;
|
|
172
|
+
if (!set) {
|
|
173
|
+
set = new Set();
|
|
174
|
+
g.__ns_solid_hmr_listener_set = set;
|
|
175
|
+
}
|
|
176
|
+
return set;
|
|
177
|
+
}
|
|
178
|
+
function nsSolidHmrSubscribe(fn) {
|
|
179
|
+
const listeners = getNsSolidHmrListenerSet();
|
|
180
|
+
listeners.add(fn);
|
|
181
|
+
if (VERBOSE)
|
|
182
|
+
console.log('[hmr][solid] subscribe — listeners=', listeners.size);
|
|
183
|
+
return () => listeners.delete(fn);
|
|
184
|
+
}
|
|
185
|
+
function nsSolidHmrEmit(ev) {
|
|
186
|
+
const listeners = getNsSolidHmrListenerSet();
|
|
187
|
+
if (VERBOSE)
|
|
188
|
+
console.log('[hmr][solid] emit listeners=', listeners.size, 'changedFiles=', ev.changedFiles);
|
|
189
|
+
for (const fn of Array.from(listeners)) {
|
|
190
|
+
try {
|
|
191
|
+
fn(ev);
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
if (VERBOSE)
|
|
195
|
+
console.warn('[hmr][solid] listener threw', err);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const g = getGlobalScope();
|
|
201
|
+
g.__ns_solid_hmr_subscribe = nsSolidHmrSubscribe;
|
|
202
|
+
// Eagerly create the listener set so the global exists at module load time.
|
|
203
|
+
getNsSolidHmrListenerSet();
|
|
204
|
+
if (VERBOSE)
|
|
205
|
+
console.log('[hmr][solid] HMR client loaded. global set=', typeof g.__ns_solid_hmr_subscribe, 'listenerSet=', typeof g.__ns_solid_hmr_listener_set);
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
console.warn('[hmr][solid] could not install global __ns_solid_hmr_subscribe', err);
|
|
209
|
+
}
|
|
210
|
+
// Eagerly drive the HMR-applying overlay's 'received' frame as soon
|
|
211
|
+
// as the server emits `ns:hmr-pending`, BEFORE the framework-specific
|
|
212
|
+
// (`ns:angular-update` / `ns:css-updates`) payload arrives. The
|
|
213
|
+
// flavor-specific handler later walks through 'evicting' →
|
|
214
|
+
// 'reimporting' → 'rebooting' → 'complete'. Calling 'received' twice
|
|
215
|
+
// in the same cycle is safe: the overlay preserves
|
|
216
|
+
// `updateCycleStartedAt` when a 'received' frame replaces an existing
|
|
217
|
+
// 'received' frame so the minimum-visible window is still timed
|
|
218
|
+
// against the FIRST frame.
|
|
113
219
|
//
|
|
114
220
|
// Soft-fails when the overlay isn't installed (production builds,
|
|
115
221
|
// vitest, etc.) or when the user opted out via
|
|
@@ -124,6 +230,22 @@ let hasOpenedHmrSocket = false;
|
|
|
124
230
|
let awaitingHealthyHmrMessage = false;
|
|
125
231
|
let pendingConnectionOverlayStage = 'connecting';
|
|
126
232
|
let pendingConnectionOverlayDetail = '';
|
|
233
|
+
// The stage currently PAINTED on screen (set only when we actually show
|
|
234
|
+
// the overlay, not when we merely schedule it). Used to suppress the
|
|
235
|
+
// sub-perceptible offline↔reconnecting flicker during the reconnect
|
|
236
|
+
// retry loop — see the deferral in `connectHmr`/`tryNext`.
|
|
237
|
+
let shownConnectionOverlayStage = null;
|
|
238
|
+
// While the terminal 'offline' frame is showing, a fresh reconnect
|
|
239
|
+
// attempt that is refused near-instantly (the norm when the dev server
|
|
240
|
+
// is down — especially on the Android emulator, where connection-refused
|
|
241
|
+
// returns in ~1ms) would flip the overlay to 'reconnecting' and straight
|
|
242
|
+
// back to 'offline', producing a jarring 1-frame flicker. We defer the
|
|
243
|
+
// 'reconnecting' frame by this much so an instant failure (→ back to
|
|
244
|
+
// 'offline') or a success (→ 'synchronizing') cancels it before it ever
|
|
245
|
+
// paints; only a genuinely in-flight attempt surfaces 'reconnecting'.
|
|
246
|
+
// On iOS real connect attempts exceed this threshold, so the behaviour
|
|
247
|
+
// there is unchanged.
|
|
248
|
+
const RECONNECTING_OVER_OFFLINE_DELAY_MS = 500;
|
|
127
249
|
function clearConnectionOverlayTimer() {
|
|
128
250
|
if (connectionOverlayTimer) {
|
|
129
251
|
clearTimeout(connectionOverlayTimer);
|
|
@@ -131,9 +253,14 @@ function clearConnectionOverlayTimer() {
|
|
|
131
253
|
}
|
|
132
254
|
}
|
|
133
255
|
function showConnectionOverlayNow(stage, detail) {
|
|
256
|
+
// Painting a stage now supersedes any scheduled (deferred) stage, so
|
|
257
|
+
// cancel the pending timer — otherwise a deferred 'reconnecting' could
|
|
258
|
+
// fire moments after we settle back on 'offline' and revive the flicker.
|
|
259
|
+
clearConnectionOverlayTimer();
|
|
134
260
|
pendingConnectionOverlayStage = stage;
|
|
135
261
|
pendingConnectionOverlayDetail = detail || '';
|
|
136
262
|
connectionOverlayVisible = true;
|
|
263
|
+
shownConnectionOverlayStage = stage;
|
|
137
264
|
setConnectionOverlayStage(stage, detail);
|
|
138
265
|
}
|
|
139
266
|
function scheduleConnectionOverlay(stage, detail, delayMs = 1200) {
|
|
@@ -154,24 +281,25 @@ function updateConnectionOverlay(stage, detail) {
|
|
|
154
281
|
function markHmrConnectionHealthy() {
|
|
155
282
|
awaitingHealthyHmrMessage = false;
|
|
156
283
|
clearConnectionOverlayTimer();
|
|
284
|
+
shownConnectionOverlayStage = null;
|
|
157
285
|
if (connectionOverlayVisible) {
|
|
158
286
|
connectionOverlayVisible = false;
|
|
159
287
|
hideConnectionOverlay();
|
|
160
288
|
}
|
|
161
289
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
290
|
+
let CLIENT_STRATEGY;
|
|
291
|
+
const CLIENT_STRATEGY_READY = TARGET_FLAVOR === 'vue' || TARGET_FLAVOR === 'angular'
|
|
292
|
+
? import(`../frameworks/${TARGET_FLAVOR}/client/strategy.js`)
|
|
293
|
+
.then((mod) => {
|
|
294
|
+
CLIENT_STRATEGY = mod && mod[`${TARGET_FLAVOR}ClientStrategy`];
|
|
295
|
+
if (VERBOSE)
|
|
296
|
+
console.log('[hmr-client] client strategy loaded for flavor:', TARGET_FLAVOR);
|
|
297
|
+
CLIENT_STRATEGY?.install();
|
|
298
|
+
})
|
|
299
|
+
.catch((err) => {
|
|
300
|
+
console.warn('[hmr-client] failed to load client strategy for', TARGET_FLAVOR, err);
|
|
301
|
+
})
|
|
302
|
+
: Promise.resolve();
|
|
175
303
|
// Track whether we've mounted an initial app root yet in HTTP-only boot
|
|
176
304
|
let initialMounted = !!globalThis.__NS_HMR_BOOT_COMPLETE__;
|
|
177
305
|
// Prevent duplicate initial-mount scheduling across rapid full-graph broadcasts and re-evaluations
|
|
@@ -189,7 +317,7 @@ let processingPromise = null;
|
|
|
189
317
|
// Detect whether the early placeholder root is still active on screen
|
|
190
318
|
function isPlaceholderActive() {
|
|
191
319
|
try {
|
|
192
|
-
const g =
|
|
320
|
+
const g = getGlobalScope();
|
|
193
321
|
if (g.__NS_DEV_PLACEHOLDER_ROOT_VIEW__)
|
|
194
322
|
return true;
|
|
195
323
|
if (g.__NS_DEV_PLACEHOLDER_ROOT_EARLY__)
|
|
@@ -219,9 +347,9 @@ function applyFullGraph(payload) {
|
|
|
219
347
|
// causes a double-mount race (rescue fires at 450ms, then main.ts fires ~1s later,
|
|
220
348
|
// causing a visual flash and leaving the app in an inconsistent state).
|
|
221
349
|
try {
|
|
222
|
-
const g =
|
|
350
|
+
const g = getGlobalScope();
|
|
223
351
|
const bootDone = !!g.__NS_HMR_BOOT_COMPLETE__;
|
|
224
|
-
if (!bootDone && !initialMounted && !initialMounting && !g.__NS_HMR_RESCUE_SCHEDULED__ &&
|
|
352
|
+
if (!bootDone && !initialMounted && !initialMounting && !g.__NS_HMR_RESCUE_SCHEDULED__ && !TS_LIKE_FLAVOR) {
|
|
225
353
|
// simple snapshot helpers
|
|
226
354
|
const getTopmost = () => {
|
|
227
355
|
try {
|
|
@@ -271,10 +399,7 @@ function applyFullGraph(payload) {
|
|
|
271
399
|
if (g.__NS_HMR_BOOT_COMPLETE__)
|
|
272
400
|
return; // app already mounted
|
|
273
401
|
if (hasRealRoot()) {
|
|
274
|
-
|
|
275
|
-
g.__NS_HMR_BOOT_COMPLETE__ = true;
|
|
276
|
-
}
|
|
277
|
-
catch { }
|
|
402
|
+
markDevBootComplete();
|
|
278
403
|
if (VERBOSE)
|
|
279
404
|
console.log('[hmr][init] detected real root after delay; skipping client initial mount');
|
|
280
405
|
// Detach placeholder handler if still present
|
|
@@ -294,7 +419,7 @@ function applyFullGraph(payload) {
|
|
|
294
419
|
if (VERBOSE)
|
|
295
420
|
console.log('[hmr][init] placeholder persists after delay; evaluating rescue policy');
|
|
296
421
|
// Flavor-specific rescue handling
|
|
297
|
-
if (
|
|
422
|
+
if (TS_LIKE_FLAVOR) {
|
|
298
423
|
// For TS apps, perform a one-time resetRootView to the conventional
|
|
299
424
|
// app root module. This mimics what Application.run would do and
|
|
300
425
|
// replaces the placeholder with the real UI without trying to
|
|
@@ -311,10 +436,7 @@ function applyFullGraph(payload) {
|
|
|
311
436
|
catch { }
|
|
312
437
|
App.resetRootView({ moduleName: 'app-root' });
|
|
313
438
|
initialMounted = true;
|
|
314
|
-
|
|
315
|
-
g.__NS_HMR_BOOT_COMPLETE__ = true;
|
|
316
|
-
}
|
|
317
|
-
catch { }
|
|
439
|
+
markDevBootComplete();
|
|
318
440
|
if (VERBOSE)
|
|
319
441
|
console.log('[hmr][init] TS rescue resetRootView complete');
|
|
320
442
|
}
|
|
@@ -334,39 +456,7 @@ function applyFullGraph(payload) {
|
|
|
334
456
|
}
|
|
335
457
|
return;
|
|
336
458
|
}
|
|
337
|
-
|
|
338
|
-
switch (TARGET_FLAVOR) {
|
|
339
|
-
case 'vue': {
|
|
340
|
-
const appEntry = graph.get(APP_MAIN_ENTRY_SPEC);
|
|
341
|
-
if (appEntry && Array.isArray(appEntry.deps)) {
|
|
342
|
-
const vueDep = appEntry.deps.find((d) => typeof d === 'string' && /\.vue$/i.test(d));
|
|
343
|
-
if (vueDep)
|
|
344
|
-
candidate = vueDep;
|
|
345
|
-
}
|
|
346
|
-
if (!candidate) {
|
|
347
|
-
for (const id of graph.keys()) {
|
|
348
|
-
if (/\.vue$/i.test(id)) {
|
|
349
|
-
candidate = id;
|
|
350
|
-
break;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
// Fallback: when the module graph is empty (Vite 7+ may not populate it
|
|
355
|
-
// before the first full-graph broadcast), check the SFC artifact registry
|
|
356
|
-
// which is populated from the ns:vue-sfc-registry message.
|
|
357
|
-
if (!candidate && sfcArtifactMap.size > 0) {
|
|
358
|
-
for (const id of sfcArtifactMap.keys()) {
|
|
359
|
-
if (/\.vue$/i.test(id)) {
|
|
360
|
-
candidate = id;
|
|
361
|
-
if (VERBOSE)
|
|
362
|
-
console.log('[hmr][init] rescue candidate from SFC registry:', id);
|
|
363
|
-
break;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
break;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
459
|
+
const candidate = CLIENT_STRATEGY?.selectMountCandidate?.({ graph, appMainEntrySpec: APP_MAIN_ENTRY_SPEC }) ?? null;
|
|
370
460
|
if (!candidate)
|
|
371
461
|
return;
|
|
372
462
|
initialMounting = true;
|
|
@@ -377,20 +467,15 @@ function applyFullGraph(payload) {
|
|
|
377
467
|
(async () => {
|
|
378
468
|
try {
|
|
379
469
|
let comp = null;
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
comp = await loadSfcComponent(candidate, 'initial_mount_rescue');
|
|
383
|
-
break;
|
|
470
|
+
if (CLIENT_STRATEGY?.loadComponentForMount) {
|
|
471
|
+
comp = await CLIENT_STRATEGY.loadComponentForMount(candidate, 'initial_mount_rescue');
|
|
384
472
|
}
|
|
385
473
|
if (!comp)
|
|
386
474
|
return;
|
|
387
475
|
const ok = await performResetRoot(comp);
|
|
388
476
|
if (ok) {
|
|
389
477
|
initialMounted = true;
|
|
390
|
-
|
|
391
|
-
g.__NS_HMR_BOOT_COMPLETE__ = true;
|
|
392
|
-
}
|
|
393
|
-
catch { }
|
|
478
|
+
markDevBootComplete();
|
|
394
479
|
if (VERBOSE)
|
|
395
480
|
console.log('[hmr][init] initial mount (rescue) complete');
|
|
396
481
|
}
|
|
@@ -421,41 +506,11 @@ function applyFullGraph(payload) {
|
|
|
421
506
|
// Only allow initial mount when explicitly enabled. Rely on the app's own main entry start() for the first mount
|
|
422
507
|
// to avoid double-mount races that can cause duplicate navigation logs.
|
|
423
508
|
if (ALLOW_INITIAL_MOUNT && !initialMounted && !bootDone && !bootInProgress && !getCurrentApp() && !getRootFrame()) {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
const appEntry = graph.get(APP_MAIN_ENTRY_SPEC);
|
|
428
|
-
if (appEntry && Array.isArray(appEntry.deps)) {
|
|
429
|
-
const vueDep = appEntry.deps.find((d) => typeof d === 'string' && /\.vue$/i.test(d));
|
|
430
|
-
if (vueDep)
|
|
431
|
-
candidate = vueDep;
|
|
432
|
-
}
|
|
433
|
-
if (!candidate) {
|
|
434
|
-
for (const id of graph.keys()) {
|
|
435
|
-
if (/\.vue$/i.test(id)) {
|
|
436
|
-
candidate = id;
|
|
437
|
-
break;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
// Fallback: SFC registry (same as rescue mount above)
|
|
442
|
-
if (!candidate && sfcArtifactMap.size > 0) {
|
|
443
|
-
for (const id of sfcArtifactMap.keys()) {
|
|
444
|
-
if (/\.vue$/i.test(id)) {
|
|
445
|
-
candidate = id;
|
|
446
|
-
if (VERBOSE)
|
|
447
|
-
console.log('[hmr][init] initial mount candidate from SFC registry:', id);
|
|
448
|
-
break;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
break;
|
|
453
|
-
}
|
|
454
|
-
case 'typescript': {
|
|
455
|
-
// For TS flavor, do not perform client-driven initial mount; rely on Application.run.
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
509
|
+
if (TS_LIKE_FLAVOR) {
|
|
510
|
+
// For TS flavor, do not perform client-driven initial mount; rely on Application.run.
|
|
511
|
+
return;
|
|
458
512
|
}
|
|
513
|
+
const candidate = CLIENT_STRATEGY?.selectMountCandidate?.({ graph, appMainEntrySpec: APP_MAIN_ENTRY_SPEC }) ?? null;
|
|
459
514
|
if (candidate) {
|
|
460
515
|
// Mark initial-mount in progress (both module-local and global) BEFORE scheduling async work
|
|
461
516
|
initialMounting = true;
|
|
@@ -469,7 +524,7 @@ function applyFullGraph(payload) {
|
|
|
469
524
|
console.log('[hmr][init] mounting initial root from', candidate, 'flavor=', TARGET_FLAVOR);
|
|
470
525
|
// Android-only: avoid racing entry-runtime reset and Activity bring-up
|
|
471
526
|
try {
|
|
472
|
-
const g =
|
|
527
|
+
const g = getGlobalScope();
|
|
473
528
|
const App = getCore('Application') || g.Application;
|
|
474
529
|
const isAndroid = !!(App && App.android !== undefined);
|
|
475
530
|
if (isAndroid) {
|
|
@@ -500,30 +555,25 @@ function applyFullGraph(payload) {
|
|
|
500
555
|
}
|
|
501
556
|
catch { }
|
|
502
557
|
let comp = null;
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
}
|
|
517
|
-
break;
|
|
558
|
+
if (TS_LIKE_FLAVOR) {
|
|
559
|
+
try {
|
|
560
|
+
const url = await requestModuleFromServer(candidate);
|
|
561
|
+
const mod = await import(/* @vite-ignore */ url);
|
|
562
|
+
comp = mod && (mod.default || mod);
|
|
563
|
+
}
|
|
564
|
+
catch (e) {
|
|
565
|
+
if (VERBOSE)
|
|
566
|
+
console.warn('[hmr][init] TS initial mount failed to import', candidate, e);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
else if (CLIENT_STRATEGY?.loadComponentForMount) {
|
|
570
|
+
comp = await CLIENT_STRATEGY.loadComponentForMount(candidate, 'initial_mount');
|
|
518
571
|
}
|
|
519
572
|
if (comp) {
|
|
520
573
|
const ok = await performResetRoot(comp);
|
|
521
574
|
if (ok) {
|
|
522
575
|
initialMounted = true;
|
|
523
|
-
|
|
524
|
-
globalThis.__NS_HMR_BOOT_COMPLETE__ = true;
|
|
525
|
-
}
|
|
526
|
-
catch { }
|
|
576
|
+
markDevBootComplete();
|
|
527
577
|
if (VERBOSE)
|
|
528
578
|
console.log('[hmr][init] initial mount complete');
|
|
529
579
|
}
|
|
@@ -575,11 +625,7 @@ function applyDelta(payload) {
|
|
|
575
625
|
setGraphVersion(payload.newVersion);
|
|
576
626
|
}
|
|
577
627
|
const changed = payload.changed || [];
|
|
578
|
-
|
|
579
|
-
case 'vue':
|
|
580
|
-
recordVuePayloadChanges(changed, getGraphVersion());
|
|
581
|
-
break;
|
|
582
|
-
}
|
|
628
|
+
CLIENT_STRATEGY?.recordPayloadChanges?.(changed, getGraphVersion());
|
|
583
629
|
(payload.changed || []).forEach((m) => {
|
|
584
630
|
if (!m || !m.id)
|
|
585
631
|
return;
|
|
@@ -646,7 +692,7 @@ function applyDelta(payload) {
|
|
|
646
692
|
}
|
|
647
693
|
if (isAppMainEntryId(id)) {
|
|
648
694
|
try {
|
|
649
|
-
const exists =
|
|
695
|
+
const exists = getGlobalScope().require?.(id) || globalThis.__nsGetModuleExports?.(id);
|
|
650
696
|
if (!exists && VERBOSE)
|
|
651
697
|
console.log(`[hmr][delta] skipping unresolved ${APP_MAIN_ENTRY_SPEC} change`);
|
|
652
698
|
if (!exists)
|
|
@@ -662,14 +708,10 @@ function applyDelta(payload) {
|
|
|
662
708
|
processQueue();
|
|
663
709
|
}
|
|
664
710
|
}
|
|
665
|
-
// Deterministic navigation using the current Vue app instance rather than vendor-held rootApp
|
|
711
|
+
// Deterministic navigation using the current Vue app instance rather than vendor-held rootApp.
|
|
666
712
|
function __nsNavigateUsingApp(comp, opts = {}) {
|
|
667
|
-
const g =
|
|
668
|
-
|
|
669
|
-
case 'vue':
|
|
670
|
-
ensureVueGlobals();
|
|
671
|
-
break;
|
|
672
|
-
}
|
|
713
|
+
const g = getGlobalScope();
|
|
714
|
+
CLIENT_STRATEGY?.beforeNavigateBuild?.();
|
|
673
715
|
const AppFactory = g.createApp;
|
|
674
716
|
const RootCtor = g.NSVRoot;
|
|
675
717
|
if (typeof AppFactory !== 'function' || typeof RootCtor !== 'function') {
|
|
@@ -692,12 +734,14 @@ function __nsNavigateUsingApp(comp, opts = {}) {
|
|
|
692
734
|
const buildTarget = () => {
|
|
693
735
|
const existingApp = getCurrentApp();
|
|
694
736
|
const baseProvides = (existingApp && existingApp._context && existingApp._context.provides) || {};
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
737
|
+
// Forward `opts.props` as Vue's rootProps so `$navigateTo(Comp, { props: { … } })`
|
|
738
|
+
// reaches the destination component. nativescript-vue's stock `$navigateTo`
|
|
739
|
+
// does the same via `createNativeView(target, options?.props, …)` →
|
|
740
|
+
// `renderer.createApp(component, props)`. Dropping props here would surface
|
|
741
|
+
// at the destination as `[Vue warn]: Missing required prop` and any
|
|
742
|
+
// required-prop component would render with `undefined` bindings.
|
|
743
|
+
const app = AppFactory(normalizeComponent(comp, comp && (comp.__name || comp.name)), opts && opts.props);
|
|
744
|
+
CLIENT_STRATEGY?.onNavAppCreated?.(app);
|
|
701
745
|
try {
|
|
702
746
|
const registry = g.__nsVendorRegistry;
|
|
703
747
|
const req = registry?.get ? g.__nsVendorRequire || g.__nsRequire || g.require : g.__nsRequire || g.require;
|
|
@@ -792,6 +836,184 @@ try {
|
|
|
792
836
|
globalThis.__nsNavigateUsingApp = __nsNavigateUsingApp;
|
|
793
837
|
}
|
|
794
838
|
catch { }
|
|
839
|
+
const openModalRecords = [];
|
|
840
|
+
let modalTrackingInstalled = false;
|
|
841
|
+
/**
|
|
842
|
+
* Map a served/graph module id (e.g. `/app/modal-page.xml`) to its app-root
|
|
843
|
+
* relative path (`modal-page.xml`). Single mapping point — the raw-asset
|
|
844
|
+
* re-registration, page-navigation targets, and modal matching all derive
|
|
845
|
+
* from this; keep them in sync by construction.
|
|
846
|
+
*/
|
|
847
|
+
function toAppRelativePath(id) {
|
|
848
|
+
try {
|
|
849
|
+
const spec = normalizeSpec(id);
|
|
850
|
+
const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
|
|
851
|
+
let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
|
|
852
|
+
if (relPath.startsWith(appVirtual))
|
|
853
|
+
relPath = relPath.slice(appVirtual.length);
|
|
854
|
+
return relPath || null;
|
|
855
|
+
}
|
|
856
|
+
catch {
|
|
857
|
+
return null;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
/** App-root relative module name (no extension) for page-shaped files, else null. */
|
|
861
|
+
function toAppModuleName(id) {
|
|
862
|
+
const relPath = toAppRelativePath(id);
|
|
863
|
+
if (!relPath || !/\.(xml|ts|js)$/i.test(relPath))
|
|
864
|
+
return null;
|
|
865
|
+
return relPath.replace(/\.(xml|ts|js)$/i, '');
|
|
866
|
+
}
|
|
867
|
+
function ensureModalTracking() {
|
|
868
|
+
if (modalTrackingInstalled)
|
|
869
|
+
return;
|
|
870
|
+
try {
|
|
871
|
+
const View = getCore('View') || getGlobalScope().View;
|
|
872
|
+
const proto = View?.prototype;
|
|
873
|
+
if (!proto || typeof proto.showModal !== 'function')
|
|
874
|
+
return;
|
|
875
|
+
const orig = proto.showModal;
|
|
876
|
+
if (orig.__nsHmrModalTracked) {
|
|
877
|
+
modalTrackingInstalled = true;
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
const wrapped = function (...args) {
|
|
881
|
+
const result = orig.apply(this, args);
|
|
882
|
+
try {
|
|
883
|
+
if (typeof args[0] === 'string' && result) {
|
|
884
|
+
// Mirror core's getModalOptions arg shapes: (moduleName, options)
|
|
885
|
+
// or the deprecated positional form.
|
|
886
|
+
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] };
|
|
887
|
+
const moduleName = String(args[0])
|
|
888
|
+
.replace(/^\.\//, '')
|
|
889
|
+
.replace(/\.(xml|ts|js)$/i, '');
|
|
890
|
+
openModalRecords.push({ moduleName, options, parent: this, modal: result });
|
|
891
|
+
if (VERBOSE)
|
|
892
|
+
console.log('[hmr][modal] tracked open modal', moduleName);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
catch { }
|
|
896
|
+
return result;
|
|
897
|
+
};
|
|
898
|
+
wrapped.__nsHmrModalTracked = true;
|
|
899
|
+
proto.showModal = wrapped;
|
|
900
|
+
modalTrackingInstalled = true;
|
|
901
|
+
}
|
|
902
|
+
catch (e) {
|
|
903
|
+
if (VERBOSE)
|
|
904
|
+
console.warn('[hmr][modal] tracking install failed', e);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Enumerate the modals that are currently presented AND were opened by module
|
|
909
|
+
* name, with everything needed to re-present them.
|
|
910
|
+
*
|
|
911
|
+
* Source of truth is core's live modal stack (`_getRootModalViews()`):
|
|
912
|
+
* - `modal._moduleName` — set by the Builder on every createViewFromEntry
|
|
913
|
+
* view (longstanding, used by livesync), so available on any core.
|
|
914
|
+
* - `modal._modalOptions` — the original ShowModalOptions, stored by core's
|
|
915
|
+
* `_showNativeModalView` (newer cores). For older cores the showModal
|
|
916
|
+
* wrap's records (see ensureModalTracking) fill the gap.
|
|
917
|
+
* - `modal._modalParent` — the presenting view.
|
|
918
|
+
* Stale wrap records are pruned against the live stack while we're here.
|
|
919
|
+
*/
|
|
920
|
+
function getOpenStringModuleModals() {
|
|
921
|
+
const out = [];
|
|
922
|
+
try {
|
|
923
|
+
const App = getCore('Application');
|
|
924
|
+
const root = App?.getRootView?.() || App?._rootView;
|
|
925
|
+
const stack = root?._getRootModalViews?.() || [];
|
|
926
|
+
// Prune wrap records whose modal is gone (keeps the fallback list small).
|
|
927
|
+
for (let i = openModalRecords.length - 1; i >= 0; i--) {
|
|
928
|
+
if (!stack.includes(openModalRecords[i].modal)) {
|
|
929
|
+
openModalRecords.splice(i, 1);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
for (const modal of stack) {
|
|
933
|
+
const record = openModalRecords.find((r) => r.modal === modal);
|
|
934
|
+
const rawModuleName = typeof modal?._moduleName === 'string' && modal._moduleName ? modal._moduleName : record?.moduleName;
|
|
935
|
+
const parent = modal?._modalParent || record?.parent;
|
|
936
|
+
const options = modal?._modalOptions || record?.options;
|
|
937
|
+
if (!rawModuleName || !parent)
|
|
938
|
+
continue;
|
|
939
|
+
const moduleName = String(rawModuleName)
|
|
940
|
+
.replace(/^\.\//, '')
|
|
941
|
+
.replace(/\.(xml|ts|js)$/i, '');
|
|
942
|
+
out.push({ moduleName, options, parent, modal });
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
catch (e) {
|
|
946
|
+
if (VERBOSE)
|
|
947
|
+
console.warn('[hmr][modal] open-modal enumeration failed', e);
|
|
948
|
+
}
|
|
949
|
+
return out;
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Close and re-present an open modal so it rebuilds from the freshly
|
|
953
|
+
* re-registered XML/code-behind. Core clears the modal stack synchronously on
|
|
954
|
+
* close but the NATIVE dismissal completes asynchronously; iOS refuses a
|
|
955
|
+
* present while a dismissal is in flight. Newer cores fire `closedModally` on
|
|
956
|
+
* the modal at exactly that completion point — preferred signal. Older cores
|
|
957
|
+
* fall back to polling `isLoaded` (flipped by `_tearDownUI` in the same
|
|
958
|
+
* completion callback).
|
|
959
|
+
*/
|
|
960
|
+
async function reshowOpenModal(record) {
|
|
961
|
+
const { parent, modal, moduleName, options } = record;
|
|
962
|
+
await new Promise((resolve) => {
|
|
963
|
+
let settled = false;
|
|
964
|
+
const finish = () => {
|
|
965
|
+
if (!settled) {
|
|
966
|
+
settled = true;
|
|
967
|
+
resolve();
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
let eventArmed = false;
|
|
971
|
+
try {
|
|
972
|
+
if (typeof modal.once === 'function') {
|
|
973
|
+
modal.once('closedModally', finish);
|
|
974
|
+
eventArmed = true;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
catch { }
|
|
978
|
+
// Poll fallback (also the safety net if the event never fires —
|
|
979
|
+
// e.g. an interactive-dismiss cancellation).
|
|
980
|
+
const deadline = Date.now() + 2000;
|
|
981
|
+
const poll = () => {
|
|
982
|
+
if (settled)
|
|
983
|
+
return;
|
|
984
|
+
let stillLoaded = false;
|
|
985
|
+
try {
|
|
986
|
+
stillLoaded = !!modal.isLoaded;
|
|
987
|
+
}
|
|
988
|
+
catch { }
|
|
989
|
+
if ((!eventArmed && !stillLoaded) || Date.now() > deadline) {
|
|
990
|
+
finish();
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
setTimeout(poll, 50);
|
|
994
|
+
};
|
|
995
|
+
setTimeout(poll, 50);
|
|
996
|
+
try {
|
|
997
|
+
modal.closeModal();
|
|
998
|
+
}
|
|
999
|
+
catch (e) {
|
|
1000
|
+
if (VERBOSE)
|
|
1001
|
+
console.warn('[hmr][modal] close failed for', moduleName, e);
|
|
1002
|
+
finish();
|
|
1003
|
+
}
|
|
1004
|
+
});
|
|
1005
|
+
// One settle beat so the platform finishes releasing the presentation
|
|
1006
|
+
// before the new present begins.
|
|
1007
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1008
|
+
try {
|
|
1009
|
+
parent.showModal(moduleName, { ...(options || {}), animated: false });
|
|
1010
|
+
if (VERBOSE)
|
|
1011
|
+
console.log('[hmr][modal] re-presented', moduleName);
|
|
1012
|
+
}
|
|
1013
|
+
catch (e) {
|
|
1014
|
+
console.warn('[hmr][modal] re-present failed for', moduleName, e);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
795
1017
|
async function processQueue() {
|
|
796
1018
|
if (!globalThis.__NS_HMR_BOOT_COMPLETE__) {
|
|
797
1019
|
if (VERBOSE)
|
|
@@ -827,26 +1049,39 @@ async function processQueue() {
|
|
|
827
1049
|
return;
|
|
828
1050
|
if (VERBOSE)
|
|
829
1051
|
console.log('[hmr][queue] processing changed ids', drained);
|
|
830
|
-
//
|
|
1052
|
+
// Track wall-clock so the 'complete' frame can show a meaningful
|
|
1053
|
+
// total. Only the Solid + TypeScript flavors drive the overlay
|
|
1054
|
+
// from here; Angular has its own flow inside
|
|
1055
|
+
// `frameworks/angular/client/index.ts`.
|
|
1056
|
+
const tQueueStart = Date.now();
|
|
1057
|
+
const driveSolidOverlay = TARGET_FLAVOR === 'solid';
|
|
1058
|
+
// Explicit eviction step.
|
|
831
1059
|
//
|
|
832
|
-
//
|
|
833
|
-
// `__ns_hmr__/<tag>/` segment back to a stable cache key, so
|
|
834
|
-
// without explicit eviction the upcoming `import(url)` would
|
|
1060
|
+
// Without explicit eviction the upcoming `import(url)` would
|
|
835
1061
|
// resolve via V8's `g_moduleRegistry` and return the cached
|
|
836
1062
|
// stale module — making the queue drain a silent no-op for
|
|
837
|
-
// every save after the first.
|
|
838
|
-
//
|
|
839
|
-
//
|
|
840
|
-
//
|
|
841
|
-
//
|
|
842
|
-
//
|
|
843
|
-
|
|
844
|
-
|
|
1063
|
+
// every save after the first. Eviction also arms the runtime's
|
|
1064
|
+
// bust-next-fetch nonce so the re-fetch defeats OS-level HTTP
|
|
1065
|
+
// caches; the re-import below then uses the STABLE canonical
|
|
1066
|
+
// URL (module identity IS the URL). node_modules and virtual
|
|
1067
|
+
// specs are filtered out by `buildEvictionUrls` so vendor
|
|
1068
|
+
// modules stay hot.
|
|
1069
|
+
if (driveSolidOverlay) {
|
|
1070
|
+
setUpdateOverlayStage('evicting', {
|
|
1071
|
+
detail: drained.length === 1 ? `Invalidating ${drained[0]}` : `Invalidating ${drained.length} modules`,
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
845
1074
|
const evictUrls = buildEvictionUrls(drained);
|
|
846
1075
|
const evicted = invalidateModulesByUrls(evictUrls);
|
|
847
1076
|
if (VERBOSE)
|
|
848
1077
|
console.log(`[hmr][queue] eviction count=${evictUrls.length} ok=${evicted}`);
|
|
849
1078
|
// Evaluate changed modules best-effort; failures shouldn't completely break HMR.
|
|
1079
|
+
if (driveSolidOverlay) {
|
|
1080
|
+
setUpdateOverlayStage('reimporting', {
|
|
1081
|
+
detail: drained.length === 1 ? `Re-importing ${drained[0]}` : `Re-importing ${drained.length} modules`,
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
let reimportFailures = 0;
|
|
850
1085
|
for (const id of drained) {
|
|
851
1086
|
try {
|
|
852
1087
|
const spec = normalizeSpec(id);
|
|
@@ -856,18 +1091,77 @@ async function processQueue() {
|
|
|
856
1091
|
if (VERBOSE)
|
|
857
1092
|
console.log('[hmr][queue] re-import', { id, spec, url });
|
|
858
1093
|
const mod = await import(/* @vite-ignore */ url);
|
|
1094
|
+
// TS/XML flavor: refresh the bundler module registry with the fresh
|
|
1095
|
+
// exports so Builder.createViewFromEntry / loadModule('<page>')
|
|
1096
|
+
// resolves the NEW code-behind (tap handlers, page events) instead
|
|
1097
|
+
// of the stale module captured in the boot bundle. Without this,
|
|
1098
|
+
// XML re-renders pick up new markup but keep old behavior.
|
|
1099
|
+
if (TS_LIKE_FLAVOR && mod && /\.(ts|js)$/i.test(id)) {
|
|
1100
|
+
try {
|
|
1101
|
+
const g = getGlobalScope();
|
|
1102
|
+
const moduleName = toAppModuleName(id);
|
|
1103
|
+
if (moduleName && typeof g.registerModule === 'function') {
|
|
1104
|
+
g.registerModule(moduleName, () => mod);
|
|
1105
|
+
g.registerModule('./' + moduleName, () => mod);
|
|
1106
|
+
if (VERBOSE)
|
|
1107
|
+
console.log('[hmr][queue] re-registered code-behind', moduleName);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
catch (e) {
|
|
1111
|
+
if (VERBOSE)
|
|
1112
|
+
console.warn('[hmr][queue] code-behind re-register failed for', id, e);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
859
1115
|
}
|
|
860
1116
|
catch (e) {
|
|
861
|
-
|
|
862
|
-
|
|
1117
|
+
// Never silent: a failed re-import means the device may keep
|
|
1118
|
+
// running the previous module body, so always surface it (not
|
|
1119
|
+
// verbose-gated) — otherwise the overlay reports success while
|
|
1120
|
+
// the app runs stale code.
|
|
1121
|
+
reimportFailures++;
|
|
1122
|
+
console.warn('[hmr][queue] re-import FAILED for', id, '-', e?.message ?? e);
|
|
863
1123
|
}
|
|
864
1124
|
}
|
|
1125
|
+
if (reimportFailures > 0) {
|
|
1126
|
+
console.warn(`[hmr][queue] ${reimportFailures}/${drained.length} module(s) failed to re-import; the applied update may be incomplete.`);
|
|
1127
|
+
}
|
|
865
1128
|
// After evaluating the batch, perform flavor-specific UI refresh.
|
|
866
1129
|
switch (TARGET_FLAVOR) {
|
|
867
1130
|
case 'vue':
|
|
868
|
-
//
|
|
1131
|
+
// graph + performResetRoot + getOverlay let the Vue strategy
|
|
1132
|
+
// propagate non-SFC dep changes to the nearest `.vue` boundary
|
|
1133
|
+
// and remount it (see `propagateDepChangeToSfcBoundary`).
|
|
1134
|
+
await CLIENT_STRATEGY?.refreshAfterBatch?.(drained, { setUpdateOverlayStage, startedAt: tQueueStart, graph, performResetRoot, getOverlay: getHmrOverlayApi });
|
|
1135
|
+
break;
|
|
1136
|
+
case 'react': {
|
|
1137
|
+
// React has no Fast Refresh, and (like Solid) mounts a dominative
|
|
1138
|
+
// document root via `startReactApp` rather than an `app-root`
|
|
1139
|
+
// module — so the TypeScript `resetRootView({moduleName:'app-root'})`
|
|
1140
|
+
// path doesn't apply. The changed modules were already re-imported
|
|
1141
|
+
// into the device cache above; the per-page remount is driven by the
|
|
1142
|
+
// app's `__NS_HMR_ON_UPDATE__` hook (see
|
|
1143
|
+
// `@nativescript/tanstack-router/react`'s `subscribeReactHmrRemount`),
|
|
1144
|
+
// which fires right after this queue drains. Here we only mark the
|
|
1145
|
+
// overlay complete so it doesn't stick at 'received' (5%).
|
|
1146
|
+
setUpdateOverlayStage('complete', {
|
|
1147
|
+
detail: `Total ${Math.max(0, Date.now() - tQueueStart)}ms`,
|
|
1148
|
+
});
|
|
869
1149
|
break;
|
|
1150
|
+
}
|
|
870
1151
|
case 'solid': {
|
|
1152
|
+
// Boundaries discovered in this HMR cycle (tsx files reachable
|
|
1153
|
+
// via the reverse import graph from any changed file, plus route
|
|
1154
|
+
// files reachable from any tsx start point). Declared at the top
|
|
1155
|
+
// of the case block so the emit step below can include the
|
|
1156
|
+
// complete set in the listener event — framework integrations
|
|
1157
|
+
// use it to map route boundaries → fresh component references.
|
|
1158
|
+
const boundaries = new Set();
|
|
1159
|
+
// FULL reverse-import chain from every changed file up to the app
|
|
1160
|
+
// entry (all importers, transitively — not just tsx ones). Emitted
|
|
1161
|
+
// as `ev.ancestors` so app-level remount helpers can evict the
|
|
1162
|
+
// whole chain: re-importing only the root would resolve cached
|
|
1163
|
+
// intermediates and hand the fresh root a stale subtree.
|
|
1164
|
+
const ancestors = new Set();
|
|
871
1165
|
// Solid .tsx components are self-accepting via solid-refresh's inline
|
|
872
1166
|
// patchRegistry — re-importing them is sufficient. For non-component
|
|
873
1167
|
// .ts utility modules, we must propagate up the import graph to find
|
|
@@ -886,8 +1180,28 @@ async function processQueue() {
|
|
|
886
1180
|
arr.push(id);
|
|
887
1181
|
}
|
|
888
1182
|
}
|
|
889
|
-
//
|
|
890
|
-
|
|
1183
|
+
// Pass 0: collect the FULL importer chain of every changed file
|
|
1184
|
+
// (BFS up the reverse graph, no filtering). This feeds
|
|
1185
|
+
// `ev.ancestors` for app-level remount helpers.
|
|
1186
|
+
for (const id of drained) {
|
|
1187
|
+
const queue = [id];
|
|
1188
|
+
while (queue.length) {
|
|
1189
|
+
const cur = queue.shift();
|
|
1190
|
+
const importers = reverseIndex.get(cur);
|
|
1191
|
+
if (!importers)
|
|
1192
|
+
continue;
|
|
1193
|
+
for (const imp of importers) {
|
|
1194
|
+
if (ancestors.has(imp))
|
|
1195
|
+
continue;
|
|
1196
|
+
ancestors.add(imp);
|
|
1197
|
+
queue.push(imp);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
// Pass 1: BFS from each non-tsx changed module up to tsx/jsx
|
|
1202
|
+
// boundaries. These get re-imported below so solid-refresh's
|
|
1203
|
+
// inline patchRegistry runs and (best-effort) swaps the proxy
|
|
1204
|
+
// signals for any components defined in those tsx boundaries.
|
|
891
1205
|
for (const id of drained) {
|
|
892
1206
|
if (/\.(tsx|jsx)$/i.test(id))
|
|
893
1207
|
continue; // already self-accepting
|
|
@@ -911,6 +1225,51 @@ async function processQueue() {
|
|
|
911
1225
|
}
|
|
912
1226
|
}
|
|
913
1227
|
}
|
|
1228
|
+
// Pass 2: walk further from any tsx starting point (a tsx file
|
|
1229
|
+
// in `drained` OR a tsx boundary discovered in pass 1) to find
|
|
1230
|
+
// route files (`/src/routes/*.{tsx,jsx}`) that transitively
|
|
1231
|
+
// import them. Re-importing a route file refreshes its
|
|
1232
|
+
// `Route.options.component` to the freshly-imported reference
|
|
1233
|
+
// and the existing boundary loop below patches the live router
|
|
1234
|
+
// with that fresh reference.
|
|
1235
|
+
//
|
|
1236
|
+
// This is the key fix for "edit home.tsx → save → no visual
|
|
1237
|
+
// update": the old BFS skipped tsx files in `drained` (assuming
|
|
1238
|
+
// solid-refresh's in-place proxy patch was sufficient), but in
|
|
1239
|
+
// the universal-renderer + nested-context configuration that
|
|
1240
|
+
// patch does not always propagate to the visible page tree.
|
|
1241
|
+
// Adding the route file as a boundary lets us patch
|
|
1242
|
+
// `route.options.component` directly to a fresh module export,
|
|
1243
|
+
// which the framework subscriber then passes through to the
|
|
1244
|
+
// page remount — making the cycle robust to the proxy patch
|
|
1245
|
+
// silently failing.
|
|
1246
|
+
const tsxStarts = new Set();
|
|
1247
|
+
for (const id of drained) {
|
|
1248
|
+
if (/\.(tsx|jsx)$/i.test(id))
|
|
1249
|
+
tsxStarts.add(id);
|
|
1250
|
+
}
|
|
1251
|
+
for (const b of boundaries)
|
|
1252
|
+
tsxStarts.add(b);
|
|
1253
|
+
const ROUTE_FILE_RE = /\/src\/routes\/.+\.(tsx|jsx)$/i;
|
|
1254
|
+
for (const start of tsxStarts) {
|
|
1255
|
+
const visited = new Set();
|
|
1256
|
+
const queue = [start];
|
|
1257
|
+
while (queue.length) {
|
|
1258
|
+
const cur = queue.shift();
|
|
1259
|
+
if (visited.has(cur))
|
|
1260
|
+
continue;
|
|
1261
|
+
visited.add(cur);
|
|
1262
|
+
if (cur !== start && ROUTE_FILE_RE.test(cur)) {
|
|
1263
|
+
boundaries.add(cur);
|
|
1264
|
+
}
|
|
1265
|
+
const importers = reverseIndex.get(cur);
|
|
1266
|
+
if (!importers)
|
|
1267
|
+
continue;
|
|
1268
|
+
for (const imp of importers) {
|
|
1269
|
+
queue.push(imp);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
914
1273
|
// Re-import each boundary so solid-refresh patchRegistry fires.
|
|
915
1274
|
// For route files (TanStack Router), capture the new Route export
|
|
916
1275
|
// and patch the router's existing route with the fresh loader.
|
|
@@ -921,7 +1280,7 @@ async function processQueue() {
|
|
|
921
1280
|
const findRouter = () => {
|
|
922
1281
|
if (discoveredRouter)
|
|
923
1282
|
return discoveredRouter;
|
|
924
|
-
const g =
|
|
1283
|
+
const g = getGlobalScope();
|
|
925
1284
|
if (g.__ns_router?.routesById)
|
|
926
1285
|
return (discoveredRouter = g.__ns_router);
|
|
927
1286
|
// Fallback: scan common global keys for router
|
|
@@ -949,53 +1308,97 @@ async function processQueue() {
|
|
|
949
1308
|
p = p.replace(/(^|\/)-([\w])/g, '$1$2');
|
|
950
1309
|
return '/' + p;
|
|
951
1310
|
};
|
|
952
|
-
// Find existing route by fullPath (since new Route has no id yet)
|
|
1311
|
+
// Find existing route by fullPath (since new Route has no id yet).
|
|
1312
|
+
// The root route (`__root__`) shares fullPath '/' with the index
|
|
1313
|
+
// route, so prefer the non-root (leaf) match — otherwise editing
|
|
1314
|
+
// `index.tsx` would clobber the root layout's component instead of
|
|
1315
|
+
// the page's. @nativescript/tanstack-router's remount uses the same
|
|
1316
|
+
// preference so patch + read target the same route.
|
|
953
1317
|
const findRouteByFullPath = (router, fp) => {
|
|
954
1318
|
if (!router?.routesById)
|
|
955
1319
|
return null;
|
|
1320
|
+
let rootMatch = null;
|
|
956
1321
|
for (const rid of Object.keys(router.routesById)) {
|
|
957
1322
|
const r = router.routesById[rid];
|
|
958
|
-
if (r?.fullPath
|
|
959
|
-
|
|
1323
|
+
if (r?.fullPath !== fp)
|
|
1324
|
+
continue;
|
|
1325
|
+
if (rid === '__root__') {
|
|
1326
|
+
rootMatch = rootMatch || r;
|
|
1327
|
+
continue;
|
|
1328
|
+
}
|
|
1329
|
+
return r;
|
|
960
1330
|
}
|
|
961
|
-
return
|
|
1331
|
+
return rootMatch;
|
|
962
1332
|
};
|
|
963
|
-
//
|
|
964
|
-
//
|
|
965
|
-
//
|
|
966
|
-
//
|
|
1333
|
+
// A changed route file is itself a boundary to re-import + patch — Pass 2
|
|
1334
|
+
// only adds OTHER route files that import a changed module, never the
|
|
1335
|
+
// changed route file itself. Without this, editing `about.tsx` while on
|
|
1336
|
+
// /about never refreshes the About component.
|
|
1337
|
+
for (const id of drained) {
|
|
1338
|
+
if (ROUTE_FILE_RE.test(id))
|
|
1339
|
+
boundaries.add(id);
|
|
1340
|
+
}
|
|
1341
|
+
// Evict ONLY the boundary set (changed component + route files) — NOT the
|
|
1342
|
+
// router / route-tree / app chain. Keeping those cached preserves the live
|
|
1343
|
+
// router instance and the user's current route across the re-mount; fresh
|
|
1344
|
+
// route components reach the screen via the in-place `route.options.component`
|
|
1345
|
+
// patch below, not a router rebuild (which would reset to the initial route).
|
|
967
1346
|
const boundaryIds = Array.from(boundaries);
|
|
968
1347
|
const solidEvictUrls = buildEvictionUrls(boundaryIds);
|
|
969
1348
|
const solidEvicted = invalidateModulesByUrls(solidEvictUrls);
|
|
970
1349
|
if (VERBOSE)
|
|
971
1350
|
console.log(`[hmr][solid] eviction count=${solidEvictUrls.length} ok=${solidEvicted}`);
|
|
972
1351
|
for (const id of boundaries) {
|
|
973
|
-
|
|
1352
|
+
// Skip already-drained modules — EXCEPT route files. The main
|
|
1353
|
+
// drain loop above re-imports drained modules but does NOT patch
|
|
1354
|
+
// `route.options.component`; a changed ROUTE file (which is in
|
|
1355
|
+
// `drained`/`seen`) must still flow through this loop so its route
|
|
1356
|
+
// gets patched with the fresh component, otherwise the router's
|
|
1357
|
+
// per-page HMR remount re-renders the stale (one-edit-behind) one.
|
|
1358
|
+
if (seen.has(id) && !/\/src\/routes\/.+\.(?:tsx|jsx)$/i.test(id))
|
|
1359
|
+
continue;
|
|
1360
|
+
// Skip the root layout (`__root`): it commonly imports a global
|
|
1361
|
+
// stylesheet (`./styles.css?url`) with no ESM `default` export on
|
|
1362
|
+
// device, so re-importing it throws and aborts the cycle. The root
|
|
1363
|
+
// layout rarely needs a component hot-swap anyway.
|
|
1364
|
+
if (/\/routes\/__root\.(?:tsx|jsx|ts|js)$/i.test(id))
|
|
974
1365
|
continue;
|
|
975
1366
|
try {
|
|
976
1367
|
const spec = normalizeSpec(id);
|
|
1368
|
+
// Re-import the canonical URL. The boundary was evicted above
|
|
1369
|
+
// (`invalidateModulesByUrls(solidEvictUrls)`), which also armed
|
|
1370
|
+
// the runtime's bust-next-fetch nonce, so this import re-fetches
|
|
1371
|
+
// the just-saved transform rather than a cached one. This is what
|
|
1372
|
+
// `route.options.component` is patched from below, and what
|
|
1373
|
+
// @nativescript/tanstack-router's per-page HMR remount reads.
|
|
977
1374
|
const url = await requestModuleFromServer(spec);
|
|
978
1375
|
if (!url)
|
|
979
1376
|
continue;
|
|
980
1377
|
if (VERBOSE)
|
|
981
1378
|
console.log('[hmr][solid] propagated to boundary', { id, url });
|
|
982
1379
|
const mod = await import(/* @vite-ignore */ url);
|
|
983
|
-
// Patch TanStack Router route
|
|
1380
|
+
// Patch TanStack Router route options for any module
|
|
1381
|
+
// that exports a `Route`. We patch BOTH the component
|
|
1382
|
+
// and the loader (when present); components-only routes
|
|
1383
|
+
// were previously skipped because the gate required a
|
|
1384
|
+
// loader, which left their `options.component` pointing
|
|
1385
|
+
// at the stale module's exports after HMR.
|
|
984
1386
|
try {
|
|
985
1387
|
const newRoute = mod?.Route;
|
|
986
|
-
if (newRoute?.options
|
|
1388
|
+
if (newRoute?.options) {
|
|
987
1389
|
const router = findRouter();
|
|
988
1390
|
const fullPath = boundaryToFullPath(id);
|
|
989
1391
|
if (VERBOSE)
|
|
990
|
-
console.log('[hmr][solid][diag] route patch attempt', { id, fullPath, hasRouter: !!router,
|
|
1392
|
+
console.log('[hmr][solid][diag] route patch attempt', { id, fullPath, hasRouter: !!router, hasLoader: !!newRoute.options.loader, hasComponent: !!newRoute.options.component });
|
|
991
1393
|
const existingRoute = fullPath && router ? findRouteByFullPath(router, fullPath) : null;
|
|
992
1394
|
if (existingRoute?.options) {
|
|
993
|
-
|
|
1395
|
+
if (newRoute.options.loader)
|
|
1396
|
+
existingRoute.options.loader = newRoute.options.loader;
|
|
994
1397
|
if (newRoute.options.component)
|
|
995
1398
|
existingRoute.options.component = newRoute.options.component;
|
|
996
1399
|
routesPatchCount++;
|
|
997
1400
|
if (VERBOSE)
|
|
998
|
-
console.log('[hmr][solid] patched route
|
|
1401
|
+
console.log('[hmr][solid] patched route', existingRoute.id, 'fullPath=', fullPath);
|
|
999
1402
|
}
|
|
1000
1403
|
else if (VERBOSE) {
|
|
1001
1404
|
console.log('[hmr][solid] no matching route for fullPath', fullPath);
|
|
@@ -1024,13 +1427,52 @@ async function processQueue() {
|
|
|
1024
1427
|
if (VERBOSE) {
|
|
1025
1428
|
if (boundaries.size)
|
|
1026
1429
|
console.log('[hmr][solid] propagated non-component change to', boundaries.size, 'boundaries', Array.from(boundaries));
|
|
1027
|
-
console.log('[hmr][queue] Solid: modules re-imported
|
|
1430
|
+
console.log('[hmr][queue] Solid: modules re-imported; UI refresh is driven by the framework subscriber (router per-page remount or startSolidApp re-render)', drained);
|
|
1028
1431
|
}
|
|
1029
1432
|
}
|
|
1030
1433
|
catch (e) {
|
|
1031
1434
|
if (VERBOSE)
|
|
1032
1435
|
console.warn('[hmr][solid] propagation failed', e);
|
|
1033
1436
|
}
|
|
1437
|
+
// Notify any framework integrations (e.g.
|
|
1438
|
+
// `@nativescript/tanstack-router`) that a Solid HMR
|
|
1439
|
+
// cycle has completed. They use this signal to perform
|
|
1440
|
+
// framework-specific UI refresh (e.g. remount the active
|
|
1441
|
+
// router page) when solid-refresh's own reactive
|
|
1442
|
+
// propagation does not reach the visible tree under
|
|
1443
|
+
// the current renderer/context configuration.
|
|
1444
|
+
//
|
|
1445
|
+
// Boundaries include both the directly-changed tsx files
|
|
1446
|
+
// AND every tsx ancestor reachable via the reverse import
|
|
1447
|
+
// graph (route files in particular). The framework
|
|
1448
|
+
// listener uses the route-file boundaries to look up the
|
|
1449
|
+
// freshly-patched `route.options.component` and pass it
|
|
1450
|
+
// through to the page remount.
|
|
1451
|
+
try {
|
|
1452
|
+
const tsxChangedInDrained = drained.filter((id) => /\.(tsx|jsx)$/i.test(id));
|
|
1453
|
+
const allBoundaries = Array.from(new Set([...tsxChangedInDrained, ...boundaries]));
|
|
1454
|
+
nsSolidHmrEmit({
|
|
1455
|
+
kind: 'solid',
|
|
1456
|
+
changedFiles: drained.slice(),
|
|
1457
|
+
boundaries: allBoundaries,
|
|
1458
|
+
ancestors: Array.from(ancestors),
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
catch (err) {
|
|
1462
|
+
if (VERBOSE)
|
|
1463
|
+
console.warn('[hmr][solid] emit failed', err);
|
|
1464
|
+
}
|
|
1465
|
+
// Tell the overlay the cycle is done. solid-refresh's
|
|
1466
|
+
// inline patchRegistry has already flushed the new
|
|
1467
|
+
// component bodies into the live tree (the `case
|
|
1468
|
+
// 'solid'` block above re-imports each .tsx
|
|
1469
|
+
// boundary), so by the time we get here the user is
|
|
1470
|
+
// already looking at the new render. The 'complete'
|
|
1471
|
+
// frame surfaces the wall-clock total and triggers
|
|
1472
|
+
// the overlay's auto-hide.
|
|
1473
|
+
setUpdateOverlayStage('complete', {
|
|
1474
|
+
detail: `Total ${Math.max(0, Date.now() - tQueueStart)}ms`,
|
|
1475
|
+
});
|
|
1034
1476
|
break;
|
|
1035
1477
|
}
|
|
1036
1478
|
case 'typescript': {
|
|
@@ -1038,7 +1480,7 @@ async function processQueue() {
|
|
|
1038
1480
|
// This preserves the shell (Frame, ActionBar, etc.) that the app's
|
|
1039
1481
|
// own bootstrapping wires up via `Application.run`.
|
|
1040
1482
|
try {
|
|
1041
|
-
const g =
|
|
1483
|
+
const g = getGlobalScope();
|
|
1042
1484
|
const App = getCore('Application') || g.Application;
|
|
1043
1485
|
if (!App || typeof App.resetRootView !== 'function') {
|
|
1044
1486
|
if (VERBOSE)
|
|
@@ -1064,10 +1506,9 @@ async function processQueue() {
|
|
|
1064
1506
|
const rawContent = await resp.text();
|
|
1065
1507
|
// Register under all nickname variants the module registry uses.
|
|
1066
1508
|
// The bundler context registers XML as e.g., './main-page.xml' and 'main-page.xml'
|
|
1067
|
-
const
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
relPath = relPath.slice(appVirtual.length);
|
|
1509
|
+
const relPath = toAppRelativePath(id);
|
|
1510
|
+
if (!relPath)
|
|
1511
|
+
continue;
|
|
1071
1512
|
const nicknames = ['./' + relPath, relPath];
|
|
1072
1513
|
// Also add without extension for CSS
|
|
1073
1514
|
const extIdx = relPath.lastIndexOf('.');
|
|
@@ -1093,21 +1534,24 @@ async function processQueue() {
|
|
|
1093
1534
|
}
|
|
1094
1535
|
}
|
|
1095
1536
|
}
|
|
1537
|
+
// Modal-aware refresh: pages currently PRESENTED AS MODALS must be
|
|
1538
|
+
// closed + re-presented in place — navigating the top frame to a
|
|
1539
|
+
// modal's page would push it as a frame page, and resetRootView
|
|
1540
|
+
// would dismiss the modal entirely. State comes from core's live
|
|
1541
|
+
// modal stack (_moduleName/_modalOptions/_modalParent); the
|
|
1542
|
+
// showModal wrap only backfills options on older cores.
|
|
1543
|
+
ensureModalTracking();
|
|
1544
|
+
const openModals = getOpenStringModuleModals();
|
|
1545
|
+
const changedModuleNames = new Set(drained.map(toAppModuleName).filter(Boolean));
|
|
1546
|
+
const modalsToReshow = openModals.filter((record) => changedModuleNames.has(record.moduleName));
|
|
1547
|
+
const reshowModuleNames = new Set(modalsToReshow.map((record) => record.moduleName));
|
|
1096
1548
|
// Determine if we can navigate in-place to a changed page
|
|
1097
1549
|
// instead of resetting all the way back to app-root.
|
|
1098
1550
|
// This keeps the user on the page they're editing for faster iteration.
|
|
1099
1551
|
const changedXmlPages = drained
|
|
1100
1552
|
.filter((id) => /\.xml$/i.test(id))
|
|
1101
|
-
.map((id) =>
|
|
1102
|
-
|
|
1103
|
-
const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
|
|
1104
|
-
let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
|
|
1105
|
-
if (relPath.startsWith(appVirtual))
|
|
1106
|
-
relPath = relPath.slice(appVirtual.length);
|
|
1107
|
-
// Strip .xml extension to get the moduleName (e.g., 'pages/status-bar')
|
|
1108
|
-
return relPath.replace(/\.xml$/i, '');
|
|
1109
|
-
})
|
|
1110
|
-
.filter((m) => m && m !== 'app-root');
|
|
1553
|
+
.map((id) => toAppModuleName(id))
|
|
1554
|
+
.filter((m) => m && m !== 'app-root' && !reshowModuleNames.has(m));
|
|
1111
1555
|
// Resolve the topmost Frame from the bundled realm.
|
|
1112
1556
|
// Frame.topmost() relies on an internal frameStack array, so we must
|
|
1113
1557
|
// call it on the bundled-realm class. Multiple strategies to find it:
|
|
@@ -1143,7 +1587,7 @@ async function processQueue() {
|
|
|
1143
1587
|
catch { }
|
|
1144
1588
|
}
|
|
1145
1589
|
if (VERBOSE)
|
|
1146
|
-
console.log('[hmr][queue] TS: changedXmlPages=', changedXmlPages, 'topFrame=', !!topFrame);
|
|
1590
|
+
console.log('[hmr][queue] TS: changedXmlPages=', changedXmlPages, 'topFrame=', !!topFrame, 'modalsToReshow=', modalsToReshow.length);
|
|
1147
1591
|
if (changedXmlPages.length > 0 && topFrame) {
|
|
1148
1592
|
// Navigate the current frame to the changed page directly.
|
|
1149
1593
|
// Use the last changed XML page (most specific).
|
|
@@ -1158,15 +1602,32 @@ async function processQueue() {
|
|
|
1158
1602
|
App.resetRootView({ moduleName: 'app-root' });
|
|
1159
1603
|
}
|
|
1160
1604
|
}
|
|
1161
|
-
else {
|
|
1605
|
+
else if (modalsToReshow.length === 0) {
|
|
1606
|
+
// No frame page to refresh and no open modal owns the change —
|
|
1607
|
+
// fall back to a full root reset. (Skipped when an open modal is
|
|
1608
|
+
// being re-presented below: resetRootView would dismiss it.)
|
|
1162
1609
|
if (VERBOSE)
|
|
1163
1610
|
console.log('[hmr][queue] TS flavor: resetRootView(app-root) after changes');
|
|
1164
1611
|
App.resetRootView({ moduleName: 'app-root' });
|
|
1165
1612
|
}
|
|
1613
|
+
// Re-present any open modals whose XML/code-behind changed. The
|
|
1614
|
+
// modules were already re-registered above (raw XML assets + fresh
|
|
1615
|
+
// code-behind exports), so the re-presented modal rebuilds from
|
|
1616
|
+
// the new content while the page beneath it stays put.
|
|
1617
|
+
for (const record of modalsToReshow) {
|
|
1618
|
+
await reshowOpenModal(record);
|
|
1619
|
+
}
|
|
1166
1620
|
}
|
|
1167
1621
|
catch (e) {
|
|
1168
1622
|
console.warn('[hmr][queue] TS flavor: resetRootView(app-root) failed', e);
|
|
1169
1623
|
}
|
|
1624
|
+
// Tell the overlay the cycle is done — same as the solid path
|
|
1625
|
+
// above. Without this the applying overlay sticks at
|
|
1626
|
+
// 'received' (5%) forever even though the in-place navigate /
|
|
1627
|
+
// resetRootView already applied the update.
|
|
1628
|
+
setUpdateOverlayStage('complete', {
|
|
1629
|
+
detail: `Total ${Math.max(0, Date.now() - tQueueStart)}ms`,
|
|
1630
|
+
});
|
|
1170
1631
|
break;
|
|
1171
1632
|
}
|
|
1172
1633
|
}
|
|
@@ -1174,11 +1635,37 @@ async function processQueue() {
|
|
|
1174
1635
|
finally {
|
|
1175
1636
|
processingQueue = false;
|
|
1176
1637
|
processingPromise = null;
|
|
1638
|
+
// If a delta arrived mid-cycle (pushed onto changedQueue while
|
|
1639
|
+
// processingQueue was still true, so its processQueue() call early-
|
|
1640
|
+
// returned), re-drain — otherwise that save is stranded until the next
|
|
1641
|
+
// delta. Deferred via setTimeout to avoid synchronous re-entrancy as
|
|
1642
|
+
// this promise settles.
|
|
1643
|
+
if (changedQueue.length) {
|
|
1644
|
+
setTimeout(() => {
|
|
1645
|
+
try {
|
|
1646
|
+
processQueue();
|
|
1647
|
+
}
|
|
1648
|
+
catch { }
|
|
1649
|
+
}, 0);
|
|
1650
|
+
}
|
|
1177
1651
|
}
|
|
1178
1652
|
})();
|
|
1179
1653
|
return processingPromise;
|
|
1180
1654
|
}
|
|
1181
1655
|
let hmrSocket = null;
|
|
1656
|
+
// Single reconnect timer. Overlapping close/timeout events used to each schedule
|
|
1657
|
+
// their own `setTimeout(connectHmr, …)`, stacking multiple pending reconnects
|
|
1658
|
+
// that could spawn (and leak) duplicate sockets/listeners. Route all reconnect
|
|
1659
|
+
// scheduling through here so only one is ever pending.
|
|
1660
|
+
let reconnectTimer = null;
|
|
1661
|
+
function scheduleReconnect(delayMs) {
|
|
1662
|
+
if (reconnectTimer)
|
|
1663
|
+
clearTimeout(reconnectTimer);
|
|
1664
|
+
reconnectTimer = setTimeout(() => {
|
|
1665
|
+
reconnectTimer = null;
|
|
1666
|
+
connectHmr();
|
|
1667
|
+
}, delayMs);
|
|
1668
|
+
}
|
|
1182
1669
|
// Track server-announced batches for each version so we can import in-order client-side
|
|
1183
1670
|
const txnClientBatches = new Map();
|
|
1184
1671
|
// Public hook for NativeScript runtime to call from ImportModuleDynamicallyCallback later.
|
|
@@ -1206,10 +1693,16 @@ function connectHmr() {
|
|
|
1206
1693
|
if (hmrSocket?.readyState === WebSocket.OPEN)
|
|
1207
1694
|
return;
|
|
1208
1695
|
if (hmrSocket?.readyState === WebSocket.CONNECTING) {
|
|
1209
|
-
if (
|
|
1696
|
+
if (VERBOSE)
|
|
1210
1697
|
console.log('[hmr-client] Already connecting to HMR WebSocket, skipping');
|
|
1211
1698
|
return;
|
|
1212
1699
|
}
|
|
1700
|
+
// A reconnect fired (or a manual connect raced one) — cancel any other pending
|
|
1701
|
+
// reconnect so we don't end up with overlapping connect attempts.
|
|
1702
|
+
if (reconnectTimer) {
|
|
1703
|
+
clearTimeout(reconnectTimer);
|
|
1704
|
+
reconnectTimer = null;
|
|
1705
|
+
}
|
|
1213
1706
|
try {
|
|
1214
1707
|
globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = false;
|
|
1215
1708
|
}
|
|
@@ -1217,7 +1710,7 @@ function connectHmr() {
|
|
|
1217
1710
|
const overlayStage = hasOpenedHmrSocket ? 'reconnecting' : 'connecting';
|
|
1218
1711
|
const baseUrl = getHMRWsUrl() || 'ws://localhost:5173/ns-hmr';
|
|
1219
1712
|
const buildCandidates = (url) => {
|
|
1220
|
-
|
|
1713
|
+
const candidates = [];
|
|
1221
1714
|
try {
|
|
1222
1715
|
const u = new URL(url);
|
|
1223
1716
|
const proto = u.protocol === 'wss:' ? ['wss'] : ['ws'];
|
|
@@ -1225,7 +1718,7 @@ function connectHmr() {
|
|
|
1225
1718
|
// Build ordered host candidates with preference to the active HTTP origin
|
|
1226
1719
|
const orderedHosts = [];
|
|
1227
1720
|
try {
|
|
1228
|
-
const g =
|
|
1721
|
+
const g = getGlobalScope();
|
|
1229
1722
|
const httpOrigin = g && typeof g.__NS_HTTP_ORIGIN__ === 'string' ? g.__NS_HTTP_ORIGIN__ : undefined;
|
|
1230
1723
|
if (httpOrigin) {
|
|
1231
1724
|
try {
|
|
@@ -1276,19 +1769,29 @@ function connectHmr() {
|
|
|
1276
1769
|
if (idx >= candidates.length) {
|
|
1277
1770
|
showConnectionOverlayNow('offline', 'Waiting for the Vite websocket to come back.');
|
|
1278
1771
|
console.warn('[hmr-client] All WS candidates failed:', candidates.join(', '));
|
|
1279
|
-
|
|
1772
|
+
scheduleReconnect(1500);
|
|
1280
1773
|
return;
|
|
1281
1774
|
}
|
|
1282
1775
|
const url = candidates[idx++];
|
|
1283
1776
|
const connectionDetail = `${overlayStage === 'reconnecting' ? 'Retrying' : 'Opening'} ${url}`;
|
|
1284
|
-
if (
|
|
1777
|
+
if (overlayStage === 'reconnecting' && shownConnectionOverlayStage === 'offline') {
|
|
1778
|
+
// Don't flip the visible 'offline' frame to 'reconnecting' for an
|
|
1779
|
+
// attempt that may fail instantly — defer it so only a genuinely
|
|
1780
|
+
// in-flight attempt surfaces. The deferred show is cancelled by
|
|
1781
|
+
// showConnectionOverlayNow (offline re-show / synchronizing) the
|
|
1782
|
+
// moment this attempt resolves. Keeps 'offline' stable instead of
|
|
1783
|
+
// flickering once per retry. (Checking the PAINTED stage, not the
|
|
1784
|
+
// pending one, so every candidate in the loop keeps deferring.)
|
|
1785
|
+
scheduleConnectionOverlay(overlayStage, connectionDetail, RECONNECTING_OVER_OFFLINE_DELAY_MS);
|
|
1786
|
+
}
|
|
1787
|
+
else if (connectionOverlayVisible) {
|
|
1285
1788
|
updateConnectionOverlay(overlayStage, connectionDetail);
|
|
1286
1789
|
}
|
|
1287
1790
|
else {
|
|
1288
1791
|
scheduleConnectionOverlay(overlayStage, connectionDetail);
|
|
1289
1792
|
}
|
|
1290
1793
|
try {
|
|
1291
|
-
if (
|
|
1794
|
+
if (VERBOSE)
|
|
1292
1795
|
console.log('[hmr-client] Connecting to HMR WebSocket:', url);
|
|
1293
1796
|
const sock = new WebSocket(url);
|
|
1294
1797
|
hmrSocket = sock;
|
|
@@ -1315,13 +1818,29 @@ function connectHmr() {
|
|
|
1315
1818
|
globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = true;
|
|
1316
1819
|
}
|
|
1317
1820
|
catch { }
|
|
1821
|
+
// Wire the JS hot registry's server channel to this socket so
|
|
1822
|
+
// `import.meta.hot.send(event, data)` reaches Vite plugins as
|
|
1823
|
+
// `{ type: 'custom', event, data }` (Vite wire protocol).
|
|
1824
|
+
try {
|
|
1825
|
+
getNsHotRegistry().setSendToServer((event, data) => {
|
|
1826
|
+
try {
|
|
1827
|
+
if (sock.readyState === WebSocket.OPEN) {
|
|
1828
|
+
sock.send(JSON.stringify({ type: 'custom', event, data }));
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
catch { }
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
catch { }
|
|
1318
1835
|
if (connectionOverlayVisible) {
|
|
1319
1836
|
showConnectionOverlayNow('synchronizing', 'Connected. Synchronizing the HMR graph.');
|
|
1320
1837
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
//
|
|
1324
|
-
//
|
|
1838
|
+
if (VERBOSE)
|
|
1839
|
+
console.log('[hmr-client] Connected to HMR WebSocket');
|
|
1840
|
+
// Print the active module reload mode once on first
|
|
1841
|
+
// successful connect so the user can correlate HMR latency
|
|
1842
|
+
// with runtime capability without grepping for protocol
|
|
1843
|
+
// details. The banner is verbose-gated.
|
|
1325
1844
|
try {
|
|
1326
1845
|
emitHmrModeBannerOnce();
|
|
1327
1846
|
}
|
|
@@ -1349,7 +1868,7 @@ function connectHmr() {
|
|
|
1349
1868
|
console.log('[hmr-client] WebSocket closed (code', ev?.code, '), will reconnect…');
|
|
1350
1869
|
scheduleConnectionOverlay('reconnecting', 'The websocket closed. Waiting to reconnect.', 700);
|
|
1351
1870
|
// try to reconnect with full candidate list again
|
|
1352
|
-
|
|
1871
|
+
scheduleReconnect(1000);
|
|
1353
1872
|
}
|
|
1354
1873
|
};
|
|
1355
1874
|
}
|
|
@@ -1385,21 +1904,26 @@ async function handleHmrMessage(ev) {
|
|
|
1385
1904
|
catch { }
|
|
1386
1905
|
}
|
|
1387
1906
|
if (msg) {
|
|
1388
|
-
//
|
|
1389
|
-
//
|
|
1390
|
-
//
|
|
1391
|
-
//
|
|
1392
|
-
//
|
|
1393
|
-
//
|
|
1394
|
-
//
|
|
1907
|
+
// `ns:hmr-pending` is a fire-and-forget UX hint emitted by the
|
|
1908
|
+
// server at the START of handleHotUpdate. We drive the
|
|
1909
|
+
// HMR-applying overlay's 'received' frame here (synchronously),
|
|
1910
|
+
// well before the authoritative payload (`ns:angular-update` /
|
|
1911
|
+
// `ns:css-updates`) lands. Skip running any other handlers —
|
|
1912
|
+
// the pending message has no module payload and intentionally
|
|
1913
|
+
// does not bump the graph version.
|
|
1395
1914
|
if (msg.type === 'ns:hmr-pending' && typeof msg.path === 'string') {
|
|
1396
1915
|
setHmrPendingOverlay(msg.path);
|
|
1397
1916
|
return;
|
|
1398
1917
|
}
|
|
1918
|
+
// The per-flavor client strategy is loaded by a dynamic import(); make
|
|
1919
|
+
// sure it has resolved (and `install()` has run) before any handler that
|
|
1920
|
+
// delegates through it. After the first message this is an already-settled
|
|
1921
|
+
// promise (microtask only); for Solid/TypeScript it is `Promise.resolve()`.
|
|
1922
|
+
await CLIENT_STRATEGY_READY;
|
|
1399
1923
|
if (msg.type === 'ns:hmr-full-graph') {
|
|
1400
1924
|
// Bump a monotonic nonce so HTTP ESM imports can always be cache-busted per update.
|
|
1401
1925
|
try {
|
|
1402
|
-
const g =
|
|
1926
|
+
const g = getGlobalScope();
|
|
1403
1927
|
g.__NS_HMR_IMPORT_NONCE__ = (typeof g.__NS_HMR_IMPORT_NONCE__ === 'number' ? g.__NS_HMR_IMPORT_NONCE__ : 0) + 1;
|
|
1404
1928
|
}
|
|
1405
1929
|
catch { }
|
|
@@ -1465,7 +1989,7 @@ async function handleHmrMessage(ev) {
|
|
|
1465
1989
|
});
|
|
1466
1990
|
if (toReimport.length && VERBOSE)
|
|
1467
1991
|
console.log('[hmr][full-graph] inferred changed modules; re-importing', toReimport);
|
|
1468
|
-
//
|
|
1992
|
+
// Evict the inferred changed set before re-importing.
|
|
1469
1993
|
// See `processQueue` for the architectural rationale; the
|
|
1470
1994
|
// full-graph code path is the resync fallback (server chose
|
|
1471
1995
|
// not to send a delta) and shares the same V8 cache pitfall.
|
|
@@ -1524,7 +2048,7 @@ async function handleHmrMessage(ev) {
|
|
|
1524
2048
|
if (msg.type === 'ns:hmr-delta') {
|
|
1525
2049
|
// Bump a monotonic nonce so HTTP ESM imports can always be cache-busted per update.
|
|
1526
2050
|
try {
|
|
1527
|
-
const g =
|
|
2051
|
+
const g = getGlobalScope();
|
|
1528
2052
|
g.__NS_HMR_IMPORT_NONCE__ = (typeof g.__NS_HMR_IMPORT_NONCE__ === 'number' ? g.__NS_HMR_IMPORT_NONCE__ : 0) + 1;
|
|
1529
2053
|
}
|
|
1530
2054
|
catch { }
|
|
@@ -1546,10 +2070,100 @@ async function handleHmrMessage(ev) {
|
|
|
1546
2070
|
return;
|
|
1547
2071
|
}
|
|
1548
2072
|
else {
|
|
2073
|
+
// Vite custom-event dispatch.
|
|
2074
|
+
//
|
|
2075
|
+
// `server.ws.send('event-name', payload)` from any Vite plugin lands
|
|
2076
|
+
// on the wire as `{ type: 'custom', event: 'event-name', data: payload }`.
|
|
2077
|
+
// On the web, Vite's stock client owns a `customListenersMap` that
|
|
2078
|
+
// fires every `import.meta.hot.on('event-name', cb)` callback. We
|
|
2079
|
+
// don't run Vite's stock client on device — the JS hot registry
|
|
2080
|
+
// (`hot-context.ts`, installed by the bootstrap before the entry
|
|
2081
|
+
// graph) owns the listener registry populated by user code +
|
|
2082
|
+
// compiled Angular components via `import.meta.hot.on`.
|
|
2083
|
+
// Forwarding `type: 'custom'` here is the only thing standing
|
|
2084
|
+
// between server-emitted events and the listeners they were
|
|
2085
|
+
// meant for.
|
|
2086
|
+
//
|
|
2087
|
+
// `angular:component-update` is the canonical example. Analog's
|
|
2088
|
+
// plugin sends it on `.html` / component-style edits; the
|
|
2089
|
+
// compiled component `.mjs` registered a listener that
|
|
2090
|
+
// dynamic-imports `/@ng/component?c=<id>&t=<ts>` and calls
|
|
2091
|
+
// `ɵɵreplaceMetadata` on the live class — swapping the template
|
|
2092
|
+
// definition AND walking live `LView`s to recreate matching views
|
|
2093
|
+
// in-place. The page stays mounted and only the changed bits
|
|
2094
|
+
// re-render. We MUST `return` after dispatch so the reboot path
|
|
2095
|
+
// (`handleAngularHotUpdateMessage` → `__reboot_ng_modules__`)
|
|
2096
|
+
// never runs for these updates — that's the whole point of the
|
|
2097
|
+
// component-replacement pipeline.
|
|
2098
|
+
//
|
|
2099
|
+
// All other custom events are forwarded but NOT short-circuited
|
|
2100
|
+
// (Vite spec: custom events are additive — they don't replace
|
|
2101
|
+
// any framework-specific handling). The reboot path falls through
|
|
2102
|
+
// for `ns:angular-update` (the legacy/`.ts`-edit broadcast) and
|
|
2103
|
+
// for any framework not yet using the in-place replacement path.
|
|
2104
|
+
if (msg.type === 'custom' && typeof msg.event === 'string') {
|
|
2105
|
+
// Dispatch every Vite "custom" event through the JS hot
|
|
2106
|
+
// registry so `import.meta.hot.on(event, cb)` callbacks fire
|
|
2107
|
+
// on the device. Critical contract: this is the ONLY route by
|
|
2108
|
+
// which Analog's `angular:component-update` reaches the
|
|
2109
|
+
// compiled component's `(d) => d.id === id && Component_HmrLoad(...)`
|
|
2110
|
+
// listener — without it, server-side broadcasts log green
|
|
2111
|
+
// (`(client) hmr update`) while the device sees nothing happen.
|
|
2112
|
+
//
|
|
2113
|
+
// Diagnostic policy: listener exceptions log loud (compiled
|
|
2114
|
+
// HmrLoad fetch/parse error). Successful dispatches are silent.
|
|
2115
|
+
try {
|
|
2116
|
+
getNsHotRegistry().dispatchHotEvent(msg.event, msg.data);
|
|
2117
|
+
}
|
|
2118
|
+
catch (err) {
|
|
2119
|
+
console.warn('[hmr-client][custom] dispatch threw for', msg.event, err);
|
|
2120
|
+
}
|
|
2121
|
+
if (msg.event === 'angular:component-update') {
|
|
2122
|
+
if (VERBOSE)
|
|
2123
|
+
console.log('[hmr-client][custom] dispatched angular:component-update — skipping reboot path');
|
|
2124
|
+
// Walk the apply-progress overlay through its
|
|
2125
|
+
// remaining stages for the in-place template-swap
|
|
2126
|
+
// path. The full reboot path
|
|
2127
|
+
// (`handleAngularHotUpdateMessage`) drives the
|
|
2128
|
+
// overlay itself ('received' → 'evicting' →
|
|
2129
|
+
// 'reimporting' → 'rebooting' → 'complete'); the
|
|
2130
|
+
// in-place path bypasses that handler entirely
|
|
2131
|
+
// because the work happens inside Angular's
|
|
2132
|
+
// `ɵɵreplaceMetadata` after the runtime forwards the
|
|
2133
|
+
// `angular:component-update` event to the compiled
|
|
2134
|
+
// component's listener. Without this update the
|
|
2135
|
+
// overlay would freeze at 5% ('received') even
|
|
2136
|
+
// though the visual swap completes a few frames
|
|
2137
|
+
// later — exactly the "Preparing update (5%)" stuck
|
|
2138
|
+
// frame we have been chasing.
|
|
2139
|
+
//
|
|
2140
|
+
// We transition straight to 'reimporting' to
|
|
2141
|
+
// communicate that metadata is being fetched (the
|
|
2142
|
+
// runtime listener fires `__ns_import('/@ng/component?c=...&t=...')`),
|
|
2143
|
+
// then schedule 'complete' on the next macrotask so
|
|
2144
|
+
// the auto-hide timer kicks in. The actual
|
|
2145
|
+
// template swap is fire-and-forget from this point;
|
|
2146
|
+
// the user sees the overlay close at the same time
|
|
2147
|
+
// as Angular re-renders the bound text/structure.
|
|
2148
|
+
try {
|
|
2149
|
+
const filePath = typeof msg.data?.id === 'string' ? decodeURIComponent(msg.data.id).split('@')[0] : undefined;
|
|
2150
|
+
const detail = filePath ? `Applying template update to ${filePath}` : 'Applying template update';
|
|
2151
|
+
setUpdateOverlayStage('reimporting', { detail });
|
|
2152
|
+
setTimeout(() => {
|
|
2153
|
+
try {
|
|
2154
|
+
setUpdateOverlayStage('complete', { detail: filePath ? `Updated ${filePath}` : 'Update applied' });
|
|
2155
|
+
}
|
|
2156
|
+
catch { }
|
|
2157
|
+
}, 16);
|
|
2158
|
+
}
|
|
2159
|
+
catch { }
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
1549
2163
|
if (msg.type === 'ns:angular-update' && typeof msg.version === 'number') {
|
|
1550
2164
|
setGraphVersion(Number(msg.version || getGraphVersion() || 0));
|
|
1551
2165
|
}
|
|
1552
|
-
if (await
|
|
2166
|
+
if (CLIENT_STRATEGY?.handleHotUpdateMessage && (await CLIENT_STRATEGY.handleHotUpdateMessage(msg, { getCore, verbose: VERBOSE, performResetRoot, getOverlay: getHmrOverlayApi }))) {
|
|
1553
2167
|
return;
|
|
1554
2168
|
}
|
|
1555
2169
|
}
|
|
@@ -1583,27 +2197,47 @@ async function handleHmrMessage(ev) {
|
|
|
1583
2197
|
return;
|
|
1584
2198
|
}
|
|
1585
2199
|
if (msg.type === 'ns:css-updates' && Array.isArray(msg.updates)) {
|
|
2200
|
+
// Drive the HMR-applying overlay past the 'received' (5%) frame
|
|
2201
|
+
// that `ns:hmr-pending` set earlier in the cycle. Without this
|
|
2202
|
+
// the overlay sticks at "Preparing update" forever for CSS-only
|
|
2203
|
+
// edits because `handleCssUpdates` is a leaf — there's no
|
|
2204
|
+
// downstream module-evaluation path that would hit the queue's
|
|
2205
|
+
// 'complete' transition.
|
|
2206
|
+
const cssCount = msg.updates.length;
|
|
2207
|
+
try {
|
|
2208
|
+
setUpdateOverlayStage('reimporting', { detail: buildCssApplyingDetail(cssCount) });
|
|
2209
|
+
}
|
|
2210
|
+
catch { }
|
|
1586
2211
|
try {
|
|
1587
2212
|
const origin = msg.origin || getHttpOriginForVite() || deriveHttpOrigin(getHMRWsUrl());
|
|
1588
2213
|
await handleCssUpdates(msg.updates, origin);
|
|
2214
|
+
try {
|
|
2215
|
+
setUpdateOverlayStage('complete', { detail: buildCssAppliedDetail(cssCount) });
|
|
2216
|
+
}
|
|
2217
|
+
catch { }
|
|
1589
2218
|
return;
|
|
1590
2219
|
}
|
|
1591
2220
|
catch (e) {
|
|
1592
2221
|
console.warn('[hmr-client] CSS updates handling failed:', e);
|
|
2222
|
+
try {
|
|
2223
|
+
setUpdateOverlayStage('complete', { detail: 'CSS update failed' });
|
|
2224
|
+
}
|
|
2225
|
+
catch { }
|
|
1593
2226
|
return;
|
|
1594
2227
|
}
|
|
1595
2228
|
}
|
|
1596
2229
|
if (msg.type === 'ns:vue-sfc-registry') {
|
|
1597
|
-
|
|
2230
|
+
CLIENT_STRATEGY?.handleSfcRegistry?.(msg);
|
|
1598
2231
|
return;
|
|
1599
2232
|
}
|
|
1600
2233
|
if (msg.type === 'ns:vue-sfc-registry-update') {
|
|
1601
2234
|
if (typeof msg.version === 'number')
|
|
1602
2235
|
setGraphVersion(msg.version);
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
2236
|
+
// `ns:hmr-pending` already set the overlay to 'received' (5%). The Vue
|
|
2237
|
+
// strategy walks 'evicting' → 'reimporting' → 'rebooting' → 'complete'
|
|
2238
|
+
// around the SFC load + reset so the toast always lands on 'complete'
|
|
2239
|
+
// (or a failure detail) and the auto-hide timer can dismiss it.
|
|
2240
|
+
await CLIENT_STRATEGY?.handleSfcRegistryUpdate?.(msg, getGraphVersion(), { getCore, verbose: VERBOSE, performResetRoot, getOverlay: getHmrOverlayApi });
|
|
1607
2241
|
return;
|
|
1608
2242
|
}
|
|
1609
2243
|
}
|
|
@@ -1623,9 +2257,9 @@ function normalizeComponent(input, nameHint) {
|
|
|
1623
2257
|
}
|
|
1624
2258
|
// If provided a render function, wrap with defineComponent
|
|
1625
2259
|
if (typeof input === 'function') {
|
|
1626
|
-
|
|
1627
|
-
const comp =
|
|
1628
|
-
?
|
|
2260
|
+
CLIENT_STRATEGY?.beforeNavigateBuild?.();
|
|
2261
|
+
const comp = getGlobalScope().defineComponent
|
|
2262
|
+
? getGlobalScope().defineComponent({
|
|
1629
2263
|
name: nameHint || input.name || 'AnonymousSFC',
|
|
1630
2264
|
render: input,
|
|
1631
2265
|
})
|
|
@@ -1634,9 +2268,9 @@ function normalizeComponent(input, nameHint) {
|
|
|
1634
2268
|
}
|
|
1635
2269
|
// If object has a render function property
|
|
1636
2270
|
if (input?.render && typeof input.render === 'function') {
|
|
1637
|
-
|
|
1638
|
-
const comp =
|
|
1639
|
-
?
|
|
2271
|
+
CLIENT_STRATEGY?.beforeNavigateBuild?.();
|
|
2272
|
+
const comp = getGlobalScope().defineComponent
|
|
2273
|
+
? getGlobalScope().defineComponent({
|
|
1640
2274
|
name: nameHint || input.name || 'AnonymousSFC',
|
|
1641
2275
|
render: input.render,
|
|
1642
2276
|
})
|
|
@@ -1692,35 +2326,32 @@ async function performResetRoot(newComponent) {
|
|
|
1692
2326
|
if (cachedRoot)
|
|
1693
2327
|
return cachedRoot;
|
|
1694
2328
|
try {
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
root = newComponent();
|
|
1705
|
-
}
|
|
1706
|
-
else {
|
|
1707
|
-
root = newComponent;
|
|
1708
|
-
}
|
|
2329
|
+
if (CLIENT_STRATEGY?.createRoot) {
|
|
2330
|
+
cachedRoot = CLIENT_STRATEGY.createRoot(newComponent, state);
|
|
2331
|
+
}
|
|
2332
|
+
else if (TS_LIKE_FLAVOR) {
|
|
2333
|
+
// For TS flavor, treat the component as a factory or direct NS view.
|
|
2334
|
+
let root = null;
|
|
2335
|
+
try {
|
|
2336
|
+
if (typeof newComponent === 'function') {
|
|
2337
|
+
root = newComponent();
|
|
1709
2338
|
}
|
|
1710
|
-
|
|
1711
|
-
|
|
2339
|
+
else {
|
|
2340
|
+
root = newComponent;
|
|
1712
2341
|
}
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
2342
|
+
}
|
|
2343
|
+
catch (e) {
|
|
2344
|
+
console.warn('[hmr-client][ts] root factory invocation failed', e);
|
|
2345
|
+
}
|
|
2346
|
+
cachedRoot = root || {};
|
|
2347
|
+
// Heuristic: if the root "looks" like a Frame, prefer frame semantics
|
|
2348
|
+
try {
|
|
2349
|
+
const name = String(cachedRoot?.constructor?.name || '').replace(/^_+/, '');
|
|
2350
|
+
if (/^Frame(\$\d+)?$/.test(name)) {
|
|
2351
|
+
rootKind = 'frame';
|
|
1720
2352
|
}
|
|
1721
|
-
catch { }
|
|
1722
|
-
break;
|
|
1723
2353
|
}
|
|
2354
|
+
catch { }
|
|
1724
2355
|
}
|
|
1725
2356
|
return cachedRoot;
|
|
1726
2357
|
}
|
|
@@ -1738,7 +2369,7 @@ async function performResetRoot(newComponent) {
|
|
|
1738
2369
|
return factory;
|
|
1739
2370
|
}
|
|
1740
2371
|
// Android readiness before any root changes
|
|
1741
|
-
const App = getCore('Application') ||
|
|
2372
|
+
const App = getCore('Application') || getGlobalScope().Application;
|
|
1742
2373
|
const isAndroid = !!(App && App.android !== undefined);
|
|
1743
2374
|
if (isAndroid) {
|
|
1744
2375
|
const isReady = () => {
|
|
@@ -1840,7 +2471,7 @@ async function performResetRoot(newComponent) {
|
|
|
1840
2471
|
}
|
|
1841
2472
|
catch { }
|
|
1842
2473
|
try {
|
|
1843
|
-
const AppAny = getCore('Application') ||
|
|
2474
|
+
const AppAny = getCore('Application') || getGlobalScope().Application;
|
|
1844
2475
|
isIOS = !!(AppAny && AppAny.ios !== undefined);
|
|
1845
2476
|
}
|
|
1846
2477
|
catch { }
|
|
@@ -1850,7 +2481,7 @@ async function performResetRoot(newComponent) {
|
|
|
1850
2481
|
// - Otherwise (subsequent HMR updates with an authoritative Frame already in place), re-use the
|
|
1851
2482
|
// current app Frame and navigate to the new Page. This avoids a brief flash that can occur
|
|
1852
2483
|
// when swapping the entire root view on Android. The placeholder is never involved here.
|
|
1853
|
-
const gAnyForPolicy =
|
|
2484
|
+
const gAnyForPolicy = getGlobalScope();
|
|
1854
2485
|
const placeholderFrame = (() => {
|
|
1855
2486
|
try {
|
|
1856
2487
|
return gAnyForPolicy.__NS_DEV_PLACEHOLDER_ROOT_VIEW__ || null;
|
|
@@ -1865,7 +2496,14 @@ async function performResetRoot(newComponent) {
|
|
|
1865
2496
|
}
|
|
1866
2497
|
catch { }
|
|
1867
2498
|
const isAuthoritativeFrame = !!existingAppFrame && existingAppFrame !== placeholderFrame;
|
|
1868
|
-
|
|
2499
|
+
// Vue: skip the in-place navigate path. After `app.mount(NSVRoot)` in getRootForVue the
|
|
2500
|
+
// new Page already has a parent (the freshly-constructed NSVRoot), so an attempt to navigate
|
|
2501
|
+
// the existing app Frame to that same Page completes silently without ever rebinding the
|
|
2502
|
+
// page to the Frame — the screen keeps showing the previous render. resetRootView with a
|
|
2503
|
+
// fresh Frame correctly reparents the Page and is the proven path that produces visible
|
|
2504
|
+
// in-place updates for SFC HMR cycles. Non-Vue flavors keep the legacy navigate fast path.
|
|
2505
|
+
const allowNavigateFastPath = CLIENT_STRATEGY?.allowNavigateFastPath ?? true;
|
|
2506
|
+
if (allowNavigateFastPath && !hadPlaceholder && !isFrameRoot && isAuthoritativeFrame && typeof existingAppFrame.navigate === 'function') {
|
|
1869
2507
|
try {
|
|
1870
2508
|
const navEntry = {
|
|
1871
2509
|
create: () => preparedRoot,
|
|
@@ -1884,7 +2522,7 @@ async function performResetRoot(newComponent) {
|
|
|
1884
2522
|
console.log('[hmr-client] full root replacement via resetRootView (placeholder will be discarded)', { isFrameRoot, isIOS, hadPlaceholder });
|
|
1885
2523
|
// Fallback or preferred path: resetRootView with a creator that builds a fresh Frame and navigates to the new Page
|
|
1886
2524
|
try {
|
|
1887
|
-
const App2 = getCore('Application') ||
|
|
2525
|
+
const App2 = getCore('Application') || getGlobalScope().Application;
|
|
1888
2526
|
if (!App2 || typeof App2.resetRootView !== 'function') {
|
|
1889
2527
|
console.warn('[hmr-client] Application.resetRootView unavailable');
|
|
1890
2528
|
return false;
|
|
@@ -1903,7 +2541,7 @@ async function performResetRoot(newComponent) {
|
|
|
1903
2541
|
if (VERBOSE)
|
|
1904
2542
|
console.warn('[hmr-client] iOS Application.window is boolean false; attempting to clear cached window');
|
|
1905
2543
|
try {
|
|
1906
|
-
const g =
|
|
2544
|
+
const g = getGlobalScope();
|
|
1907
2545
|
const reg = g.__nsVendorRegistry;
|
|
1908
2546
|
const req = reg?.get ? g.__nsVendorRequire || g.__nsRequire || g.require : g.__nsRequire || g.require;
|
|
1909
2547
|
let helpers = null;
|
|
@@ -2007,6 +2645,20 @@ export function initHmrClient(opts) {
|
|
|
2007
2645
|
}
|
|
2008
2646
|
g.__NS_HMR_CLIENT_ACTIVE__ = true;
|
|
2009
2647
|
ensureCoreAliasesOnGlobalThis();
|
|
2648
|
+
// XML flavor: record string-module modals from the moment the client is up
|
|
2649
|
+
// so an already-open modal can be re-presented when its files change.
|
|
2650
|
+
// Installed at init (not first-update time) because the wrap can only
|
|
2651
|
+
// observe showModal calls made AFTER it lands. Retried briefly because
|
|
2652
|
+
// getCore('View') may not resolve until the vendor realm finishes booting.
|
|
2653
|
+
if (TS_LIKE_FLAVOR) {
|
|
2654
|
+
const tryInstallModalTracking = (attempts) => {
|
|
2655
|
+
ensureModalTracking();
|
|
2656
|
+
if (!modalTrackingInstalled && attempts > 0) {
|
|
2657
|
+
setTimeout(() => tryInstallModalTracking(attempts - 1), 250);
|
|
2658
|
+
}
|
|
2659
|
+
};
|
|
2660
|
+
tryInstallModalTracking(40);
|
|
2661
|
+
}
|
|
2010
2662
|
// Defer WebSocket connection until boot completes to avoid native V8 crashes
|
|
2011
2663
|
// caused by concurrent WebSocket message handling + HTTP fetch during early startup.
|
|
2012
2664
|
// The WebSocket is only needed for HMR updates, not the initial boot sequence.
|
|
@@ -2028,12 +2680,9 @@ export function initHmrClient(opts) {
|
|
|
2028
2680
|
console.log('[hmr-client] deferring WebSocket connection until boot completes');
|
|
2029
2681
|
setTimeout(waitForBoot, 100);
|
|
2030
2682
|
}
|
|
2031
|
-
// Best-effort: install back wrapper even before first remount; original root may be captured later
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
ensureBackWrapperInstalled(performResetRoot, getCore);
|
|
2035
|
-
break;
|
|
2036
|
-
}
|
|
2683
|
+
// Best-effort: install back wrapper even before first remount; original root may be captured later.
|
|
2684
|
+
// Deferred until the dynamically-imported strategy resolves.
|
|
2685
|
+
void CLIENT_STRATEGY_READY.then(() => CLIENT_STRATEGY?.installBackWrapper?.(performResetRoot, getCore));
|
|
2037
2686
|
}
|
|
2038
2687
|
export default function startViteHMR(opts) {
|
|
2039
2688
|
if (VERBOSE)
|