@nativescript/vite 8.0.0-alpha.4 → 8.0.0-alpha.40
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 +256 -30
- package/configuration/base.js.map +1 -1
- package/configuration/javascript.js +12 -90
- package/configuration/javascript.js.map +1 -1
- package/configuration/solid.js +33 -4
- package/configuration/solid.js.map +1 -1
- package/configuration/typescript.js +10 -90
- 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/config-as-json.js +10 -0
- package/helpers/config-as-json.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 +692 -0
- package/helpers/dev-host.js.map +1 -0
- package/helpers/dynamic-import-plugin.js +1 -1
- package/helpers/dynamic-import-plugin.js.map +1 -1
- package/helpers/esbuild-platform-resolver.js +4 -1
- package/helpers/esbuild-platform-resolver.js.map +1 -1
- package/helpers/external-configs.d.ts +10 -12
- package/helpers/external-configs.js +54 -35
- package/helpers/external-configs.js.map +1 -1
- package/helpers/global-defines.d.ts +128 -0
- package/helpers/global-defines.js +174 -11
- package/helpers/global-defines.js.map +1 -1
- package/helpers/hmr-scope.d.ts +26 -0
- package/helpers/hmr-scope.js +67 -0
- package/helpers/hmr-scope.js.map +1 -0
- package/helpers/init.js +0 -18
- package/helpers/init.js.map +1 -1
- package/helpers/logging.d.ts +1 -0
- package/helpers/logging.js +65 -4
- package/helpers/logging.js.map +1 -1
- package/helpers/main-entry.d.ts +3 -1
- package/helpers/main-entry.js +444 -50
- 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 +106 -0
- package/helpers/ns-core-url.js +225 -0
- package/helpers/ns-core-url.js.map +1 -0
- package/helpers/optimize-deps.d.ts +16 -0
- package/helpers/optimize-deps.js +17 -0
- package/helpers/optimize-deps.js.map +1 -0
- package/helpers/package-platform-aliases.js +34 -49
- package/helpers/package-platform-aliases.js.map +1 -1
- package/helpers/platform-types.d.ts +2 -0
- package/helpers/platform-types.js +2 -0
- package/helpers/platform-types.js.map +1 -0
- package/helpers/postcss-platform-config.d.ts +17 -1
- package/helpers/postcss-platform-config.js +20 -37
- package/helpers/postcss-platform-config.js.map +1 -1
- package/helpers/project.d.ts +35 -0
- package/helpers/project.js +120 -2
- package/helpers/project.js.map +1 -1
- package/helpers/resolve-main-field-platform.d.ts +20 -0
- package/helpers/resolve-main-field-platform.js +49 -0
- package/helpers/resolve-main-field-platform.js.map +1 -0
- package/helpers/resolver.js +17 -2
- package/helpers/resolver.js.map +1 -1
- package/helpers/theme-core-plugins.js +1 -1
- package/helpers/theme-core-plugins.js.map +1 -1
- package/helpers/ts-config-paths.d.ts +14 -0
- package/helpers/ts-config-paths.js +90 -9
- package/helpers/ts-config-paths.js.map +1 -1
- package/helpers/typescript-check.d.ts +2 -1
- package/helpers/typescript-check.js.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.js +1 -2
- package/helpers/utils.js.map +1 -1
- package/helpers/workers.d.ts +20 -19
- package/helpers/workers.js +624 -4
- package/helpers/workers.js.map +1 -1
- package/hmr/client/css-handler.d.ts +2 -1
- package/hmr/client/css-handler.js +50 -26
- package/hmr/client/css-handler.js.map +1 -1
- package/hmr/client/css-update-overlay.d.ts +18 -0
- package/hmr/client/css-update-overlay.js +27 -0
- package/hmr/client/css-update-overlay.js.map +1 -0
- package/hmr/client/framework-client-strategy.d.ts +79 -0
- package/hmr/client/framework-client-strategy.js +19 -0
- package/hmr/client/framework-client-strategy.js.map +1 -0
- package/hmr/client/hmr-pending-overlay.d.ts +13 -0
- package/hmr/client/hmr-pending-overlay.js +60 -0
- package/hmr/client/hmr-pending-overlay.js.map +1 -0
- package/hmr/client/index.js +787 -211
- package/hmr/client/index.js.map +1 -1
- package/hmr/client/utils.d.ts +7 -1
- package/hmr/client/utils.js +207 -29
- package/hmr/client/utils.js.map +1 -1
- package/hmr/entry-runtime.d.ts +2 -1
- package/hmr/entry-runtime.js +256 -69
- package/hmr/entry-runtime.js.map +1 -1
- package/hmr/frameworks/angular/build/angular-linker.d.ts +12 -0
- package/hmr/frameworks/angular/build/angular-linker.js +109 -0
- package/hmr/frameworks/angular/build/angular-linker.js.map +1 -0
- package/hmr/frameworks/angular/build/inject-component-hmr-registration.d.ts +112 -0
- package/hmr/frameworks/angular/build/inject-component-hmr-registration.js +291 -0
- package/hmr/frameworks/angular/build/inject-component-hmr-registration.js.map +1 -0
- package/hmr/frameworks/angular/build/inject-hmr-vite-ignore.d.ts +75 -0
- package/hmr/frameworks/angular/build/inject-hmr-vite-ignore.js +221 -0
- package/hmr/frameworks/angular/build/inject-hmr-vite-ignore.js.map +1 -0
- package/{helpers/angular → hmr/frameworks/angular/build}/inline-decorator-component-templates.js +1 -170
- package/hmr/frameworks/angular/build/inline-decorator-component-templates.js.map +1 -0
- package/hmr/frameworks/angular/build/js-lexer.d.ts +4 -0
- package/{helpers/angular/synthesize-decorator-ctor-parameters.js → hmr/frameworks/angular/build/js-lexer.js} +22 -96
- package/hmr/frameworks/angular/build/js-lexer.js.map +1 -0
- package/hmr/frameworks/angular/build/shared-linker.d.ts +39 -0
- package/hmr/frameworks/angular/build/shared-linker.js +128 -0
- package/hmr/frameworks/angular/build/shared-linker.js.map +1 -0
- package/hmr/frameworks/angular/build/synthesize-decorator-ctor-parameters.js +88 -0
- package/hmr/frameworks/angular/build/synthesize-decorator-ctor-parameters.js.map +1 -0
- package/{helpers/angular → hmr/frameworks/angular/build}/synthesize-injectable-factories.js +1 -174
- package/hmr/frameworks/angular/build/synthesize-injectable-factories.js.map +1 -0
- package/{helpers/angular → hmr/frameworks/angular/build}/util.d.ts +1 -0
- package/hmr/frameworks/angular/build/util.js +155 -0
- package/hmr/frameworks/angular/build/util.js.map +1 -0
- package/hmr/frameworks/angular/client/index.d.ts +1 -0
- package/hmr/frameworks/angular/client/index.js +803 -21
- package/hmr/frameworks/angular/client/index.js.map +1 -1
- package/hmr/frameworks/angular/client/strategy.d.ts +9 -0
- package/hmr/frameworks/angular/client/strategy.js +19 -0
- package/hmr/frameworks/angular/client/strategy.js.map +1 -0
- package/hmr/frameworks/angular/server/angular-root-component.d.ts +79 -0
- package/hmr/frameworks/angular/server/angular-root-component.js +149 -0
- package/hmr/frameworks/angular/server/angular-root-component.js.map +1 -0
- package/hmr/frameworks/angular/server/linker.js +1 -4
- package/hmr/frameworks/angular/server/linker.js.map +1 -1
- package/hmr/frameworks/angular/server/strategy.js +460 -12
- package/hmr/frameworks/angular/server/strategy.js.map +1 -1
- package/hmr/{server → frameworks/angular/server}/websocket-angular-entry.js +2 -2
- package/hmr/frameworks/angular/server/websocket-angular-entry.js.map +1 -0
- package/hmr/{server → frameworks/angular/server}/websocket-angular-hot-update.d.ts +17 -11
- package/hmr/frameworks/angular/server/websocket-angular-hot-update.js +336 -0
- package/hmr/frameworks/angular/server/websocket-angular-hot-update.js.map +1 -0
- package/hmr/frameworks/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/server/strategy.js +291 -16
- package/hmr/frameworks/solid/server/strategy.js.map +1 -1
- package/hmr/frameworks/typescript/server/strategy.js +38 -14
- package/hmr/frameworks/typescript/server/strategy.js.map +1 -1
- package/hmr/frameworks/vue/client/dep-propagation.d.ts +36 -0
- package/hmr/frameworks/vue/client/dep-propagation.js +101 -0
- package/hmr/frameworks/vue/client/dep-propagation.js.map +1 -0
- package/hmr/frameworks/vue/client/index.d.ts +8 -0
- package/hmr/frameworks/vue/client/index.js +56 -243
- package/hmr/frameworks/vue/client/index.js.map +1 -1
- package/hmr/frameworks/vue/client/strategy.d.ts +33 -0
- package/hmr/frameworks/vue/client/strategy.js +157 -0
- package/hmr/frameworks/vue/client/strategy.js.map +1 -0
- package/hmr/frameworks/vue/client/vue-sfc-update-overlay.d.ts +49 -0
- package/hmr/frameworks/vue/client/vue-sfc-update-overlay.js +142 -0
- package/hmr/frameworks/vue/client/vue-sfc-update-overlay.js.map +1 -0
- package/hmr/frameworks/vue/server/sfc-route-assemble.d.ts +7 -0
- package/hmr/frameworks/vue/server/sfc-route-assemble.js +747 -0
- package/hmr/frameworks/vue/server/sfc-route-assemble.js.map +1 -0
- package/hmr/frameworks/vue/server/sfc-route-meta.d.ts +7 -0
- package/hmr/frameworks/vue/server/sfc-route-meta.js +80 -0
- package/hmr/frameworks/vue/server/sfc-route-meta.js.map +1 -0
- package/hmr/frameworks/vue/server/sfc-route-serve.d.ts +8 -0
- package/hmr/frameworks/vue/server/sfc-route-serve.js +459 -0
- package/hmr/frameworks/vue/server/sfc-route-serve.js.map +1 -0
- package/hmr/frameworks/vue/server/sfc-route-shared.d.ts +38 -0
- package/hmr/frameworks/vue/server/sfc-route-shared.js +48 -0
- package/hmr/frameworks/vue/server/sfc-route-shared.js.map +1 -0
- package/hmr/frameworks/vue/server/strategy.d.ts +35 -0
- package/hmr/frameworks/vue/server/strategy.js +278 -1
- package/hmr/frameworks/vue/server/strategy.js.map +1 -1
- package/hmr/frameworks/vue/server/websocket-sfc.d.ts +15 -0
- package/hmr/frameworks/vue/server/websocket-sfc.js +20 -0
- package/hmr/frameworks/vue/server/websocket-sfc.js.map +1 -0
- package/hmr/helpers/ast-normalizer.d.ts +3 -1
- package/hmr/helpers/ast-normalizer.js +77 -10
- package/hmr/helpers/ast-normalizer.js.map +1 -1
- package/hmr/helpers/cjs-named-exports.d.ts +23 -0
- package/hmr/helpers/cjs-named-exports.js +152 -0
- package/hmr/helpers/cjs-named-exports.js.map +1 -0
- package/hmr/helpers/package-exports.d.ts +16 -0
- package/hmr/helpers/package-exports.js +396 -0
- package/hmr/helpers/package-exports.js.map +1 -0
- package/hmr/server/constants.js +20 -5
- package/hmr/server/constants.js.map +1 -1
- package/hmr/server/core-sanitize.d.ts +90 -7
- package/hmr/server/core-sanitize.js +211 -56
- package/hmr/server/core-sanitize.js.map +1 -1
- package/hmr/server/device-transform-helpers.d.ts +24 -0
- package/hmr/server/device-transform-helpers.js +369 -0
- package/hmr/server/device-transform-helpers.js.map +1 -0
- package/hmr/server/framework-strategy.d.ts +108 -11
- package/hmr/server/hmr-module-graph.d.ts +37 -0
- package/hmr/server/hmr-module-graph.js +214 -0
- package/hmr/server/hmr-module-graph.js.map +1 -0
- package/hmr/server/import-map.d.ts +11 -2
- package/hmr/server/import-map.js +95 -44
- 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 +204 -0
- package/hmr/server/ns-core-cjs-shape.js +271 -0
- package/hmr/server/ns-core-cjs-shape.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 +38 -0
- package/hmr/server/ns-rt-route.js.map +1 -0
- package/hmr/server/perf-instrumentation.d.ts +114 -0
- package/hmr/server/perf-instrumentation.js +197 -0
- package/hmr/server/perf-instrumentation.js.map +1 -0
- package/hmr/server/process-code-for-device.d.ts +14 -0
- package/hmr/server/process-code-for-device.js +702 -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 +600 -0
- package/hmr/server/rewrite-imports.js.map +1 -0
- package/hmr/server/route-helpers.d.ts +7 -0
- package/hmr/server/route-helpers.js +13 -0
- package/hmr/server/route-helpers.js.map +1 -0
- package/hmr/server/server-origin.d.ts +2 -0
- package/hmr/server/server-origin.js +83 -0
- package/hmr/server/server-origin.js.map +1 -0
- package/hmr/server/shared-transform-request.js +13 -7
- package/hmr/server/shared-transform-request.js.map +1 -1
- package/hmr/server/transform-cache-invalidation.d.ts +37 -0
- package/hmr/server/transform-cache-invalidation.js +156 -0
- package/hmr/server/transform-cache-invalidation.js.map +1 -0
- package/hmr/server/vendor-bare-module-shims.d.ts +4 -0
- package/hmr/server/vendor-bare-module-shims.js +80 -0
- package/hmr/server/vendor-bare-module-shims.js.map +1 -0
- package/hmr/server/vite-plugin.js +72 -42
- package/hmr/server/vite-plugin.js.map +1 -1
- package/hmr/server/websocket-core-bridge.d.ts +45 -6
- package/hmr/server/websocket-core-bridge.js +81 -77
- package/hmr/server/websocket-core-bridge.js.map +1 -1
- package/hmr/server/websocket-css-hot-update.d.ts +33 -0
- package/hmr/server/websocket-css-hot-update.js +65 -0
- package/hmr/server/websocket-css-hot-update.js.map +1 -0
- package/hmr/server/websocket-device-transform.d.ts +3 -0
- package/hmr/server/websocket-device-transform.js +7 -0
- package/hmr/server/websocket-device-transform.js.map +1 -0
- package/hmr/server/websocket-graph-upsert.d.ts +15 -0
- package/hmr/server/websocket-graph-upsert.js +20 -0
- package/hmr/server/websocket-graph-upsert.js.map +1 -1
- package/hmr/server/websocket-hmr-pending.d.ts +37 -0
- package/hmr/server/websocket-hmr-pending.js +55 -0
- package/hmr/server/websocket-hmr-pending.js.map +1 -0
- package/hmr/server/websocket-hot-update.d.ts +77 -0
- package/hmr/server/websocket-hot-update.js +360 -0
- package/hmr/server/websocket-hot-update.js.map +1 -0
- package/hmr/server/websocket-import-map-route.d.ts +15 -0
- package/hmr/server/websocket-import-map-route.js +50 -0
- package/hmr/server/websocket-import-map-route.js.map +1 -0
- package/hmr/server/websocket-module-bindings.js +3 -3
- package/hmr/server/websocket-module-bindings.js.map +1 -1
- package/hmr/server/websocket-module-specifiers.d.ts +66 -2
- package/hmr/server/websocket-module-specifiers.js +203 -20
- package/hmr/server/websocket-module-specifiers.js.map +1 -1
- package/hmr/server/websocket-ns-core.d.ts +21 -0
- package/hmr/server/websocket-ns-core.js +311 -0
- package/hmr/server/websocket-ns-core.js.map +1 -0
- package/hmr/server/websocket-ns-entry.d.ts +21 -0
- package/hmr/server/websocket-ns-entry.js +164 -0
- package/hmr/server/websocket-ns-entry.js.map +1 -0
- package/hmr/server/websocket-ns-m-paths.d.ts +1 -1
- package/hmr/server/websocket-ns-m-paths.js +58 -13
- package/hmr/server/websocket-ns-m-paths.js.map +1 -1
- package/hmr/server/websocket-ns-m-request.d.ts +11 -1
- package/hmr/server/websocket-ns-m-request.js +18 -25
- package/hmr/server/websocket-ns-m-request.js.map +1 -1
- package/hmr/server/websocket-ns-m.d.ts +33 -0
- package/hmr/server/websocket-ns-m.js +752 -0
- package/hmr/server/websocket-ns-m.js.map +1 -0
- package/hmr/server/websocket-served-module-helpers.d.ts +82 -0
- package/hmr/server/websocket-served-module-helpers.js +879 -0
- package/hmr/server/websocket-served-module-helpers.js.map +1 -0
- package/hmr/server/websocket-txn.js +2 -8
- package/hmr/server/websocket-txn.js.map +1 -1
- package/hmr/server/websocket-vendor-unifier.d.ts +0 -1
- package/hmr/server/websocket-vendor-unifier.js +4 -9
- package/hmr/server/websocket-vendor-unifier.js.map +1 -1
- package/hmr/server/websocket.d.ts +8 -39
- package/hmr/server/websocket.js +514 -4030
- package/hmr/server/websocket.js.map +1 -1
- package/hmr/shared/ns-globals.d.ts +118 -0
- package/hmr/shared/ns-globals.js +29 -0
- package/hmr/shared/ns-globals.js.map +1 -0
- package/hmr/shared/protocol.d.ts +145 -0
- package/hmr/shared/protocol.js +28 -0
- package/hmr/shared/protocol.js.map +1 -0
- package/hmr/shared/runtime/boot-placeholder-ui.d.ts +69 -0
- package/hmr/shared/runtime/boot-placeholder-ui.js +101 -0
- package/hmr/shared/runtime/boot-placeholder-ui.js.map +1 -0
- package/hmr/shared/runtime/boot-progress.d.ts +44 -0
- package/hmr/shared/runtime/boot-progress.js +133 -0
- package/hmr/shared/runtime/boot-progress.js.map +1 -0
- package/hmr/shared/runtime/boot-timeline.d.ts +18 -0
- package/hmr/shared/runtime/boot-timeline.js +42 -0
- package/hmr/shared/runtime/boot-timeline.js.map +1 -0
- package/hmr/shared/runtime/browser-runtime-contract.js.map +1 -1
- package/hmr/shared/runtime/dev-overlay-snapshots.d.ts +31 -0
- package/hmr/shared/runtime/dev-overlay-snapshots.js +324 -0
- package/hmr/shared/runtime/dev-overlay-snapshots.js.map +1 -0
- package/hmr/shared/runtime/dev-overlay.d.ts +75 -26
- package/hmr/shared/runtime/dev-overlay.js +992 -261
- 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 +371 -197
- package/hmr/shared/runtime/root-placeholder.js.map +1 -1
- package/hmr/shared/runtime/session-bootstrap.js +168 -4
- package/hmr/shared/runtime/session-bootstrap.js.map +1 -1
- package/hmr/shared/runtime/vendor-bootstrap.js +3 -10
- package/hmr/shared/runtime/vendor-bootstrap.js.map +1 -1
- package/hmr/shared/vendor/manifest-collect.d.ts +4 -0
- package/hmr/shared/vendor/manifest-collect.js +512 -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 +102 -741
- package/hmr/shared/vendor/manifest.js.map +1 -1
- package/hmr/shared/vendor/vendor-device-shim.d.ts +1 -0
- package/hmr/shared/vendor/vendor-device-shim.js +208 -0
- package/hmr/shared/vendor/vendor-device-shim.js.map +1 -0
- package/hmr/shared/vendor/vendor-esbuild-plugins.d.ts +16 -0
- package/hmr/shared/vendor/vendor-esbuild-plugins.js +203 -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 +55 -11
- package/runtime/core-aliases-early.js +25 -55
- package/runtime/core-aliases-early.js.map +1 -1
- package/helpers/angular/angular-linker.d.ts +0 -13
- package/helpers/angular/angular-linker.js +0 -194
- package/helpers/angular/angular-linker.js.map +0 -1
- package/helpers/angular/inline-decorator-component-templates.js.map +0 -1
- package/helpers/angular/shared-linker.d.ts +0 -11
- package/helpers/angular/shared-linker.js +0 -75
- package/helpers/angular/shared-linker.js.map +0 -1
- package/helpers/angular/synthesize-decorator-ctor-parameters.js.map +0 -1
- package/helpers/angular/synthesize-injectable-factories.js.map +0 -1
- package/helpers/angular/util.js +0 -67
- package/helpers/angular/util.js.map +0 -1
- package/helpers/prelink-angular.d.ts +0 -2
- package/helpers/prelink-angular.js +0 -117
- package/helpers/prelink-angular.js.map +0 -1
- package/hmr/server/websocket-angular-entry.js.map +0 -1
- package/hmr/server/websocket-angular-hot-update.js +0 -239
- package/hmr/server/websocket-angular-hot-update.js.map +0 -1
- package/hmr/server/websocket-ns-m-finalize.d.ts +0 -32
- package/hmr/server/websocket-ns-m-finalize.js +0 -73
- 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 -35
- package/hmr/server/websocket-vue-sfc.js +0 -1116
- 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/server/websocket.js
CHANGED
|
@@ -1,87 +1,116 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
-
import {
|
|
3
|
-
// AST tooling for robust transformations
|
|
4
|
-
import { parse as babelParse } from '@babel/parser';
|
|
5
|
-
import { genCode } from '../helpers/babel.js';
|
|
6
|
-
import traverse from '@babel/traverse';
|
|
7
|
-
// Ensure traverse callable across CJS/ESM builds
|
|
8
|
-
const babelTraverse = traverse?.default || traverse;
|
|
9
|
-
import * as t from '@babel/types';
|
|
10
|
-
import { existsSync, readFileSync, statSync } from 'fs';
|
|
11
|
-
import { astNormalizeModuleImportsAndHelpers, astVerifyAndAnnotateDuplicates } from '../helpers/ast-normalizer.js';
|
|
12
|
-
import { stripDanglingViteCjsImports } from '../helpers/sanitize.js';
|
|
2
|
+
import { existsSync, readFileSync } from 'fs';
|
|
13
3
|
import { WebSocketServer } from 'ws';
|
|
14
4
|
import * as path from 'path';
|
|
15
5
|
import { createHash } from 'crypto';
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
18
|
-
import { getProjectRootPath } from '../../helpers/project.js';
|
|
6
|
+
import { getVendorManifest } from '../shared/vendor/registry.js';
|
|
7
|
+
import { getPackageJson, getProjectFilePath } from '../../helpers/project.js';
|
|
19
8
|
import { loadPrebuiltVendorManifest } from '../shared/vendor/manifest-loader.js';
|
|
20
9
|
import '../vendor-bootstrap.js';
|
|
21
|
-
import {
|
|
22
|
-
import { vueServerStrategy } from '../frameworks/vue/server/strategy.js';
|
|
10
|
+
import { vueServerStrategy, processSfcCode } from '../frameworks/vue/server/strategy.js';
|
|
23
11
|
import { angularServerStrategy } from '../frameworks/angular/server/strategy.js';
|
|
24
12
|
import { solidServerStrategy } from '../frameworks/solid/server/strategy.js';
|
|
25
13
|
import { typescriptServerStrategy } from '../frameworks/typescript/server/strategy.js';
|
|
26
|
-
import { createProcessSfcCode } from '../frameworks/vue/server/sfc-transforms.js';
|
|
27
14
|
import { getProjectAppPath, getProjectAppRelativePath, getProjectAppVirtualPath } from '../../helpers/utils.js';
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import { canonicalizeTransformRequestCacheKey
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import { ensureNativeScriptModuleBindings, getProcessCodeResolvedSpecifierOverrides } from './websocket-module-bindings.js';
|
|
36
|
-
import { buildVersionedCoreMainBridgeModule, buildVersionedCoreSubpathAliasModule, collectStaticExportNamesFromFile, collectStaticExportOriginsFromFile, ensureVersionedCoreImports, extractDirectExportedNames, hasModuleDefaultExport, normalizeCoreExportOriginsForRuntime, parseCoreBridgeRequest, resolveRuntimeCoreModulePath } from './websocket-core-bridge.js';
|
|
37
|
-
import { finalizeNsMServedModule } from './websocket-ns-m-finalize.js';
|
|
38
|
-
import { createNsMRequestContext, resolveNsMTransformedModule } from './websocket-ns-m-request.js';
|
|
39
|
-
import { getNumericServeVersionTag, rewriteNsMImportPathForHmr } from './websocket-ns-m-paths.js';
|
|
40
|
-
import { registerRuntimeCompatHandlers } from './websocket-runtime-compat.js';
|
|
41
|
-
import { registerTxnHandler } from './websocket-txn.js';
|
|
15
|
+
import { shouldIncludeRuntimeGraphFile, shouldSkipRuntimeGraphDirectoryName } from './runtime-graph-filter.js';
|
|
16
|
+
import { getHmrSourceRoots } from '../../helpers/hmr-scope.js';
|
|
17
|
+
import { getTsConfigData } from '../../helpers/ts-config-paths.js';
|
|
18
|
+
import { normalizeHotReloadMatchPath, shouldSuppressViteFullReloadPayload } from '../frameworks/angular/server/websocket-angular-hot-update.js';
|
|
19
|
+
import { canonicalizeTransformRequestCacheKey } from './transform-cache-invalidation.js';
|
|
20
|
+
import { HmrModuleGraph } from './hmr-module-graph.js';
|
|
21
|
+
import { registerNsRtBridgeRoute } from './ns-rt-route.js';
|
|
42
22
|
import { registerVendorUnifierHandler } from './websocket-vendor-unifier.js';
|
|
43
|
-
import {
|
|
23
|
+
import { registerTxnHandler } from './websocket-txn.js';
|
|
24
|
+
import { registerNsModuleServerRoute } from './websocket-ns-m.js';
|
|
25
|
+
import { registerNsCoreRoute } from './websocket-ns-core.js';
|
|
26
|
+
import { registerNsEntryRoutes } from './websocket-ns-entry.js';
|
|
27
|
+
import { registerImportMapRoute } from './websocket-import-map-route.js';
|
|
28
|
+
import { isAngularRootComponentUpdate, resolveBootstrapRootComponent } from '../frameworks/angular/server/angular-root-component.js';
|
|
29
|
+
import { cleanCode, collectImportDependencies, rewriteImports, shouldRemapImport } from './websocket-device-transform.js';
|
|
30
|
+
import { classifyBootRoute, createColdBootRequestCounter, formatPopulateInitialGraphSummary, formatServerStartupBanner } from './perf-instrumentation.js';
|
|
31
|
+
import { isCoreGlobalsReference, isNativeScriptCoreModule, isNativeScriptPluginModule, resolveVendorFromCandidate } from './websocket-module-specifiers.js';
|
|
44
32
|
import { createSharedTransformRequestRunner } from './shared-transform-request.js';
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// Skip internal NativeScript build flags
|
|
59
|
-
if (['ios', 'android', 'visionos', 'platform', 'hmr', 'verbose'].includes(k))
|
|
60
|
-
continue;
|
|
61
|
-
__processEnvEntries[k] = String(v);
|
|
33
|
+
import { getGlobalScope } from '../shared/runtime/global-scope.js';
|
|
34
|
+
const APP_ROOT_DIR = getProjectAppPath();
|
|
35
|
+
// Absolute directories HMR is allowed to react to: the app source dir
|
|
36
|
+
// (`nativescript.config.ts` > `appPath`) plus tsconfig-configured shared
|
|
37
|
+
// libraries. Computed once per process (tsconfig data is itself memoized) and
|
|
38
|
+
// used to scope `handleHotUpdate` so non-source changes.
|
|
39
|
+
let _hmrSourceRoots = null;
|
|
40
|
+
function getHmrSourceRootsCached() {
|
|
41
|
+
if (_hmrSourceRoots)
|
|
42
|
+
return _hmrSourceRoots;
|
|
43
|
+
let tsConfig = {};
|
|
44
|
+
try {
|
|
45
|
+
tsConfig = getTsConfigData({ platform: '' });
|
|
62
46
|
}
|
|
47
|
+
catch { }
|
|
48
|
+
_hmrSourceRoots = getHmrSourceRoots(tsConfig);
|
|
49
|
+
return _hmrSourceRoots;
|
|
63
50
|
}
|
|
64
|
-
catch { }
|
|
65
|
-
const __processEnvJson = JSON.stringify(__processEnvEntries);
|
|
66
|
-
const APP_ROOT_DIR = getProjectAppPath();
|
|
67
51
|
const APP_VIRTUAL_PREFIX = getProjectAppVirtualPath();
|
|
68
52
|
const APP_VIRTUAL_WITH_SLASH = `${APP_VIRTUAL_PREFIX}/`;
|
|
69
53
|
const DEFAULT_MAIN_ENTRY = getProjectAppRelativePath('app.ts');
|
|
70
54
|
const DEFAULT_MAIN_ENTRY_VIRTUAL = getProjectAppVirtualPath('app.ts');
|
|
55
|
+
// Memoized resolver for the project bootstrap entry as a posix
|
|
56
|
+
// project-relative path (e.g. `/src/main.ts`). This mirrors the
|
|
57
|
+
// resolution the cold-boot wrapper performs (`getPackageJson().main` →
|
|
58
|
+
// project-relative under `/<APP_ROOT_DIR>/`) so the eviction set for
|
|
59
|
+
// HMR always lines up with the URL the runtime actually re-imports.
|
|
60
|
+
// Resolved at first call and cached: `package.json` is read at startup
|
|
61
|
+
// and never changes during a dev session, so it's safe to memoize.
|
|
62
|
+
let __ns_bootstrap_entry_rel_cached = null;
|
|
63
|
+
function getBootstrapEntryRelPath() {
|
|
64
|
+
if (__ns_bootstrap_entry_rel_cached)
|
|
65
|
+
return __ns_bootstrap_entry_rel_cached;
|
|
66
|
+
let entry = DEFAULT_MAIN_ENTRY_VIRTUAL;
|
|
67
|
+
try {
|
|
68
|
+
const pkg = getPackageJson();
|
|
69
|
+
const main = (pkg && pkg.main) || DEFAULT_MAIN_ENTRY;
|
|
70
|
+
const abs = getProjectFilePath(main).replace(/\\/g, '/');
|
|
71
|
+
const marker = `/${APP_ROOT_DIR}/`;
|
|
72
|
+
const idx = abs.indexOf(marker);
|
|
73
|
+
entry = idx >= 0 ? abs.substring(idx) : DEFAULT_MAIN_ENTRY_VIRTUAL;
|
|
74
|
+
}
|
|
75
|
+
catch { }
|
|
76
|
+
if (!entry.startsWith('/')) {
|
|
77
|
+
entry = '/' + entry;
|
|
78
|
+
}
|
|
79
|
+
__ns_bootstrap_entry_rel_cached = entry;
|
|
80
|
+
return entry;
|
|
81
|
+
}
|
|
82
|
+
// Memoized resolver for the project's Angular bootstrap (root) component.
|
|
83
|
+
// The root component owns the navigation `Frame` via `<page-router-outlet>`,
|
|
84
|
+
// so Analog's in-place `ɵɵreplaceMetadata` HMR is destructive for it (it
|
|
85
|
+
// recreates the root view without re-navigating → permanent white screen).
|
|
86
|
+
// Knowing which file is the root lets the bridge drop the in-place update
|
|
87
|
+
// and the hot-update handler route the edit through the reboot path instead.
|
|
88
|
+
// `undefined` = not yet computed; `null` = computed but unresolvable (the
|
|
89
|
+
// caller then keeps its default behavior, e.g. NgModule bootstrap shapes).
|
|
90
|
+
let __ns_root_component_cached;
|
|
91
|
+
function getRootComponentIdentity() {
|
|
92
|
+
if (__ns_root_component_cached !== undefined)
|
|
93
|
+
return __ns_root_component_cached;
|
|
94
|
+
__ns_root_component_cached = null;
|
|
95
|
+
try {
|
|
96
|
+
const pkg = getPackageJson();
|
|
97
|
+
const main = (pkg && pkg.main) || DEFAULT_MAIN_ENTRY;
|
|
98
|
+
const entrySource = readFileSync(getProjectFilePath(main), 'utf-8');
|
|
99
|
+
__ns_root_component_cached = resolveBootstrapRootComponent({
|
|
100
|
+
entrySource,
|
|
101
|
+
entryRel: getBootstrapEntryRelPath(),
|
|
102
|
+
appRootRel: '/' + APP_ROOT_DIR,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
catch { }
|
|
106
|
+
return __ns_root_component_cached;
|
|
107
|
+
}
|
|
71
108
|
const STRATEGY_REGISTRY = new Map([
|
|
72
109
|
['vue', vueServerStrategy],
|
|
73
110
|
['angular', angularServerStrategy],
|
|
74
111
|
['solid', solidServerStrategy],
|
|
75
112
|
['typescript', typescriptServerStrategy],
|
|
76
113
|
]);
|
|
77
|
-
function resolveFrameworkStrategy(flavor) {
|
|
78
|
-
const strategy = STRATEGY_REGISTRY.get(flavor);
|
|
79
|
-
if (!strategy) {
|
|
80
|
-
throw new Error(`[ns-hmr] Unsupported framework strategy: ${flavor}`);
|
|
81
|
-
}
|
|
82
|
-
return strategy;
|
|
83
|
-
}
|
|
84
|
-
let ACTIVE_STRATEGY;
|
|
85
114
|
function isSocketClientOpen(client) {
|
|
86
115
|
if (!client) {
|
|
87
116
|
return false;
|
|
@@ -94,2357 +123,63 @@ function getHmrSocketRoleFromRequestUrl(requestUrl) {
|
|
|
94
123
|
const url = new URL(requestUrl || '/ns-hmr', 'http://localhost');
|
|
95
124
|
return url.searchParams.get('ns_hmr_role') || 'unknown';
|
|
96
125
|
}
|
|
97
|
-
catch {
|
|
98
|
-
return 'unknown';
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
function getHmrSocketRole(client) {
|
|
102
|
-
if (!client) {
|
|
103
|
-
return 'unknown';
|
|
104
|
-
}
|
|
105
|
-
return typeof client.__nsHmrClientRole === 'string' && client.__nsHmrClientRole ? client.__nsHmrClientRole : 'unknown';
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
namedBindings: Array.isArray(node.specifiers)
|
|
141
|
-
? node.specifiers
|
|
142
|
-
.filter((spec) => t.isImportSpecifier(spec) && typeof spec.start === 'number' && typeof spec.end === 'number')
|
|
143
|
-
.map((spec) => ({
|
|
144
|
-
importedName: t.isIdentifier(spec.imported) ? spec.imported.name : String(spec.imported?.value || ''),
|
|
145
|
-
text: code.slice(spec.start, spec.end),
|
|
146
|
-
}))
|
|
147
|
-
: [],
|
|
148
|
-
}));
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
151
|
-
return [];
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
function hoistTopLevelStaticImports(code) {
|
|
155
|
-
const imports = collectTopLevelImportRecords(code);
|
|
156
|
-
if (!imports.length) {
|
|
157
|
-
return code;
|
|
158
|
-
}
|
|
159
|
-
let stripped = code;
|
|
160
|
-
for (const imp of [...imports].sort((left, right) => right.start - left.start)) {
|
|
161
|
-
stripped = stripped.slice(0, imp.start) + stripped.slice(imp.end);
|
|
162
|
-
}
|
|
163
|
-
const hoisted = [];
|
|
164
|
-
const seen = new Set();
|
|
165
|
-
for (const imp of imports) {
|
|
166
|
-
const text = imp.text.trim();
|
|
167
|
-
if (!text || seen.has(text)) {
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
seen.add(text);
|
|
171
|
-
hoisted.push(text);
|
|
172
|
-
}
|
|
173
|
-
if (!hoisted.length) {
|
|
174
|
-
return stripped;
|
|
175
|
-
}
|
|
176
|
-
return `${hoisted.join('\n')}\n${stripped.replace(/^\s*\n+/, '')}`;
|
|
177
|
-
}
|
|
178
|
-
export function buildBootProgressSnippet(bootModuleLabel) {
|
|
179
|
-
const normalizedLabel = JSON.stringify(String(bootModuleLabel || '').replace(/\\/g, '/'));
|
|
180
|
-
return [
|
|
181
|
-
`const __nsBootGlobal=globalThis;`,
|
|
182
|
-
`try{if(!__nsBootGlobal.__NS_HMR_BOOT_COMPLETE__){const __nsBootApi=__nsBootGlobal.__NS_HMR_DEV_OVERLAY__;if(__nsBootApi&&typeof __nsBootApi.setBootStage==='function'){const __nsBootCount=(__nsBootGlobal.__NS_HMR_BOOT_MODULE_COUNT__=Number(__nsBootGlobal.__NS_HMR_BOOT_MODULE_COUNT__||0)+1);__nsBootGlobal.__NS_HMR_BOOT_LAST_MODULE__=${normalizedLabel};const __nsBootNow=Date.now();const __nsBootLast=Number(__nsBootGlobal.__NS_HMR_BOOT_LAST_PROGRESS_AT__||0);if(__nsBootCount<=8||__nsBootCount%6===0||__nsBootNow-__nsBootLast>90){__nsBootGlobal.__NS_HMR_BOOT_LAST_PROGRESS_AT__=__nsBootNow;const __nsBootProgress=Math.min(94,82+Math.min(10,Math.round((Math.log(__nsBootCount+1)/Math.LN2)*2)));__nsBootApi.setBootStage('importing-main',{detail:'Evaluated '+__nsBootCount+' modules\\n'+__nsBootGlobal.__NS_HMR_BOOT_LAST_MODULE__,attempt:Number(__nsBootGlobal.__NS_HMR_BOOT_MAIN_ATTEMPT__||1),attempts:Number(__nsBootGlobal.__NS_HMR_BOOT_MAIN_ATTEMPTS__||6),progress:__nsBootProgress});}}}}catch(__nsBootErr){}`,
|
|
183
|
-
`if(!__nsBootGlobal.__NS_HMR_BOOT_COMPLETE__){const __nsBootCount=Number(__nsBootGlobal.__NS_HMR_BOOT_MODULE_COUNT__||0);if(__nsBootCount<=24||__nsBootCount%8===0){await new Promise((resolve)=>setTimeout(resolve,0));}}`,
|
|
184
|
-
'',
|
|
185
|
-
].join('\n');
|
|
186
|
-
}
|
|
187
|
-
function rewriteVitePrebundleImportsForDevice(code, preserveVendorImports) {
|
|
188
|
-
const imports = collectTopLevelImportRecords(code);
|
|
189
|
-
if (!imports.length) {
|
|
190
|
-
return code;
|
|
191
|
-
}
|
|
192
|
-
const edits = [];
|
|
193
|
-
for (const imp of imports) {
|
|
194
|
-
const source = imp.source;
|
|
195
|
-
const depMatch = source.match(/(?:^|\/)node_modules\/\.vite\/deps\/(.+)$/);
|
|
196
|
-
const depPath = depMatch?.[1] || (source.startsWith('.vite/deps/') ? source.slice('.vite/deps/'.length) : null);
|
|
197
|
-
if (!depPath) {
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
let replacement = '';
|
|
201
|
-
if (preserveVendorImports) {
|
|
202
|
-
const canonical = resolveVendorFromCandidate(`.vite/deps/${depPath}`);
|
|
203
|
-
const bareSpecifier = canonical || viteDepsPathToBareSpecifier(depPath);
|
|
204
|
-
if (bareSpecifier) {
|
|
205
|
-
replacement = imp.text.replace(source, bareSpecifier);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
edits.push({
|
|
209
|
-
start: imp.start,
|
|
210
|
-
end: imp.end,
|
|
211
|
-
text: replacement,
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
if (!edits.length) {
|
|
215
|
-
return code;
|
|
216
|
-
}
|
|
217
|
-
let next = code;
|
|
218
|
-
for (const edit of edits.sort((left, right) => right.start - left.start)) {
|
|
219
|
-
next = next.slice(0, edit.start) + edit.text + next.slice(edit.end);
|
|
220
|
-
}
|
|
221
|
-
return next;
|
|
222
|
-
}
|
|
223
|
-
function buildNodeModuleProvenancePrelude(sourceId) {
|
|
224
|
-
if (!sourceId) {
|
|
225
|
-
return '';
|
|
226
|
-
}
|
|
227
|
-
const cleaned = sourceId.replace(PAT.QUERY_PATTERN, '');
|
|
228
|
-
let normalized = normalizeNodeModulesSpecifier(cleaned);
|
|
229
|
-
if (!normalized) {
|
|
230
|
-
const viteDepsMatch = cleaned.match(/(?:^|\/)node_modules\/\.vite\/deps\/([^?#]+)/);
|
|
231
|
-
if (viteDepsMatch?.[1]) {
|
|
232
|
-
normalized = `.vite/deps/${viteDepsMatch[1]}`;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
if (!normalized) {
|
|
236
|
-
return '';
|
|
237
|
-
}
|
|
238
|
-
let packageSpecifier = normalized;
|
|
239
|
-
let via = 'node_modules';
|
|
240
|
-
if (normalized.startsWith('.vite/deps/')) {
|
|
241
|
-
via = 'vite-deps';
|
|
242
|
-
packageSpecifier = viteDepsPathToBareSpecifier(normalized.slice('.vite/deps/'.length)) || normalized;
|
|
243
|
-
}
|
|
244
|
-
const rootPackage = resolveNodeModulesPackageBoundary(packageSpecifier, getProjectRootPath()).packageName;
|
|
245
|
-
if (!rootPackage) {
|
|
246
|
-
return '';
|
|
247
|
-
}
|
|
248
|
-
return `try { const __nsRecord = globalThis.__NS_RECORD_MODULE_PROVENANCE__; if (typeof __nsRecord === 'function') { __nsRecord(${JSON.stringify(rootPackage)}, ${JSON.stringify({ kind: 'http-esm', specifier: packageSpecifier, url: sourceId, via })}); } } catch {}\n`;
|
|
249
|
-
}
|
|
250
|
-
// Guard any bare dynamic import(spec) occurring in assembled module code.
|
|
251
|
-
// We cannot override native dynamic import globally; for SFC assembler outputs we inline
|
|
252
|
-
// a tiny helper and rewrite "import(" to "__nsDynImport(" to prevent anomalous specs like '@'.
|
|
253
|
-
function guardBareDynamicImports(code) {
|
|
254
|
-
try {
|
|
255
|
-
if (!code || typeof code !== 'string')
|
|
256
|
-
return code;
|
|
257
|
-
const NEEDLE = /(^|\n)\s*(?:\/\/[^\n]*\n|\/\*[\s\S]*?\*\/\s*)*/;
|
|
258
|
-
const hasImportCall = /\bimport\s*\(/.test(code);
|
|
259
|
-
if (!hasImportCall)
|
|
260
|
-
return code;
|
|
261
|
-
const helper = "const __nsDynImport = (spec) => { try { if (!spec || spec === '@') { return import(new URL('/ns/m/__invalid_at__.mjs', import.meta.url).href); } } catch {} try { return import(spec); } catch (e) { return Promise.reject(e); } };\n";
|
|
262
|
-
// Avoid double injection
|
|
263
|
-
const inject = code.includes('const __nsDynImport =') ? '' : helper;
|
|
264
|
-
// Replace bare import( ... ) that are not part of 'import.meta' or type-only contexts
|
|
265
|
-
// Heuristic: replace 'import(' occurrences; skip 'import.meta'
|
|
266
|
-
const rewritten = code.replace(/\bimport\s*\(/g, '__nsDynImport(');
|
|
267
|
-
if (rewritten === code && !inject)
|
|
268
|
-
return code;
|
|
269
|
-
return inject + rewritten;
|
|
270
|
-
}
|
|
271
|
-
catch {
|
|
272
|
-
return code;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
function stripCoreGlobalsImports(code) {
|
|
276
|
-
const pattern = /^\s*(?:import\s+(?:[^'"\n]*from\s+)?|export\s+\*\s+from\s+)["'][^"']*(?:@nativescript(?:[/_-])core(?:[\/_-])globals|@nativescript_core_globals)[^"']*["'];?\s*$/gm;
|
|
277
|
-
return code.replace(pattern, '');
|
|
278
|
-
}
|
|
279
|
-
function ensureVariableDynamicImportHelper(code) {
|
|
280
|
-
if (!code.includes('__variableDynamicImportRuntimeHelper')) {
|
|
281
|
-
return code;
|
|
282
|
-
}
|
|
283
|
-
if (PAT.VARIABLE_DYNAMIC_IMPORT_HELPER_PATTERN.test(code)) {
|
|
284
|
-
return code;
|
|
285
|
-
}
|
|
286
|
-
const helper = `const __variableDynamicImportRuntimeHelper = (map, request, importMode) => {\n` +
|
|
287
|
-
` try { if (request === '@') { return import(new URL('/ns/m/__invalid_at__.mjs', import.meta.url).href); } } catch {}\n` +
|
|
288
|
-
` const loader = map && (map[request] || map[request?.replace(/\\\\/g, "/")]);\n` +
|
|
289
|
-
` if (!loader) {\n` +
|
|
290
|
-
` const error = new Error(\"Cannot dynamically import: \" + request);\n` +
|
|
291
|
-
` error.code = 'ERR_MODULE_NOT_FOUND';\n` +
|
|
292
|
-
` return Promise.reject(error);\n` +
|
|
293
|
-
` }\n` +
|
|
294
|
-
` try {\n` +
|
|
295
|
-
` return loader(importMode);\n` +
|
|
296
|
-
` } catch (err) {\n` +
|
|
297
|
-
` return Promise.reject(err);\n` +
|
|
298
|
-
` }\n` +
|
|
299
|
-
`};\n`;
|
|
300
|
-
return `${helper}${code}`;
|
|
301
|
-
}
|
|
302
|
-
function ensureGuardPlainDynamicImports(code, origin) {
|
|
303
|
-
try {
|
|
304
|
-
if (!code || !/\bimport\s*\(/.test(code))
|
|
305
|
-
return code;
|
|
306
|
-
const wrapper = `const __ns_import = (s) => { try { if (s === '@') { return import(new URL('/ns/m/__invalid_at__.mjs', import.meta.url).href); } } catch {} return import(s); }\n`;
|
|
307
|
-
const replaced = code.replace(/(^|[^\.\w$])import\s*\(/g, (_m, p1) => `${p1}__ns_import(`);
|
|
308
|
-
if (replaced !== code) {
|
|
309
|
-
return wrapper + replaced;
|
|
310
|
-
}
|
|
311
|
-
return code;
|
|
312
|
-
}
|
|
313
|
-
catch {
|
|
314
|
-
return code;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
function ensureDynamicHmrImportHelper(code) {
|
|
318
|
-
try {
|
|
319
|
-
if (!code.includes('__nsDynamicHmrImport('))
|
|
320
|
-
return code;
|
|
321
|
-
if (code.includes('const __nsDynamicHmrImport ='))
|
|
322
|
-
return code;
|
|
323
|
-
const helper = 'const __nsDynamicHmrImport = (spec) => {\n' +
|
|
324
|
-
" const __nsm = '/ns' + '/m';\n" +
|
|
325
|
-
" const __nsBootPrefix = typeof import.meta !== 'undefined' && import.meta && typeof import.meta.url === 'string' && import.meta.url.includes('/__ns_boot__/b1/') ? '/__ns_boot__/b1' : '';\n" +
|
|
326
|
-
" const __nsImporterTagMatch = typeof import.meta !== 'undefined' && import.meta && typeof import.meta.url === 'string' ? import.meta.url.match(/\\/__ns_hmr__\\/([^/]+)\\//) : null;\n" +
|
|
327
|
-
" const __nsImporterTag = __nsImporterTagMatch && __nsImporterTagMatch[1] ? decodeURIComponent(__nsImporterTagMatch[1]) : '';\n" +
|
|
328
|
-
" try { if (!spec || spec === '@') { return import(new URL(__nsm + '/__invalid_at__.mjs', import.meta.url).href); } } catch {}\n" +
|
|
329
|
-
' try {\n' +
|
|
330
|
-
" if (typeof spec === 'string' && spec.startsWith(__nsm + '/')) {\n" +
|
|
331
|
-
' const g = globalThis;\n' +
|
|
332
|
-
" const graphVersion = typeof g.__NS_HMR_GRAPH_VERSION__ === 'number' ? g.__NS_HMR_GRAPH_VERSION__ : 0;\n" +
|
|
333
|
-
" const nonce = typeof g.__NS_HMR_IMPORT_NONCE__ === 'number' ? g.__NS_HMR_IMPORT_NONCE__ : 0;\n" +
|
|
334
|
-
" const __nsActiveBootPrefix = graphVersion || nonce ? '' : __nsBootPrefix;\n" +
|
|
335
|
-
" if (spec.includes('/__ns_hmr__/')) {\n" +
|
|
336
|
-
" const __preservedSpec = !nonce && __nsBootPrefix && spec.startsWith(__nsm + '/__ns_hmr__/') && !spec.includes('/node_modules/') ? __nsm + __nsBootPrefix + spec.slice(__nsm.length) : spec;\n" +
|
|
337
|
-
' return import(new URL(__preservedSpec, import.meta.url).href);\n' +
|
|
338
|
-
' }\n' +
|
|
339
|
-
" if (spec.startsWith(__nsm + '/node_modules/')) { return import(new URL(spec, import.meta.url).href); }\n" +
|
|
340
|
-
" const tag = nonce ? `n${nonce}` : (graphVersion ? `v${graphVersion}` : (__nsImporterTag || 'live'));\n" +
|
|
341
|
-
" const nextPath = __nsm + __nsActiveBootPrefix + '/__ns_hmr__/' + encodeURIComponent(tag) + spec.slice(__nsm.length);\n" +
|
|
342
|
-
" const origin = typeof g.__NS_HTTP_ORIGIN__ === 'string' && /^https?:\\/\\//.test(g.__NS_HTTP_ORIGIN__) ? g.__NS_HTTP_ORIGIN__ : '';\n" +
|
|
343
|
-
' return import(origin ? origin + nextPath : new URL(nextPath, import.meta.url).href);\n' +
|
|
344
|
-
' }\n' +
|
|
345
|
-
' } catch {}\n' +
|
|
346
|
-
' return import(spec);\n' +
|
|
347
|
-
'};\n';
|
|
348
|
-
return helper + code;
|
|
349
|
-
}
|
|
350
|
-
catch {
|
|
351
|
-
return code;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
async function expandStarExports(code, server, projectRoot, verbose) {
|
|
355
|
-
const STAR_RE = /^[ \t]*(export\s+\*\s+from\s+["'])([^"']+)(["'];?)[ \t]*$/gm;
|
|
356
|
-
let match;
|
|
357
|
-
const replacements = [];
|
|
358
|
-
while ((match = STAR_RE.exec(code)) !== null) {
|
|
359
|
-
const url = match[2];
|
|
360
|
-
if (!url.includes('/node_modules/'))
|
|
361
|
-
continue;
|
|
362
|
-
replacements.push({ full: match[0], url, prefix: match[1], suffix: match[3] });
|
|
363
|
-
}
|
|
364
|
-
if (!replacements.length)
|
|
365
|
-
return code;
|
|
366
|
-
for (const rep of replacements) {
|
|
367
|
-
try {
|
|
368
|
-
let vitePath = rep.url.replace(/^https?:\/\/[^/]+/, '');
|
|
369
|
-
vitePath = vitePath.replace(/^\/ns\/m\//, '/');
|
|
370
|
-
vitePath = vitePath.replace(/^\/__ns_boot__\/[^/]+/, '');
|
|
371
|
-
vitePath = vitePath.replace(/\/__ns_hmr__\/[^/]+/, '');
|
|
372
|
-
const result = await server.transformRequest(vitePath);
|
|
373
|
-
if (!result?.code)
|
|
374
|
-
continue;
|
|
375
|
-
const names = extractExportedNames(result.code);
|
|
376
|
-
if (!names.length)
|
|
377
|
-
continue;
|
|
378
|
-
const explicit = `export { ${names.join(', ')} } from ${JSON.stringify(rep.url)};`;
|
|
379
|
-
code = code.replace(rep.full, explicit);
|
|
380
|
-
if (verbose) {
|
|
381
|
-
console.log(`[ns/m] expanded export* -> ${names.length} names from ${vitePath}`);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
catch { }
|
|
385
|
-
}
|
|
386
|
-
return code;
|
|
387
|
-
}
|
|
388
|
-
function extractExportedNames(code) {
|
|
389
|
-
return extractDirectExportedNames(code);
|
|
390
|
-
}
|
|
391
|
-
function repairImportEqualsAssignments(code) {
|
|
392
|
-
try {
|
|
393
|
-
if (!code || typeof code !== 'string')
|
|
394
|
-
return code;
|
|
395
|
-
code = code.replace(/(^|\n)\s*import\s*\{([^}]+)\}\s*=\s*([^;]+);?/g, (_m, p1, specList, rhs) => {
|
|
396
|
-
const cleaned = String(specList)
|
|
397
|
-
.split(',')
|
|
398
|
-
.map((s) => s.trim())
|
|
399
|
-
.filter(Boolean)
|
|
400
|
-
.map((seg) => seg.replace(/\s+as\s+/i, ': '))
|
|
401
|
-
.join(', ');
|
|
402
|
-
return `${p1}const { ${cleaned} } = ${rhs};`;
|
|
403
|
-
});
|
|
404
|
-
code = code.replace(/(^|\n)\s*import\s*\*\s*as\s*([A-Za-z_$][\w$]*)\s*=\s*([^;]+);?/g, (_m, p1, ns, rhs) => `${p1}const ${ns} = (${rhs});`);
|
|
405
|
-
code = code.replace(/(^|\n)\s*import\s+([A-Za-z_$][\w$]*)\s*=\s*([^;]+);?/g, (_m, p1, id, rhs) => `${p1}const ${id} = ${rhs};`);
|
|
406
|
-
}
|
|
407
|
-
catch { }
|
|
408
|
-
return code;
|
|
409
|
-
}
|
|
410
|
-
function ensureVersionedRtImports(code, origin, ver) {
|
|
411
|
-
if (!code || !origin || !Number.isFinite(ver))
|
|
412
|
-
return code;
|
|
413
|
-
code = code.replace(/(from\s+["'])(?:https?:\/\/[^"']+)?\/(?:\ns|ns)\/rt(?:\/[\d]+)?(["'])/g, (_m, p1, p3) => `${p1}/ns/rt/${ver}${p3}`);
|
|
414
|
-
code = code.replace(/(import\(\s*["'])(?:https?:\/\/[^"']+)?\/(?:\@ns|ns)\/rt(?:\/[\d]+)?(["']\s*\))/g, (_m, p1, p3) => `${p1}/ns/rt/${ver}${p3}`);
|
|
415
|
-
return code;
|
|
416
|
-
}
|
|
417
|
-
function stripViteDynamicImportVirtual(code) {
|
|
418
|
-
if (!/\/@id\/__x00__vite\/dynamic-import-helper/.test(code)) {
|
|
419
|
-
return code;
|
|
420
|
-
}
|
|
421
|
-
const original = code;
|
|
422
|
-
code = code.replace(/^[\t ]*import[^\n]*\/@id\/__x00__vite\/dynamic-import-helper[^\n]*$/gm, '');
|
|
423
|
-
if (/\/@id\/__x00__vite\/dynamic-import-helper/.test(code)) {
|
|
424
|
-
code = code.replace(/\/@id\/__x00__vite\/dynamic-import-helper[^"'`)]*/g, '/__NS_UNUSED_DYNAMIC_IMPORT_HELPER__');
|
|
425
|
-
}
|
|
426
|
-
if (!/__variableDynamicImportRuntimeHelper/.test(code)) {
|
|
427
|
-
const inline = `const __variableDynamicImportRuntimeHelper = (map, request, importMode) => {\n try { if (request === '@') { return import('/ns/m/__invalid_at__.mjs'); } } catch {}\n const loader = map && (map[request] || map[request?.replace(/\\\\/g, '/')]);\n if (!loader) { const e = new Error('Cannot dynamically import: ' + request); /*@ts-ignore*/ e.code = 'ERR_MODULE_NOT_FOUND'; return Promise.reject(e); }\n try { return loader(importMode); } catch (e) { return Promise.reject(e); }\n};\n`;
|
|
428
|
-
code = inline + code;
|
|
429
|
-
}
|
|
430
|
-
if (code !== original) {
|
|
431
|
-
code = `// [hmr-sanitize] removed virtual dynamic-import-helper\n${code}`;
|
|
432
|
-
}
|
|
433
|
-
return code;
|
|
434
|
-
}
|
|
435
|
-
const REQUIRE_GUARD_SNIPPET = `// [guard] install require('http(s)://') detector\n(()=>{try{var g=globalThis;if(g.__NS_REQUIRE_GUARD_INSTALLED__){}else{var mk=function(o,l){return function(){try{var s=arguments[0];if(typeof s==='string'&&/^(?:https?:)\\/\\//.test(s)){var e=new Error('[ns-hmr][require-guard] require of URL: '+s+' via '+l);try{console.error(e.message+'\\n'+(e.stack||''));}catch(e2){}try{g.__NS_REQUIRE_GUARD_LAST__={spec:s,stack:e.stack,label:l,ts:Date.now()};}catch(e3){}}}catch(e1){}return o.apply(this, arguments);};};if(typeof g.require==='function'&&!g.require.__NS_REQ_GUARDED__){var o1=g.require;g.require=mk(o1,'require');g.require.__NS_REQ_GUARDED__=true;}if(typeof g.__nsRequire==='function'&&!g.__nsRequire.__NS_REQ_GUARDED__){var o2=g.__nsRequire;g.__nsRequire=mk(o2,'__nsRequire');g.__nsRequire.__NS_REQ_GUARDED__=true;}g.__NS_REQUIRE_GUARD_INSTALLED__=true;}}catch(e){}})();\n`;
|
|
436
|
-
function shouldRemapImport(spec) {
|
|
437
|
-
if (!spec || typeof spec !== 'string')
|
|
438
|
-
return false;
|
|
439
|
-
if (VENDOR_PACKAGES.test(spec))
|
|
440
|
-
return false;
|
|
441
|
-
if (isNativeScriptCoreModule(spec))
|
|
442
|
-
return false;
|
|
443
|
-
if (isNativeScriptPluginModule(spec))
|
|
444
|
-
return false;
|
|
445
|
-
if (resolveVendorFromCandidate(spec))
|
|
446
|
-
return false;
|
|
447
|
-
if (spec.startsWith('~/'))
|
|
448
|
-
return false;
|
|
449
|
-
if (SKIP_PATTERNS.test(spec))
|
|
450
|
-
return false;
|
|
451
|
-
if (!spec.startsWith('/') && !spec.startsWith('./') && !spec.startsWith('../')) {
|
|
452
|
-
return false;
|
|
453
|
-
}
|
|
454
|
-
return true;
|
|
455
|
-
}
|
|
456
|
-
function removeNamedImports(code, names) {
|
|
457
|
-
const regex = /^(\s*import\s*\{)([^}]*)(\}\s*from\s*['"][^'"]+['"];?)/gm;
|
|
458
|
-
return code.replace(regex, (_m, p1, specList, p3) => {
|
|
459
|
-
const srcMatch = /from\s*['"]\s*([^'"\s]+)\s*['"]/i.exec(_m);
|
|
460
|
-
const src = (srcMatch?.[1] || '').toLowerCase();
|
|
461
|
-
const isVueSource = /^(?:vue|nativescript-vue)(?:\b|\/)/i.test(src);
|
|
462
|
-
if (!isVueSource) {
|
|
463
|
-
return _m;
|
|
464
|
-
}
|
|
465
|
-
const remaining = specList
|
|
466
|
-
.split(',')
|
|
467
|
-
.map((s) => s.trim())
|
|
468
|
-
.filter(Boolean)
|
|
469
|
-
.filter((entry) => {
|
|
470
|
-
const base = entry.split(/\s+as\s+/i)[0].trim();
|
|
471
|
-
return !names.includes(base);
|
|
472
|
-
});
|
|
473
|
-
if (!remaining.length)
|
|
474
|
-
return '';
|
|
475
|
-
return `${p1} ${remaining.join(', ')} ${p3}`;
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
/**
|
|
479
|
-
* Inject global bindings for given names
|
|
480
|
-
*/
|
|
481
|
-
function injectGlobalBindings(code, names) {
|
|
482
|
-
if (!names.length)
|
|
483
|
-
return code;
|
|
484
|
-
const lines = names.map((n) => `const ${n} = globalThis.${n};`);
|
|
485
|
-
return lines.join('\n') + '\n' + code;
|
|
486
|
-
}
|
|
487
|
-
/**
|
|
488
|
-
* Strip import.meta.hot blocks (balanced braces)
|
|
489
|
-
*/
|
|
490
|
-
function stripImportMetaHotBlocks(code) {
|
|
491
|
-
let result = code;
|
|
492
|
-
const regex = /if\s*\(\s*import\.meta\.hot\s*\)\s*\{/g;
|
|
493
|
-
let match;
|
|
494
|
-
while ((match = regex.exec(result)) !== null) {
|
|
495
|
-
let start = match.index;
|
|
496
|
-
let i = start + match[0].length;
|
|
497
|
-
let depth = 1;
|
|
498
|
-
while (i < result.length && depth > 0) {
|
|
499
|
-
const ch = result[i++];
|
|
500
|
-
if (ch === '{')
|
|
501
|
-
depth++;
|
|
502
|
-
else if (ch === '}')
|
|
503
|
-
depth--;
|
|
504
|
-
}
|
|
505
|
-
result = result.slice(0, start) + '/* removed import.meta.hot */\n' + result.slice(i);
|
|
506
|
-
regex.lastIndex = start;
|
|
507
|
-
}
|
|
508
|
-
return result;
|
|
509
|
-
}
|
|
510
|
-
// Extract a quick set of export names and whether a default export exists from ESM code.
|
|
511
|
-
// This uses conservative regex scanning for metadata only (no code rewriting).
|
|
512
|
-
function extractExportMetadata(code) {
|
|
513
|
-
const named = new Set();
|
|
514
|
-
let hasDefault = /\bexport\s+default\b/.test(code);
|
|
515
|
-
try {
|
|
516
|
-
// export const foo, export let foo, export function bar, export class Baz
|
|
517
|
-
for (const m of code.matchAll(/\bexport\s+(?:const|let|var|function|class)\s+([A-Za-z_$][A-Za-z0-9_$]*)/g)) {
|
|
518
|
-
if (m[1])
|
|
519
|
-
named.add(m[1]);
|
|
520
|
-
}
|
|
521
|
-
// export { a, b as c }
|
|
522
|
-
for (const m of code.matchAll(/\bexport\s*\{([^}]+)\}/g)) {
|
|
523
|
-
const inner = (m[1] || '')
|
|
524
|
-
.split(',')
|
|
525
|
-
.map((s) => s.trim())
|
|
526
|
-
.filter(Boolean);
|
|
527
|
-
for (const seg of inner) {
|
|
528
|
-
// forms: name or name as alias or default as name
|
|
529
|
-
const dm = seg.match(/^([A-Za-z_$][A-Za-z0-9_$]*)(?:\s+as\s+([A-Za-z_$][A-Za-z0-9_$]*))?$/);
|
|
530
|
-
if (dm) {
|
|
531
|
-
const base = dm[1];
|
|
532
|
-
const alias = dm[2];
|
|
533
|
-
if (base === 'default') {
|
|
534
|
-
hasDefault = true;
|
|
535
|
-
continue;
|
|
536
|
-
}
|
|
537
|
-
named.add(alias || base);
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
catch { }
|
|
543
|
-
// Remove default if accidentally included
|
|
544
|
-
named.delete('default');
|
|
545
|
-
return { hasDefault, named: Array.from(named) };
|
|
546
|
-
}
|
|
547
|
-
function normalizeImportPath(spec, importerDir) {
|
|
548
|
-
if (!spec)
|
|
549
|
-
return null;
|
|
550
|
-
let key;
|
|
551
|
-
if (spec.startsWith('/')) {
|
|
552
|
-
key = spec;
|
|
553
|
-
}
|
|
554
|
-
else if (spec.startsWith('./') || spec.startsWith('../')) {
|
|
555
|
-
key = path.posix.normalize(path.posix.join(importerDir, spec));
|
|
556
|
-
if (!key.startsWith('/')) {
|
|
557
|
-
key = `/${key}`;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
else {
|
|
561
|
-
key = spec;
|
|
562
|
-
}
|
|
563
|
-
return key.replace(PAT.QUERY_PATTERN, '');
|
|
564
|
-
}
|
|
565
|
-
function registerDependencyFile(depFileMap, candidate, fileName) {
|
|
566
|
-
if (!candidate)
|
|
567
|
-
return;
|
|
568
|
-
const cleaned = candidate.replace(PAT.QUERY_PATTERN, '');
|
|
569
|
-
const normalizedCandidate = normalizeNativeScriptCoreSpecifier(cleaned);
|
|
570
|
-
if (isCoreGlobalsReference(normalizedCandidate)) {
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
if (isNativeScriptCoreModule(normalizedCandidate)) {
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
if (isNativeScriptPluginModule(normalizedCandidate)) {
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
if (resolveVendorFromCandidate(normalizedCandidate)) {
|
|
580
|
-
return;
|
|
581
|
-
}
|
|
582
|
-
if (!cleaned)
|
|
583
|
-
return;
|
|
584
|
-
const variants = new Set();
|
|
585
|
-
variants.add(normalizedCandidate);
|
|
586
|
-
variants.add(cleaned);
|
|
587
|
-
const normalized = path.posix.normalize(cleaned);
|
|
588
|
-
variants.add(normalized);
|
|
589
|
-
const withSlash = normalized.startsWith('/') ? normalized : `/${normalized}`;
|
|
590
|
-
variants.add(withSlash);
|
|
591
|
-
const withoutExt = withSlash.replace(/\.(ts|js|mjs|tsx|jsx)$/i, '');
|
|
592
|
-
variants.add(withoutExt);
|
|
593
|
-
variants.add(`${withoutExt}.js`);
|
|
594
|
-
variants.add(`${withoutExt}.mjs`);
|
|
595
|
-
variants.add(`${withoutExt}.ts`);
|
|
596
|
-
for (const variant of variants) {
|
|
597
|
-
depFileMap.set(variant, fileName);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
function findDependencyFileName(depFileMap, key) {
|
|
601
|
-
const variants = new Set();
|
|
602
|
-
const base = key.replace(PAT.QUERY_PATTERN, '');
|
|
603
|
-
variants.add(base);
|
|
604
|
-
const normalized = path.posix.normalize(base);
|
|
605
|
-
variants.add(normalized);
|
|
606
|
-
const withSlash = normalized.startsWith('/') ? normalized : `/${normalized}`;
|
|
607
|
-
variants.add(withSlash);
|
|
608
|
-
for (const variant of Array.from(variants)) {
|
|
609
|
-
if (variant.endsWith('.js')) {
|
|
610
|
-
variants.add(variant.replace(/\.js$/i, '.mjs'));
|
|
611
|
-
}
|
|
612
|
-
else if (variant.endsWith('.mjs')) {
|
|
613
|
-
variants.add(variant.replace(/\.mjs$/i, '.js'));
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
for (const variant of variants) {
|
|
617
|
-
const value = depFileMap.get(variant);
|
|
618
|
-
if (value) {
|
|
619
|
-
return value;
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
return undefined;
|
|
623
|
-
}
|
|
624
|
-
function isRuntimePluginRootEntrySpecifier(specifier, projectRoot) {
|
|
625
|
-
if (!specifier) {
|
|
626
|
-
return false;
|
|
627
|
-
}
|
|
628
|
-
const cleaned = specifier.replace(PAT.QUERY_PATTERN, '');
|
|
629
|
-
const normalized = normalizeNodeModulesSpecifier(cleaned) || cleaned.replace(/^\/+/, '');
|
|
630
|
-
if (!normalized) {
|
|
631
|
-
return false;
|
|
632
|
-
}
|
|
633
|
-
const { packageName, subpath } = resolveNodeModulesPackageBoundary(normalized, projectRoot);
|
|
634
|
-
if (!packageName || !isLikelyNativeScriptRuntimePluginSpecifier(packageName, projectRoot)) {
|
|
635
|
-
return false;
|
|
636
|
-
}
|
|
637
|
-
if (!subpath) {
|
|
638
|
-
return true;
|
|
639
|
-
}
|
|
640
|
-
if (subpath.includes('/')) {
|
|
641
|
-
return false;
|
|
642
|
-
}
|
|
643
|
-
const pkgBaseName = packageName.split('/').pop() || '';
|
|
644
|
-
const withoutExt = /(?:\.(?:ios|android|visionos))?\.(?:ts|tsx|js|jsx|mjs|mts|cts)$/i.test(subpath) ? subpath.replace(/\.[^.]+$/, '') : subpath;
|
|
645
|
-
const withoutPlatform = withoutExt.replace(/\.(ios|android|visionos)$/i, '');
|
|
646
|
-
return withoutPlatform === 'index' || withoutPlatform === pkgBaseName;
|
|
647
|
-
}
|
|
648
|
-
function collectMixedRuntimePluginHttpRootPackages(code, projectRoot) {
|
|
649
|
-
const nonRootSubpathPackages = new Set();
|
|
650
|
-
const rootEntryPackages = new Set();
|
|
651
|
-
const visitSpecifier = (rawSpecifier) => {
|
|
652
|
-
if (!rawSpecifier) {
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
|
-
const specifier = normalizeNativeScriptCoreSpecifier(rawSpecifier).replace(PAT.QUERY_PATTERN, '');
|
|
656
|
-
if (!specifier) {
|
|
657
|
-
return;
|
|
658
|
-
}
|
|
659
|
-
if (/^https?:\/\//.test(specifier) || specifier.startsWith('/ns/')) {
|
|
660
|
-
return;
|
|
661
|
-
}
|
|
662
|
-
if (/^(?:\.|\/)/.test(specifier) && !specifier.includes('/node_modules/')) {
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
const normalized = normalizeNodeModulesSpecifier(specifier) || specifier.replace(/^\/+/, '');
|
|
666
|
-
if (!normalized) {
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
const { packageName } = resolveNodeModulesPackageBoundary(normalized, projectRoot);
|
|
670
|
-
if (!packageName || !isLikelyNativeScriptRuntimePluginSpecifier(packageName, projectRoot)) {
|
|
671
|
-
return;
|
|
672
|
-
}
|
|
673
|
-
if (isRuntimePluginRootEntrySpecifier(normalized, projectRoot)) {
|
|
674
|
-
rootEntryPackages.add(packageName);
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
nonRootSubpathPackages.add(packageName);
|
|
678
|
-
};
|
|
679
|
-
for (const pattern of [PAT.IMPORT_PATTERN_1, PAT.IMPORT_PATTERN_2, PAT.IMPORT_PATTERN_3, PAT.IMPORT_PATTERN_SIDE_EFFECT]) {
|
|
680
|
-
pattern.lastIndex = 0;
|
|
681
|
-
let match;
|
|
682
|
-
while ((match = pattern.exec(code)) !== null) {
|
|
683
|
-
visitSpecifier(match[2]);
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
return new Set(Array.from(nonRootSubpathPackages).filter((packageName) => rootEntryPackages.has(packageName)));
|
|
687
|
-
}
|
|
688
|
-
function collectImportDependencies(code, importerPath) {
|
|
689
|
-
const importerDir = path.posix.dirname(importerPath);
|
|
690
|
-
const deps = new Set();
|
|
691
|
-
const patterns = [PAT.IMPORT_PATTERN_1, PAT.IMPORT_PATTERN_2, PAT.EXPORT_PATTERN, PAT.IMPORT_PATTERN_3];
|
|
692
|
-
for (const pattern of patterns) {
|
|
693
|
-
pattern.lastIndex = 0;
|
|
694
|
-
let match;
|
|
695
|
-
while ((match = pattern.exec(code)) !== null) {
|
|
696
|
-
const rawSpec = match[2];
|
|
697
|
-
const spec = normalizeNativeScriptCoreSpecifier(rawSpec);
|
|
698
|
-
if (!spec || !shouldRemapImport(spec)) {
|
|
699
|
-
continue;
|
|
700
|
-
}
|
|
701
|
-
if (resolveVendorFromCandidate(spec)) {
|
|
702
|
-
continue;
|
|
703
|
-
}
|
|
704
|
-
// Manifest-aware vendor spec detection
|
|
705
|
-
try {
|
|
706
|
-
if (resolveVendorSpecifier && resolveVendorSpecifier(spec)) {
|
|
707
|
-
continue;
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
catch { }
|
|
711
|
-
const normalized = normalizeImportPath(spec, importerDir);
|
|
712
|
-
if (normalized) {
|
|
713
|
-
if (resolveVendorFromCandidate(normalized)) {
|
|
714
|
-
continue;
|
|
715
|
-
}
|
|
716
|
-
try {
|
|
717
|
-
if (resolveVendorSpecifier && resolveVendorSpecifier(normalized)) {
|
|
718
|
-
continue;
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
catch { }
|
|
722
|
-
if (isCoreGlobalsReference(normalized)) {
|
|
723
|
-
continue;
|
|
724
|
-
}
|
|
725
|
-
if (isNativeScriptCoreModule(normalized)) {
|
|
726
|
-
continue;
|
|
727
|
-
}
|
|
728
|
-
if (isNativeScriptPluginModule(normalized)) {
|
|
729
|
-
continue;
|
|
730
|
-
}
|
|
731
|
-
deps.add(normalized);
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
return deps;
|
|
736
|
-
}
|
|
737
|
-
/**
|
|
738
|
-
* Clean code: remove Vite/Vue noise, rewrite to vendor
|
|
739
|
-
*/
|
|
740
|
-
function cleanCode(code) {
|
|
741
|
-
let result = code;
|
|
742
|
-
// Remove Vite client and hot module noise
|
|
743
|
-
result = result.replace(PAT.VITE_CLIENT_IMPORT, '');
|
|
744
|
-
result = result.replace(PAT.IMPORT_META_HOT_ASSIGNMENT, '');
|
|
745
|
-
// Keep import.meta.hot call sites; runtime now provides a stable import.meta.hot.
|
|
746
|
-
result = ACTIVE_STRATEGY.preClean(result);
|
|
747
|
-
result = ACTIVE_STRATEGY.rewriteFrameworkImports(result);
|
|
748
|
-
// Vendor manifest-driven import rewrites
|
|
749
|
-
// NOTE: Static and side-effect vendor imports are intentionally NOT rewritten here.
|
|
750
|
-
// They are left as import statements so that ensureNativeScriptModuleBindings()
|
|
751
|
-
// (called later in processCodeForDevice) can transform them using the robust
|
|
752
|
-
// __nsVendorRequire + __nsPick pattern that works on device.
|
|
753
|
-
// Only dynamic imports are handled here since ensureNativeScriptModuleBindings
|
|
754
|
-
// does not process dynamic import() calls.
|
|
755
|
-
try {
|
|
756
|
-
const manifest = getVendorManifest();
|
|
757
|
-
if (manifest) {
|
|
758
|
-
// Dynamic import rewrites: import('pkg') -> Promise.resolve(__nsVendor('id'))
|
|
759
|
-
const dynImportRE = /(import\(\s*["'])([^"']+)(["']\s*\))/g;
|
|
760
|
-
result = result.replace(dynImportRE, (full, pre, spec, post) => {
|
|
761
|
-
if (isNativeScriptCoreModule(spec))
|
|
762
|
-
return full;
|
|
763
|
-
const resolved = resolveVendorSpecifier(spec);
|
|
764
|
-
if (!resolved || /^@nativescript\/core(\b|\/)/i.test(resolved))
|
|
765
|
-
return full;
|
|
766
|
-
return `Promise.resolve(__nsVendor(${JSON.stringify(resolved)}))`;
|
|
767
|
-
});
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
catch (e) {
|
|
771
|
-
// Non-fatal; fallback to original code if manifest logic fails
|
|
772
|
-
}
|
|
773
|
-
result = result.replace(PAT.VITE_CLIENT_IMPORT, '').replace(PAT.IMPORT_META_HOT_ASSIGNMENT, '');
|
|
774
|
-
// Clean up HMR noise
|
|
775
|
-
result = ACTIVE_STRATEGY.postClean(result);
|
|
776
|
-
result = stripCoreGlobalsImports(result);
|
|
777
|
-
return result;
|
|
778
|
-
}
|
|
779
|
-
// ============================================================================
|
|
780
|
-
// APPLICATION IMPORT HELPERS
|
|
781
|
-
// ============================================================================
|
|
782
|
-
/**
|
|
783
|
-
* Check if a path is an application module (not node_modules, not vendor, not relative)
|
|
784
|
-
* This is generic and works for ANY project structure.
|
|
785
|
-
*
|
|
786
|
-
* Examples of application imports: /core/v4/store.ts, /src/utils/helper.ts, /custom/module.ts
|
|
787
|
-
* Examples of NON-application imports: node_modules/..., ~/vendor.mjs, ./relative.ts, ../parent.ts
|
|
788
|
-
*/
|
|
789
|
-
function isApplicationImport(importPath) {
|
|
790
|
-
if (!importPath.startsWith('/')) {
|
|
791
|
-
return false; // Relative paths (./..., ../...) are not application imports
|
|
792
|
-
}
|
|
793
|
-
// Exclude node_modules and special paths
|
|
794
|
-
if (importPath.includes('node_modules') || importPath.startsWith('/@') || importPath.startsWith('~/')) {
|
|
795
|
-
return false;
|
|
796
|
-
}
|
|
797
|
-
return true;
|
|
798
|
-
}
|
|
799
|
-
function stripToProjectRelative(importPath, projectRoot) {
|
|
800
|
-
if (!importPath) {
|
|
801
|
-
return '';
|
|
802
|
-
}
|
|
803
|
-
let normalized = importPath.replace(/\\/g, '/');
|
|
804
|
-
normalized = normalized.replace(/^\/?@fs\//, '/');
|
|
805
|
-
if (normalized.startsWith('file://')) {
|
|
806
|
-
normalized = normalized.replace(/^file:\/\//, '/');
|
|
807
|
-
}
|
|
808
|
-
const documentsMarker = '/Documents/';
|
|
809
|
-
const documentsIndex = normalized.indexOf(documentsMarker);
|
|
810
|
-
if (documentsIndex !== -1) {
|
|
811
|
-
return normalized.substring(documentsIndex + documentsMarker.length);
|
|
812
|
-
}
|
|
813
|
-
if (projectRoot) {
|
|
814
|
-
const normalizedRoot = projectRoot.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
815
|
-
const rootIndex = normalized.indexOf(normalizedRoot);
|
|
816
|
-
if (rootIndex !== -1) {
|
|
817
|
-
const sliced = normalized.substring(rootIndex + normalizedRoot.length);
|
|
818
|
-
return sliced.replace(/^\/+/, '');
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
return normalized.replace(/^\/+/, '');
|
|
822
|
-
}
|
|
823
|
-
/**
|
|
824
|
-
* Convert absolute application import to relative .mjs path for Documents folder
|
|
825
|
-
* /core/v4/store.ts → ./core/v4/store.mjs
|
|
826
|
-
*/
|
|
827
|
-
function getProjectRelativeImportPath(importPath, projectRoot) {
|
|
828
|
-
if (!importPath) {
|
|
829
|
-
return null;
|
|
830
|
-
}
|
|
831
|
-
let normalized = importPath.replace(PAT.QUERY_PATTERN, '');
|
|
832
|
-
normalized = normalized.replace(/\\/g, '/');
|
|
833
|
-
if (normalized.startsWith('file://')) {
|
|
834
|
-
normalized = normalized.replace(/^file:\/\//, '/');
|
|
835
|
-
}
|
|
836
|
-
const documentsMarker = '/Documents/';
|
|
837
|
-
const documentsIndex = normalized.indexOf(documentsMarker);
|
|
838
|
-
if (documentsIndex !== -1) {
|
|
839
|
-
normalized = normalized.substring(documentsIndex + documentsMarker.length);
|
|
840
|
-
}
|
|
841
|
-
else if (projectRoot) {
|
|
842
|
-
const normalizedRoot = projectRoot.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
843
|
-
const rootIndex = normalized.indexOf(normalizedRoot);
|
|
844
|
-
if (rootIndex !== -1) {
|
|
845
|
-
normalized = normalized.substring(rootIndex + normalizedRoot.length);
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
normalized = normalized.replace(/^\/+/, '');
|
|
849
|
-
if (!normalized) {
|
|
850
|
-
return null;
|
|
851
|
-
}
|
|
852
|
-
if (normalized.startsWith('dep-')) {
|
|
853
|
-
return null;
|
|
854
|
-
}
|
|
855
|
-
if (normalized.startsWith('sfc-')) {
|
|
856
|
-
return null;
|
|
857
|
-
}
|
|
858
|
-
normalized = path.posix.normalize(normalized);
|
|
859
|
-
if (!normalized) {
|
|
860
|
-
return null;
|
|
861
|
-
}
|
|
862
|
-
normalized = normalized.replace(/\.(ts|js|tsx|jsx|mjs|mts|cts)$/i, '.mjs');
|
|
863
|
-
if (!normalized.endsWith('.mjs')) {
|
|
864
|
-
normalized = `${normalized}.mjs`;
|
|
865
|
-
}
|
|
866
|
-
return normalized.replace(/^\/+/, '');
|
|
867
|
-
}
|
|
868
|
-
function toDocumentsAbsoluteImport(importPath, projectRoot) {
|
|
869
|
-
const projectRelative = getProjectRelativeImportPath(importPath, projectRoot);
|
|
870
|
-
if (!projectRelative) {
|
|
871
|
-
return null;
|
|
872
|
-
}
|
|
873
|
-
if (isNativeScriptCoreModule(projectRelative)) {
|
|
874
|
-
return null;
|
|
875
|
-
}
|
|
876
|
-
return `__NSDOC__/${projectRelative}`;
|
|
877
|
-
}
|
|
878
|
-
function toAppModuleBaseId(importPath, projectRoot) {
|
|
879
|
-
const projectRelative = getProjectRelativeImportPath(importPath, projectRoot);
|
|
880
|
-
if (!projectRelative) {
|
|
881
|
-
return null;
|
|
882
|
-
}
|
|
883
|
-
const base = projectRelative.replace(/\.mjs$/i, '');
|
|
884
|
-
return `/${base}`;
|
|
885
|
-
}
|
|
886
|
-
function toNodeModulesHttpModuleId(importPath) {
|
|
887
|
-
const nodeModulesSpecifier = normalizeNodeModulesSpecifier(importPath);
|
|
888
|
-
if (!nodeModulesSpecifier) {
|
|
889
|
-
return null;
|
|
890
|
-
}
|
|
891
|
-
return `/ns/m/node_modules/${nodeModulesSpecifier}`;
|
|
892
|
-
}
|
|
893
|
-
function normalizeAbsoluteFilesystemImport(spec, importerPath, projectRoot) {
|
|
894
|
-
if (!spec || typeof spec !== 'string') {
|
|
895
|
-
return null;
|
|
896
|
-
}
|
|
897
|
-
let normalized = spec;
|
|
898
|
-
if (normalized.startsWith('file://')) {
|
|
899
|
-
normalized = normalized.replace(/^file:\/\//, '/');
|
|
900
|
-
}
|
|
901
|
-
if (!normalized.startsWith('/')) {
|
|
902
|
-
return null;
|
|
903
|
-
}
|
|
904
|
-
const containsDocuments = normalized.includes('/Documents/');
|
|
905
|
-
const projectRootNormalized = projectRoot?.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
906
|
-
const containsProjectRoot = projectRootNormalized ? normalized.includes(projectRootNormalized) : false;
|
|
907
|
-
if (isNativeScriptCoreModule(normalized)) {
|
|
908
|
-
return null;
|
|
909
|
-
}
|
|
910
|
-
if (!containsDocuments && !containsProjectRoot) {
|
|
911
|
-
return null;
|
|
912
|
-
}
|
|
913
|
-
const absolute = toDocumentsAbsoluteImport(normalized, projectRoot);
|
|
914
|
-
if (!absolute || absolute === spec) {
|
|
915
|
-
return null;
|
|
916
|
-
}
|
|
917
|
-
return absolute;
|
|
918
|
-
}
|
|
919
|
-
/**
|
|
920
|
-
* After the Angular linker runs on code that Vite has already resolved (bare
|
|
921
|
-
* specifiers → full URLs), the linker injects NEW import statements with bare
|
|
922
|
-
* specifiers (e.g. `import {Component} from '@angular/core'`). These cause:
|
|
923
|
-
* 1. Duplicate-identifier SyntaxErrors (the name was already imported via URL)
|
|
924
|
-
* 2. Unresolvable bare specifiers at runtime on device
|
|
925
|
-
*
|
|
926
|
-
* This function:
|
|
927
|
-
* • builds a map packageName → resolvedURL from existing resolved imports
|
|
928
|
-
* • collects all binding names already imported per package
|
|
929
|
-
* • for each bare-specifier import, removes duplicate bindings
|
|
930
|
-
* • rewrites any genuinely-new bindings to use the resolved URL
|
|
931
|
-
*/
|
|
932
|
-
function deduplicateLinkerImports(code) {
|
|
933
|
-
if (!code)
|
|
934
|
-
return code;
|
|
935
|
-
try {
|
|
936
|
-
const imports = collectTopLevelImportRecords(code);
|
|
937
|
-
if (!imports.length) {
|
|
938
|
-
return code;
|
|
939
|
-
}
|
|
940
|
-
// ── Step 1: collect resolved imports already in the file ──────────
|
|
941
|
-
const pkgUrlMap = new Map();
|
|
942
|
-
const pkgBindings = new Map();
|
|
943
|
-
for (const imp of imports) {
|
|
944
|
-
const url = imp.source;
|
|
945
|
-
if (!/^https?:\/\//.test(url) && !url.startsWith('/')) {
|
|
946
|
-
continue;
|
|
947
|
-
}
|
|
948
|
-
const nmIdx = url.lastIndexOf('/node_modules/');
|
|
949
|
-
if (nmIdx === -1)
|
|
950
|
-
continue;
|
|
951
|
-
const afterNm = url.substring(nmIdx + '/node_modules/'.length);
|
|
952
|
-
const parts = afterNm.split('/');
|
|
953
|
-
const pkg = parts[0].startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
|
|
954
|
-
if (!pkgUrlMap.has(pkg))
|
|
955
|
-
pkgUrlMap.set(pkg, url);
|
|
956
|
-
if (imp.namedBindings.length) {
|
|
957
|
-
if (!pkgBindings.has(pkg))
|
|
958
|
-
pkgBindings.set(pkg, new Set());
|
|
959
|
-
for (const binding of imp.namedBindings) {
|
|
960
|
-
if (binding.importedName)
|
|
961
|
-
pkgBindings.get(pkg).add(binding.importedName);
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
if (pkgUrlMap.size === 0)
|
|
966
|
-
return code;
|
|
967
|
-
// ── Step 2: rewrite bare-specifier imports ───────────────────────
|
|
968
|
-
const edits = [];
|
|
969
|
-
for (const imp of imports) {
|
|
970
|
-
if (!imp.hasOnlyNamedSpecifiers) {
|
|
971
|
-
continue;
|
|
972
|
-
}
|
|
973
|
-
const specifier = imp.source;
|
|
974
|
-
if (specifier.startsWith('/') || specifier.startsWith('.') || specifier.startsWith('http')) {
|
|
975
|
-
continue;
|
|
976
|
-
}
|
|
977
|
-
const parts = specifier.split('/');
|
|
978
|
-
const pkg = specifier.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
|
|
979
|
-
const url = pkgUrlMap.get(pkg);
|
|
980
|
-
if (!url) {
|
|
981
|
-
continue;
|
|
982
|
-
}
|
|
983
|
-
const existing = pkgBindings.get(pkg) || new Set();
|
|
984
|
-
const newBindings = imp.namedBindings.filter((binding) => !existing.has(binding.importedName));
|
|
985
|
-
if (newBindings.length === 0) {
|
|
986
|
-
edits.push({ start: imp.start, end: imp.end, text: '' });
|
|
987
|
-
continue;
|
|
988
|
-
}
|
|
989
|
-
if (newBindings.length === imp.namedBindings.length) {
|
|
990
|
-
continue;
|
|
991
|
-
}
|
|
992
|
-
for (const binding of newBindings) {
|
|
993
|
-
existing.add(binding.importedName);
|
|
994
|
-
}
|
|
995
|
-
edits.push({
|
|
996
|
-
start: imp.start,
|
|
997
|
-
end: imp.end,
|
|
998
|
-
text: `import { ${newBindings.map((binding) => binding.text).join(', ')} } from ${JSON.stringify(url)};`,
|
|
999
|
-
});
|
|
1000
|
-
}
|
|
1001
|
-
if (!edits.length) {
|
|
1002
|
-
return code;
|
|
1003
|
-
}
|
|
1004
|
-
let next = code;
|
|
1005
|
-
for (const edit of edits.sort((left, right) => right.start - left.start)) {
|
|
1006
|
-
next = next.slice(0, edit.start) + edit.text + next.slice(edit.end);
|
|
1007
|
-
}
|
|
1008
|
-
return next;
|
|
1009
|
-
}
|
|
1010
|
-
catch {
|
|
1011
|
-
return code;
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
export function wrapCommonJsModuleForDevice(code) {
|
|
1015
|
-
if (!code)
|
|
1016
|
-
return code;
|
|
1017
|
-
try {
|
|
1018
|
-
const hasExportDefault = /\bexport\s+default\b/.test(code) || /export\s*\{\s*default\s*(?:as\s*default)?\s*\}/.test(code);
|
|
1019
|
-
const hasNamedExports = /\bexport\s+(?:const|let|var|function|class|async)\b/.test(code) || /\bexport\s*\{/.test(code);
|
|
1020
|
-
const hasCjsExports = /\bmodule\s*\.\s*exports\b/.test(code) || /\bexports\s*\.\s*\w/.test(code);
|
|
1021
|
-
if (hasExportDefault || hasNamedExports || !hasCjsExports) {
|
|
1022
|
-
return code;
|
|
1023
|
-
}
|
|
1024
|
-
const namedExports = new Set();
|
|
1025
|
-
const exportsRe = /\bexports\s*\.\s*([A-Za-z_$][\w$]*)\s*=/g;
|
|
1026
|
-
let em;
|
|
1027
|
-
while ((em = exportsRe.exec(code)) !== null) {
|
|
1028
|
-
const name = em[1];
|
|
1029
|
-
if (name !== '__esModule' && name !== 'default') {
|
|
1030
|
-
namedExports.add(name);
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
const defPropRe = /Object\s*\.\s*defineProperty\s*\(\s*exports\s*,\s*['"]([^'"]+)['"]/g;
|
|
1034
|
-
while ((em = defPropRe.exec(code)) !== null) {
|
|
1035
|
-
const name = em[1];
|
|
1036
|
-
if (name !== '__esModule' && name !== 'default') {
|
|
1037
|
-
namedExports.add(name);
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
let suffix = `\nvar __cjs_mod = module.exports;\nexport default __cjs_mod;\n`;
|
|
1041
|
-
if (namedExports.size) {
|
|
1042
|
-
const entries = Array.from(namedExports);
|
|
1043
|
-
const temps = entries.map((name, i) => `var __cjs_e${i} = __cjs_mod[${JSON.stringify(name)}];`);
|
|
1044
|
-
const reExports = entries.map((name, i) => `__cjs_e${i} as ${name}`);
|
|
1045
|
-
suffix += `${temps.join(' ')}\nexport { ${reExports.join(', ')} };\n`;
|
|
1046
|
-
}
|
|
1047
|
-
const prelude = `var module = { exports: {} }; var exports = module.exports;\n` +
|
|
1048
|
-
`var __ns_cjs_require_base = (typeof globalThis.__nsBaseRequire === 'function' ? globalThis.__nsBaseRequire : (typeof globalThis.__nsRequire === 'function' ? globalThis.__nsRequire : (typeof globalThis.require === 'function' ? globalThis.require : undefined)));\n` +
|
|
1049
|
-
`var __ns_cjs_require_kind = (typeof globalThis.__nsBaseRequire === 'function' ? 'base-require' : (typeof globalThis.__nsRequire === 'function' ? 'vendor-require' : 'global-require'));\n` +
|
|
1050
|
-
`var require = function(spec) {\n` +
|
|
1051
|
-
` if (!__ns_cjs_require_base) { throw new Error('require is not defined'); }\n` +
|
|
1052
|
-
` try { var __nsRecord = globalThis.__NS_RECORD_MODULE_PROVENANCE__; if (typeof __nsRecord === 'function') { __nsRecord(String(spec), { kind: __ns_cjs_require_kind, specifier: String(spec), via: 'cjs-wrapper', parent: (typeof import.meta !== 'undefined' && import.meta && import.meta.url) ? import.meta.url : undefined }); } } catch (e) {}\n` +
|
|
1053
|
-
` var mod = __ns_cjs_require_base(spec);\n` +
|
|
1054
|
-
` try {\n` +
|
|
1055
|
-
` if (mod && (typeof mod === 'object' || typeof mod === 'function') && mod.default !== undefined) {\n` +
|
|
1056
|
-
` var keys = [];\n` +
|
|
1057
|
-
` try { keys = Object.keys(mod); } catch (e) {}\n` +
|
|
1058
|
-
` var defaultOnly = keys.length === 1 && keys[0] === 'default';\n` +
|
|
1059
|
-
` var esModuleOnly = keys.length === 2 && keys.indexOf('default') !== -1 && keys.indexOf('__esModule') !== -1;\n` +
|
|
1060
|
-
` if (mod.__esModule || defaultOnly || esModuleOnly) { return mod.default; }\n` +
|
|
1061
|
-
` }\n` +
|
|
1062
|
-
` } catch (e) {}\n` +
|
|
1063
|
-
` return mod;\n` +
|
|
1064
|
-
`};\n`;
|
|
1065
|
-
return `${prelude}${code}${suffix}`;
|
|
1066
|
-
}
|
|
1067
|
-
catch {
|
|
1068
|
-
return code;
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
/**
|
|
1072
|
-
* Process code for device: inject globals, remove framework imports
|
|
1073
|
-
*/
|
|
1074
|
-
function processCodeForDevice(code, isVitePreBundled, preserveVendorImports = false, isNodeModule = false, sourceId, options) {
|
|
1075
|
-
let result = code;
|
|
1076
|
-
const resolvedSpecifierOverrides = options?.resolvedSpecifierOverrides || getProcessCodeResolvedSpecifierOverrides(sourceId, getProjectRootPath());
|
|
1077
|
-
const bindingOptions = {
|
|
1078
|
-
preserveNonPluginVendorImports: preserveVendorImports,
|
|
1079
|
-
resolvedSpecifierOverrides,
|
|
1080
|
-
};
|
|
1081
|
-
// Ensure Angular partial declarations are linked before any sanitizers run so runtime never hits the JIT path.
|
|
1082
|
-
result = linkAngularPartialsIfNeeded(result);
|
|
1083
|
-
// Post-linker: deduplicate/resolve imports the Angular linker injected with bare specifiers
|
|
1084
|
-
result = deduplicateLinkerImports(result);
|
|
1085
|
-
// First: aggressively strip any lingering virtual dynamic-import-helper before anything else.
|
|
1086
|
-
// Doing this up-front prevents downstream dependency collection from seeing the virtual id.
|
|
1087
|
-
result = stripViteDynamicImportVirtual(result);
|
|
1088
|
-
// Skip reactive injection for Vite pre-bundled deps (they have Vue bundled already)
|
|
1089
|
-
if (isVitePreBundled) {
|
|
1090
|
-
return result;
|
|
1091
|
-
}
|
|
1092
|
-
// Inject ALL NativeScript/build globals at the top (matching global-defines.ts)
|
|
1093
|
-
// This ensures any code using __DEV__, __ANDROID__, __IOS__, etc. works correctly
|
|
1094
|
-
const allGlobals = [
|
|
1095
|
-
// Minimal process shim — populated with CLI --env.* flags at module load time.
|
|
1096
|
-
// In production builds, Vite/Rollup replaces process.env.* statically.
|
|
1097
|
-
// In HMR dev mode the code runs as-is on device, so we need the shim.
|
|
1098
|
-
`if (typeof process === "undefined") { globalThis.process = { env: ${__processEnvJson} }; } else if (!process.env) { process.env = ${__processEnvJson}; }`,
|
|
1099
|
-
'const __ANDROID__ = globalThis.__ANDROID__ !== undefined ? globalThis.__ANDROID__ : false;',
|
|
1100
|
-
'const __IOS__ = globalThis.__IOS__ !== undefined ? globalThis.__IOS__ : false;',
|
|
1101
|
-
'const __VISIONOS__ = globalThis.__VISIONOS__ !== undefined ? globalThis.__VISIONOS__ : false;',
|
|
1102
|
-
'const __APPLE__ = globalThis.__APPLE__ !== undefined ? globalThis.__APPLE__ : (__IOS__ || __VISIONOS__);',
|
|
1103
|
-
'const __DEV__ = globalThis.__DEV__ !== undefined ? globalThis.__DEV__ : false;',
|
|
1104
|
-
'const __COMMONJS__ = globalThis.__COMMONJS__ !== undefined ? globalThis.__COMMONJS__ : false;',
|
|
1105
|
-
'const __NS_WEBPACK__ = globalThis.__NS_WEBPACK__ !== undefined ? globalThis.__NS_WEBPACK__ : false;',
|
|
1106
|
-
'const __NS_ENV_VERBOSE__ = globalThis.__NS_ENV_VERBOSE__ !== undefined ? !!globalThis.__NS_ENV_VERBOSE__ : false;',
|
|
1107
|
-
"const __CSS_PARSER__ = globalThis.__CSS_PARSER__ !== undefined ? globalThis.__CSS_PARSER__ : 'css-tree';",
|
|
1108
|
-
'const __UI_USE_XML_PARSER__ = globalThis.__UI_USE_XML_PARSER__ !== undefined ? globalThis.__UI_USE_XML_PARSER__ : true;',
|
|
1109
|
-
'const __UI_USE_EXTERNAL_RENDERER__ = globalThis.__UI_USE_EXTERNAL_RENDERER__ !== undefined ? globalThis.__UI_USE_EXTERNAL_RENDERER__ : false;',
|
|
1110
|
-
'const __TEST__ = globalThis.__TEST__ !== undefined ? globalThis.__TEST__ : false;',
|
|
1111
|
-
];
|
|
1112
|
-
result = allGlobals.join('\n') + '\n' + result;
|
|
1113
|
-
const nodeModuleProvenancePrelude = buildNodeModuleProvenancePrelude(sourceId);
|
|
1114
|
-
if (nodeModuleProvenancePrelude) {
|
|
1115
|
-
result = nodeModuleProvenancePrelude + result;
|
|
1116
|
-
}
|
|
1117
|
-
// AST normalization: inject /ns/rt helper aliases for underscore-prefixed identifiers.
|
|
1118
|
-
// ONLY for app source files — library code in node_modules should be served as-is.
|
|
1119
|
-
// Running the normalizer on libraries like tslib injects harmful destructures
|
|
1120
|
-
// (e.g., `const { SuppressedError } = __ns_rt_ns_1`) that shadow globals.
|
|
1121
|
-
if (!isNodeModule) {
|
|
1122
|
-
try {
|
|
1123
|
-
result = astNormalizeModuleImportsAndHelpers(result);
|
|
1124
|
-
}
|
|
1125
|
-
catch { }
|
|
1126
|
-
// Verify there are no duplicate top-level const/let bindings after AST normalization
|
|
1127
|
-
try {
|
|
1128
|
-
result = astVerifyAndAnnotateDuplicates(result);
|
|
1129
|
-
}
|
|
1130
|
-
catch { }
|
|
1131
|
-
}
|
|
1132
|
-
// If AST marker present OR this is a node_modules file, skip regex-based helper
|
|
1133
|
-
// alias injection. Library code should NOT get /ns/rt destructures injected —
|
|
1134
|
-
// underscore-prefixed identifiers in libraries are internal variables, not NS helpers.
|
|
1135
|
-
if (!isNodeModule && !/^\s*(?:\/\/|\/\*) \[ast-normalized\]/m.test(result)) {
|
|
1136
|
-
try {
|
|
1137
|
-
const underscored = new Set();
|
|
1138
|
-
const re = /(^|[^.\w$])_([A-Za-z]\w*)\b/g;
|
|
1139
|
-
let m;
|
|
1140
|
-
while ((m = re.exec(result))) {
|
|
1141
|
-
const name = m[2];
|
|
1142
|
-
if (name === 'ctx' || name === 'cache')
|
|
1143
|
-
continue;
|
|
1144
|
-
if (name.startsWith('hoisted_'))
|
|
1145
|
-
continue;
|
|
1146
|
-
if (name.startsWith('component_'))
|
|
1147
|
-
continue;
|
|
1148
|
-
if (name.startsWith('directive_'))
|
|
1149
|
-
continue;
|
|
1150
|
-
if (name === 'sfc_main')
|
|
1151
|
-
continue;
|
|
1152
|
-
if (name === 'ns_sfc__' || name.startsWith('ns_sfc'))
|
|
1153
|
-
continue;
|
|
1154
|
-
if (name.startsWith('sfc'))
|
|
1155
|
-
continue;
|
|
1156
|
-
try {
|
|
1157
|
-
const declRE = new RegExp(`(^|\\n)\\s*(?:const|let|var)\\s+_${name}\\b`);
|
|
1158
|
-
if (declRE.test(result))
|
|
1159
|
-
continue;
|
|
1160
|
-
}
|
|
1161
|
-
catch { }
|
|
1162
|
-
underscored.add(name);
|
|
1163
|
-
}
|
|
1164
|
-
if (underscored.size) {
|
|
1165
|
-
const needed = [];
|
|
1166
|
-
for (const n of underscored) {
|
|
1167
|
-
const aliasNeedle = new RegExp(`\\b${n}\\s+as\\s+_${n}\\b`);
|
|
1168
|
-
if (!aliasNeedle.test(result))
|
|
1169
|
-
needed.push(n);
|
|
1170
|
-
}
|
|
1171
|
-
if (needed.length) {
|
|
1172
|
-
const importLine = `import { ${needed.map((n) => `${n} as _${n}`).join(', ')} } from "/ns/rt";\n`;
|
|
1173
|
-
result = importLine + result;
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
catch { }
|
|
1178
|
-
}
|
|
1179
|
-
// In strict dev mode, proactively surface duplicate-binding diagnostics to avoid "already declared" runtime errors
|
|
1180
|
-
try {
|
|
1181
|
-
if (/^\s*\/\/ \[ast-verify\]\[duplicate-bindings\]/m.test(result)) {
|
|
1182
|
-
const diagnosticLine = (result.match(/^\s*\/\/ \[ast-verify\]\[duplicate-bindings\][^\n]*/m) || [])[0] || '// [ast-verify][duplicate-bindings]';
|
|
1183
|
-
const brief = diagnosticLine.replace(/^[^:]*:?\s?/, '');
|
|
1184
|
-
const escaped = brief.replace(/["\\]/g, '\\$&');
|
|
1185
|
-
const thrower = `throw new Error("[nsv-hmr] Duplicate top-level bindings detected: ${escaped}");`;
|
|
1186
|
-
result = `${thrower}\n` + result;
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
catch { }
|
|
1190
|
-
// Remove Vite internal imports (dynamic-import-helper, etc.)
|
|
1191
|
-
// This handles both standalone lines and concatenated imports on the same line
|
|
1192
|
-
result = result.replace(/import\s+[^;]+\s+from\s+["']\/@id\/[^"']*["'];?\s*/g, '');
|
|
1193
|
-
// Also remove side-effect only virtual id imports like: import "/@id/__x00__vite/dynamic-import-helper.js";
|
|
1194
|
-
// These can slip through and cause the NativeScript runtime to attempt resolving
|
|
1195
|
-
// a non-existent physical file (e.g. /@id/__x00__vite/dynamic-import-helper.js) leading to
|
|
1196
|
-
// module not found crashes during HMR evaluation.
|
|
1197
|
-
if (/^[\t ]*import\s+["']\/@id\/[^"']+["'];?\s*$/m.test(result)) {
|
|
1198
|
-
result = result.replace(/^[\t ]*import\s+["']\/@id\/[^"']+["'];?\s*$/gm, '');
|
|
1199
|
-
// Inject a lightweight marker comment (harmless if left in output) so devs can confirm rewrite took place when viewing streamed artifact.
|
|
1200
|
-
result = `// [hmr-sanitize] stripped virtual /@id/ side-effect imports\n${result}`;
|
|
1201
|
-
}
|
|
1202
|
-
// IMPORTANT: Perform vendor-module binding injection BEFORE stripping Vite prebundle imports.
|
|
1203
|
-
// This allows the rewriter to see and canonicalize '/node_modules/.vite/deps/*' specifiers back
|
|
1204
|
-
// to their package ids (e.g., '@nativescript/firebase-core') and generate require-based bindings
|
|
1205
|
-
// so named imports like `{ firebase }` are preserved as const bindings.
|
|
1206
|
-
//
|
|
1207
|
-
// Some upstream transforms can emit a multiline form:
|
|
1208
|
-
// import { x } from
|
|
1209
|
-
// "/node_modules/.vite/deps/...";
|
|
1210
|
-
// If we don't normalize it, later stripping of naked string-only lines can leave
|
|
1211
|
-
// an invalid `import ... from` statement.
|
|
1212
|
-
try {
|
|
1213
|
-
result = result.replace(/(^|\n)([\t ]*import\s+[^;]*?\s+from)\s*\n\s*("\/?node_modules\/\.vite\/deps\/[^"\n]+"\s*;?\s*)/gm, (_m, p1, p2, p3) => `${p1}${p2} ${p3}`);
|
|
1214
|
-
}
|
|
1215
|
-
catch { }
|
|
1216
|
-
// When preserveVendorImports is true (HMR /ns/m/ endpoint), skip the
|
|
1217
|
-
// __nsVendorRequire + __nsPick rewrite. Vendor imports stay as bare
|
|
1218
|
-
// specifiers so the device-side import map resolves them via V8's native
|
|
1219
|
-
// module system, which correctly handles export * re-exports.
|
|
1220
|
-
result = ensureNativeScriptModuleBindings(result, bindingOptions);
|
|
1221
|
-
// Repair any accidental "import ... = expr" assignments that may have slipped in.
|
|
1222
|
-
try {
|
|
1223
|
-
result = repairImportEqualsAssignments(result);
|
|
1224
|
-
}
|
|
1225
|
-
catch { }
|
|
1226
|
-
// Strip Vite prebundle deps imports (both named and side-effect) and any malformed const string artifacts
|
|
1227
|
-
// Example problematic line observed: const "/node_modules/.vite/deps/@nativescript_firebase-messaging.js?v=...";
|
|
1228
|
-
if (/node_modules\/\.vite\/deps\//.test(result)) {
|
|
1229
|
-
result = rewriteVitePrebundleImportsForDevice(result, preserveVendorImports);
|
|
1230
|
-
// Malformed const string lines accidentally produced by upstream transforms
|
|
1231
|
-
result = result.replace(/^[\t ]*const\s+["']\/?node_modules\/\.vite\/deps\/[^"']+["'];?\s*$/gm, '// [hmr-sanitize] stripped malformed const prebundle ref\n');
|
|
1232
|
-
// Naked string-only lines pointing at prebundle deps
|
|
1233
|
-
result = result.replace(/^[\t ]*["']\/?node_modules\/\.vite\/deps\/[^"']+["'];?\s*$/gm, '// [hmr-sanitize] stripped prebundle side-effect literal\n');
|
|
1234
|
-
}
|
|
1235
|
-
// Dynamic aliasing covers helper imports; no further static list handling needed.
|
|
1236
|
-
// Handle navigation helpers (dev bridge): $navigateTo, $navigateBack
|
|
1237
|
-
// Note: do NOT inject $showModal as a named import; AST normalizer/destructure covers it when imported,
|
|
1238
|
-
// and free-uses are handled via AST injection. This avoids duplicate identifier redeclarations.
|
|
1239
|
-
if (/\$(?:navigate(?:To|Back)|showModal)\b/.test(result)) {
|
|
1240
|
-
const navHelpers = [];
|
|
1241
|
-
// Only consider free uses (not property access like obj.$navigateTo)
|
|
1242
|
-
const needTo = /(^|[^.\w$])\$navigateTo\b/.test(result);
|
|
1243
|
-
const needBack = /(^|[^.\w$])\$navigateBack\b/.test(result);
|
|
1244
|
-
const needShow = /\$showModal\b/.test(result);
|
|
1245
|
-
if (needTo)
|
|
1246
|
-
navHelpers.push('$navigateTo');
|
|
1247
|
-
if (needBack)
|
|
1248
|
-
navHelpers.push('$navigateBack');
|
|
1249
|
-
// Intentionally exclude $showModal from navHelpers injection to prevent named import reinsertion
|
|
1250
|
-
// Remove any direct imports/usages that might shadow globals
|
|
1251
|
-
// 1) From 'vue' or 'nativescript-vue' sources
|
|
1252
|
-
result = removeNamedImports(result, navHelpers);
|
|
1253
|
-
// 2) From our runtime bridge '/ns/rt' (versioned or not)
|
|
1254
|
-
try {
|
|
1255
|
-
// Do NOT re-introduce named imports from /ns/rt for nav helpers; drop them entirely after removing nav helpers.
|
|
1256
|
-
const rtNamedRE = /(^|\n)\s*import\s*\{([^}]+)\}\s*from\s*["'](?:https?:\/\/[^"']+)?\/ns\/rt(?:\/[\d]+)?["'];?\s*/gm;
|
|
1257
|
-
// Also compute locally-declared helpers to drop regardless of free-use detection
|
|
1258
|
-
const hasLocalToForDrop = /(^|[\n;])\s*(?:const|let|var|function)\s+\$navigateTo\b/.test(result);
|
|
1259
|
-
const hasLocalBackForDrop = /(^|[\n;])\s*(?:const|let|var|function)\s+\$navigateBack\b/.test(result);
|
|
1260
|
-
result = result.replace(rtNamedRE, (full, pfx, specList) => {
|
|
1261
|
-
const drop = new Set(navHelpers);
|
|
1262
|
-
if (hasLocalToForDrop)
|
|
1263
|
-
drop.add('$navigateTo');
|
|
1264
|
-
if (hasLocalBackForDrop)
|
|
1265
|
-
drop.add('$navigateBack');
|
|
1266
|
-
const remaining = String(specList)
|
|
1267
|
-
.split(',')
|
|
1268
|
-
.map((s) => s.trim())
|
|
1269
|
-
.filter(Boolean)
|
|
1270
|
-
.filter((entry) => {
|
|
1271
|
-
const base = entry.split(/\s+as\s+/i)[0].trim();
|
|
1272
|
-
return !drop.has(base);
|
|
1273
|
-
});
|
|
1274
|
-
if (!remaining.length)
|
|
1275
|
-
return pfx || '';
|
|
1276
|
-
// Preserve non-navigation named imports for later normalization
|
|
1277
|
-
return `${pfx}import { ${remaining.join(', ')} } from "/ns/rt";`;
|
|
1278
|
-
});
|
|
1279
|
-
// Also strip $navigateTo/$navigateBack from any destructuring previously created from /ns/rt
|
|
1280
|
-
// Also remove from destructures bound off any __ns_rt_ns temp (including _re)
|
|
1281
|
-
const rtDestructureRE = /(^|[\n;])\s*const\s*\{([^}]+)\}\s*=\s*(__ns_rt_ns(?:\d+|_re))\s*;?\s*/gm;
|
|
1282
|
-
result = result.replace(rtDestructureRE, (full, pfx, specList, varName) => {
|
|
1283
|
-
const cleaned = String(specList)
|
|
1284
|
-
.split(',')
|
|
1285
|
-
.map((s) => s.trim())
|
|
1286
|
-
.filter(Boolean)
|
|
1287
|
-
.filter((seg) => {
|
|
1288
|
-
const lhs = seg.split(':')[0].trim();
|
|
1289
|
-
return !/^\$navigate(?:To|Back)$/.test(lhs);
|
|
1290
|
-
});
|
|
1291
|
-
if (!cleaned.length)
|
|
1292
|
-
return pfx || '';
|
|
1293
|
-
return `${pfx}const { ${cleaned.join(', ')} } = ${varName};`;
|
|
1294
|
-
});
|
|
1295
|
-
}
|
|
1296
|
-
catch { }
|
|
1297
|
-
// Inject named imports from /ns/rt to provide bindings without redeclaration collisions
|
|
1298
|
-
const imports = [];
|
|
1299
|
-
const hasImportTo = /(^|\n)\s*import\s*\{[^}]*\$navigateTo[^}]*\}\s*from\s*["'](?:https?:\/\/[^"']+)?\/ns\/rt(?:\/[\d]+)?["']/.test(result);
|
|
1300
|
-
const hasImportBack = /(^|\n)\s*import\s*\{[^}]*\$navigateBack[^}]*\}\s*from\s*["'](?:https?:\/\/[^"']+)?\/ns\/rt(?:\/[\d]+)?["']/.test(result);
|
|
1301
|
-
const hasImportShow = /(^|\n)\s*import\s*\{[^}]*\$showModal[^}]*\}\s*from\s*["'](?:https?:\/\/[^"']+)?\/ns\/rt(?:\/[\d]+)?["']/.test(result);
|
|
1302
|
-
// Avoid adding named imports if a local binding already exists (e.g., wrapper const)
|
|
1303
|
-
const hasLocalTo = /(^|[\n;])\s*(?:const|let|var|function)\s+\$navigateTo\b/.test(result);
|
|
1304
|
-
const hasLocalBack = /(^|[\n;])\s*(?:const|let|var|function)\s+\$navigateBack\b/.test(result);
|
|
1305
|
-
if (needTo && !hasImportTo && !hasLocalTo)
|
|
1306
|
-
imports.push('$navigateTo');
|
|
1307
|
-
if (needBack && !hasImportBack && !hasLocalBack)
|
|
1308
|
-
imports.push('$navigateBack');
|
|
1309
|
-
// Do not inject $showModal named import; avoid duplicates with destructures created upstream
|
|
1310
|
-
if (imports.length) {
|
|
1311
|
-
result = `import { ${imports.join(', ')} } from "/ns/rt";\n` + result;
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
// Ensure vendor bindings also apply after potential wrapper injections above
|
|
1315
|
-
// (idempotent: second pass will be a no-op if imports already consumed).
|
|
1316
|
-
result = ensureNativeScriptModuleBindings(result, bindingOptions);
|
|
1317
|
-
try {
|
|
1318
|
-
result = repairImportEqualsAssignments(result);
|
|
1319
|
-
}
|
|
1320
|
-
catch { }
|
|
1321
|
-
// Rewrite any previously-injected vendor-based core access to the unified HTTP core bridge
|
|
1322
|
-
try {
|
|
1323
|
-
const vendorCoreRE1 = /globalThis\.__nsVendor\s*\(\s*["']@nativescript\/core["']\s*\)/g;
|
|
1324
|
-
const vendorCoreRE2 = /__nsVendor\s*\(\s*["']@nativescript\/core["']\s*\)/g;
|
|
1325
|
-
if (vendorCoreRE1.test(result) || vendorCoreRE2.test(result)) {
|
|
1326
|
-
// Ensure an import for the core bridge exists
|
|
1327
|
-
const hasImport = /import\s+__ns_core_bridge\s+from\s+["'][^"']*\/(?:\@ns|ns)\/core(?:\/[\d]+)?(?:\?p=[^"']+)?["']\s*;?/.test(result) || /(^|\n)\s*import\s+__ns_core_bridge\s+from\s+["']\/(?:\@ns|ns)\/core["']\s*;?/m.test(result);
|
|
1328
|
-
if (!hasImport) {
|
|
1329
|
-
result = `import __ns_core_bridge from "/ns/core";\n` + result;
|
|
1330
|
-
}
|
|
1331
|
-
result = result.replace(vendorCoreRE1, '__ns_core_bridge');
|
|
1332
|
-
result = result.replace(vendorCoreRE2, '__ns_core_bridge');
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
catch { }
|
|
1336
|
-
// Rewrite any explicit require('@nativescript/core[/sub]') calls to the unified core bridge
|
|
1337
|
-
try {
|
|
1338
|
-
const reqCoreRE1 = /(^|[^.\w$])require\(\s*["']@nativescript\/core([^"'\n]*)["']\s*\)/g;
|
|
1339
|
-
const reqCoreRE2 = /(?:globalThis|window|self)\.require\(\s*["']@nativescript\/core([^"'\n]*)["']\s*\)/g;
|
|
1340
|
-
if (reqCoreRE1.test(result) || reqCoreRE2.test(result)) {
|
|
1341
|
-
const hasImport = /import\s+__ns_core_bridge\s+from\s+["'][^"']*\/(?:\@ns|ns)\/core(?:\/[\d]+)?(?:\?p=[^"']+)?["']\s*;?/.test(result) || /(^|\n)\s*import\s+__ns_core_bridge\s+from\s+["']\/(?:\@ns|ns)\/core["']\s*;?/m.test(result);
|
|
1342
|
-
if (!hasImport) {
|
|
1343
|
-
result = `import __ns_core_bridge from "/ns/core";\n` + result;
|
|
1344
|
-
}
|
|
1345
|
-
result = result.replace(reqCoreRE1, (_m, p1, _sub) => `${p1}__ns_core_bridge`);
|
|
1346
|
-
result = result.replace(reqCoreRE2, '__ns_core_bridge');
|
|
1347
|
-
}
|
|
1348
|
-
}
|
|
1349
|
-
catch { }
|
|
1350
|
-
// Normalize stray string-literal side-effect lines that still reference @nativescript/core
|
|
1351
|
-
// into proper imports of the unified core bridge. This prevents the local-core-path
|
|
1352
|
-
// fast-fail from triggering due to upstream transforms that emitted naked literals.
|
|
1353
|
-
try {
|
|
1354
|
-
result = normalizeStrayCoreStringLiterals(result);
|
|
1355
|
-
}
|
|
1356
|
-
catch { }
|
|
1357
|
-
try {
|
|
1358
|
-
result = fixDanglingCoreFrom(result);
|
|
1359
|
-
}
|
|
1360
|
-
catch { }
|
|
1361
|
-
try {
|
|
1362
|
-
result = normalizeAnyCoreSpecToBridge(result);
|
|
1363
|
-
}
|
|
1364
|
-
catch { }
|
|
1365
|
-
result = ensureVariableDynamicImportHelper(result);
|
|
1366
|
-
// Normalize any lingering @nativescript/core imports to the /ns/core bridge (non-destructive best-effort)
|
|
1367
|
-
try {
|
|
1368
|
-
// Rewrite named imports from the /ns/core bridge into default import + destructuring.
|
|
1369
|
-
// This makes `import { Frame } from '@nativescript/core'` work even if the bridge provides only a default export.
|
|
1370
|
-
{
|
|
1371
|
-
let __core_ns_seq = 0;
|
|
1372
|
-
const toDestructureCore = (specList) => specList
|
|
1373
|
-
.split(',')
|
|
1374
|
-
.map((s) => s.trim())
|
|
1375
|
-
.filter(Boolean)
|
|
1376
|
-
.map((seg) => {
|
|
1377
|
-
const m = seg.split(/\s+as\s+/i);
|
|
1378
|
-
return m.length === 2 ? `${m[0].trim()}: ${m[1].trim()}` : seg;
|
|
1379
|
-
})
|
|
1380
|
-
.join(', ');
|
|
1381
|
-
const reNamed = /(^|\n)\s*import\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[\d]+)?(?:\?p=[^"']+)?)['"];?\s*/gm;
|
|
1382
|
-
result = result.replace(reNamed, (_full, pfx, specList, src) => {
|
|
1383
|
-
// Deep subpath URLs serve actual ESM with real named exports — skip.
|
|
1384
|
-
if (isDeepCoreSubpath(src))
|
|
1385
|
-
return _full;
|
|
1386
|
-
__core_ns_seq++;
|
|
1387
|
-
const tmp = `__ns_core_ns${__core_ns_seq}`;
|
|
1388
|
-
const decl = `const { ${toDestructureCore(specList)} } = ${tmp};`;
|
|
1389
|
-
return `${pfx}import ${tmp} from ${JSON.stringify(src)};\n${decl}\n`;
|
|
1390
|
-
});
|
|
1391
|
-
const reMixed = /(^|\n)\s*import\s+([A-Za-z_$][\w$]*)\s*,\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[\d]+)?(?:\?p=[^"']+)?)['"];?\s*/gm;
|
|
1392
|
-
result = result.replace(reMixed, (_full, pfx, defName, specList, src) => {
|
|
1393
|
-
if (isDeepCoreSubpath(src))
|
|
1394
|
-
return _full;
|
|
1395
|
-
const decl = `const { ${toDestructureCore(specList)} } = ${defName};`;
|
|
1396
|
-
return `${pfx}import ${defName} from ${JSON.stringify(src)};\n${decl}\n`;
|
|
1397
|
-
});
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
catch { }
|
|
1401
|
-
// Note: Removed legacy var-based underscore prelude to avoid const/var redeclaration conflicts.
|
|
1402
|
-
// Normalize concatenated imports that may have ended up after a statement on the same line
|
|
1403
|
-
try {
|
|
1404
|
-
// Common concatenations
|
|
1405
|
-
// Keep a single semicolon before the import to avoid generating ';;'
|
|
1406
|
-
result = result.replace(/;\s*import\s+/g, ';\nimport ');
|
|
1407
|
-
result = result.replace(/}\s*import\s+/g, '}\nimport ');
|
|
1408
|
-
// Fallback: ensure any static import that isn't at start of line gets a newline before it.
|
|
1409
|
-
// Only match after statement-ending characters (;, }, ), ], quotes) — NOT after `*` or
|
|
1410
|
-
// spaces inside JSDoc comment blocks, which would accidentally extract example imports
|
|
1411
|
-
// from documentation comments and hoist them as real code.
|
|
1412
|
-
result = result.replace(/([;}\)\]'"`])\s*(import\s+[^;\n]*\s+from\s*["'][^"']+["'])/g, '$1\n$2');
|
|
1413
|
-
}
|
|
1414
|
-
catch { }
|
|
1415
|
-
// Collapse duplicate destructuring from the same temp namespace var (e.g., multiple const { x } = __ns_rt_ns1)
|
|
1416
|
-
try {
|
|
1417
|
-
const collapse = (code, prefix) => {
|
|
1418
|
-
const re = new RegExp(`(^|\\n)\\s*const\\s*\\{([^}]+)\\}\\s*=\\s*(${prefix}\\d+)\\s*;?\\s*`, 'gm');
|
|
1419
|
-
const byVar = {};
|
|
1420
|
-
code.replace(re, (_full, _pfx, specList, varName) => {
|
|
1421
|
-
const set = (byVar[varName] || (byVar[varName] = new Set()));
|
|
1422
|
-
String(specList)
|
|
1423
|
-
.split(',')
|
|
1424
|
-
.map((s) => s.trim())
|
|
1425
|
-
.filter(Boolean)
|
|
1426
|
-
.forEach((seg) => set.add(seg));
|
|
1427
|
-
return '';
|
|
1428
|
-
});
|
|
1429
|
-
if (Object.keys(byVar).length) {
|
|
1430
|
-
// Remove all existing destructures first
|
|
1431
|
-
code = code.replace(re, (full, pfx) => pfx || '');
|
|
1432
|
-
// Re-emit one per var
|
|
1433
|
-
const blocks = Object.entries(byVar).map(([varName, set]) => `const { ${Array.from(set).join(', ')} } = ${varName};`);
|
|
1434
|
-
code = blocks.join('\n') + '\n' + code;
|
|
1435
|
-
}
|
|
1436
|
-
return code;
|
|
1437
|
-
};
|
|
1438
|
-
result = collapse(result, '__ns_rt_ns');
|
|
1439
|
-
result = collapse(result, '__ns_core_ns');
|
|
1440
|
-
}
|
|
1441
|
-
catch { }
|
|
1442
|
-
// After consolidating destructures, hoist static import declarations to the very top so imports
|
|
1443
|
-
// always come before any statements that might reference their bindings. This ordering avoids
|
|
1444
|
-
// device runtimes that are stricter about imports-first semantics during module instantiation.
|
|
1445
|
-
try {
|
|
1446
|
-
result = hoistTopLevelStaticImports(result);
|
|
1447
|
-
}
|
|
1448
|
-
catch { }
|
|
1449
|
-
// Final safety: normalize any lingering named imports from /ns/rt into default+destructure
|
|
1450
|
-
// Skip for node_modules (no /ns/rt helpers needed) and when AST marker present
|
|
1451
|
-
try {
|
|
1452
|
-
if (!isNodeModule && !/^\s*\/\* \[ast-normalized\] \*\//m.test(result)) {
|
|
1453
|
-
result = ensureDestructureRtImports(result);
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
catch { }
|
|
1457
|
-
// Post-pass: if both a destructure from __ns_rt_ns* and named imports from /ns/rt exist,
|
|
1458
|
-
// remove overlapping named specifiers to avoid "Identifier has already been declared".
|
|
1459
|
-
try {
|
|
1460
|
-
// Collect all bindings destructured from any __ns_rt_ns* temp
|
|
1461
|
-
const rtDestructureRE = /(^|\n)\s*const\s*\{([^}]+)\}\s*=\s*(__ns_rt_ns(?:\d+|_re))\s*;?\s*/gm;
|
|
1462
|
-
const rtBound = new Set();
|
|
1463
|
-
let m;
|
|
1464
|
-
while ((m = rtDestructureRE.exec(result)) !== null) {
|
|
1465
|
-
const specList = String(m[2] || '');
|
|
1466
|
-
specList
|
|
1467
|
-
.split(',')
|
|
1468
|
-
.map((s) => s.trim())
|
|
1469
|
-
.filter(Boolean)
|
|
1470
|
-
.forEach((seg) => {
|
|
1471
|
-
const bind = seg.includes(':') ? seg.split(':')[1].trim() : seg;
|
|
1472
|
-
if (bind)
|
|
1473
|
-
rtBound.add(bind);
|
|
1474
|
-
});
|
|
1475
|
-
}
|
|
1476
|
-
if (rtBound.size) {
|
|
1477
|
-
// Rewrite named imports from /ns/rt by removing any specifiers already provided via destructure
|
|
1478
|
-
const rtNamedImportRE = /(^|\n)\s*import\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/rt(?:\/[\d]+)?)["'];?\s*/gm;
|
|
1479
|
-
const edits = [];
|
|
1480
|
-
while ((m = rtNamedImportRE.exec(result)) !== null) {
|
|
1481
|
-
const full = m[0];
|
|
1482
|
-
const pfx = m[1] || '';
|
|
1483
|
-
const specList = String(m[2] || '');
|
|
1484
|
-
const src = m[3];
|
|
1485
|
-
const kept = [];
|
|
1486
|
-
specList
|
|
1487
|
-
.split(',')
|
|
1488
|
-
.map((s) => s.trim())
|
|
1489
|
-
.filter(Boolean)
|
|
1490
|
-
.forEach((seg) => {
|
|
1491
|
-
const name = seg.split(/\s+as\s+/i)[0].trim();
|
|
1492
|
-
if (!rtBound.has(name))
|
|
1493
|
-
kept.push(seg);
|
|
1494
|
-
});
|
|
1495
|
-
let replacement = '';
|
|
1496
|
-
if (kept.length) {
|
|
1497
|
-
replacement = `${pfx}import { ${kept.join(', ')} } from ${JSON.stringify(src)};`;
|
|
1498
|
-
}
|
|
1499
|
-
else {
|
|
1500
|
-
// Drop the import entirely if nothing remains
|
|
1501
|
-
replacement = pfx || '';
|
|
1502
|
-
}
|
|
1503
|
-
edits.push({
|
|
1504
|
-
start: rtNamedImportRE.lastIndex - full.length,
|
|
1505
|
-
end: rtNamedImportRE.lastIndex,
|
|
1506
|
-
text: replacement,
|
|
1507
|
-
});
|
|
1508
|
-
}
|
|
1509
|
-
if (edits.length) {
|
|
1510
|
-
// Apply edits in reverse order
|
|
1511
|
-
edits
|
|
1512
|
-
.sort((a, b) => b.start - a.start)
|
|
1513
|
-
.forEach((e) => {
|
|
1514
|
-
result = result.slice(0, e.start) + e.text + result.slice(e.end);
|
|
1515
|
-
});
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
catch { }
|
|
1520
|
-
// Tidy-ups: remove stray lines that are only semicolons and collapse excessive blank lines
|
|
1521
|
-
try {
|
|
1522
|
-
// Remove lines that contain only an optional whitespace and a single ';'
|
|
1523
|
-
result = result.replace(/^[ \t]*;[ \t]*$/gm, '');
|
|
1524
|
-
// Collapse 3+ blank lines into at most 2 to keep output compact and consistent
|
|
1525
|
-
result = result.replace(/\n{3,}/g, '\n\n');
|
|
1526
|
-
}
|
|
1527
|
-
catch { }
|
|
1528
|
-
// De-duplicate destructured bindings across different temp vars to avoid 'Identifier has already been declared'
|
|
1529
|
-
try {
|
|
1530
|
-
const reDestructureAny = /(^|\n)\s*const\s*\{([^}]+)\}\s*=\s*(__ns_(?:rt|core)_[A-Za-z0-9_]+)\s*;?\s*/gm;
|
|
1531
|
-
const seenBindings = new Set();
|
|
1532
|
-
const edits = [];
|
|
1533
|
-
let m;
|
|
1534
|
-
while ((m = reDestructureAny.exec(result)) !== null) {
|
|
1535
|
-
const full = m[0];
|
|
1536
|
-
const pfx = m.index;
|
|
1537
|
-
const specList = m[2];
|
|
1538
|
-
const varName = m[3];
|
|
1539
|
-
const entries = specList
|
|
1540
|
-
.split(',')
|
|
1541
|
-
.map((s) => s.trim())
|
|
1542
|
-
.filter(Boolean);
|
|
1543
|
-
const kept = [];
|
|
1544
|
-
for (const seg of entries) {
|
|
1545
|
-
const bind = seg.includes(':') ? seg.split(':')[1].trim() : seg;
|
|
1546
|
-
if (seenBindings.has(bind))
|
|
1547
|
-
continue;
|
|
1548
|
-
seenBindings.add(bind);
|
|
1549
|
-
kept.push(seg);
|
|
1550
|
-
}
|
|
1551
|
-
const replacement = kept.length ? `${m[1]}const { ${kept.join(', ')} } = ${varName};` : m[1] || '';
|
|
1552
|
-
edits.push({ start: pfx, end: pfx + full.length, text: replacement });
|
|
1553
|
-
}
|
|
1554
|
-
if (edits.length) {
|
|
1555
|
-
// Apply edits in reverse order to not mess up indices
|
|
1556
|
-
edits
|
|
1557
|
-
.sort((a, b) => b.start - a.start)
|
|
1558
|
-
.forEach((e) => {
|
|
1559
|
-
result = result.slice(0, e.start) + e.text + result.slice(e.end);
|
|
1560
|
-
});
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
catch { }
|
|
1564
|
-
// As a final safety net, neutralize any uses of Vite's CJS import helpers when the helper variable
|
|
1565
|
-
// itself is not declared (e.g., stripped earlier during sanitation). This prevents runtime errors like
|
|
1566
|
-
// "Cannot read properties of undefined (reading 'initNorrix')" on device when accessing
|
|
1567
|
-
// __vite__cjsImportX__pkg["prop"].
|
|
1568
|
-
try {
|
|
1569
|
-
result = stripDanglingViteCjsImports(result);
|
|
1570
|
-
}
|
|
1571
|
-
catch { }
|
|
1572
|
-
return result;
|
|
1573
|
-
}
|
|
1574
|
-
// Assert that sanitized code no longer contains any Vite optimized deps artifacts
|
|
1575
|
-
// or virtual ids that could break the HTTP ESM loader on device. Throws with
|
|
1576
|
-
// a helpful diagnostics message if any are found.
|
|
1577
|
-
function assertNoOptimizedArtifacts(code, contextLabel) {
|
|
1578
|
-
try {
|
|
1579
|
-
const offenders = [];
|
|
1580
|
-
const lines = code.split('\n');
|
|
1581
|
-
const tests = [
|
|
1582
|
-
// Allow Vite dev indirections like /@id/ and /.vite/deps when served via HTTP.
|
|
1583
|
-
// Only flag clearly invalid virtual placeholders if they surface in output.
|
|
1584
|
-
/\b__VITE_PLUGIN__\b/,
|
|
1585
|
-
/\b__VITE_PRELOAD__\b/,
|
|
1586
|
-
];
|
|
1587
|
-
// Absolute or relative local @nativescript/core usage indicates a split realm risk; fail fast
|
|
1588
|
-
const localCore = /(^|[^\w@])(?:\.\.?\/|\/)??@nativescript[\/_-]core\//i;
|
|
1589
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1590
|
-
const ln = lines[i];
|
|
1591
|
-
for (const re of tests) {
|
|
1592
|
-
if (re.test(ln)) {
|
|
1593
|
-
offenders.push(`${i + 1}: ${ln.substring(0, 200)}`);
|
|
1594
|
-
break;
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
if (localCore.test(ln)) {
|
|
1598
|
-
// Comments can never cause split-realm risk at runtime — skip them.
|
|
1599
|
-
// Library authors commonly reference @nativescript/core in comments
|
|
1600
|
-
// (e.g. TSDoc /// <reference> directives, module resolution notes).
|
|
1601
|
-
const trimmed = ln.trimStart();
|
|
1602
|
-
if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) {
|
|
1603
|
-
continue;
|
|
1604
|
-
}
|
|
1605
|
-
if (shouldAllowLocalCoreSanitizerPaths(contextLabel)) {
|
|
1606
|
-
continue;
|
|
1607
|
-
}
|
|
1608
|
-
offenders.push(`${i + 1}: ${ln.substring(0, 200)} [local-core-path]`);
|
|
1609
|
-
}
|
|
1610
|
-
if (offenders.length >= 10)
|
|
1611
|
-
break;
|
|
1612
|
-
}
|
|
1613
|
-
if (offenders.length) {
|
|
1614
|
-
const msg = `[sanitize-fail] Optimized deps/virtual id artifacts detected in ${contextLabel}. These cannot be evaluated by the device HTTP ESM loader. Offending lines (first ${Math.min(5, offenders.length)} shown):\n` + offenders.slice(0, 5).join('\n');
|
|
1615
|
-
const err = new Error(msg);
|
|
1616
|
-
// Attach details for server logs / higher-level handlers
|
|
1617
|
-
err.code = 'NS_SANITIZE_FAIL';
|
|
1618
|
-
err.offenders = offenders;
|
|
1619
|
-
throw err;
|
|
1620
|
-
}
|
|
1621
|
-
}
|
|
1622
|
-
catch (e) {
|
|
1623
|
-
// If diagnostics generation itself fails, do not mask the underlying issue
|
|
1624
|
-
throw e;
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
// Ensure there are no lingering named imports from the unified core bridge.
|
|
1628
|
-
// Converts named imports from /ns/core[/ver][?p=...] into default import + destructuring.
|
|
1629
|
-
function ensureDestructureCoreImports(code) {
|
|
1630
|
-
try {
|
|
1631
|
-
let result = code;
|
|
1632
|
-
let coreImportCounter = 0;
|
|
1633
|
-
const toDestructure = (specList) => specList
|
|
1634
|
-
.split(',')
|
|
1635
|
-
.map((s) => s.trim())
|
|
1636
|
-
.filter(Boolean)
|
|
1637
|
-
.map((seg) => {
|
|
1638
|
-
const m = seg.split(/\s+as\s+/i);
|
|
1639
|
-
return m.length === 2 ? `${m[0].trim()}: ${m[1].trim()}` : seg;
|
|
1640
|
-
})
|
|
1641
|
-
.join(', ');
|
|
1642
|
-
// import { A, B } from '/ns/core[/ver][?p=...]'
|
|
1643
|
-
const reNamed = /(^|\n)\s*import\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[\d]+)?(?:\?p=[^"']+)?)['"];?\s*/gm;
|
|
1644
|
-
result = result.replace(reNamed, (_full, pfx, specList, src) => {
|
|
1645
|
-
// Deep subpath URLs serve actual ESM with real named exports — skip.
|
|
1646
|
-
if (isDeepCoreSubpath(src))
|
|
1647
|
-
return _full;
|
|
1648
|
-
const tmp = `__ns_core_ns_re${coreImportCounter > 0 ? `_${coreImportCounter}` : ''}`;
|
|
1649
|
-
coreImportCounter++;
|
|
1650
|
-
const decl = `const { ${toDestructure(specList)} } = ${tmp};`;
|
|
1651
|
-
return `${pfx}import ${tmp} from ${JSON.stringify(src)};\n${decl}\n`;
|
|
1652
|
-
});
|
|
1653
|
-
// import Default, { A, B } from '/ns/core[...]'
|
|
1654
|
-
const reMixed = /(^|\n)\s*import\s+([A-Za-z_$][\w$]*)\s*,\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[\d]+)?(?:\?p=[^"']+)?)['"];?\s*/gm;
|
|
1655
|
-
result = result.replace(reMixed, (_full, pfx, defName, specList, src) => {
|
|
1656
|
-
if (isDeepCoreSubpath(src))
|
|
1657
|
-
return _full;
|
|
1658
|
-
const decl = `const { ${toDestructure(specList)} } = ${defName};`;
|
|
1659
|
-
return `${pfx}import ${defName} from ${JSON.stringify(src)};\n${decl}\n`;
|
|
1660
|
-
});
|
|
1661
|
-
return result;
|
|
1662
|
-
}
|
|
1663
|
-
catch {
|
|
1664
|
-
return code;
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1667
|
-
// Converts any named imports from the runtime bridge (/ns/rt[/ver]) into a default import + destructuring.
|
|
1668
|
-
// This guarantees helper aliases like _resolveComponent remain bound even if we later normalize imports.
|
|
1669
|
-
function ensureDestructureRtImports(code) {
|
|
1670
|
-
try {
|
|
1671
|
-
let result = code;
|
|
1672
|
-
const toDestructure = (specList) => specList
|
|
1673
|
-
.split(',')
|
|
1674
|
-
.map((s) => s.trim())
|
|
1675
|
-
.filter(Boolean)
|
|
1676
|
-
.map((seg) => {
|
|
1677
|
-
// Preserve alias mapping (e.g., resolveComponent as _resolveComponent)
|
|
1678
|
-
const m = seg.split(/\s+as\s+/i);
|
|
1679
|
-
return m.length === 2 ? `${m[0].trim()}: ${m[1].trim()}` : seg;
|
|
1680
|
-
})
|
|
1681
|
-
.join(', ');
|
|
1682
|
-
const reNamed = /(^|\n)\s*import\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/rt(?:\/[\d]+)?)['"];?\s*/gm;
|
|
1683
|
-
result = result.replace(reNamed, (_full, pfx, specList, src) => {
|
|
1684
|
-
const tmp = `__ns_rt_ns_re`;
|
|
1685
|
-
const decl = `const { ${toDestructure(specList)} } = ${tmp};`;
|
|
1686
|
-
return `${pfx}import ${tmp} from ${JSON.stringify(src)};\n${decl}\n`;
|
|
1687
|
-
});
|
|
1688
|
-
const reMixed = /(^|\n)\s*import\s+([A-Za-z_$][\w$]*)\s*,\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/rt(?:\/[\d]+)?)['"];?\s*/gm;
|
|
1689
|
-
result = result.replace(reMixed, (_full, pfx, defName, specList, _src) => {
|
|
1690
|
-
const decl = `const { ${toDestructure(specList)} } = ${defName};`;
|
|
1691
|
-
return `${pfx}import ${defName} from ${JSON.stringify(_src)};\n${decl}\n`;
|
|
1692
|
-
});
|
|
1693
|
-
return result;
|
|
1694
|
-
}
|
|
1695
|
-
catch {
|
|
1696
|
-
return code;
|
|
1697
|
-
}
|
|
1698
|
-
}
|
|
1699
|
-
// Remove overlapping named imports from /ns/rt that duplicate bindings already provided
|
|
1700
|
-
// by destructuring a default /ns/rt import (e.g., const { $showModal } = __ns_rt_ns_1;).
|
|
1701
|
-
function dedupeRtNamedImportsAgainstDestructures(code) {
|
|
1702
|
-
try {
|
|
1703
|
-
let result = code;
|
|
1704
|
-
// Collect bindings created from any destructure of __ns_rt_ns* temps
|
|
1705
|
-
const rtDestructureRE = /(^|\n)\s*const\s*\{([^}]+)\}\s*=\s*(__ns_rt_ns(?:\d+|_re))\s*;?/gm;
|
|
1706
|
-
const rtBound = new Set();
|
|
1707
|
-
let m;
|
|
1708
|
-
while ((m = rtDestructureRE.exec(result)) !== null) {
|
|
1709
|
-
const specList = String(m[2] || '');
|
|
1710
|
-
specList
|
|
1711
|
-
.split(',')
|
|
1712
|
-
.map((s) => s.trim())
|
|
1713
|
-
.filter(Boolean)
|
|
1714
|
-
.forEach((seg) => {
|
|
1715
|
-
const bind = seg.includes(':') ? seg.split(':')[1].trim() : seg;
|
|
1716
|
-
if (bind)
|
|
1717
|
-
rtBound.add(bind);
|
|
1718
|
-
});
|
|
1719
|
-
}
|
|
1720
|
-
if (!rtBound.size)
|
|
1721
|
-
return result;
|
|
1722
|
-
// For any named import from /ns/rt (versioned or not), drop specifiers that are already bound
|
|
1723
|
-
const rtNamedImportRE = /(^|\n)\s*import\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/rt(?:\/[\d]+)?)["'];?\s*/gm;
|
|
1724
|
-
const edits = [];
|
|
1725
|
-
while ((m = rtNamedImportRE.exec(result)) !== null) {
|
|
1726
|
-
const full = m[0];
|
|
1727
|
-
const pfx = m[1] || '';
|
|
1728
|
-
const specList = String(m[2] || '');
|
|
1729
|
-
const src = m[3];
|
|
1730
|
-
const kept = [];
|
|
1731
|
-
specList
|
|
1732
|
-
.split(',')
|
|
1733
|
-
.map((s) => s.trim())
|
|
1734
|
-
.filter(Boolean)
|
|
1735
|
-
.forEach((seg) => {
|
|
1736
|
-
const importedName = seg.split(/\s+as\s+/i)[0].trim();
|
|
1737
|
-
if (!rtBound.has(importedName))
|
|
1738
|
-
kept.push(seg);
|
|
1739
|
-
});
|
|
1740
|
-
let replacement = '';
|
|
1741
|
-
if (kept.length) {
|
|
1742
|
-
replacement = `${pfx}import { ${kept.join(', ')} } from ${JSON.stringify(src)};`;
|
|
1743
|
-
}
|
|
1744
|
-
else {
|
|
1745
|
-
replacement = pfx || '';
|
|
1746
|
-
}
|
|
1747
|
-
edits.push({
|
|
1748
|
-
start: rtNamedImportRE.lastIndex - full.length,
|
|
1749
|
-
end: rtNamedImportRE.lastIndex,
|
|
1750
|
-
text: replacement,
|
|
1751
|
-
});
|
|
1752
|
-
}
|
|
1753
|
-
if (edits.length) {
|
|
1754
|
-
edits
|
|
1755
|
-
.sort((a, b) => b.start - a.start)
|
|
1756
|
-
.forEach((e) => {
|
|
1757
|
-
result = result.slice(0, e.start) + e.text + result.slice(e.end);
|
|
1758
|
-
});
|
|
1759
|
-
}
|
|
1760
|
-
return result;
|
|
1761
|
-
}
|
|
1762
|
-
catch {
|
|
1763
|
-
return code;
|
|
1764
|
-
}
|
|
1765
|
-
}
|
|
1766
|
-
/**
|
|
1767
|
-
* THE SINGLE REWRITE FUNCTION - used everywhere for consistency
|
|
1768
|
-
*/
|
|
1769
|
-
export function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot, verbose = false, outputDirOverrideRel, httpOrigin, resolveVendorAsHttp = false) {
|
|
1770
|
-
let result = code;
|
|
1771
|
-
const httpOriginSafe = httpOrigin;
|
|
1772
|
-
const mixedRuntimePluginHttpRootPackages = collectMixedRuntimePluginHttpRootPackages(result, projectRoot);
|
|
1773
|
-
const isDynamicImportPrefix = (prefix) => /import\(\s*["']?$/.test(prefix.trimStart());
|
|
1774
|
-
const importerDir = path.posix.dirname(importerPath);
|
|
1775
|
-
// Determine importer output relative path (project-relative .mjs) to compute relative imports consistently
|
|
1776
|
-
const importerOutRel = outputDirOverrideRel || getProjectRelativeImportPath(importerPath, projectRoot) || stripToProjectRelative(importerPath, projectRoot).replace(/\.(ts|js|tsx|jsx|mjs|mts|cts)$/i, '.mjs');
|
|
1777
|
-
const importerOutDir = importerOutRel ? path.posix.dirname(importerOutRel) : '';
|
|
1778
|
-
const ensureRel = (p) => (p.startsWith('.') ? p : `./${p}`);
|
|
1779
|
-
const isNsSfcSpecifier = (spec) => /^(?:https?:\/\/[^/]+)?\/ns\/sfc(?:\/\d+)?(?:\/|$)/.test(spec.replace(PAT.QUERY_PATTERN, ''));
|
|
1780
|
-
// Normalize all @nativescript/core imports to the unified HTTP ESM core bridge to guarantee a single realm on device
|
|
1781
|
-
try {
|
|
1782
|
-
let coreAliasIdx = 0;
|
|
1783
|
-
const mkAlias = () => `__NSC${coreAliasIdx++}`;
|
|
1784
|
-
const coreUrl = (sub) => {
|
|
1785
|
-
const p = (sub || '').replace(/^\//, '');
|
|
1786
|
-
return `${httpOriginSafe || ''}/ns/core` + (p ? `?p=${p}` : '');
|
|
1787
|
-
};
|
|
1788
|
-
// Case 1: import { A, B } from '@nativescript/core[/sub]'
|
|
1789
|
-
result = result.replace(/(^|\n)\s*import\s*\{\s*([^}]+?)\s*\}\s*from\s+["']@nativescript\/core([^"'\n]*)["'];?/g, (_m, pfx, names, sub) => {
|
|
1790
|
-
const alias = mkAlias();
|
|
1791
|
-
const url = coreUrl(sub || '');
|
|
1792
|
-
const cleaned = names
|
|
1793
|
-
.split(',')
|
|
1794
|
-
.map((s) => s.trim())
|
|
1795
|
-
.filter(Boolean)
|
|
1796
|
-
.join(', ');
|
|
1797
|
-
return `${pfx}import * as ${alias} from ${JSON.stringify(url)};\nconst { ${cleaned} } = ${alias};`;
|
|
1798
|
-
});
|
|
1799
|
-
// Case 2: import Default, { A, B } from '@nativescript/core[/sub]'
|
|
1800
|
-
result = result.replace(/(^|\n)\s*import\s+([A-Za-z_$][\w$]*)\s*,\s*\{([^}]+?)\s*\}\s*from\s*["']@nativescript\/core([^"'\n]*)["'];?/g, (_m, pfx, defName, names, sub) => {
|
|
1801
|
-
const alias = mkAlias();
|
|
1802
|
-
const url = coreUrl(sub || '');
|
|
1803
|
-
const cleaned = names
|
|
1804
|
-
.split(',')
|
|
1805
|
-
.map((s) => s.trim())
|
|
1806
|
-
.filter(Boolean)
|
|
1807
|
-
.join(', ');
|
|
1808
|
-
return `${pfx}import * as ${alias} from ${JSON.stringify(url)};\nconst ${defName} = (${alias}.default || ${alias});\nconst { ${cleaned} } = ${alias};`;
|
|
1809
|
-
});
|
|
1810
|
-
// Case 3: import Default from '@nativescript/core[/sub]'
|
|
1811
|
-
result = result.replace(/(^|\n)\s*import\s+([A-Za-z_$][\w$]*)\s+from\s*["']@nativescript\/core([^"'\n]*)["'];?/g, (_m, pfx, defName, sub) => {
|
|
1812
|
-
const alias = mkAlias();
|
|
1813
|
-
const url = coreUrl(sub || '');
|
|
1814
|
-
return `${pfx}import * as ${alias} from ${JSON.stringify(url)};\nconst ${defName} = (${alias}.default || ${alias});`;
|
|
1815
|
-
});
|
|
1816
|
-
// Case 4: import * as NS from '@nativescript/core[/sub]'
|
|
1817
|
-
result = result.replace(/(^|\n)\s*import\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\s*["']@nativescript\/core([^"'\n]*)["'];?/g, (_m, pfx, nsName, sub) => {
|
|
1818
|
-
const url = coreUrl(sub || '');
|
|
1819
|
-
return `${pfx}import * as ${nsName} from ${JSON.stringify(url)};`;
|
|
1820
|
-
});
|
|
1821
|
-
// Case 5: side-effect import '@nativescript/core[/sub]'
|
|
1822
|
-
result = result.replace(/(^|\n)\s*import\s*["']@nativescript\/core([^"'\n]*)["'];?/g, (_m, pfx, sub) => {
|
|
1823
|
-
const url = coreUrl(sub || '');
|
|
1824
|
-
return `${pfx}import ${JSON.stringify(url)};`;
|
|
1825
|
-
});
|
|
1826
|
-
// Case 6: dynamic import('@nativescript/core[/sub]')
|
|
1827
|
-
result = result.replace(/import\(\s*["']@nativescript\/core([^"'\n]*)["']\s*\)/g, (_m, sub) => {
|
|
1828
|
-
const url = coreUrl(sub || '');
|
|
1829
|
-
return `import(${JSON.stringify(url)})`;
|
|
1830
|
-
});
|
|
1831
|
-
}
|
|
1832
|
-
catch { }
|
|
1833
|
-
// Inline JSON imports (package.json, config.json, etc.)
|
|
1834
|
-
// This must happen BEFORE other rewrites because JSON imports get a ?import query added by Vite
|
|
1835
|
-
result = result.replace(/import\s+(\w+)\s+from\s+["']([^"']+\.json(?:\?[^"']*)?)["'];?/g, (match, varName, jsonPath) => {
|
|
1836
|
-
try {
|
|
1837
|
-
// Remove query params like ?import
|
|
1838
|
-
const cleanPath = jsonPath.split('?')[0];
|
|
1839
|
-
// Resolve the JSON file path relative to the importer
|
|
1840
|
-
let fullPath;
|
|
1841
|
-
if (cleanPath.startsWith('/')) {
|
|
1842
|
-
// Absolute from project root
|
|
1843
|
-
fullPath = path.join(projectRoot, cleanPath);
|
|
1844
|
-
}
|
|
1845
|
-
else if (cleanPath.startsWith('./') || cleanPath.startsWith('../')) {
|
|
1846
|
-
// Relative to importer - resolve from importer's location in project
|
|
1847
|
-
const importerFullPath = path.join(projectRoot, importerDir);
|
|
1848
|
-
fullPath = path.resolve(importerFullPath, cleanPath);
|
|
1849
|
-
}
|
|
1850
|
-
else {
|
|
1851
|
-
// Skip node_modules JSON imports
|
|
1852
|
-
return match;
|
|
1853
|
-
}
|
|
1854
|
-
if (existsSync(fullPath)) {
|
|
1855
|
-
const jsonContent = readFileSync(fullPath, 'utf-8');
|
|
1856
|
-
if (verbose) {
|
|
1857
|
-
console.log(`[rewrite] JSON inline: ${jsonPath} → const ${varName} = {...}`);
|
|
1858
|
-
}
|
|
1859
|
-
// Inline the JSON as a const declaration
|
|
1860
|
-
return `const ${varName} = ${jsonContent};`;
|
|
1861
|
-
}
|
|
1862
|
-
else {
|
|
1863
|
-
console.warn(`[rewrite] JSON file not found: ${fullPath}`);
|
|
1864
|
-
}
|
|
1865
|
-
}
|
|
1866
|
-
catch (error) {
|
|
1867
|
-
console.warn(`[rewrite] Could not inline JSON import: ${jsonPath}`, error);
|
|
1868
|
-
}
|
|
1869
|
-
// If we can't inline it, remove the import to prevent runtime errors
|
|
1870
|
-
// The code will fail with "varName is not defined" which is more debuggable
|
|
1871
|
-
return `/* JSON import failed: ${match} */`;
|
|
1872
|
-
});
|
|
1873
|
-
// Helper to resolve .vue file imports to absolute project paths
|
|
1874
|
-
const resolveVueKey = (spec) => {
|
|
1875
|
-
if (!spec || typeof spec !== 'string') {
|
|
1876
|
-
return null;
|
|
1877
|
-
}
|
|
1878
|
-
// Only process .vue files
|
|
1879
|
-
if (!PAT.VUE_FILE_PATTERN.test(spec)) {
|
|
1880
|
-
return null;
|
|
1881
|
-
}
|
|
1882
|
-
let key;
|
|
1883
|
-
if (spec.startsWith('/')) {
|
|
1884
|
-
key = spec;
|
|
1885
|
-
}
|
|
1886
|
-
else if (spec.startsWith('./') || spec.startsWith('../')) {
|
|
1887
|
-
key = path.posix.normalize(path.posix.join(importerDir, spec));
|
|
1888
|
-
if (!key.startsWith('/'))
|
|
1889
|
-
key = '/' + key;
|
|
1890
|
-
}
|
|
1891
|
-
else {
|
|
1892
|
-
return null;
|
|
1893
|
-
}
|
|
1894
|
-
// Strip query params
|
|
1895
|
-
key = key.replace(PAT.QUERY_PATTERN, '');
|
|
1896
|
-
return key;
|
|
1897
|
-
};
|
|
1898
|
-
// Replacement function for all imports
|
|
1899
|
-
const replaceVueImport = (_match, prefix, spec, suffix) => {
|
|
1900
|
-
// Safety check
|
|
1901
|
-
if (!spec || typeof spec !== 'string') {
|
|
1902
|
-
return `${prefix}${spec}${suffix}`;
|
|
1903
|
-
}
|
|
1904
|
-
// Guard 0: leave fully-qualified HTTP(S) URLs alone
|
|
1905
|
-
if (/^https?:\/\//.test(spec)) {
|
|
1906
|
-
return `${prefix}${spec}${suffix}`;
|
|
1907
|
-
}
|
|
1908
|
-
// Guard: anomalous bare '@' spec should be rewritten to a safe stub module.
|
|
1909
|
-
// This can surface from upstream alias mishaps; mapping it here avoids device-side
|
|
1910
|
-
// "instantiate failed @" errors.
|
|
1911
|
-
if (spec === '@') {
|
|
1912
|
-
const stub = `/ns/m/__invalid_at__.mjs`;
|
|
1913
|
-
if (verbose) {
|
|
1914
|
-
try {
|
|
1915
|
-
console.warn(`[rewrite] mapped bare '@' spec to stub: ${stub}`);
|
|
1916
|
-
}
|
|
1917
|
-
catch { }
|
|
1918
|
-
}
|
|
1919
|
-
return `${prefix}${stub}${suffix}`;
|
|
1920
|
-
}
|
|
1921
|
-
spec = normalizeNativeScriptCoreSpecifier(spec);
|
|
1922
|
-
// Route Vite virtual modules (/@solid-refresh, etc.) through /ns/m/ so their
|
|
1923
|
-
// internal imports (e.g. solid-js) get vendor-rewritten by our pipeline.
|
|
1924
|
-
// Skip known Vite internals (/@vite/, /@id/, /@fs/) which are handled elsewhere.
|
|
1925
|
-
if (spec.startsWith('/@') && !/^\/@(?:vite|id|fs)\//.test(spec)) {
|
|
1926
|
-
const out = `/ns/m${spec}`;
|
|
1927
|
-
if (httpOriginSafe) {
|
|
1928
|
-
return `${prefix}${httpOriginSafe}${out}${suffix}`;
|
|
1929
|
-
}
|
|
1930
|
-
return `${prefix}${out}${suffix}`;
|
|
1931
|
-
}
|
|
1932
|
-
// Route internal NS endpoints to absolute HTTP origin for device
|
|
1933
|
-
if (spec.startsWith('/ns/')) {
|
|
1934
|
-
if (httpOriginSafe) {
|
|
1935
|
-
return `${prefix}${httpOriginSafe}${spec}${suffix}`;
|
|
1936
|
-
}
|
|
1937
|
-
return `${prefix}${spec}${suffix}`;
|
|
1938
|
-
}
|
|
1939
|
-
if (isNativeScriptCoreModule(spec)) {
|
|
1940
|
-
return `${prefix}${spec}${suffix}`;
|
|
1941
|
-
}
|
|
1942
|
-
const nodeModulesSpecifier = normalizeNodeModulesSpecifier(spec);
|
|
1943
|
-
const normalizedRuntimePluginSpec = nodeModulesSpecifier || spec.replace(PAT.QUERY_PATTERN, '').replace(/^\/+/, '');
|
|
1944
|
-
if (normalizedRuntimePluginSpec && mixedRuntimePluginHttpRootPackages.size > 0) {
|
|
1945
|
-
const { packageName } = resolveNodeModulesPackageBoundary(normalizedRuntimePluginSpec, projectRoot);
|
|
1946
|
-
if (packageName && mixedRuntimePluginHttpRootPackages.has(packageName)) {
|
|
1947
|
-
const httpNodeModulesSpecifier = nodeModulesSpecifier || normalizedRuntimePluginSpec;
|
|
1948
|
-
const httpSpec = `/ns/m/node_modules/${httpNodeModulesSpecifier}`;
|
|
1949
|
-
if (httpOriginSafe) {
|
|
1950
|
-
return `${prefix}${httpOriginSafe}${httpSpec}${suffix}`;
|
|
1951
|
-
}
|
|
1952
|
-
return `${prefix}${httpSpec}${suffix}`;
|
|
1953
|
-
}
|
|
1954
|
-
}
|
|
1955
|
-
if (shouldPreserveBareRuntimePluginSubpathImport(spec, projectRoot)) {
|
|
1956
|
-
const httpSpec = `/ns/m/node_modules/${spec.replace(PAT.QUERY_PATTERN, '').replace(/^\/+/, '')}`;
|
|
1957
|
-
if (httpOriginSafe) {
|
|
1958
|
-
return `${prefix}${httpOriginSafe}${httpSpec}${suffix}`;
|
|
1959
|
-
}
|
|
1960
|
-
return `${prefix}${httpSpec}${suffix}`;
|
|
1961
|
-
}
|
|
1962
|
-
const candidateNativeScriptSpec = nodeModulesSpecifier ?? spec;
|
|
1963
|
-
// ── Node modules routing ──────────────────────────────────────
|
|
1964
|
-
// Uses the package's own package.json exports field to determine
|
|
1965
|
-
// whether an import is the main entry (→ vendor bridge) or a
|
|
1966
|
-
// subpath entry (→ HTTP). This replaces the old heuristic-based
|
|
1967
|
-
// approach that tried to guess from file paths.
|
|
1968
|
-
if (nodeModulesSpecifier) {
|
|
1969
|
-
const vendorRouting = resolveVendorRouting(nodeModulesSpecifier, projectRoot);
|
|
1970
|
-
if (vendorRouting) {
|
|
1971
|
-
if (vendorRouting.route === 'vendor') {
|
|
1972
|
-
return `${prefix}${vendorRouting.bareSpec}${suffix}`;
|
|
1973
|
-
}
|
|
1974
|
-
// Vendor package but subpath/platform-specific → HTTP
|
|
1975
|
-
const httpSpec = `/ns/m/node_modules/${nodeModulesSpecifier}`;
|
|
1976
|
-
if (httpOriginSafe) {
|
|
1977
|
-
return `${prefix}${httpOriginSafe}${httpSpec}${suffix}`;
|
|
1978
|
-
}
|
|
1979
|
-
return `${prefix}${httpSpec}${suffix}`;
|
|
1980
|
-
}
|
|
1981
|
-
// Not a vendor package → serve via HTTP from Vite dev server
|
|
1982
|
-
const httpSpec = `/ns/m/node_modules/${nodeModulesSpecifier}`;
|
|
1983
|
-
if (httpOriginSafe) {
|
|
1984
|
-
return `${prefix}${httpOriginSafe}${httpSpec}${suffix}`;
|
|
1985
|
-
}
|
|
1986
|
-
return `${prefix}${httpSpec}${suffix}`;
|
|
1987
|
-
}
|
|
1988
|
-
// Handle .vue imports
|
|
1989
|
-
if (PAT.VUE_FILE_PATTERN.test(spec)) {
|
|
1990
|
-
// Case A: SFC submodule variant (template/script) must keep its query to avoid self-import cycles.
|
|
1991
|
-
const isVueVariant = /\bvue&type=/.test(spec);
|
|
1992
|
-
if (isVueVariant) {
|
|
1993
|
-
// Preserve the full ?vue&type=... query and route via /ns/sfc so sanitation applies
|
|
1994
|
-
const qIdx = spec.indexOf('?');
|
|
1995
|
-
const base = qIdx !== -1 ? spec.slice(0, qIdx) : spec;
|
|
1996
|
-
const query = qIdx !== -1 ? spec.slice(qIdx) : '';
|
|
1997
|
-
// Resolve to absolute project path if relative
|
|
1998
|
-
let abs = base;
|
|
1999
|
-
if (!abs.startsWith('/')) {
|
|
2000
|
-
const joined = path.posix.normalize(path.posix.join(importerDir, abs));
|
|
2001
|
-
abs = joined.startsWith('/') ? joined : `/${joined}`;
|
|
2002
|
-
}
|
|
2003
|
-
const out = `/ns/sfc${abs}${query}`;
|
|
2004
|
-
if (verbose) {
|
|
2005
|
-
console.log(`[rewrite] .vue variant routed (http): ${spec} → ${out}`);
|
|
2006
|
-
}
|
|
2007
|
-
return `${prefix}${out}${suffix}`;
|
|
2008
|
-
}
|
|
2009
|
-
// Case B: plain .vue module → rewrite to SFC endpoint or local artifact
|
|
2010
|
-
const vueKey = resolveVueKey(spec.replace(PAT.QUERY_PATTERN, '')) || '';
|
|
2011
|
-
if (vueKey) {
|
|
2012
|
-
if (true) {
|
|
2013
|
-
const absVue = vueKey.startsWith('/') ? vueKey : '/' + vueKey;
|
|
2014
|
-
const out = `/ns/sfc${absVue}`;
|
|
2015
|
-
if (verbose) {
|
|
2016
|
-
console.log(`[rewrite] .vue rewrite (http): ${spec} → ${out}`);
|
|
2017
|
-
}
|
|
2018
|
-
return `${prefix}${out}${suffix}`;
|
|
2019
|
-
}
|
|
2020
|
-
else {
|
|
2021
|
-
const vueFile = sfcFileMap.get(vueKey);
|
|
2022
|
-
if (vueFile) {
|
|
2023
|
-
const target = `_ns_hmr/${APP_ROOT_DIR}/sfc/${vueFile}`;
|
|
2024
|
-
const relPath = importerOutDir ? ensureRel(path.posix.relative(importerOutDir, target)) : ensureRel(target);
|
|
2025
|
-
if (verbose) {
|
|
2026
|
-
console.log(`[rewrite] .vue rewrite: ${spec} → ${relPath}`);
|
|
2027
|
-
}
|
|
2028
|
-
return `${prefix}${relPath}${suffix}`;
|
|
2029
|
-
}
|
|
2030
|
-
else if (verbose) {
|
|
2031
|
-
console.log(`[rewrite] .vue NOT in sfcFileMap: ${vueKey}`);
|
|
2032
|
-
}
|
|
2033
|
-
}
|
|
2034
|
-
}
|
|
2035
|
-
return `${prefix}${spec}${suffix}`;
|
|
2036
|
-
}
|
|
2037
|
-
// Rewrite relative application imports to HTTP for served modules
|
|
2038
|
-
if (spec.startsWith('./') || spec.startsWith('../')) {
|
|
2039
|
-
const absMaybe = normalizeImportPath(spec, importerDir);
|
|
2040
|
-
const nodeModulesHttpSpec = absMaybe ? toNodeModulesHttpModuleId(absMaybe) : null;
|
|
2041
|
-
if (nodeModulesHttpSpec) {
|
|
2042
|
-
if (isDynamicImportPrefix(prefix)) {
|
|
2043
|
-
if (verbose)
|
|
2044
|
-
console.log(`[rewrite][http] dynamic relative node_modules import → ${nodeModulesHttpSpec} (from ${spec})`);
|
|
2045
|
-
return `__nsDynamicHmrImport(${JSON.stringify(nodeModulesHttpSpec)})`;
|
|
2046
|
-
}
|
|
2047
|
-
if (verbose)
|
|
2048
|
-
console.log(`[rewrite][http] relative node_modules import → ${nodeModulesHttpSpec} (from ${spec})`);
|
|
2049
|
-
return `${prefix}${nodeModulesHttpSpec}${suffix}`;
|
|
2050
|
-
}
|
|
2051
|
-
const baseId = absMaybe ? toAppModuleBaseId(absMaybe, projectRoot) : null; // e.g. /src/foo.mjs
|
|
2052
|
-
if (baseId) {
|
|
2053
|
-
const httpSpec = `/ns/m${baseId}`;
|
|
2054
|
-
if (isDynamicImportPrefix(prefix)) {
|
|
2055
|
-
if (verbose)
|
|
2056
|
-
console.log(`[rewrite][http] dynamic relative app import → ${httpSpec} (from ${spec})`);
|
|
2057
|
-
return `__nsDynamicHmrImport(${JSON.stringify(httpSpec)})`;
|
|
2058
|
-
}
|
|
2059
|
-
if (verbose)
|
|
2060
|
-
console.log(`[rewrite][http] relative app import → ${httpSpec} (from ${spec})`);
|
|
2061
|
-
return `${prefix}${httpSpec}${suffix}`;
|
|
2062
|
-
}
|
|
2063
|
-
return `${prefix}${spec}${suffix}`;
|
|
2064
|
-
}
|
|
2065
|
-
// Note: Do NOT rewrite bare application specifiers (e.g. "core/foo").
|
|
2066
|
-
// These will be inlined during bundleDependenciesInline() to avoid static HTTP imports on device.
|
|
2067
|
-
if (isApplicationImport(spec)) {
|
|
2068
|
-
const baseId = toAppModuleBaseId(spec, projectRoot);
|
|
2069
|
-
if (baseId) {
|
|
2070
|
-
const httpSpec = `/ns/m${baseId}`;
|
|
2071
|
-
if (isDynamicImportPrefix(prefix)) {
|
|
2072
|
-
if (verbose)
|
|
2073
|
-
console.log(`[rewrite][http] dynamic app import → ${httpSpec} (from ${spec})`);
|
|
2074
|
-
return `__nsDynamicHmrImport(${JSON.stringify(httpSpec)})`;
|
|
2075
|
-
}
|
|
2076
|
-
if (verbose)
|
|
2077
|
-
console.log(`[rewrite][http] absolute app import → ${httpSpec} (from ${spec})`);
|
|
2078
|
-
return `${prefix}${httpSpec}${suffix}`;
|
|
2079
|
-
}
|
|
2080
|
-
return `${prefix}${spec}${suffix}`;
|
|
2081
|
-
}
|
|
2082
|
-
// In HTTP mode, avoid rewriting to dep-*.mjs; let imports resolve via server endpoints
|
|
2083
|
-
const depKey = normalizeImportPath(spec, importerDir);
|
|
2084
|
-
if (depKey && !httpOriginSafe) {
|
|
2085
|
-
if (isNativeScriptCoreModule(depKey)) {
|
|
2086
|
-
return `${prefix}${spec}${suffix}`;
|
|
2087
|
-
}
|
|
2088
|
-
if (isNativeScriptPluginModule(depKey)) {
|
|
2089
|
-
return `${prefix}${spec}${suffix}`;
|
|
2090
|
-
}
|
|
2091
|
-
const depFile = findDependencyFileName(depFileMap, depKey);
|
|
2092
|
-
if (depFile) {
|
|
2093
|
-
if (verbose) {
|
|
2094
|
-
console.log(`[rewrite] dep import: ${spec} → ./${depFile}`);
|
|
2095
|
-
}
|
|
2096
|
-
return `${prefix}./${depFile}${suffix}`;
|
|
2097
|
-
}
|
|
2098
|
-
}
|
|
2099
|
-
// Leave everything else unchanged (vendor imports, etc.)
|
|
2100
|
-
return `${prefix}${spec}${suffix}`;
|
|
2101
|
-
};
|
|
2102
|
-
// Apply to all import/export patterns (but only .vue files will be rewritten)
|
|
2103
|
-
result = result.replace(PAT.IMPORT_PATTERN_1, replaceVueImport);
|
|
2104
|
-
result = result.replace(PAT.IMPORT_PATTERN_2, replaceVueImport);
|
|
2105
|
-
result = result.replace(PAT.EXPORT_PATTERN, replaceVueImport);
|
|
2106
|
-
result = result.replace(PAT.IMPORT_PATTERN_3, replaceVueImport);
|
|
2107
|
-
// Side-effect imports (import "spec") — must run AFTER named-import patterns
|
|
2108
|
-
// since IMPORT_PATTERN_1 already handles `import ... from "spec"`.
|
|
2109
|
-
result = result.replace(PAT.IMPORT_PATTERN_SIDE_EFFECT, replaceVueImport);
|
|
2110
|
-
result = ensureDynamicHmrImportHelper(result);
|
|
2111
|
-
// Extra guard: map any lingering dynamic import('@') to a safe stub module path
|
|
2112
|
-
// to prevent device runtime normalization errors.
|
|
2113
|
-
// Example matched: import('@') or import("@") with optional whitespace before closing paren
|
|
2114
|
-
result = result.replace(/(import\(\s*['"])@(['"]\s*\))/g, (_m) => {
|
|
2115
|
-
const stubExpr = `import(new URL('/ns/m/__invalid_at__.mjs', import.meta.url).href)`;
|
|
2116
|
-
if (verbose) {
|
|
2117
|
-
try {
|
|
2118
|
-
console.warn(`[rewrite] mapped dynamic import('@') to /ns/m/__invalid_at__.mjs via import.meta.url`);
|
|
2119
|
-
}
|
|
2120
|
-
catch { }
|
|
2121
|
-
}
|
|
2122
|
-
return stubExpr;
|
|
2123
|
-
});
|
|
2124
|
-
// Also guard static spec forms that could erroneously be '@'
|
|
2125
|
-
// 1) ESM: import { x } from '@'
|
|
2126
|
-
result = result.replace(/(from\s*['"])@(['"])/g, (_m, p1, p2) => {
|
|
2127
|
-
const stub = `/ns/m/__invalid_at__.mjs`;
|
|
2128
|
-
if (verbose) {
|
|
2129
|
-
try {
|
|
2130
|
-
console.warn(`[rewrite] mapped static from '@' to ${stub}`);
|
|
2131
|
-
}
|
|
2132
|
-
catch { }
|
|
2133
|
-
}
|
|
2134
|
-
return `${p1}${stub}${p2}`;
|
|
2135
|
-
});
|
|
2136
|
-
// 2) ESM side-effect: import '@'
|
|
2137
|
-
result = result.replace(/(import\s*(?!\()\s*['"])@(['"])/g, (_m, p1, p2) => {
|
|
2138
|
-
const stub = `/ns/m/__invalid_at__.mjs`;
|
|
2139
|
-
if (verbose) {
|
|
2140
|
-
try {
|
|
2141
|
-
console.warn(`[rewrite] mapped side-effect import '@' to ${stub}`);
|
|
2142
|
-
}
|
|
2143
|
-
catch { }
|
|
2144
|
-
}
|
|
2145
|
-
return `${p1}${stub}${p2}`;
|
|
2146
|
-
});
|
|
2147
|
-
// Handle .vue imports with queries
|
|
2148
|
-
// In HTTP mode, skip legacy local-path rewrite to avoid mixing module origins
|
|
2149
|
-
result = result.replace(PAT.VUE_FILE_IMPORT, (_m, p1, spec, p3) => {
|
|
2150
|
-
if (httpOrigin) {
|
|
2151
|
-
if (isNsSfcSpecifier(spec)) {
|
|
2152
|
-
if (verbose)
|
|
2153
|
-
console.log(`[rewrite] .vue already routed (VUE_FILE_IMPORT http): ${spec}`);
|
|
2154
|
-
return `${p1}${spec}${p3}`;
|
|
2155
|
-
}
|
|
2156
|
-
// Route via /ns/sfc with full query preserved
|
|
2157
|
-
try {
|
|
2158
|
-
let base = spec;
|
|
2159
|
-
const qIdx = base.indexOf('?');
|
|
2160
|
-
const query = qIdx !== -1 ? base.slice(qIdx) : '';
|
|
2161
|
-
base = qIdx !== -1 ? base.slice(0, qIdx) : base;
|
|
2162
|
-
// Resolve relative to importerDir
|
|
2163
|
-
let abs = base;
|
|
2164
|
-
if (!abs.startsWith('/')) {
|
|
2165
|
-
const joined = path.posix.normalize(path.posix.join(importerDir, abs));
|
|
2166
|
-
abs = joined.startsWith('/') ? joined : '/' + joined;
|
|
2167
|
-
}
|
|
2168
|
-
const out = `/ns/sfc${abs}${query}`;
|
|
2169
|
-
if (verbose)
|
|
2170
|
-
console.log(`[rewrite] .vue variant routed (VUE_FILE_IMPORT http): ${spec} → ${out}`);
|
|
2171
|
-
return `${p1}${out}${p3}`;
|
|
2172
|
-
}
|
|
2173
|
-
catch { }
|
|
2174
|
-
return `${p1}${spec}${p3}`;
|
|
2175
|
-
}
|
|
2176
|
-
const vueKey = resolveVueKey(spec);
|
|
2177
|
-
if (vueKey) {
|
|
2178
|
-
const vueFile = sfcFileMap.get(vueKey);
|
|
2179
|
-
if (vueFile) {
|
|
2180
|
-
const target = `_ns_hmr/${APP_ROOT_DIR}/sfc/${vueFile}`;
|
|
2181
|
-
const relPath = importerOutDir ? ensureRel(path.posix.relative(importerOutDir, target)) : ensureRel(target);
|
|
2182
|
-
if (verbose) {
|
|
2183
|
-
console.log(`[rewrite] .vue rewrite (VUE_FILE_IMPORT): ${spec} → ${relPath}`);
|
|
2184
|
-
}
|
|
2185
|
-
return `${p1}${relPath}${p3}`;
|
|
2186
|
-
}
|
|
2187
|
-
else if (verbose) {
|
|
2188
|
-
console.log(`[rewrite] .vue NOT in sfcFileMap (VUE_FILE_IMPORT): ${vueKey}`);
|
|
2189
|
-
}
|
|
2190
|
-
}
|
|
2191
|
-
return `${p1}${spec}${p3}`;
|
|
2192
|
-
});
|
|
2193
|
-
// Final safeguard: normalize any remaining absolute filesystem dynamic imports to HTTP origin spec
|
|
2194
|
-
const absoluteDynamicPattern = /(import\(\s*["'])([^"']+)(["']\s*\))/g;
|
|
2195
|
-
result = result.replace(absoluteDynamicPattern, (match, _prefix, spec, _suffix) => {
|
|
2196
|
-
if (!spec || !spec.startsWith('/'))
|
|
2197
|
-
return match;
|
|
2198
|
-
// Handle internal NS endpoints
|
|
2199
|
-
if (spec.startsWith('/ns/')) {
|
|
2200
|
-
const expr = `import(new URL('${spec}', import.meta.url).href)`;
|
|
2201
|
-
if (verbose)
|
|
2202
|
-
console.log(`[rewrite][http] internal ns import (dynamic) → ${spec} via import.meta.url`);
|
|
2203
|
-
return expr;
|
|
2204
|
-
}
|
|
2205
|
-
const nodeModulesHttpSpec = toNodeModulesHttpModuleId(spec);
|
|
2206
|
-
if (nodeModulesHttpSpec) {
|
|
2207
|
-
const expr = `import(new URL('${nodeModulesHttpSpec}', import.meta.url).href)`;
|
|
2208
|
-
if (verbose)
|
|
2209
|
-
console.log(`[rewrite][http] absolute dynamic node_modules import → ${nodeModulesHttpSpec} via import.meta.url (from ${spec})`);
|
|
2210
|
-
return expr;
|
|
2211
|
-
}
|
|
2212
|
-
const baseId = toAppModuleBaseId(spec, projectRoot);
|
|
2213
|
-
if (!baseId)
|
|
2214
|
-
return match;
|
|
2215
|
-
const expr = `import(new URL('/ns/m${baseId}', import.meta.url).href)`;
|
|
2216
|
-
if (verbose)
|
|
2217
|
-
console.log(`[rewrite][http] absolute dynamic import → /ns/m${baseId} via import.meta.url (from ${spec})`);
|
|
2218
|
-
return expr;
|
|
2219
|
-
});
|
|
2220
|
-
return result;
|
|
2221
|
-
}
|
|
2222
|
-
// ============================================================================
|
|
2223
|
-
// PLUGIN
|
|
2224
|
-
// ============================================================================
|
|
2225
|
-
function createHmrWebSocketPlugin(opts) {
|
|
2226
|
-
const verbose = !!opts.verbose;
|
|
2227
|
-
let wss = null;
|
|
2228
|
-
let sharedTransformRequest;
|
|
2229
|
-
const pendingAngularReloadSuppressions = new Map();
|
|
2230
|
-
const sfcFileMap = new Map();
|
|
2231
|
-
const depFileMap = new Map();
|
|
2232
|
-
// Generic module manifest (spec -> emitted relative .mjs path)
|
|
2233
|
-
// Populated lazily on-demand via ns:fetch-module responses and broadcast to clients.
|
|
2234
|
-
// Enables clients to short-circuit fetches for already-known modules and aids
|
|
2235
|
-
// consistent path normalization across reconnects.
|
|
2236
|
-
const moduleManifest = new Map();
|
|
2237
|
-
let registrySent = false;
|
|
2238
|
-
let vendorBootstrapDone = false;
|
|
2239
|
-
let pluginRoot;
|
|
2240
|
-
let graphVersion = 0;
|
|
2241
|
-
// Transactional HMR batches: map graphVersion -> ordered list of changed ids for that version
|
|
2242
|
-
const txnBatches = new Map();
|
|
2243
|
-
const graph = new Map();
|
|
2244
|
-
function rememberAngularReloadSuppression(root, file, ttlMs = 3000) {
|
|
2245
|
-
const absPath = normalizeHotReloadMatchPath(file);
|
|
2246
|
-
const relPath = normalizeHotReloadMatchPath(file, root);
|
|
2247
|
-
pendingAngularReloadSuppressions.set(absPath, {
|
|
2248
|
-
absPath,
|
|
2249
|
-
relPath,
|
|
2250
|
-
expiresAt: Date.now() + ttlMs,
|
|
2251
|
-
});
|
|
2252
|
-
}
|
|
2253
|
-
function pruneAngularReloadSuppressions(now = Date.now()) {
|
|
2254
|
-
for (const [key, entry] of pendingAngularReloadSuppressions) {
|
|
2255
|
-
if (!entry || entry.expiresAt <= now) {
|
|
2256
|
-
pendingAngularReloadSuppressions.delete(key);
|
|
2257
|
-
}
|
|
2258
|
-
}
|
|
2259
|
-
}
|
|
2260
|
-
// Compute a dependency-closed, topologically sorted list of modules for a given set of changed ids.
|
|
2261
|
-
// Only include application modules we can serve (e.g., under /src and known .vue/.ts/.js entries in the graph).
|
|
2262
|
-
function computeTxnOrderForChanged(changedIds) {
|
|
2263
|
-
const includeAppModule = (id) => matchesRuntimeGraphModuleId(id, APP_VIRTUAL_WITH_SLASH, /\.(ts|js|mjs|tsx|jsx)$/i);
|
|
2264
|
-
const includeExt = (id) => ACTIVE_STRATEGY.matchesFile(id) || includeAppModule(id);
|
|
2265
|
-
const isApp = (id) => id.startsWith(APP_VIRTUAL_WITH_SLASH);
|
|
2266
|
-
const roots = changedIds.map(normalizeGraphId).filter((id) => graph.has(id) && (isApp(id) || ACTIVE_STRATEGY.matchesFile(id)) && includeExt(id));
|
|
2267
|
-
const toVisit = new Set();
|
|
2268
|
-
// Collect dependency closure (downstream deps from roots)
|
|
2269
|
-
const stack = [...roots];
|
|
2270
|
-
while (stack.length) {
|
|
2271
|
-
const id = stack.pop();
|
|
2272
|
-
if (toVisit.has(id))
|
|
2273
|
-
continue;
|
|
2274
|
-
toVisit.add(id);
|
|
2275
|
-
const m = graph.get(id);
|
|
2276
|
-
if (m) {
|
|
2277
|
-
for (const d of m.deps) {
|
|
2278
|
-
if (!graph.has(d))
|
|
2279
|
-
continue;
|
|
2280
|
-
if ((isApp(d) || ACTIVE_STRATEGY.matchesFile(d)) && includeExt(d) && !toVisit.has(d)) {
|
|
2281
|
-
stack.push(d);
|
|
2282
|
-
}
|
|
2283
|
-
}
|
|
2284
|
-
}
|
|
2285
|
-
}
|
|
2286
|
-
// Topological order: deps first (post-order DFS)
|
|
2287
|
-
const ordered = [];
|
|
2288
|
-
const temp = new Set();
|
|
2289
|
-
const perm = new Set();
|
|
2290
|
-
const visit = (id) => {
|
|
2291
|
-
if (perm.has(id))
|
|
2292
|
-
return;
|
|
2293
|
-
if (temp.has(id)) {
|
|
2294
|
-
perm.add(id);
|
|
2295
|
-
return;
|
|
2296
|
-
} // cycle: bail out
|
|
2297
|
-
temp.add(id);
|
|
2298
|
-
const m = graph.get(id);
|
|
2299
|
-
if (m) {
|
|
2300
|
-
for (const d of m.deps) {
|
|
2301
|
-
if (toVisit.has(d))
|
|
2302
|
-
visit(d);
|
|
2303
|
-
}
|
|
2304
|
-
}
|
|
2305
|
-
temp.delete(id);
|
|
2306
|
-
perm.add(id);
|
|
2307
|
-
ordered.push(id);
|
|
2308
|
-
};
|
|
2309
|
-
for (const id of toVisit)
|
|
2310
|
-
visit(id);
|
|
2311
|
-
// Ensure we only include those that were part of closure (already ensured) and preserve an order where deps appear before dependents
|
|
2312
|
-
return ordered;
|
|
2313
|
-
}
|
|
2314
|
-
function normalizeGraphId(raw) {
|
|
2315
|
-
if (!raw)
|
|
2316
|
-
return raw;
|
|
2317
|
-
let id = raw.split('?')[0].replace(/\\/g, '/');
|
|
2318
|
-
// Strip file:// prefix
|
|
2319
|
-
id = id.replace(/^file:\/\//, '');
|
|
2320
|
-
if (pluginRoot) {
|
|
2321
|
-
const rootNorm = pluginRoot.replace(/\\/g, '/');
|
|
2322
|
-
if (id.startsWith(rootNorm)) {
|
|
2323
|
-
id = id.slice(rootNorm.length);
|
|
2324
|
-
}
|
|
2325
|
-
}
|
|
2326
|
-
if (!id.startsWith('/'))
|
|
2327
|
-
id = '/' + id;
|
|
2328
|
-
// Collapse nested app root indicators when present (defensive)
|
|
2329
|
-
const idx = id.indexOf(APP_VIRTUAL_WITH_SLASH);
|
|
2330
|
-
if (idx !== -1)
|
|
2331
|
-
id = id.slice(idx);
|
|
2332
|
-
return id;
|
|
2333
|
-
}
|
|
2334
|
-
function computeHash(content) {
|
|
2335
|
-
let h = 0;
|
|
2336
|
-
for (let i = 0; i < content.length; i++) {
|
|
2337
|
-
h = (h * 31 + content.charCodeAt(i)) | 0;
|
|
2338
|
-
}
|
|
2339
|
-
return ('00000000' + (h >>> 0).toString(16)).slice(-8);
|
|
2340
|
-
}
|
|
2341
|
-
function fullGraphPayload() {
|
|
2342
|
-
return {
|
|
2343
|
-
type: 'ns:hmr-full-graph',
|
|
2344
|
-
version: graphVersion,
|
|
2345
|
-
modules: Array.from(graph.values()).map((m) => ({
|
|
2346
|
-
id: m.id,
|
|
2347
|
-
deps: m.deps,
|
|
2348
|
-
hash: m.hash,
|
|
2349
|
-
})),
|
|
2350
|
-
};
|
|
2351
|
-
}
|
|
2352
|
-
function emitFullGraph(ws) {
|
|
2353
|
-
try {
|
|
2354
|
-
if (verbose) {
|
|
2355
|
-
try {
|
|
2356
|
-
const payload = fullGraphPayload();
|
|
2357
|
-
console.log('[hmr-ws][graph] emitFullGraph version', payload.version, 'modules=', payload.modules.length);
|
|
2358
|
-
if (payload.modules.length) {
|
|
2359
|
-
console.log('[hmr-ws][graph] sample module ids', payload.modules.slice(0, 5).map((m) => m.id));
|
|
2360
|
-
}
|
|
2361
|
-
}
|
|
2362
|
-
catch { }
|
|
2363
|
-
}
|
|
2364
|
-
ws.send(JSON.stringify(fullGraphPayload()));
|
|
2365
|
-
}
|
|
2366
|
-
catch { }
|
|
2367
|
-
}
|
|
2368
|
-
function emitDelta(changed, removed) {
|
|
2369
|
-
if (!wss)
|
|
2370
|
-
return;
|
|
2371
|
-
// Record this version's txn batch order
|
|
2372
|
-
try {
|
|
2373
|
-
const changedIds = changed.map((m) => m.id);
|
|
2374
|
-
if (changedIds.length) {
|
|
2375
|
-
const ordered = computeTxnOrderForChanged(changedIds);
|
|
2376
|
-
txnBatches.set(graphVersion, ordered);
|
|
2377
|
-
// Keep only the last ~20 versions
|
|
2378
|
-
if (txnBatches.size > 20) {
|
|
2379
|
-
const drop = Array.from(txnBatches.keys())
|
|
2380
|
-
.sort((a, b) => a - b)
|
|
2381
|
-
.slice(0, txnBatches.size - 20);
|
|
2382
|
-
for (const k of drop)
|
|
2383
|
-
txnBatches.delete(k);
|
|
2384
|
-
}
|
|
2385
|
-
}
|
|
2386
|
-
}
|
|
2387
|
-
catch { }
|
|
2388
|
-
const payload = {
|
|
2389
|
-
type: 'ns:hmr-delta',
|
|
2390
|
-
baseVersion: graphVersion - 1,
|
|
2391
|
-
newVersion: graphVersion,
|
|
2392
|
-
changed: changed.map((m) => ({ id: m.id, deps: m.deps, hash: m.hash })),
|
|
2393
|
-
removed,
|
|
2394
|
-
};
|
|
2395
|
-
const json = JSON.stringify(payload);
|
|
2396
|
-
wss.clients.forEach((c) => {
|
|
2397
|
-
try {
|
|
2398
|
-
c.send(json);
|
|
2399
|
-
}
|
|
2400
|
-
catch { }
|
|
126
|
+
catch {
|
|
127
|
+
return 'unknown';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function getHmrSocketRole(client) {
|
|
131
|
+
if (!client) {
|
|
132
|
+
return 'unknown';
|
|
133
|
+
}
|
|
134
|
+
return typeof client.__nsHmrClientRole === 'string' && client.__nsHmrClientRole ? client.__nsHmrClientRole : 'unknown';
|
|
135
|
+
}
|
|
136
|
+
// Plugin
|
|
137
|
+
function createHmrWebSocketPlugin(opts, strategy) {
|
|
138
|
+
const verbose = !!opts.verbose;
|
|
139
|
+
let wss = null;
|
|
140
|
+
let sharedTransformRequest;
|
|
141
|
+
const pendingAngularReloadSuppressions = new Map();
|
|
142
|
+
const sfcFileMap = new Map();
|
|
143
|
+
const depFileMap = new Map();
|
|
144
|
+
let vendorBootstrapDone = false;
|
|
145
|
+
let pluginRoot;
|
|
146
|
+
// HMR module graph (spec -> deps/hash) with version tagging and delta/full
|
|
147
|
+
// broadcasts. `wss`/`pluginRoot` are read lazily via accessors because both
|
|
148
|
+
// are established later, during configureServer.
|
|
149
|
+
const moduleGraph = new HmrModuleGraph({
|
|
150
|
+
verbose,
|
|
151
|
+
strategy,
|
|
152
|
+
getWss: () => wss,
|
|
153
|
+
getPluginRoot: () => pluginRoot,
|
|
154
|
+
});
|
|
155
|
+
// Tracks the background initial-graph population so handleHotUpdate can
|
|
156
|
+
// await completion before computing delta roots for the first HMR event.
|
|
157
|
+
let graphInitialPopulationPromise = null;
|
|
158
|
+
// Cold-boot /ns/m request counter — populated the first time a /ns/m
|
|
159
|
+
// request arrives, finalized when the request window goes idle.
|
|
160
|
+
// See Shared across requests so a single counter spans the whole cold boot.
|
|
161
|
+
let coldBootCounter = null;
|
|
162
|
+
function rememberAngularReloadSuppression(root, file, ttlMs = 3000) {
|
|
163
|
+
const absPath = normalizeHotReloadMatchPath(file);
|
|
164
|
+
const relPath = normalizeHotReloadMatchPath(file, root);
|
|
165
|
+
pendingAngularReloadSuppressions.set(absPath, {
|
|
166
|
+
absPath,
|
|
167
|
+
relPath,
|
|
168
|
+
expiresAt: Date.now() + ttlMs,
|
|
2401
169
|
});
|
|
2402
170
|
}
|
|
2403
|
-
function
|
|
2404
|
-
const
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
.filter(Boolean)
|
|
2408
|
-
.slice()
|
|
2409
|
-
.sort();
|
|
2410
|
-
const hash = computeHash(code);
|
|
2411
|
-
const existing = graph.get(id);
|
|
2412
|
-
const classification = classifyGraphUpsert(existing, hash, normDeps);
|
|
2413
|
-
if (classification === 'unchanged')
|
|
2414
|
-
return existing;
|
|
2415
|
-
graphVersion++;
|
|
2416
|
-
const gm = { id, deps: normDeps, hash };
|
|
2417
|
-
graph.set(id, gm);
|
|
2418
|
-
if (verbose) {
|
|
2419
|
-
try {
|
|
2420
|
-
console.log('[hmr-ws][graph] upsert', { id, deps: normDeps, hash, graphVersion, classification });
|
|
2421
|
-
console.log('[hmr-ws][graph] size', graph.size);
|
|
171
|
+
function pruneAngularReloadSuppressions(now = Date.now()) {
|
|
172
|
+
for (const [key, entry] of pendingAngularReloadSuppressions) {
|
|
173
|
+
if (!entry || entry.expiresAt <= now) {
|
|
174
|
+
pendingAngularReloadSuppressions.delete(key);
|
|
2422
175
|
}
|
|
2423
|
-
catch { }
|
|
2424
|
-
}
|
|
2425
|
-
if (shouldBroadcastGraphUpsertDelta(classification, options?.emitDeltaOnInsert === true, options?.broadcastDelta !== false)) {
|
|
2426
|
-
emitDelta([gm], []);
|
|
2427
|
-
}
|
|
2428
|
-
return gm;
|
|
2429
|
-
}
|
|
2430
|
-
function isTypescriptFlavor() {
|
|
2431
|
-
try {
|
|
2432
|
-
return ACTIVE_STRATEGY?.flavor === 'typescript';
|
|
2433
|
-
}
|
|
2434
|
-
catch {
|
|
2435
|
-
return false;
|
|
2436
176
|
}
|
|
2437
177
|
}
|
|
2438
|
-
function removeGraphModule(id) {
|
|
2439
|
-
if (!graph.has(id))
|
|
2440
|
-
return;
|
|
2441
|
-
graph.delete(id);
|
|
2442
|
-
graphVersion++;
|
|
2443
|
-
emitDelta([], [id]);
|
|
2444
|
-
}
|
|
2445
178
|
async function populateInitialGraph(server) {
|
|
2446
|
-
if (
|
|
179
|
+
if (moduleGraph.size)
|
|
2447
180
|
return; // already populated
|
|
181
|
+
const tStart = Date.now();
|
|
182
|
+
const versionAtStart = moduleGraph.version;
|
|
2448
183
|
const root = server.config.root || process.cwd();
|
|
2449
184
|
// Avoid direct require in ESM build: lazily obtain fs & path via createRequire or dynamic import
|
|
2450
185
|
let fs;
|
|
@@ -2460,6 +195,18 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2460
195
|
fs = await import('fs');
|
|
2461
196
|
pathMod = await import('path');
|
|
2462
197
|
}
|
|
198
|
+
// Route every bulk transform through `sharedTransformRequest` when it's
|
|
199
|
+
// already been wired up — this way the background walk shares the 60s
|
|
200
|
+
// TTL cache with live /ns/m requests, so the device sees cached results
|
|
201
|
+
// for any file the walker already visited. The fallback keeps the
|
|
202
|
+
// walker working during server tests where the shared runner isn't
|
|
203
|
+
// constructed yet.
|
|
204
|
+
const bulkTransform = (rel) => {
|
|
205
|
+
if (sharedTransformRequest) {
|
|
206
|
+
return sharedTransformRequest(rel);
|
|
207
|
+
}
|
|
208
|
+
return server.transformRequest(rel);
|
|
209
|
+
};
|
|
2463
210
|
async function walk(dir) {
|
|
2464
211
|
for (const name of fs.readdirSync(dir)) {
|
|
2465
212
|
if (name === 'node_modules' || name.startsWith('.') || shouldSkipRuntimeGraphDirectoryName(name))
|
|
@@ -2474,7 +221,7 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2474
221
|
const rel = '/' + pathMod.relative(root, full).split(pathMod.sep).join('/');
|
|
2475
222
|
// Transform via Vite to gather deps (ignore failures)
|
|
2476
223
|
try {
|
|
2477
|
-
const transformed = await
|
|
224
|
+
const transformed = await bulkTransform(rel);
|
|
2478
225
|
const code = transformed?.code || '';
|
|
2479
226
|
const deps = [];
|
|
2480
227
|
// fallback to import relationships via moduleGraph
|
|
@@ -2485,7 +232,10 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2485
232
|
deps.push(m.id.split('?')[0]);
|
|
2486
233
|
}
|
|
2487
234
|
}
|
|
2488
|
-
|
|
235
|
+
// bumpVersion: false — the initial walk is a bulk load, not a live
|
|
236
|
+
// edit. Keeping graphVersion stable during cold boot avoids double
|
|
237
|
+
// cache-key drift.
|
|
238
|
+
moduleGraph.upsert(rel, code, deps, { bumpVersion: false });
|
|
2489
239
|
}
|
|
2490
240
|
catch { }
|
|
2491
241
|
}
|
|
@@ -2498,6 +248,37 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2498
248
|
await walk(pathMod.join(root, 'src'));
|
|
2499
249
|
}
|
|
2500
250
|
catch { }
|
|
251
|
+
// Diagnostic summary. Gated behind the verbose flag so the
|
|
252
|
+
// dev console stays quiet on a normal save. Flip
|
|
253
|
+
// NS_VITE_VERBOSE=1 to surface slow cold-boot walks; a
|
|
254
|
+
// `bumpedVersion=no` result is the happy path, `yes`
|
|
255
|
+
// indicates a regression.
|
|
256
|
+
if (verbose) {
|
|
257
|
+
console.info(formatPopulateInitialGraphSummary({
|
|
258
|
+
moduleCount: moduleGraph.size,
|
|
259
|
+
durationMs: Date.now() - tStart,
|
|
260
|
+
graphVersion: moduleGraph.version,
|
|
261
|
+
bumpedVersion: moduleGraph.version !== versionAtStart,
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Kick off `populateInitialGraph` in the background (non-awaited) so /ns/m
|
|
266
|
+
// responses are never blocked on a full tree walk. Returns the shared
|
|
267
|
+
// promise so hot-update code paths can await completion before computing
|
|
268
|
+
// delta roots for the first HMR event.
|
|
269
|
+
function ensureInitialGraphPopulationStarted(server) {
|
|
270
|
+
if (graphInitialPopulationPromise) {
|
|
271
|
+
return graphInitialPopulationPromise;
|
|
272
|
+
}
|
|
273
|
+
if (moduleGraph.size) {
|
|
274
|
+
graphInitialPopulationPromise = Promise.resolve();
|
|
275
|
+
return graphInitialPopulationPromise;
|
|
276
|
+
}
|
|
277
|
+
graphInitialPopulationPromise = populateInitialGraph(server).catch((error) => {
|
|
278
|
+
if (verbose)
|
|
279
|
+
console.warn('[hmr-ws][graph] background initial population failed', error);
|
|
280
|
+
});
|
|
281
|
+
return graphInitialPopulationPromise;
|
|
2501
282
|
}
|
|
2502
283
|
return {
|
|
2503
284
|
name: 'nativescript-hmr-websocket',
|
|
@@ -2511,6 +292,93 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2511
292
|
if (!wsAny.__NS_ANGULAR_FULL_RELOAD_FILTER_INSTALLED__) {
|
|
2512
293
|
const originalSend = server.ws.send.bind(server.ws);
|
|
2513
294
|
wsAny.__NS_ANGULAR_FULL_RELOAD_FILTER_INSTALLED__ = true;
|
|
295
|
+
// Bridge Vite's stock WS broadcasts (`server.ws.send(...)`)
|
|
296
|
+
// to our `/ns-hmr` WebSocket. Vite v8 keeps two completely
|
|
297
|
+
// separate `WebSocketServer` instances: its own (default
|
|
298
|
+
// path `/`, accepting `vite-hmr`/`vite-ping` protocols) and
|
|
299
|
+
// ours (`/ns-hmr`, where the iOS device actually connects).
|
|
300
|
+
// Plugin-emitted events like Analog's
|
|
301
|
+
// `server.ws.send('angular:component-update', { id, ts })`
|
|
302
|
+
// flow through Vite's `normalizedHotChannel.send` →
|
|
303
|
+
// `wss.clients.forEach`, but those `wss.clients` are
|
|
304
|
+
// EMPTY in NativeScript dev — the device never speaks the
|
|
305
|
+
// `vite-hmr` protocol nor connects to `/`. Without a
|
|
306
|
+
// bridge, every plugin-emitted custom event is logged on
|
|
307
|
+
// the server (e.g. `(client) hmr update <html>`) but
|
|
308
|
+
// silently dropped before reaching the device. Symptom:
|
|
309
|
+
// the iOS HMR-applying overlay sticks at 5%
|
|
310
|
+
// ("Preparing update") forever because Angular's compiled
|
|
311
|
+
// `import.meta.hot.on('angular:component-update', cb)`
|
|
312
|
+
// listeners never fire. We mirror the payload onto our
|
|
313
|
+
// `/ns-hmr` clients here so the existing custom-event
|
|
314
|
+
// dispatcher in `hmr/client/index.ts` (which forwards to
|
|
315
|
+
// `__NS_DISPATCH_HOT_EVENT__`) actually runs.
|
|
316
|
+
const bridgeToNsHmrClients = (payload, args) => {
|
|
317
|
+
try {
|
|
318
|
+
let normalized;
|
|
319
|
+
if (typeof args[0] === 'string') {
|
|
320
|
+
normalized = { type: 'custom', event: args[0], data: args[1] };
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
normalized = payload;
|
|
324
|
+
}
|
|
325
|
+
if (!normalized)
|
|
326
|
+
return;
|
|
327
|
+
// Vite's stock `update` payload includes per-module
|
|
328
|
+
// HMR boundary info that our device-side client
|
|
329
|
+
// has no handler for (we drive HMR via our own
|
|
330
|
+
// `ns:angular-update`/`ns:hmr-delta`/`ns:css-updates`
|
|
331
|
+
// messages). Forwarding it would just look like
|
|
332
|
+
// noise to the client. Custom events
|
|
333
|
+
// (`type: 'custom'`) — including
|
|
334
|
+
// `angular:component-update` and Analog's
|
|
335
|
+
// CSS-direct/inline `update` shorthand — DO need
|
|
336
|
+
// to reach the device, since they drive the
|
|
337
|
+
// in-place `ɵɵreplaceMetadata` template-swap path.
|
|
338
|
+
// Filter the relay to those.
|
|
339
|
+
if (normalized.type !== 'custom')
|
|
340
|
+
return;
|
|
341
|
+
// Root-component guard. Analog's in-place
|
|
342
|
+
// `ɵɵreplaceMetadata` recreates the edited
|
|
343
|
+
// component's `LView`s without re-navigating. For
|
|
344
|
+
// the bootstrap (root) component — which hosts the
|
|
345
|
+
// navigation `Frame` via `<page-router-outlet>` —
|
|
346
|
+
// that tears the frame down and leaves an
|
|
347
|
+
// unrecoverable white screen. Drop the in-place
|
|
348
|
+
// update for the root here; the reboot path
|
|
349
|
+
// (`ns:angular-update` → `__reboot_ng_modules__`)
|
|
350
|
+
// re-bootstraps and replays route state, which is
|
|
351
|
+
// the only mechanism that correctly rebuilds the
|
|
352
|
+
// root view. The hot-update handler ensures a reboot
|
|
353
|
+
// is broadcast for root edits (including `.html`).
|
|
354
|
+
if (normalized.event === 'angular:component-update' && isAngularRootComponentUpdate(getRootComponentIdentity(), normalized.data?.id)) {
|
|
355
|
+
if (verbose) {
|
|
356
|
+
console.log('[hmr-ws][bridge] dropped angular:component-update for root component — reboot path owns the root view (PageRouterOutlet frame)');
|
|
357
|
+
}
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const stringified = JSON.stringify(normalized);
|
|
361
|
+
let recipients = 0;
|
|
362
|
+
wss?.clients.forEach((client) => {
|
|
363
|
+
try {
|
|
364
|
+
if (client && client.readyState === 1) {
|
|
365
|
+
client.send(stringified);
|
|
366
|
+
recipients++;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
catch { }
|
|
370
|
+
});
|
|
371
|
+
if (verbose) {
|
|
372
|
+
const event = normalized?.event;
|
|
373
|
+
console.log(`[hmr-ws][bridge] forwarded ${normalized.type}${event ? `:${event}` : ''} payload to ${recipients} /ns-hmr client(s)`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
catch (err) {
|
|
377
|
+
if (verbose) {
|
|
378
|
+
console.warn('[hmr-ws][bridge] failed to forward payload to /ns-hmr clients', err);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
};
|
|
2514
382
|
server.ws.send = ((payload, ...rest) => {
|
|
2515
383
|
pruneAngularReloadSuppressions();
|
|
2516
384
|
if (shouldSuppressViteFullReloadPayload({
|
|
@@ -2523,25 +391,148 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2523
391
|
}
|
|
2524
392
|
return;
|
|
2525
393
|
}
|
|
394
|
+
bridgeToNsHmrClients(payload, [payload, ...rest]);
|
|
2526
395
|
return originalSend(payload, ...rest);
|
|
2527
396
|
});
|
|
2528
397
|
}
|
|
2529
|
-
//
|
|
2530
|
-
//
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
398
|
+
// Transform concurrency. Historically we defaulted to 1 to avoid
|
|
399
|
+
// race conditions during HTTP HMR startup, but the shared runner
|
|
400
|
+
// already has per-URL coalescing and an async-cached result map,
|
|
401
|
+
// so higher fan-out is safe and dramatically reduces cold-boot
|
|
402
|
+
// time. We cap at 8 by default to match typical dev machines and
|
|
403
|
+
// respect Vite's internal worker pool limits. Override via the
|
|
404
|
+
// `NS_VITE_HMR_TRANSFORM_CONCURRENCY` env var when needed.
|
|
405
|
+
const configuredTransformConcurrency = Number.parseInt(process.env.NS_VITE_HMR_TRANSFORM_CONCURRENCY || '', 10);
|
|
406
|
+
const transformConcurrency = Number.isFinite(configuredTransformConcurrency) && configuredTransformConcurrency > 0 ? configuredTransformConcurrency : 8;
|
|
407
|
+
// Keep transformed code cached for longer across HMR updates so
|
|
408
|
+
// that unchanged neighbours of an edited file don't re-run
|
|
409
|
+
// through the Angular/TypeScript/Vite transform pipeline. The
|
|
410
|
+
// HMR flow explicitly invalidates affected URLs, so a longer TTL
|
|
411
|
+
// is safe. Override with `NS_VITE_HMR_TRANSFORM_CACHE_MS`.
|
|
412
|
+
const configuredTransformCacheMs = Number.parseInt(process.env.NS_VITE_HMR_TRANSFORM_CACHE_MS || '', 10);
|
|
413
|
+
const transformCacheMs = Number.isFinite(configuredTransformCacheMs) && configuredTransformCacheMs >= 0 ? configuredTransformCacheMs : 60000;
|
|
2535
414
|
sharedTransformRequest = createSharedTransformRequestRunner((url) => server.transformRequest(url), (url, timeoutMs) => {
|
|
2536
|
-
|
|
2537
|
-
console.warn('[ns:m] slow transformRequest for', url, '(>' + timeoutMs + 'ms)');
|
|
2538
|
-
}
|
|
2539
|
-
catch { }
|
|
415
|
+
console.warn('[ns:m] slow transformRequest for', url, '(>' + timeoutMs + 'ms)');
|
|
2540
416
|
}, {
|
|
2541
417
|
maxConcurrent: transformConcurrency,
|
|
2542
418
|
resultCacheTtlMs: transformCacheMs,
|
|
2543
419
|
getResultCacheKey: (url) => canonicalizeTransformRequestCacheKey(url, pluginRoot || process.cwd()),
|
|
2544
420
|
});
|
|
421
|
+
// Always-on startup banner — prints once per dev server process
|
|
422
|
+
// so anyone investigating perf can immediately see which build
|
|
423
|
+
// is live and what knobs are active.
|
|
424
|
+
try {
|
|
425
|
+
let pkgVersion = 'unknown';
|
|
426
|
+
try {
|
|
427
|
+
const req = createRequire(import.meta.url);
|
|
428
|
+
const pkg = req('@nativescript/vite/package.json');
|
|
429
|
+
if (pkg && typeof pkg.version === 'string')
|
|
430
|
+
pkgVersion = pkg.version;
|
|
431
|
+
}
|
|
432
|
+
catch {
|
|
433
|
+
// `@nativescript/vite/package.json` is not always exported; fall
|
|
434
|
+
// back to reading the file from disk next to this module.
|
|
435
|
+
try {
|
|
436
|
+
const here = new URL(import.meta.url).pathname;
|
|
437
|
+
const pkgPath = path.resolve(path.dirname(here), '..', '..', 'package.json');
|
|
438
|
+
if (existsSync(pkgPath)) {
|
|
439
|
+
const parsed = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
440
|
+
if (parsed && typeof parsed.version === 'string')
|
|
441
|
+
pkgVersion = parsed.version;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
catch { }
|
|
445
|
+
}
|
|
446
|
+
if (verbose) {
|
|
447
|
+
console.info(formatServerStartupBanner({
|
|
448
|
+
version: pkgVersion,
|
|
449
|
+
transformConcurrency,
|
|
450
|
+
transformCacheMs,
|
|
451
|
+
lazyInitialGraph: true,
|
|
452
|
+
graphVersion: moduleGraph.version,
|
|
453
|
+
}));
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
catch { }
|
|
457
|
+
// Always-on cold-boot request trace. Runs in front of every
|
|
458
|
+
// other middleware so it catches all NS dev routes (/ns/m/*,
|
|
459
|
+
// /ns/rt/*, /ns/core/*, /__ns_boot__/*, etc.) with a single
|
|
460
|
+
// hook. Closes itself after an idle window so HMR edits don't
|
|
461
|
+
// get rolled into the cold-boot numbers. The idle window is
|
|
462
|
+
// generous by default (5s) because V8's HTTP ESM resolver
|
|
463
|
+
// pauses between dep levels while parsing — a too-tight window
|
|
464
|
+
// was closing after the first wave and under-reporting boot by
|
|
465
|
+
// 100x. Override via `NS_VITE_HMR_BOOT_TRACE_IDLE_MS` when
|
|
466
|
+
// profiling something tricky.
|
|
467
|
+
try {
|
|
468
|
+
const configuredIdleMs = Number.parseInt(process.env.NS_VITE_HMR_BOOT_TRACE_IDLE_MS || '', 10);
|
|
469
|
+
const idleWindowMs = Number.isFinite(configuredIdleMs) && configuredIdleMs > 0 ? configuredIdleMs : 5000;
|
|
470
|
+
const configuredSummaryEvery = Number.parseInt(process.env.NS_VITE_HMR_BOOT_TRACE_PROGRESS_EVERY || '', 10);
|
|
471
|
+
const summaryEvery = Number.isFinite(configuredSummaryEvery) && configuredSummaryEvery >= 0 ? configuredSummaryEvery : 25;
|
|
472
|
+
if (!coldBootCounter) {
|
|
473
|
+
coldBootCounter = createColdBootRequestCounter({
|
|
474
|
+
summaryEvery,
|
|
475
|
+
idleWindowMs,
|
|
476
|
+
// Gated on the verbose flag so cold-boot progress and
|
|
477
|
+
// the final window-closed summary stay quiet by
|
|
478
|
+
// default. Flip NS_VITE_VERBOSE=1 to surface them.
|
|
479
|
+
log: (line) => {
|
|
480
|
+
if (!verbose)
|
|
481
|
+
return;
|
|
482
|
+
console.info(line);
|
|
483
|
+
},
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
catch { }
|
|
488
|
+
server.middlewares.use((req, res, next) => {
|
|
489
|
+
try {
|
|
490
|
+
const urlObj = new URL(req.url || '', 'http://localhost');
|
|
491
|
+
const route = classifyBootRoute(urlObj.pathname);
|
|
492
|
+
if (route === 'other')
|
|
493
|
+
return next();
|
|
494
|
+
if (!coldBootCounter)
|
|
495
|
+
return next();
|
|
496
|
+
const handle = coldBootCounter.record(urlObj.pathname);
|
|
497
|
+
const finishOnce = () => {
|
|
498
|
+
try {
|
|
499
|
+
handle.finish();
|
|
500
|
+
}
|
|
501
|
+
catch { }
|
|
502
|
+
};
|
|
503
|
+
try {
|
|
504
|
+
res.once('finish', finishOnce);
|
|
505
|
+
res.once('close', finishOnce);
|
|
506
|
+
}
|
|
507
|
+
catch { }
|
|
508
|
+
}
|
|
509
|
+
catch { }
|
|
510
|
+
next();
|
|
511
|
+
});
|
|
512
|
+
// Give `populateInitialGraph` a head start. Previously this only
|
|
513
|
+
// kicked off on the first /ns/m hit, which meant populate was
|
|
514
|
+
// competing with the device for the same 8 transform slots
|
|
515
|
+
// throughout the first 4-5 seconds of cold boot. Starting at
|
|
516
|
+
// `configureServer` time gives populate the full app
|
|
517
|
+
// build/launch window (typically 2-3s on simulator) as a head
|
|
518
|
+
// start, so more of its work lands before the device even
|
|
519
|
+
// connects. Disable via `NS_VITE_HMR_DISABLE_POPULATE=1` when
|
|
520
|
+
// profiling whether populate is helping or hurting a specific
|
|
521
|
+
// app.
|
|
522
|
+
try {
|
|
523
|
+
const disablePopulate = process.env.NS_VITE_HMR_DISABLE_POPULATE === '1' || process.env.NS_VITE_HMR_DISABLE_POPULATE === 'true';
|
|
524
|
+
if (disablePopulate) {
|
|
525
|
+
if (verbose)
|
|
526
|
+
console.info('[hmr-ws][populate] disabled via NS_VITE_HMR_DISABLE_POPULATE');
|
|
527
|
+
// Short-circuit: mark as resolved so /ns/m never schedules it and
|
|
528
|
+
// HMR still works (handleHotUpdate just has no pre-warmed graph).
|
|
529
|
+
graphInitialPopulationPromise = Promise.resolve();
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
ensureInitialGraphPopulationStarted(server);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
catch { }
|
|
2545
536
|
// Attempt early vendor manifest bootstrap once per server.
|
|
2546
537
|
if (!vendorBootstrapDone) {
|
|
2547
538
|
vendorBootstrapDone = true;
|
|
@@ -2556,7 +547,7 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2556
547
|
else if (verbose) {
|
|
2557
548
|
console.log('[hmr-ws][vendor] Manifest already present with', Object.keys(existing.modules).length, 'modules');
|
|
2558
549
|
}
|
|
2559
|
-
|
|
550
|
+
getGlobalScope().__NS_VENDOR_MANIFEST__ = getVendorManifest();
|
|
2560
551
|
}
|
|
2561
552
|
// Disable perMessageDeflate to avoid any extension negotiation quirks with native clients
|
|
2562
553
|
wss = new WebSocketServer({
|
|
@@ -2583,787 +574,94 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2583
574
|
});
|
|
2584
575
|
}
|
|
2585
576
|
});
|
|
2586
|
-
// Additional connection diagnostics
|
|
2587
|
-
wss.on('connection', (ws, req) => {
|
|
2588
|
-
const role = getHmrSocketRoleFromRequestUrl(req.url);
|
|
2589
|
-
ws.__nsHmrClientRole = role;
|
|
2590
|
-
try {
|
|
2591
|
-
if (verbose) {
|
|
2592
|
-
const ra = req.socket?.remoteAddress;
|
|
2593
|
-
const rp = req.socket?.remotePort;
|
|
2594
|
-
console.log('[hmr-ws] Client connected', { role, remote: ra + (rp ? ':' + rp : '') });
|
|
2595
|
-
}
|
|
2596
|
-
}
|
|
2597
|
-
catch { }
|
|
2598
|
-
ws.on('close', () => {
|
|
2599
|
-
try {
|
|
2600
|
-
if (verbose) {
|
|
2601
|
-
const ra = req.socket?.remoteAddress;
|
|
2602
|
-
const rp = req.socket?.remotePort;
|
|
2603
|
-
console.log('[hmr-ws] Client disconnected', { role, remote: ra + (rp ? ':' + rp : '') });
|
|
2604
|
-
}
|
|
2605
|
-
}
|
|
2606
|
-
catch { }
|
|
2607
|
-
});
|
|
2608
|
-
});
|
|
2609
|
-
wss.on('error', (err) => {
|
|
2610
|
-
try {
|
|
2611
|
-
console.warn('[hmr-ws] server error:', err?.message || String(err));
|
|
2612
|
-
}
|
|
2613
|
-
catch { }
|
|
2614
|
-
});
|
|
2615
|
-
// Import map endpoint: GET /ns/import-map.json
|
|
2616
|
-
// Returns the import map + runtime config for __nsConfigureRuntime()
|
|
2617
|
-
server.middlewares.use(async (req, res, next) => {
|
|
2618
|
-
try {
|
|
2619
|
-
const urlObj = new URL(req.url || '', 'http://localhost');
|
|
2620
|
-
if (urlObj.pathname !== '/ns/import-map.json')
|
|
2621
|
-
return next();
|
|
2622
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
2623
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
|
2624
|
-
if (req.method === 'OPTIONS') {
|
|
2625
|
-
res.statusCode = 204;
|
|
2626
|
-
res.end();
|
|
2627
|
-
return;
|
|
2628
|
-
}
|
|
2629
|
-
// Determine origin from request headers or server config
|
|
2630
|
-
const host = req.headers.host || 'localhost:5173';
|
|
2631
|
-
const protocol = 'http';
|
|
2632
|
-
const origin = `${protocol}://${host}`;
|
|
2633
|
-
const runtimeConfig = buildRuntimeConfig({
|
|
2634
|
-
origin,
|
|
2635
|
-
flavor: ACTIVE_STRATEGY?.flavor || 'typescript',
|
|
2636
|
-
});
|
|
2637
|
-
res.setHeader('Content-Type', 'application/json');
|
|
2638
|
-
res.end(JSON.stringify({
|
|
2639
|
-
importMap: JSON.parse(runtimeConfig.importMap),
|
|
2640
|
-
volatilePatterns: runtimeConfig.volatilePatterns,
|
|
2641
|
-
}, null, 2));
|
|
2642
|
-
}
|
|
2643
|
-
catch (err) {
|
|
2644
|
-
console.error('[import-map] error generating import map:', err?.message || err);
|
|
2645
|
-
res.statusCode = 500;
|
|
2646
|
-
res.end(JSON.stringify({ error: 'Failed to generate import map' }));
|
|
2647
|
-
}
|
|
2648
|
-
});
|
|
2649
|
-
// Dev-only HTTP ESM loader endpoint for device clients
|
|
2650
|
-
// 1) Legacy JSON module endpoint (kept temporarily): GET /ns-module?path=/abs -> { path, code, additionalFiles }
|
|
2651
|
-
server.middlewares.use(async (req, res, next) => {
|
|
2652
|
-
try {
|
|
2653
|
-
const urlObj = new URL(req.url || '', 'http://localhost');
|
|
2654
|
-
if (urlObj.pathname !== '/ns-module')
|
|
2655
|
-
return next();
|
|
2656
|
-
// CORS for device access
|
|
2657
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
2658
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
|
2659
|
-
if (req.method === 'OPTIONS') {
|
|
2660
|
-
res.statusCode = 204;
|
|
2661
|
-
res.end();
|
|
2662
|
-
return;
|
|
2663
|
-
}
|
|
2664
|
-
let spec = urlObj.searchParams.get('path') || urlObj.searchParams.get('spec') || '';
|
|
2665
|
-
if (!spec) {
|
|
2666
|
-
res.statusCode = 400;
|
|
2667
|
-
res.setHeader('Content-Type', 'application/json');
|
|
2668
|
-
res.end(JSON.stringify({ error: 'missing path' }));
|
|
2669
|
-
return;
|
|
2670
|
-
}
|
|
2671
|
-
// Mirror normalization from ws handler
|
|
2672
|
-
spec = spec.replace(/[?#].*$/, '');
|
|
2673
|
-
if (spec.startsWith('@/'))
|
|
2674
|
-
spec = APP_VIRTUAL_WITH_SLASH + spec.slice(2);
|
|
2675
|
-
spec = spec.replace(/\/(index)(?:\/(?:index))+$/i, '/$1');
|
|
2676
|
-
if (spec.startsWith('./'))
|
|
2677
|
-
spec = spec.slice(1);
|
|
2678
|
-
if (!spec.startsWith('/'))
|
|
2679
|
-
spec = '/' + spec;
|
|
2680
|
-
// Transform via Vite with variant resolution (same as ws ns:fetch-module)
|
|
2681
|
-
const hasExt = /\.(ts|tsx|js|jsx|mjs|mts|cts|vue)$/i.test(spec);
|
|
2682
|
-
const baseNoExt = hasExt ? spec.replace(/\.(ts|tsx|js|jsx|mjs|mts|cts)$/i, '') : spec;
|
|
2683
|
-
const transformRoot = server.config?.root || process.cwd();
|
|
2684
|
-
const candidates = [];
|
|
2685
|
-
if (hasExt)
|
|
2686
|
-
candidates.push(spec);
|
|
2687
|
-
candidates.push(baseNoExt + '.ts', baseNoExt + '.js', baseNoExt + '.tsx', baseNoExt + '.jsx', baseNoExt + '.mjs', baseNoExt + '.mts', baseNoExt + '.cts', baseNoExt + '.vue', baseNoExt + '/index.ts', baseNoExt + '/index.js', baseNoExt + '/index.tsx', baseNoExt + '/index.jsx', baseNoExt + '/index.mjs');
|
|
2688
|
-
const transformCandidates = filterExistingNodeModulesTransformCandidates(spec, candidates, transformRoot);
|
|
2689
|
-
let transformed = null;
|
|
2690
|
-
let resolvedCandidate = null;
|
|
2691
|
-
for (const cand of transformCandidates) {
|
|
2692
|
-
try {
|
|
2693
|
-
const r = await server.transformRequest(cand);
|
|
2694
|
-
if (r?.code) {
|
|
2695
|
-
transformed = r;
|
|
2696
|
-
resolvedCandidate = cand;
|
|
2697
|
-
break;
|
|
2698
|
-
}
|
|
2699
|
-
}
|
|
2700
|
-
catch { }
|
|
2701
|
-
}
|
|
2702
|
-
if (!transformed?.code) {
|
|
2703
|
-
res.statusCode = 404;
|
|
2704
|
-
res.setHeader('Content-Type', 'application/json');
|
|
2705
|
-
res.end(JSON.stringify({ error: 'transform-failed', path: spec }));
|
|
2706
|
-
return;
|
|
2707
|
-
}
|
|
2708
|
-
let code = transformed.code;
|
|
2709
|
-
// Prepend guard to capture any URL-based require attempts
|
|
2710
|
-
code = REQUIRE_GUARD_SNIPPET + code;
|
|
2711
|
-
// Apply same sanitation/rewrite pipeline used for WS path
|
|
2712
|
-
code = cleanCode(code);
|
|
2713
|
-
// preserveVendorImports=true: vendor imports stay as bare specifiers
|
|
2714
|
-
// for the device-side import map (ns-vendor://) instead of being
|
|
2715
|
-
// transformed to __nsVendorRequire calls with fragile __nsPick lookups.
|
|
2716
|
-
code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(resolvedCandidate || spec), resolvedCandidate || spec);
|
|
2717
|
-
code = rewriteImports(code, spec, sfcFileMap, depFileMap, server.config?.root || process.cwd(), !!verbose, undefined, getServerOrigin(server));
|
|
2718
|
-
code = ensureVariableDynamicImportHelper(code);
|
|
2719
|
-
// Enforce upstream guarantee: no optimized deps or virtual ids remain
|
|
2720
|
-
try {
|
|
2721
|
-
assertNoOptimizedArtifacts(code, `SFC ASM ${spec}`);
|
|
2722
|
-
}
|
|
2723
|
-
catch (e) {
|
|
2724
|
-
res.statusCode = 500;
|
|
2725
|
-
res.setHeader('Content-Type', 'application/json');
|
|
2726
|
-
return void res.end(JSON.stringify({ error: e?.message || String(e) }));
|
|
2727
|
-
}
|
|
2728
|
-
try {
|
|
2729
|
-
const origin = getServerOrigin(server);
|
|
2730
|
-
code = ensureVersionedRtImports(code, origin, graphVersion);
|
|
2731
|
-
code = ACTIVE_STRATEGY.ensureVersionedImports(code, origin, graphVersion);
|
|
2732
|
-
code = ensureVersionedCoreImports(code, origin, graphVersion);
|
|
2733
|
-
}
|
|
2734
|
-
catch { }
|
|
2735
|
-
// Compute rel .mjs output path
|
|
2736
|
-
const specForRel = resolvedCandidate || spec;
|
|
2737
|
-
let rel = specForRel.replace(/^\//, '').replace(/\.(tsx?|jsx?)$/i, '.mjs');
|
|
2738
|
-
if (!rel.endsWith('.mjs'))
|
|
2739
|
-
rel += '.mjs';
|
|
2740
|
-
// Collect immediate relative .mjs deps similarly
|
|
2741
|
-
const additionalFiles = [];
|
|
2742
|
-
try {
|
|
2743
|
-
const importRE = /from\s+["']([^"']+\.mjs)["']|import\(\s*["']([^"']+\.mjs)["']\s*\)/g;
|
|
2744
|
-
const importerDir = path.posix.dirname(specForRel);
|
|
2745
|
-
let m;
|
|
2746
|
-
const seen = new Set();
|
|
2747
|
-
while ((m = importRE.exec(code)) !== null) {
|
|
2748
|
-
let dep = m[1] || m[2];
|
|
2749
|
-
if (!dep)
|
|
2750
|
-
continue;
|
|
2751
|
-
if (!(dep.startsWith('./') || dep.startsWith('../')))
|
|
2752
|
-
continue;
|
|
2753
|
-
let abs = path.posix.normalize(path.posix.join(importerDir, dep));
|
|
2754
|
-
if (!abs.startsWith('/'))
|
|
2755
|
-
abs = '/' + abs;
|
|
2756
|
-
const depBase = abs.replace(/\.(ts|js|tsx|jsx|mjs|mts|cts)$/i, '');
|
|
2757
|
-
if (seen.has(depBase))
|
|
2758
|
-
continue;
|
|
2759
|
-
seen.add(depBase);
|
|
2760
|
-
const depCandidates = filterExistingNodeModulesTransformCandidates(depBase, [depBase + '.ts', depBase + '.js', depBase + '.tsx', depBase + '.jsx', depBase + '.mjs', depBase + '.mts', depBase + '.cts', depBase + '.vue', depBase + '/index.ts', depBase + '/index.js', depBase + '/index.tsx', depBase + '/index.jsx', depBase + '/index.mjs'], transformRoot);
|
|
2761
|
-
let depTrans = null;
|
|
2762
|
-
let depResolved = null;
|
|
2763
|
-
for (const c of depCandidates) {
|
|
2764
|
-
try {
|
|
2765
|
-
const r = await server.transformRequest(c);
|
|
2766
|
-
if (r?.code) {
|
|
2767
|
-
depTrans = r;
|
|
2768
|
-
depResolved = c;
|
|
2769
|
-
break;
|
|
2770
|
-
}
|
|
2771
|
-
}
|
|
2772
|
-
catch { }
|
|
2773
|
-
}
|
|
2774
|
-
if (depTrans?.code && depResolved) {
|
|
2775
|
-
let depCode = depTrans.code;
|
|
2776
|
-
depCode = cleanCode(depCode);
|
|
2777
|
-
depCode = processCodeForDevice(depCode, false, true, /(?:^|\/)node_modules\//.test(depResolved), depResolved);
|
|
2778
|
-
depCode = rewriteImports(depCode, depResolved, sfcFileMap, depFileMap, server.config?.root || process.cwd(), !!verbose, undefined, getServerOrigin(server));
|
|
2779
|
-
depCode = ensureVariableDynamicImportHelper(depCode);
|
|
2780
|
-
try {
|
|
2781
|
-
assertNoOptimizedArtifacts(depCode, `SFC ASM dep ${depResolved}`);
|
|
2782
|
-
}
|
|
2783
|
-
catch (e) {
|
|
2784
|
-
/* don't include bad deps */ continue;
|
|
2785
|
-
}
|
|
2786
|
-
try {
|
|
2787
|
-
depCode = ensureVersionedRtImports(depCode, getServerOrigin(server), graphVersion);
|
|
2788
|
-
depCode = ACTIVE_STRATEGY.ensureVersionedImports(depCode, getServerOrigin(server), graphVersion);
|
|
2789
|
-
depCode = ensureVersionedCoreImports(depCode, getServerOrigin(server), graphVersion);
|
|
2790
|
-
}
|
|
2791
|
-
catch { }
|
|
2792
|
-
let depRel = depResolved.replace(/^\//, '').replace(/\.(tsx?|jsx?)$/i, '.mjs');
|
|
2793
|
-
if (!depRel.endsWith('.mjs'))
|
|
2794
|
-
depRel += '.mjs';
|
|
2795
|
-
additionalFiles.push({ path: depRel, code: depCode });
|
|
2796
|
-
}
|
|
2797
|
-
}
|
|
2798
|
-
}
|
|
2799
|
-
catch { }
|
|
2800
|
-
res.statusCode = 200;
|
|
2801
|
-
res.setHeader('Content-Type', 'application/json');
|
|
2802
|
-
res.end(JSON.stringify({ path: rel, code, additionalFiles }));
|
|
2803
|
-
}
|
|
2804
|
-
catch (e) {
|
|
2805
|
-
try {
|
|
2806
|
-
res.statusCode = 500;
|
|
2807
|
-
res.setHeader('Content-Type', 'application/json');
|
|
2808
|
-
res.end(JSON.stringify({ error: e?.message || String(e) }));
|
|
2809
|
-
}
|
|
2810
|
-
catch { }
|
|
2811
|
-
}
|
|
2812
|
-
});
|
|
2813
|
-
// 2) ESM endpoint for application modules: GET /ns/m?path=/abs OR /ns/m/abs -> returns JS module
|
|
2814
|
-
server.middlewares.use(async (req, res, next) => {
|
|
2815
|
-
try {
|
|
2816
|
-
const urlObj = new URL(req.url || '', 'http://localhost');
|
|
2817
|
-
if (!urlObj.pathname.startsWith('/ns/m'))
|
|
2818
|
-
return next();
|
|
2819
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
2820
|
-
res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
|
|
2821
|
-
// Disable caching for dev ESM endpoints to avoid device-side stale module reuse
|
|
2822
|
-
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
|
|
2823
|
-
res.setHeader('Pragma', 'no-cache');
|
|
2824
|
-
res.setHeader('Expires', '0');
|
|
2825
|
-
const serverRoot = (server.config?.root || process.cwd());
|
|
2826
|
-
const requestContextResult = createNsMRequestContext(req.url || '', serverRoot, APP_VIRTUAL_WITH_SLASH);
|
|
2827
|
-
if (requestContextResult.kind === 'next')
|
|
2828
|
-
return next();
|
|
2829
|
-
if (requestContextResult.kind === 'response') {
|
|
2830
|
-
res.statusCode = requestContextResult.statusCode;
|
|
2831
|
-
res.end(requestContextResult.code);
|
|
2832
|
-
return;
|
|
2833
|
-
}
|
|
2834
|
-
let { spec, forcedVer, bootTaggedRequest, transformCandidates, candidates } = requestContextResult.value;
|
|
2835
|
-
// Serve Vite virtual modules (/@id/ prefix). These are internal
|
|
2836
|
-
// virtual modules (e.g., \0nsvite:nsconfig-json for ~/package.json)
|
|
2837
|
-
// that don't exist on disk. Decode the ID and load via plugin container.
|
|
2838
|
-
let transformed = null;
|
|
2839
|
-
let resolvedCandidate = null;
|
|
2840
|
-
// Queue and dedupe transformRequest calls so heavy app graphs do not
|
|
2841
|
-
// overwhelm Vite with concurrent work. Slow-transform warnings start only
|
|
2842
|
-
// when the transform actually begins executing, and requests stay pending
|
|
2843
|
-
// until Vite returns a real result.
|
|
2844
|
-
const transformWithTimeout = (url, timeoutMs = 120000) => {
|
|
2845
|
-
return sharedTransformRequest(url, timeoutMs);
|
|
2846
|
-
};
|
|
2847
|
-
({ transformed, resolvedCandidate } = await resolveNsMTransformedModule({
|
|
2848
|
-
context: requestContextResult.value,
|
|
2849
|
-
transformRequest: transformWithTimeout,
|
|
2850
|
-
resolveId: async (id) => {
|
|
2851
|
-
const resolved = await server.pluginContainer?.resolveId?.(id, undefined);
|
|
2852
|
-
return typeof resolved === 'string' ? resolved : resolved?.id || null;
|
|
2853
|
-
},
|
|
2854
|
-
loadVirtualId: async (id) => await server.pluginContainer.load(id),
|
|
2855
|
-
}));
|
|
2856
|
-
// Solid HMR: patch @@solid-refresh's $$refreshESM to do inline patching
|
|
2857
|
-
// during module re-evaluation instead of deferring to hot.accept() callback.
|
|
2858
|
-
// In NativeScript's HTTP ESM environment, accept callbacks are registered
|
|
2859
|
-
// but not invoked by the HMR client. By adding a direct patchRegistry()
|
|
2860
|
-
// call when hot.data already has a stored registry, component updates
|
|
2861
|
-
// apply immediately when the module re-evaluates.
|
|
2862
|
-
try {
|
|
2863
|
-
if (transformed?.code && ACTIVE_STRATEGY?.flavor === 'solid' && (resolvedCandidate || spec || '').includes('@solid-refresh')) {
|
|
2864
|
-
const PATCH_SENTINEL = '/* __ns_solid_refresh_patched__ */';
|
|
2865
|
-
const alreadyPatched = transformed.code.includes(PATCH_SENTINEL);
|
|
2866
|
-
console.log('[hmr-ws][solid] @solid-refresh patch check:', { spec: resolvedCandidate || spec, alreadyPatched, codeLen: transformed.code.length });
|
|
2867
|
-
if (!alreadyPatched) {
|
|
2868
|
-
let patchedCode = transformed.code;
|
|
2869
|
-
// Patch 1: Bypass shouldWarnAndDecline() — the vendor-bundled solid-js
|
|
2870
|
-
// may not have the 'development' condition active, making DEV empty/undefined.
|
|
2871
|
-
// In NativeScript HMR mode we are always in dev, so force it to return false.
|
|
2872
|
-
const declineCheck = 'function shouldWarnAndDecline() {';
|
|
2873
|
-
if (patchedCode.includes(declineCheck)) {
|
|
2874
|
-
patchedCode = patchedCode.replace(declineCheck, `${PATCH_SENTINEL}\nfunction shouldWarnAndDecline() { return false; /* NS HMR: always allow refresh */ }\nfunction __original_shouldWarnAndDecline() {`);
|
|
2875
|
-
console.log('[hmr-ws][solid] bypassed shouldWarnAndDecline() for NativeScript HMR');
|
|
2876
|
-
}
|
|
2877
|
-
// Patch 2: Force createMemo path in createProxy.
|
|
2878
|
-
// Without the 'development' condition, $DEVCOMP is not set on components,
|
|
2879
|
-
// so createProxy falls through to `return s(props)` — a direct call with
|
|
2880
|
-
// no reactive subscription. When patchComponent fires update() (the signal
|
|
2881
|
-
// setter), nobody is listening. By forcing the createMemo path, HMRComp
|
|
2882
|
-
// subscribes to the signal and re-renders when the component changes.
|
|
2883
|
-
const proxyCondition = 'if (!s || $DEVCOMP in s) {';
|
|
2884
|
-
if (patchedCode.includes(proxyCondition)) {
|
|
2885
|
-
patchedCode = patchedCode.replace(proxyCondition, 'if (true) { /* NS HMR: always use createMemo for reactive HMR updates */');
|
|
2886
|
-
console.log('[hmr-ws][solid] forced createMemo path in createProxy for NativeScript HMR');
|
|
2887
|
-
}
|
|
2888
|
-
// Patch 3: Inline patchRegistry call so updates apply immediately
|
|
2889
|
-
// on module re-evaluation (accept callbacks are not invoked by the HMR client).
|
|
2890
|
-
const marker = 'hot.data[SOLID_REFRESH] = hot.data[SOLID_REFRESH] || registry;';
|
|
2891
|
-
if (patchedCode.includes(marker)) {
|
|
2892
|
-
const patchCode = [
|
|
2893
|
-
`console.log('[solid-refresh][$$refreshESM] hot.data keys=', hot.data ? Object.keys(hot.data) : 'no-data', 'has=', !!(hot.data && hot.data[SOLID_REFRESH]));`,
|
|
2894
|
-
`if (hot.data[SOLID_REFRESH]) {`,
|
|
2895
|
-
` console.log('[solid-refresh][$$refreshESM] patching: oldComponents=', hot.data[SOLID_REFRESH].components ? hot.data[SOLID_REFRESH].components.size : 0, 'newComponents=', registry.components ? registry.components.size : 0);`,
|
|
2896
|
-
` var _shouldInvalidate = patchRegistry(hot.data[SOLID_REFRESH], registry);`,
|
|
2897
|
-
` console.log('[solid-refresh][$$refreshESM] patchRegistry result: shouldInvalidate=', _shouldInvalidate);`,
|
|
2898
|
-
`} else {`,
|
|
2899
|
-
` console.log('[solid-refresh][$$refreshESM] first load — creating registry, components=', registry.components ? registry.components.size : 0);`,
|
|
2900
|
-
`}`,
|
|
2901
|
-
].join('\n ');
|
|
2902
|
-
patchedCode = patchedCode.replace(marker, `${patchCode}\n ${marker}`);
|
|
2903
|
-
console.log('[hmr-ws][solid] added inline patchRegistry for NativeScript HMR');
|
|
2904
|
-
}
|
|
2905
|
-
// Work on a copy to avoid mutating Vite's cached TransformResult
|
|
2906
|
-
transformed = { ...transformed, code: patchedCode };
|
|
2907
|
-
}
|
|
2908
|
-
}
|
|
2909
|
-
}
|
|
2910
|
-
catch { }
|
|
2911
|
-
// NOTE: Path-based cache busting for /ns/m/* imports is applied in the
|
|
2912
|
-
// finalize step below (after rewriteImports adds the /ns/m/ prefix).
|
|
2913
|
-
// The block here only handles TypeScript-specific graph population.
|
|
2914
|
-
try {
|
|
2915
|
-
if (transformed?.code) {
|
|
2916
|
-
const code = transformed.code;
|
|
2917
|
-
// TypeScript-specific graph population: when TS flavor is active
|
|
2918
|
-
// and this is an application module under the virtual app root,
|
|
2919
|
-
// upsert it into the HMR graph so ns:hmr-full-graph is non-empty.
|
|
2920
|
-
try {
|
|
2921
|
-
if (isTypescriptFlavor()) {
|
|
2922
|
-
const id = (resolvedCandidate || spec).replace(/[?#].*$/, '');
|
|
2923
|
-
// Only track app modules (under APP_VIRTUAL_WITH_SLASH) and ts/js/tsx/jsx/mjs.
|
|
2924
|
-
const isApp = id.startsWith(APP_VIRTUAL_WITH_SLASH) || id.startsWith('/app/');
|
|
2925
|
-
if (isApp && /\.(ts|tsx|js|jsx|mjs|mts|cts)$/i.test(id) && !isRuntimeGraphExcludedPath(id)) {
|
|
2926
|
-
const deps = Array.from(collectImportDependencies(code, id));
|
|
2927
|
-
if (verbose) {
|
|
2928
|
-
try {
|
|
2929
|
-
console.log('[hmr-ws][ts-graph] candidate', { id, depsCount: deps.length });
|
|
2930
|
-
}
|
|
2931
|
-
catch { }
|
|
2932
|
-
}
|
|
2933
|
-
upsertGraphModule(id, code, deps);
|
|
2934
|
-
}
|
|
2935
|
-
}
|
|
2936
|
-
}
|
|
2937
|
-
catch { }
|
|
2938
|
-
}
|
|
2939
|
-
}
|
|
2940
|
-
catch { }
|
|
2941
|
-
// If transformRequest failed, handle bare-module vendor shims for 'vue' and 'pinia'
|
|
2942
|
-
if (!transformed?.code) {
|
|
2943
|
-
const bare = spec.startsWith('/') ? spec.slice(1) : spec;
|
|
2944
|
-
const isBare = bare && !bare.includes('/') && !/\.(ts|tsx|js|jsx|mjs|mts|cts|vue)$/i.test(bare);
|
|
2945
|
-
if (isBare && (bare === 'vue' || bare === 'nativescript-vue' || bare === 'pinia')) {
|
|
2946
|
-
const pkg = bare;
|
|
2947
|
-
let code = '';
|
|
2948
|
-
if (pkg === 'vue' || pkg === 'nativescript-vue') {
|
|
2949
|
-
// Re-export Vue helpers from vendor NativeScript-Vue (fallback to 'vue' if present)
|
|
2950
|
-
code = `
|
|
2951
|
-
const g = globalThis;
|
|
2952
|
-
const reg = g.__nsVendorRegistry;
|
|
2953
|
-
const req = reg && g.__nsVendorRequire ? g.__nsVendorRequire : (g.__nsRequire || g.require);
|
|
2954
|
-
let mod = reg && reg.get('${pkg === 'vue' ? 'nativescript-vue' : 'nativescript-vue'}');
|
|
2955
|
-
if (!mod && req) {
|
|
2956
|
-
try { mod = req('${pkg === 'vue' ? 'nativescript-vue' : 'nativescript-vue'}'); } catch {}
|
|
2957
|
-
${pkg === 'vue' ? "if (!mod) { try { mod = req('vue'); } catch {} }" : ''}
|
|
2958
|
-
}
|
|
2959
|
-
mod = mod || {};
|
|
2960
|
-
const v = (mod.default ?? mod);
|
|
2961
|
-
export default v;
|
|
2962
|
-
export const defineComponent = v.defineComponent;
|
|
2963
|
-
export const resolveComponent = v.resolveComponent;
|
|
2964
|
-
export const createVNode = v.createVNode;
|
|
2965
|
-
export const createTextVNode = v.createTextVNode;
|
|
2966
|
-
export const createCommentVNode = v.createCommentVNode;
|
|
2967
|
-
export const Fragment = v.Fragment;
|
|
2968
|
-
export const withCtx = v.withCtx;
|
|
2969
|
-
export const openBlock = v.openBlock;
|
|
2970
|
-
export const createBlock = v.createBlock;
|
|
2971
|
-
export const createElementVNode = v.createElementVNode || v.createVNode;
|
|
2972
|
-
export const createElementBlock = v.createElementBlock || v.createBlock;
|
|
2973
|
-
export const renderSlot = v.renderSlot;
|
|
2974
|
-
export const mergeProps = v.mergeProps;
|
|
2975
|
-
export const toHandlers = v.toHandlers;
|
|
2976
|
-
export const renderList = v.renderList;
|
|
2977
|
-
export const normalizeProps = v.normalizeProps;
|
|
2978
|
-
export const guardReactiveProps = v.guardReactiveProps;
|
|
2979
|
-
export const withDirectives = v.withDirectives;
|
|
2980
|
-
export const resolveDirective = v.resolveDirective;
|
|
2981
|
-
export const withModifiers = v.withModifiers;
|
|
2982
|
-
export const withKeys = v.withKeys;
|
|
2983
|
-
export const ref = v.ref;
|
|
2984
|
-
export const shallowRef = v.shallowRef;
|
|
2985
|
-
export const unref = v.unref;
|
|
2986
|
-
export const computed = v.computed;
|
|
2987
|
-
export const onMounted = v.onMounted;
|
|
2988
|
-
export const onBeforeUnmount = v.onBeforeUnmount;
|
|
2989
|
-
export const onUnmounted = v.onUnmounted;
|
|
2990
|
-
export const watch = v.watch;
|
|
2991
|
-
export const nextTick = v.nextTick;
|
|
2992
|
-
export const createApp = v.createApp || (vm && vm.createApp);
|
|
2993
|
-
export const registerElement = v.registerElement || (vm && vm.registerElement);
|
|
2994
|
-
export const normalizeClass = v.normalizeClass;
|
|
2995
|
-
export const normalizeStyle = v.normalizeStyle;
|
|
2996
|
-
export const toDisplayString = v.toDisplayString;
|
|
2997
|
-
`;
|
|
2998
|
-
}
|
|
2999
|
-
else if (pkg === 'pinia') {
|
|
3000
|
-
// Re-export Pinia APIs from vendor pinia module
|
|
3001
|
-
code = `
|
|
3002
|
-
const g = globalThis;
|
|
3003
|
-
const reg = g.__nsVendorRegistry;
|
|
3004
|
-
const req = reg && g.__nsVendorRequire ? g.__nsVendorRequire : (g.__nsRequire || g.require);
|
|
3005
|
-
let mod = reg && reg.get('pinia');
|
|
3006
|
-
if (!mod && req) { try { mod = req('pinia'); } catch {} }
|
|
3007
|
-
mod = mod || {};
|
|
3008
|
-
const p = (mod.default ?? mod);
|
|
3009
|
-
export default p;
|
|
3010
|
-
export const createPinia = p.createPinia;
|
|
3011
|
-
export const defineStore = p.defineStore;
|
|
3012
|
-
export const storeToRefs = p.storeToRefs;
|
|
3013
|
-
export const setActivePinia = p.setActivePinia;
|
|
3014
|
-
export const getActivePinia = p.getActivePinia;
|
|
3015
|
-
export const mapStores = p.mapStores;
|
|
3016
|
-
export const mapState = p.mapState;
|
|
3017
|
-
export const mapGetters = p.mapGetters;
|
|
3018
|
-
export const mapActions = p.mapActions;
|
|
3019
|
-
export const mapWritableState = p.mapWritableState;
|
|
3020
|
-
export const piniaSymbol = p.piniaSymbol;
|
|
3021
|
-
`;
|
|
3022
|
-
}
|
|
3023
|
-
res.statusCode = 200;
|
|
3024
|
-
res.end(code || 'export {}\n');
|
|
3025
|
-
return;
|
|
3026
|
-
}
|
|
3027
|
-
// Generic bare module resolution via Vite plugin container
|
|
3028
|
-
if (isBare) {
|
|
3029
|
-
try {
|
|
3030
|
-
const resolved = await server.pluginContainer?.resolveId?.(spec, undefined);
|
|
3031
|
-
const resolvedId = typeof resolved === 'string' ? resolved : resolved?.id || null;
|
|
3032
|
-
if (resolvedId) {
|
|
3033
|
-
const r = await server.transformRequest(resolvedId);
|
|
3034
|
-
if (r?.code) {
|
|
3035
|
-
transformed = r;
|
|
3036
|
-
resolvedCandidate = resolvedId;
|
|
3037
|
-
}
|
|
3038
|
-
}
|
|
3039
|
-
}
|
|
3040
|
-
catch { }
|
|
3041
|
-
}
|
|
3042
|
-
if (!transformed?.code) {
|
|
3043
|
-
// Emit a module that throws with context for easier on-device debugging
|
|
3044
|
-
try {
|
|
3045
|
-
const tried = Array.from(new Set(transformCandidates.length > 0 ? transformCandidates : candidates)).slice(0, 12);
|
|
3046
|
-
const out = `// [ns:m] transform miss path=${spec} tried=${tried.length}\n` + `throw new Error(${JSON.stringify(`[ns/m] transform failed for ${spec} (tried ${tried.length} candidates).`)});\nexport {};\n`;
|
|
3047
|
-
res.statusCode = 404;
|
|
3048
|
-
res.end(out);
|
|
3049
|
-
return;
|
|
3050
|
-
}
|
|
3051
|
-
catch {
|
|
3052
|
-
res.statusCode = 404;
|
|
3053
|
-
res.end('export {}\n');
|
|
3054
|
-
return;
|
|
3055
|
-
}
|
|
3056
|
-
}
|
|
3057
|
-
}
|
|
3058
|
-
const projectRoot = server.config?.root || process.cwd();
|
|
3059
|
-
const serverOrigin = getServerOrigin(server);
|
|
3060
|
-
let code;
|
|
3061
|
-
try {
|
|
3062
|
-
code = await finalizeNsMServedModule({
|
|
3063
|
-
code: transformed.code,
|
|
3064
|
-
spec,
|
|
3065
|
-
resolvedCandidate,
|
|
3066
|
-
forcedVer,
|
|
3067
|
-
bootTaggedRequest,
|
|
3068
|
-
graphVersion: Number(graphVersion || 0),
|
|
3069
|
-
serverOrigin,
|
|
3070
|
-
strategy: ACTIVE_STRATEGY,
|
|
3071
|
-
helpers: {
|
|
3072
|
-
requireGuardSnippet: REQUIRE_GUARD_SNIPPET,
|
|
3073
|
-
cleanCode,
|
|
3074
|
-
processCodeForDevice: (value, sourceId) => processCodeForDevice(value, false, true, /(?:^|\/)node_modules\//.test(sourceId || spec || ''), sourceId || spec),
|
|
3075
|
-
rewriteImports: (value, importerPath) => rewriteImports(value, importerPath, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, serverOrigin, true),
|
|
3076
|
-
rewriteAngularEntry: (value, importerPath) => prepareAngularEntryForDevice(value, importerPath, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, serverOrigin, true),
|
|
3077
|
-
expandStarExports: async (value) => expandStarExports(value, server, projectRoot, verbose),
|
|
3078
|
-
dedupeRtNamedImportsAgainstDestructures,
|
|
3079
|
-
ensureVariableDynamicImportHelper,
|
|
3080
|
-
ensureGuardPlainDynamicImports,
|
|
3081
|
-
deduplicateLinkerImports,
|
|
3082
|
-
wrapCommonJsModuleForDevice,
|
|
3083
|
-
assertNoOptimizedArtifacts,
|
|
3084
|
-
ensureVersionedRtImports,
|
|
3085
|
-
ensureDestructureCoreImports,
|
|
3086
|
-
buildBootProgressSnippet,
|
|
3087
|
-
hoistTopLevelStaticImports,
|
|
3088
|
-
warn: (message, error) => {
|
|
3089
|
-
if (verbose)
|
|
3090
|
-
console.warn(`${message}:`, error?.message || error);
|
|
3091
|
-
},
|
|
3092
|
-
},
|
|
3093
|
-
});
|
|
3094
|
-
}
|
|
3095
|
-
catch (e) {
|
|
3096
|
-
res.statusCode = 500;
|
|
3097
|
-
return void res.end(`throw new Error(${JSON.stringify(e?.message || String(e))});\nexport {};`);
|
|
3098
|
-
}
|
|
3099
|
-
// Dev-only: link-check static imports to surface missing bindings early
|
|
3100
|
-
try {
|
|
3101
|
-
const devCheck = process.env.NODE_ENV !== 'production';
|
|
3102
|
-
if (devCheck) {
|
|
3103
|
-
const ast = babelParse(code, {
|
|
3104
|
-
sourceType: 'module',
|
|
3105
|
-
plugins: MODULE_IMPORT_ANALYSIS_PLUGINS,
|
|
3106
|
-
});
|
|
3107
|
-
const imports = [];
|
|
3108
|
-
babelTraverse(ast, {
|
|
3109
|
-
ImportDeclaration(p) {
|
|
3110
|
-
const src = p.node.source.value;
|
|
3111
|
-
if (typeof src !== 'string')
|
|
3112
|
-
return;
|
|
3113
|
-
const wantsDefault = p.node.specifiers.some((s) => s.type === 'ImportDefaultSpecifier');
|
|
3114
|
-
imports.push({ src, wantsDefault });
|
|
3115
|
-
},
|
|
3116
|
-
});
|
|
3117
|
-
// Resolve and verify each static import that asks for default
|
|
3118
|
-
for (const imp of imports) {
|
|
3119
|
-
if (!imp.wantsDefault)
|
|
3120
|
-
continue;
|
|
3121
|
-
// Only check our served endpoints and app modules
|
|
3122
|
-
if (/^https?:\/\/[^\s]+\/ns\//.test(imp.src) || /^https?:\/\/[^\s]+\/.+/.test(imp.src)) {
|
|
3123
|
-
const u = new URL(imp.src, 'http://localhost');
|
|
3124
|
-
// Fetch target module's sanitized code using server.transformRequest or by routing through our own endpoints heuristically
|
|
3125
|
-
let targetCode = '';
|
|
3126
|
-
try {
|
|
3127
|
-
if (u.pathname.startsWith('/ns/asm')) {
|
|
3128
|
-
// Reconstruct: call our own assembler handler to get code (preferred)
|
|
3129
|
-
const target = await server.transformRequest(imp.src.replace(/^https?:\/\/[^/]+/, ''));
|
|
3130
|
-
targetCode = target?.code || '';
|
|
3131
|
-
}
|
|
3132
|
-
else if (u.pathname.startsWith('/ns/sfc')) {
|
|
3133
|
-
// Delegator re-exports default from /ns/asm — skip; assembler will be checked when imported by upstream
|
|
3134
|
-
continue;
|
|
3135
|
-
}
|
|
3136
|
-
else if (u.pathname.startsWith('/ns/m')) {
|
|
3137
|
-
// Resolve to local project path and transform with same candidate logic as /ns/m handler
|
|
3138
|
-
let local = u.pathname.replace(/^\/ns\/m/, '');
|
|
3139
|
-
try {
|
|
3140
|
-
// Normalize project-relative path
|
|
3141
|
-
if (local.startsWith('@/'))
|
|
3142
|
-
local = APP_VIRTUAL_WITH_SLASH + local.slice(2);
|
|
3143
|
-
if (local.startsWith('./'))
|
|
3144
|
-
local = local.slice(1);
|
|
3145
|
-
if (!local.startsWith('/'))
|
|
3146
|
-
local = '/' + local;
|
|
3147
|
-
const hasExt = /(\.ts|\.tsx|\.js|\.jsx|\.mjs|\.mts|\.cts|\.vue)$/i.test(local);
|
|
3148
|
-
const baseNoExt = hasExt ? local.replace(/\.(ts|tsx|js|jsx|mjs|mts|cts)$/i, '') : local;
|
|
3149
|
-
const cands = [...(hasExt ? [local] : []), baseNoExt + '.ts', baseNoExt + '.js', baseNoExt + '.tsx', baseNoExt + '.jsx', baseNoExt + '.mjs', baseNoExt + '.mts', baseNoExt + '.cts', baseNoExt + '.vue', baseNoExt + '/index.ts', baseNoExt + '/index.js', baseNoExt + '/index.tsx', baseNoExt + '/index.jsx', baseNoExt + '/index.mjs'];
|
|
3150
|
-
let t = null;
|
|
3151
|
-
for (const cand of cands) {
|
|
3152
|
-
try {
|
|
3153
|
-
const r = await server.transformRequest(cand);
|
|
3154
|
-
if (r?.code) {
|
|
3155
|
-
t = r;
|
|
3156
|
-
break;
|
|
3157
|
-
}
|
|
3158
|
-
}
|
|
3159
|
-
catch { }
|
|
3160
|
-
if (t?.code)
|
|
3161
|
-
break;
|
|
3162
|
-
}
|
|
3163
|
-
if (!t?.code) {
|
|
3164
|
-
try {
|
|
3165
|
-
const rid = await server.pluginContainer?.resolveId?.(local, undefined);
|
|
3166
|
-
const ridStr = typeof rid === 'string' ? rid : rid?.id || null;
|
|
3167
|
-
if (ridStr) {
|
|
3168
|
-
const r2 = await server.transformRequest(ridStr);
|
|
3169
|
-
if (r2?.code)
|
|
3170
|
-
t = r2;
|
|
3171
|
-
}
|
|
3172
|
-
}
|
|
3173
|
-
catch { }
|
|
3174
|
-
}
|
|
3175
|
-
targetCode = t?.code || '';
|
|
3176
|
-
}
|
|
3177
|
-
catch { }
|
|
3178
|
-
}
|
|
3179
|
-
else if (u.pathname.startsWith('/ns/rt') || u.pathname.startsWith('/ns/core')) {
|
|
3180
|
-
// Bridges export named/default as needed; skip default check
|
|
3181
|
-
continue;
|
|
3182
|
-
}
|
|
3183
|
-
}
|
|
3184
|
-
catch { }
|
|
3185
|
-
if (!targetCode)
|
|
3186
|
-
continue;
|
|
3187
|
-
const hasDefault = /\bexport\s+default\b/.test(targetCode) || /export\s*\{\s*default\s*(?:as\s*default)?\s*\}/.test(targetCode);
|
|
3188
|
-
if (!hasDefault) {
|
|
3189
|
-
// CJS/UMD modules won't have `export default` — they get CJS-wrapped
|
|
3190
|
-
// by the serving pipeline. Only warn, don't fatally block the importer.
|
|
3191
|
-
const hasCjsPattern = /\bmodule\s*\.\s*exports\b/.test(targetCode) || /\bexports\s*\.\s*\w/.test(targetCode);
|
|
3192
|
-
if (hasCjsPattern) {
|
|
3193
|
-
try {
|
|
3194
|
-
console.warn(`[ns:m][link-check] CJS module without export default: ${u.pathname} (will be CJS-wrapped at serve time)`);
|
|
3195
|
-
}
|
|
3196
|
-
catch { }
|
|
3197
|
-
continue;
|
|
3198
|
-
}
|
|
3199
|
-
const msg = `[link-check] Missing default export in ${u.pathname}${u.search} (imported by ${resolvedCandidate || spec})`;
|
|
3200
|
-
// Emit a module that throws to surface the exact offender
|
|
3201
|
-
res.statusCode = 200;
|
|
3202
|
-
res.end(`throw new Error(${JSON.stringify(msg)});\nexport {};`);
|
|
3203
|
-
return;
|
|
3204
|
-
}
|
|
3205
|
-
}
|
|
3206
|
-
}
|
|
3207
|
-
}
|
|
3208
|
-
}
|
|
3209
|
-
catch (eLC) {
|
|
3210
|
-
try {
|
|
3211
|
-
console.warn('[ns:m][link-check] failed', eLC?.message || eLC);
|
|
3212
|
-
}
|
|
3213
|
-
catch { }
|
|
577
|
+
// Additional connection diagnostics
|
|
578
|
+
wss.on('connection', (ws, req) => {
|
|
579
|
+
const role = getHmrSocketRoleFromRequestUrl(req.url);
|
|
580
|
+
ws.__nsHmrClientRole = role;
|
|
581
|
+
try {
|
|
582
|
+
if (verbose) {
|
|
583
|
+
const ra = req.socket?.remoteAddress;
|
|
584
|
+
const rp = req.socket?.remotePort;
|
|
585
|
+
console.log('[hmr-ws] Client connected', { role, remote: ra + (rp ? ':' + rp : '') });
|
|
3214
586
|
}
|
|
3215
|
-
res.statusCode = 200;
|
|
3216
|
-
res.end(code);
|
|
3217
587
|
}
|
|
3218
|
-
catch
|
|
588
|
+
catch { }
|
|
589
|
+
ws.on('close', () => {
|
|
3219
590
|
try {
|
|
3220
|
-
|
|
591
|
+
if (verbose) {
|
|
592
|
+
const ra = req.socket?.remoteAddress;
|
|
593
|
+
const rp = req.socket?.remotePort;
|
|
594
|
+
console.log('[hmr-ws] Client disconnected', { role, remote: ra + (rp ? ':' + rp : '') });
|
|
595
|
+
}
|
|
3221
596
|
}
|
|
3222
597
|
catch { }
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
wss.on('error', (err) => {
|
|
601
|
+
console.warn('[hmr-ws] server error:', err?.message || String(err));
|
|
602
|
+
});
|
|
603
|
+
// Import map endpoint: GET /ns/import-map.json — see websocket-import-map-route.ts
|
|
604
|
+
registerImportMapRoute(server, { getStrategy: () => strategy });
|
|
605
|
+
// Dev-only HTTP ESM loader endpoint for device clients
|
|
606
|
+
// ESM module server for application/source modules: GET /ns/m/* — see websocket-ns-m.ts
|
|
607
|
+
registerNsModuleServerRoute(server, {
|
|
608
|
+
verbose,
|
|
609
|
+
appVirtualWithSlash: APP_VIRTUAL_WITH_SLASH,
|
|
610
|
+
sfcFileMap,
|
|
611
|
+
depFileMap,
|
|
612
|
+
getGraphVersion: () => moduleGraph.version,
|
|
613
|
+
getStrategy: () => strategy,
|
|
614
|
+
sharedTransformRequest,
|
|
615
|
+
ensureInitialGraphPopulationStarted,
|
|
616
|
+
upsertGraphModule: (id, code, deps, opts) => {
|
|
617
|
+
moduleGraph.upsert(id, code, deps, opts);
|
|
618
|
+
},
|
|
3226
619
|
});
|
|
3227
|
-
|
|
620
|
+
// ESM runtime bridge for NativeScript-Vue: GET /ns/rt[/<ver>] — see ns-rt-route.ts
|
|
621
|
+
registerNsRtBridgeRoute(server, { getGraphVersion: () => moduleGraph.version });
|
|
622
|
+
// Dev-only vendor import unifier: rewrite 'vue'/'nativescript-vue' to /ns/rt/<ver>
|
|
623
|
+
// so plugins and the app share a single Vue/NativeScript-Vue realm. See websocket-vendor-unifier.ts.
|
|
624
|
+
registerVendorUnifierHandler(server, { getGraphVersion: () => moduleGraph.version, getStrategy: () => strategy });
|
|
625
|
+
// @nativescript/core device bridge (+ stray /node_modules/@nativescript/core redirect) — see websocket-ns-core.ts
|
|
626
|
+
registerNsCoreRoute(server, {
|
|
627
|
+
getGraphVersion: () => moduleGraph.version,
|
|
628
|
+
sharedTransformRequest,
|
|
629
|
+
});
|
|
630
|
+
// Device bootstrap: GET /ns/entry-rt + GET /ns/entry — see websocket-ns-entry.ts
|
|
631
|
+
registerNsEntryRoutes(server, {
|
|
3228
632
|
verbose,
|
|
3229
|
-
requireGuardSnippet: REQUIRE_GUARD_SNIPPET,
|
|
3230
633
|
appRootDir: APP_ROOT_DIR,
|
|
3231
634
|
defaultMainEntry: DEFAULT_MAIN_ENTRY,
|
|
3232
635
|
defaultMainEntryVirtual: DEFAULT_MAIN_ENTRY_VIRTUAL,
|
|
3233
|
-
getGraphVersion: () =>
|
|
3234
|
-
getServerOrigin,
|
|
3235
|
-
});
|
|
3236
|
-
// 2.55) Dev-only vendor import unifier: rewrite 'vue'/'nativescript-vue' to /ns/rt/<ver>
|
|
3237
|
-
// This ensures plugins and app share a single Vue/NativeScript-Vue instance/realm.
|
|
3238
|
-
registerVendorUnifierHandler(server, {
|
|
3239
|
-
getGraphVersion: () => Number(graphVersion || 0),
|
|
3240
|
-
getServerOrigin,
|
|
3241
|
-
getStrategy: () => ACTIVE_STRATEGY,
|
|
3242
|
-
});
|
|
3243
|
-
// 2.6) ESM bridge for @nativescript/core: GET /ns/core[/<ver>][?p=sub/path]
|
|
3244
|
-
server.middlewares.use(async (req, res, next) => {
|
|
3245
|
-
try {
|
|
3246
|
-
const urlObj = new URL(req.url || '', 'http://localhost');
|
|
3247
|
-
const coreRequest = parseCoreBridgeRequest(urlObj.pathname, urlObj.searchParams, Number(graphVersion || 0));
|
|
3248
|
-
if (!coreRequest)
|
|
3249
|
-
return next();
|
|
3250
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
3251
|
-
res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
|
|
3252
|
-
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
|
|
3253
|
-
res.setHeader('Pragma', 'no-cache');
|
|
3254
|
-
res.setHeader('Expires', '0');
|
|
3255
|
-
const { hasExplicitVersion, key, normalizedSub, sub, ver } = coreRequest;
|
|
3256
|
-
// Any @nativescript/core subpath import (including shallow ones like
|
|
3257
|
-
// `utils`) may expose exports that are not available from the root
|
|
3258
|
-
// vendor bundle namespace. Serve the actual transformed module content
|
|
3259
|
-
// instead of the lightweight proxy bridge.
|
|
3260
|
-
if (sub) {
|
|
3261
|
-
try {
|
|
3262
|
-
const resolvedSubpath = normalizedSub || sub;
|
|
3263
|
-
const projectRoot = server.config?.root || process.cwd();
|
|
3264
|
-
const resolveModuleId = async (moduleId) => {
|
|
3265
|
-
const resolved = await server.pluginContainer?.resolveId?.(moduleId, undefined);
|
|
3266
|
-
return typeof resolved === 'string' ? resolved : resolved?.id || null;
|
|
3267
|
-
};
|
|
3268
|
-
const resolvedId = await resolveRuntimeCoreModulePath(resolvedSubpath, resolveModuleId);
|
|
3269
|
-
const modulePath = resolvedId || `/node_modules/@nativescript/core/${resolvedSubpath}`;
|
|
3270
|
-
const transformed = await sharedTransformRequest(modulePath);
|
|
3271
|
-
if (!hasExplicitVersion) {
|
|
3272
|
-
if (transformed?.code) {
|
|
3273
|
-
const expandedModuleCode = await expandStarExports(transformed.code, server, projectRoot, verbose);
|
|
3274
|
-
res.statusCode = 200;
|
|
3275
|
-
res.end(buildVersionedCoreSubpathAliasModule(resolvedSubpath, ver, extractExportedNames(expandedModuleCode), hasModuleDefaultExport(expandedModuleCode)));
|
|
3276
|
-
return;
|
|
3277
|
-
}
|
|
3278
|
-
res.statusCode = 200;
|
|
3279
|
-
res.end(buildVersionedCoreSubpathAliasModule(resolvedSubpath, ver));
|
|
3280
|
-
return;
|
|
3281
|
-
}
|
|
3282
|
-
if (transformed?.code) {
|
|
3283
|
-
// Minimal pipeline: Vite already produces correct ESM.
|
|
3284
|
-
// ONLY rewrite specifier strings to device-fetchable URLs.
|
|
3285
|
-
// Do NOT run processCodeForDevice, rewriteImports, or any
|
|
3286
|
-
// other heavy transform — those mangle newlines, eat exports,
|
|
3287
|
-
// and cause cascading "does not provide an export" failures.
|
|
3288
|
-
const moduleCode = rewriteSpecifiersForDevice(transformed.code, getServerOrigin(server), Number(ver));
|
|
3289
|
-
res.statusCode = 200;
|
|
3290
|
-
res.end(moduleCode);
|
|
3291
|
-
return;
|
|
3292
|
-
}
|
|
3293
|
-
}
|
|
3294
|
-
catch (e) {
|
|
3295
|
-
try {
|
|
3296
|
-
console.warn('[ns-core-bridge] deep subpath serve failed:', sub, e?.message);
|
|
3297
|
-
}
|
|
3298
|
-
catch { }
|
|
3299
|
-
}
|
|
3300
|
-
}
|
|
3301
|
-
// Main entry or shallow subpath: use proxy bridge
|
|
3302
|
-
let code = buildVersionedCoreMainBridgeModule(key, ver);
|
|
3303
|
-
if (!sub) {
|
|
3304
|
-
try {
|
|
3305
|
-
const projectRoot = server.config?.root || process.cwd();
|
|
3306
|
-
const coreSpecifier = '@nativescript/core';
|
|
3307
|
-
const resolved = await server.pluginContainer?.resolveId?.(coreSpecifier, undefined);
|
|
3308
|
-
const resolvedId = typeof resolved === 'string' ? resolved : resolved?.id || null;
|
|
3309
|
-
const modulePath = resolvedId || '/node_modules/@nativescript/core/index.js';
|
|
3310
|
-
const staticExportNames = collectStaticExportNamesFromFile(modulePath);
|
|
3311
|
-
const staticExportOrigins = await normalizeCoreExportOriginsForRuntime(collectStaticExportOriginsFromFile(modulePath), async (moduleId) => {
|
|
3312
|
-
const nextResolved = await server.pluginContainer?.resolveId?.(moduleId, undefined);
|
|
3313
|
-
return typeof nextResolved === 'string' ? nextResolved : nextResolved?.id || null;
|
|
3314
|
-
}, modulePath);
|
|
3315
|
-
if (staticExportNames.length) {
|
|
3316
|
-
code = buildVersionedCoreMainBridgeModule(key, ver, staticExportNames, staticExportOrigins);
|
|
3317
|
-
}
|
|
3318
|
-
else {
|
|
3319
|
-
const transformed = await sharedTransformRequest(modulePath);
|
|
3320
|
-
if (transformed?.code) {
|
|
3321
|
-
const expandedModuleCode = await expandStarExports(transformed.code, server, projectRoot, verbose);
|
|
3322
|
-
code = buildVersionedCoreMainBridgeModule(key, ver, extractExportedNames(expandedModuleCode));
|
|
3323
|
-
}
|
|
3324
|
-
}
|
|
3325
|
-
}
|
|
3326
|
-
catch (e) {
|
|
3327
|
-
try {
|
|
3328
|
-
console.warn('[ns-core-bridge] main bridge export discovery failed:', e?.message);
|
|
3329
|
-
}
|
|
3330
|
-
catch { }
|
|
3331
|
-
}
|
|
3332
|
-
}
|
|
3333
|
-
res.statusCode = 200;
|
|
3334
|
-
res.end(code);
|
|
3335
|
-
}
|
|
3336
|
-
catch (e) {
|
|
3337
|
-
next();
|
|
3338
|
-
}
|
|
636
|
+
getGraphVersion: () => moduleGraph.version,
|
|
3339
637
|
});
|
|
638
|
+
// Transactional HMR endpoint: GET /ns/txn/<ver> — one ESM that sequentially
|
|
639
|
+
// imports all changed modules for the given graph version. See websocket-txn.ts.
|
|
3340
640
|
registerTxnHandler(server, {
|
|
3341
641
|
resolveTxnIds: (version, fallbackChangedIds) => {
|
|
3342
|
-
|
|
3343
|
-
if (ids.length) {
|
|
3344
|
-
|
|
642
|
+
let ids = moduleGraph.getTxnBatch(version) || [];
|
|
643
|
+
if (!ids.length && fallbackChangedIds.length) {
|
|
644
|
+
try {
|
|
645
|
+
ids = moduleGraph.computeTxnOrderForChanged(fallbackChangedIds);
|
|
646
|
+
}
|
|
647
|
+
catch { }
|
|
3345
648
|
}
|
|
3346
|
-
return
|
|
649
|
+
return ids;
|
|
3347
650
|
},
|
|
3348
651
|
});
|
|
3349
|
-
|
|
652
|
+
// Framework-owned dev HTTP endpoints (Vue: /ns/sfc, /ns/sfc-meta, /ns/asm).
|
|
653
|
+
// Only the strategy that owns routes (Vue) registers them via
|
|
654
|
+
// `registerRoutes`; SFC/assembler endpoints are inherently Vue-only
|
|
655
|
+
// (see websocket-sfc.ts).
|
|
656
|
+
strategy.registerRoutes?.({
|
|
657
|
+
server,
|
|
658
|
+
wss,
|
|
3350
659
|
verbose,
|
|
3351
|
-
requireGuardSnippet: REQUIRE_GUARD_SNIPPET,
|
|
3352
660
|
appVirtualWithSlash: APP_VIRTUAL_WITH_SLASH,
|
|
3353
661
|
sfcFileMap,
|
|
3354
662
|
depFileMap,
|
|
3355
|
-
getGraphVersion: () =>
|
|
3356
|
-
getStrategy: () =>
|
|
3357
|
-
getServerOrigin,
|
|
3358
|
-
cleanCode,
|
|
3359
|
-
processCodeForDevice,
|
|
3360
|
-
rewriteImports,
|
|
3361
|
-
ensureVariableDynamicImportHelper,
|
|
3362
|
-
ensureGuardPlainDynamicImports,
|
|
3363
|
-
ensureVersionedRtImports,
|
|
3364
|
-
ensureVersionedCoreImports,
|
|
3365
|
-
ensureDestructureCoreImports,
|
|
3366
|
-
extractExportMetadata,
|
|
663
|
+
getGraphVersion: () => moduleGraph.version,
|
|
664
|
+
getStrategy: () => strategy,
|
|
3367
665
|
});
|
|
3368
666
|
wss.on('connection', async (ws) => {
|
|
3369
667
|
if (verbose)
|
|
@@ -3373,18 +671,19 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3373
671
|
try {
|
|
3374
672
|
const msg = JSON.parse(String(data));
|
|
3375
673
|
if (msg?.type === 'ns:hmr-resync-request') {
|
|
3376
|
-
emitFullGraph(ws);
|
|
674
|
+
moduleGraph.emitFullGraph(ws);
|
|
3377
675
|
}
|
|
3378
676
|
else if (msg?.type === 'ns:hmr-sfc-registry-request') {
|
|
3379
677
|
// Resend full SFC registry (lightweight code path)
|
|
3380
|
-
|
|
678
|
+
strategy
|
|
679
|
+
.buildRegistry({
|
|
3381
680
|
server,
|
|
3382
681
|
sfcFileMap,
|
|
3383
682
|
depFileMap,
|
|
3384
683
|
wss: wss,
|
|
3385
684
|
verbose,
|
|
3386
685
|
helpers: {
|
|
3387
|
-
cleanCode,
|
|
686
|
+
cleanCode: (code) => cleanCode(code, strategy),
|
|
3388
687
|
collectImportDependencies,
|
|
3389
688
|
isCoreGlobalsReference,
|
|
3390
689
|
isNativeScriptCoreModule,
|
|
@@ -3394,238 +693,8 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3394
693
|
rewriteImports,
|
|
3395
694
|
processSfcCode,
|
|
3396
695
|
},
|
|
3397
|
-
})
|
|
3398
|
-
|
|
3399
|
-
else if (msg?.type === 'ns:fetch-module' && msg.path && typeof msg.requestId !== 'undefined') {
|
|
3400
|
-
(async () => {
|
|
3401
|
-
var _a, _b, _c;
|
|
3402
|
-
const requestId = msg.requestId;
|
|
3403
|
-
let spec = msg.path;
|
|
3404
|
-
// Normalize: strip query/hash, ensure leading '/'
|
|
3405
|
-
if (typeof spec === 'string') {
|
|
3406
|
-
spec = spec.replace(/[?#].*$/, '');
|
|
3407
|
-
if (spec.startsWith('@/'))
|
|
3408
|
-
spec = APP_VIRTUAL_WITH_SLASH + spec.slice(2);
|
|
3409
|
-
// Collapse accidental repeated index segments: /foo/index/index -> /foo/index
|
|
3410
|
-
spec = spec.replace(/\/(index)(?:\/(?:index))+$/i, '/$1');
|
|
3411
|
-
if (spec === '@') {
|
|
3412
|
-
try {
|
|
3413
|
-
((_a = globalThis).__NS_FETCH_METRICS__ || (_a.__NS_FETCH_METRICS__ = {})).invalidAtSpec = (globalThis.__NS_FETCH_METRICS__.invalidAtSpec || 0) + 1;
|
|
3414
|
-
}
|
|
3415
|
-
catch { }
|
|
3416
|
-
ws.send(JSON.stringify({
|
|
3417
|
-
type: 'ns:module-response',
|
|
3418
|
-
requestId,
|
|
3419
|
-
path: msg.path,
|
|
3420
|
-
error: 'invalid-spec-@',
|
|
3421
|
-
}));
|
|
3422
|
-
return;
|
|
3423
|
-
}
|
|
3424
|
-
// Reject device artifact paths from _ns_hmr (client should never request these)
|
|
3425
|
-
if (/(?:\/_ns_hmr|Documents)\/src\/sfc\//.test(spec) || spec.startsWith('/_ns_hmr/')) {
|
|
3426
|
-
try {
|
|
3427
|
-
((_b = globalThis).__NS_FETCH_METRICS__ || (_b.__NS_FETCH_METRICS__ = {})).artifactPathRejected = (globalThis.__NS_FETCH_METRICS__.artifactPathRejected || 0) + 1;
|
|
3428
|
-
}
|
|
3429
|
-
catch { }
|
|
3430
|
-
ws.send(JSON.stringify({
|
|
3431
|
-
type: 'ns:module-response',
|
|
3432
|
-
requestId,
|
|
3433
|
-
path: msg.path,
|
|
3434
|
-
error: 'artifact-path-disallowed',
|
|
3435
|
-
}));
|
|
3436
|
-
return;
|
|
3437
|
-
}
|
|
3438
|
-
}
|
|
3439
|
-
// Special-case JSON package metadata requests at project root ONLY: provide a tiny stub module (no transform)
|
|
3440
|
-
// Intentionally narrow match to '/package.json' (or 'package.json') to avoid catching relative imports like '../package.json'.
|
|
3441
|
-
if (/^\/?package\.json(?:\/index)?$/.test(spec)) {
|
|
3442
|
-
const root = server.config?.root || process.cwd();
|
|
3443
|
-
let json = '{}';
|
|
3444
|
-
try {
|
|
3445
|
-
json = readFileSync(path.join(root, 'package.json'), 'utf-8');
|
|
3446
|
-
}
|
|
3447
|
-
catch { }
|
|
3448
|
-
const code = `export default ${json}\n`;
|
|
3449
|
-
const rel = 'hmr-stubs/package.json.mjs';
|
|
3450
|
-
ws.send(JSON.stringify({
|
|
3451
|
-
type: 'ns:module-response',
|
|
3452
|
-
requestId,
|
|
3453
|
-
path: rel,
|
|
3454
|
-
code,
|
|
3455
|
-
}));
|
|
3456
|
-
return;
|
|
3457
|
-
}
|
|
3458
|
-
// Basic transform response cache (spec -> { code, path, hash }) to reduce CPU for rapid repeated imports
|
|
3459
|
-
const fetchCache = ((_c = globalThis).__NS_FETCH_CACHE__ || (_c.__NS_FETCH_CACHE__ = new Map()));
|
|
3460
|
-
const FETCH_CACHE_VERSION = 2;
|
|
3461
|
-
try {
|
|
3462
|
-
// Normalize leading ./
|
|
3463
|
-
if (spec.startsWith('./'))
|
|
3464
|
-
spec = spec.slice(1);
|
|
3465
|
-
if (!spec.startsWith('/'))
|
|
3466
|
-
spec = '/' + spec;
|
|
3467
|
-
const cacheKey = spec + '::' + FETCH_CACHE_VERSION;
|
|
3468
|
-
if (fetchCache.has(cacheKey)) {
|
|
3469
|
-
const cached = fetchCache.get(cacheKey);
|
|
3470
|
-
ws.send(JSON.stringify({
|
|
3471
|
-
type: 'ns:module-response',
|
|
3472
|
-
requestId,
|
|
3473
|
-
path: cached.path,
|
|
3474
|
-
code: cached.code,
|
|
3475
|
-
cached: true,
|
|
3476
|
-
}));
|
|
3477
|
-
return;
|
|
3478
|
-
}
|
|
3479
|
-
const root = server.config?.root || process.cwd();
|
|
3480
|
-
// Attempt transform via Vite with robust variant resolution (handles .mjs inputs)
|
|
3481
|
-
let transformed = null;
|
|
3482
|
-
let resolvedCandidate = null;
|
|
3483
|
-
const hasExt = /\.(ts|tsx|js|jsx|mjs|mts|cts|vue)$/i.test(spec);
|
|
3484
|
-
const baseNoExt = hasExt ? spec.replace(/\.(ts|tsx|js|jsx|mjs|mts|cts)$/i, '') : spec;
|
|
3485
|
-
const candidates = [];
|
|
3486
|
-
if (hasExt) {
|
|
3487
|
-
candidates.push(spec); // as-is
|
|
3488
|
-
}
|
|
3489
|
-
candidates.push(baseNoExt + '.ts', baseNoExt + '.js', baseNoExt + '.tsx', baseNoExt + '.jsx', baseNoExt + '.mjs', baseNoExt + '.mts', baseNoExt + '.cts', baseNoExt + '.vue', baseNoExt + '/index.ts', baseNoExt + '/index.js', baseNoExt + '/index.tsx', baseNoExt + '/index.jsx', baseNoExt + '/index.mjs');
|
|
3490
|
-
for (const cand of candidates) {
|
|
3491
|
-
try {
|
|
3492
|
-
const r = await server.transformRequest(cand);
|
|
3493
|
-
if (r?.code) {
|
|
3494
|
-
transformed = r;
|
|
3495
|
-
resolvedCandidate = cand;
|
|
3496
|
-
break;
|
|
3497
|
-
}
|
|
3498
|
-
}
|
|
3499
|
-
catch { }
|
|
3500
|
-
}
|
|
3501
|
-
if (!transformed?.code) {
|
|
3502
|
-
ws.send(JSON.stringify({
|
|
3503
|
-
type: 'ns:module-response',
|
|
3504
|
-
requestId,
|
|
3505
|
-
error: 'transform-failed',
|
|
3506
|
-
path: msg.path,
|
|
3507
|
-
}));
|
|
3508
|
-
return;
|
|
3509
|
-
}
|
|
3510
|
-
let code = transformed.code;
|
|
3511
|
-
// Reuse existing sanitation chain (lightweight)
|
|
3512
|
-
code = cleanCode(code);
|
|
3513
|
-
code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(resolvedCandidate || spec), resolvedCandidate || spec);
|
|
3514
|
-
try {
|
|
3515
|
-
code = ensureVersionedCoreImports(code, getServerOrigin(server), graphVersion);
|
|
3516
|
-
}
|
|
3517
|
-
catch { }
|
|
3518
|
-
code = rewriteImports(code, spec, sfcFileMap, depFileMap, server.config?.root || process.cwd(), !!verbose, undefined, getServerOrigin(server));
|
|
3519
|
-
code = ensureVariableDynamicImportHelper(code);
|
|
3520
|
-
code = ensureGuardPlainDynamicImports(code, origin);
|
|
3521
|
-
// Determine output relative file path (project-relative .mjs)
|
|
3522
|
-
const specForRel = resolvedCandidate || spec;
|
|
3523
|
-
let rel = specForRel.replace(/^\//, '');
|
|
3524
|
-
rel = rel.replace(/\.(tsx?|jsx?)$/i, '.mjs');
|
|
3525
|
-
if (!rel.endsWith('.mjs'))
|
|
3526
|
-
rel += '.mjs';
|
|
3527
|
-
// Collect immediate relative .mjs dependencies and transform them as additional files
|
|
3528
|
-
const additionalFiles = [];
|
|
3529
|
-
try {
|
|
3530
|
-
const importRE = /from\s+["']([^"']+\.mjs)["']|import\(\s*["']([^"']+\.mjs)["']\s*\)/g;
|
|
3531
|
-
const importerDir = path.posix.dirname(specForRel);
|
|
3532
|
-
let m;
|
|
3533
|
-
const seen = new Set();
|
|
3534
|
-
while ((m = importRE.exec(code)) !== null) {
|
|
3535
|
-
let dep = m[1] || m[2];
|
|
3536
|
-
if (!dep)
|
|
3537
|
-
continue;
|
|
3538
|
-
if (!(dep.startsWith('./') || dep.startsWith('../')))
|
|
3539
|
-
continue;
|
|
3540
|
-
// Resolve to absolute
|
|
3541
|
-
let abs = path.posix.normalize(path.posix.join(importerDir, dep));
|
|
3542
|
-
if (!abs.startsWith('/'))
|
|
3543
|
-
abs = '/' + abs;
|
|
3544
|
-
const depBase = abs.replace(/\.(ts|js|tsx|jsx|mjs|mts|cts)$/i, '');
|
|
3545
|
-
if (seen.has(depBase))
|
|
3546
|
-
continue;
|
|
3547
|
-
seen.add(depBase);
|
|
3548
|
-
// Transform dep via same candidate resolution
|
|
3549
|
-
const depHasExt = /\.(ts|tsx|js|jsx|mjs|mts|cts|vue)$/i.test(depBase);
|
|
3550
|
-
const depNoExt = depHasExt ? depBase.replace(/\.(ts|tsx|js|jsx|mjs|mts|cts|vue)$/i, '') : depBase;
|
|
3551
|
-
const depCandidates = [depNoExt + '.ts', depNoExt + '.js', depNoExt + '.tsx', depNoExt + '.jsx', depNoExt + '.mjs', depNoExt + '.mts', depNoExt + '.cts', depNoExt + '.vue', depNoExt + '/index.ts', depNoExt + '/index.js', depNoExt + '/index.tsx', depNoExt + '/index.jsx', depNoExt + '/index.mjs'];
|
|
3552
|
-
let depTrans = null;
|
|
3553
|
-
let depResolved = null;
|
|
3554
|
-
for (const cand of depCandidates) {
|
|
3555
|
-
try {
|
|
3556
|
-
const r = await server.transformRequest(cand);
|
|
3557
|
-
if (r?.code) {
|
|
3558
|
-
depTrans = r;
|
|
3559
|
-
depResolved = cand;
|
|
3560
|
-
break;
|
|
3561
|
-
}
|
|
3562
|
-
}
|
|
3563
|
-
catch { }
|
|
3564
|
-
}
|
|
3565
|
-
if (depTrans?.code && depResolved) {
|
|
3566
|
-
let depCode = depTrans.code;
|
|
3567
|
-
depCode = cleanCode(depCode);
|
|
3568
|
-
depCode = processCodeForDevice(depCode, false, true, /(?:^|\/)node_modules\//.test(depResolved), depResolved);
|
|
3569
|
-
try {
|
|
3570
|
-
depCode = ensureVersionedCoreImports(depCode, getServerOrigin(server), graphVersion);
|
|
3571
|
-
}
|
|
3572
|
-
catch { }
|
|
3573
|
-
depCode = rewriteImports(depCode, depResolved, sfcFileMap, depFileMap, server.config?.root || process.cwd(), !!verbose, undefined, getServerOrigin(server));
|
|
3574
|
-
depCode = ensureVariableDynamicImportHelper(depCode);
|
|
3575
|
-
depCode = ensureGuardPlainDynamicImports(depCode, origin);
|
|
3576
|
-
let depRel = depResolved.replace(/^\//, '').replace(/\.(tsx?|jsx?)$/i, '.mjs');
|
|
3577
|
-
if (!depRel.endsWith('.mjs'))
|
|
3578
|
-
depRel += '.mjs';
|
|
3579
|
-
additionalFiles.push({ path: depRel, code: depCode });
|
|
3580
|
-
}
|
|
3581
|
-
}
|
|
3582
|
-
}
|
|
3583
|
-
catch { }
|
|
3584
|
-
// Store in cache (simple size cap 200 entries)
|
|
3585
|
-
try {
|
|
3586
|
-
if (fetchCache.size > 200) {
|
|
3587
|
-
const firstKey = fetchCache.keys().next().value;
|
|
3588
|
-
if (firstKey)
|
|
3589
|
-
fetchCache.delete(firstKey);
|
|
3590
|
-
}
|
|
3591
|
-
fetchCache.set(cacheKey, { code, path: rel });
|
|
3592
|
-
// Update manifest and broadcast incremental update (debounced to minimize chatter)
|
|
3593
|
-
if (!moduleManifest.has(spec)) {
|
|
3594
|
-
moduleManifest.set(spec, rel);
|
|
3595
|
-
const single = {
|
|
3596
|
-
type: 'ns:module-manifest',
|
|
3597
|
-
entries: { [spec]: rel },
|
|
3598
|
-
ts: Date.now(),
|
|
3599
|
-
delta: true,
|
|
3600
|
-
};
|
|
3601
|
-
wss?.clients.forEach((c) => {
|
|
3602
|
-
if (isSocketClientOpen(c)) {
|
|
3603
|
-
try {
|
|
3604
|
-
c.send(JSON.stringify(single));
|
|
3605
|
-
}
|
|
3606
|
-
catch { }
|
|
3607
|
-
}
|
|
3608
|
-
});
|
|
3609
|
-
}
|
|
3610
|
-
}
|
|
3611
|
-
catch { }
|
|
3612
|
-
ws.send(JSON.stringify({
|
|
3613
|
-
type: 'ns:module-response',
|
|
3614
|
-
requestId,
|
|
3615
|
-
path: rel,
|
|
3616
|
-
code,
|
|
3617
|
-
additionalFiles,
|
|
3618
|
-
}));
|
|
3619
|
-
}
|
|
3620
|
-
catch (e) {
|
|
3621
|
-
ws.send(JSON.stringify({
|
|
3622
|
-
type: 'ns:module-response',
|
|
3623
|
-
requestId,
|
|
3624
|
-
path: msg.path,
|
|
3625
|
-
error: e?.message || String(e),
|
|
3626
|
-
}));
|
|
3627
|
-
}
|
|
3628
|
-
})();
|
|
696
|
+
})
|
|
697
|
+
.catch(() => { });
|
|
3629
698
|
}
|
|
3630
699
|
}
|
|
3631
700
|
catch { }
|
|
@@ -3643,14 +712,14 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3643
712
|
// JS context has an empty sfcArtifactMap. Without the registry the
|
|
3644
713
|
// rescue-mount cannot find the root .vue component.
|
|
3645
714
|
try {
|
|
3646
|
-
await
|
|
715
|
+
await strategy.buildRegistry({
|
|
3647
716
|
server,
|
|
3648
717
|
sfcFileMap,
|
|
3649
718
|
depFileMap,
|
|
3650
719
|
wss: wss,
|
|
3651
720
|
verbose,
|
|
3652
721
|
helpers: {
|
|
3653
|
-
cleanCode,
|
|
722
|
+
cleanCode: (code) => cleanCode(code, strategy),
|
|
3654
723
|
collectImportDependencies,
|
|
3655
724
|
isCoreGlobalsReference,
|
|
3656
725
|
isNativeScriptCoreModule,
|
|
@@ -3661,632 +730,47 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3661
730
|
processSfcCode,
|
|
3662
731
|
},
|
|
3663
732
|
});
|
|
3664
|
-
registrySent = true;
|
|
3665
733
|
}
|
|
3666
734
|
catch (error) {
|
|
3667
735
|
console.warn('[hmr-ws] Failed to send registry:', error);
|
|
3668
736
|
}
|
|
3669
|
-
emitFullGraph(ws);
|
|
3670
|
-
// After sending registry & graph also send current module manifest if any
|
|
3671
|
-
if (moduleManifest.size) {
|
|
3672
|
-
const manifestObj = {};
|
|
3673
|
-
moduleManifest.forEach((v, k) => (manifestObj[k] = v));
|
|
3674
|
-
try {
|
|
3675
|
-
ws.send(JSON.stringify({
|
|
3676
|
-
type: 'ns:module-manifest',
|
|
3677
|
-
entries: manifestObj,
|
|
3678
|
-
ts: Date.now(),
|
|
3679
|
-
}));
|
|
3680
|
-
}
|
|
3681
|
-
catch { }
|
|
3682
|
-
}
|
|
737
|
+
moduleGraph.emitFullGraph(ws);
|
|
3683
738
|
});
|
|
3684
739
|
},
|
|
3685
740
|
async handleHotUpdate(ctx) {
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
try {
|
|
3707
|
-
const deps = Array.from(mod.importedModules || [])
|
|
3708
|
-
.map((m) => (m.id || '').replace(/\?.*$/, ''))
|
|
3709
|
-
.filter(Boolean);
|
|
3710
|
-
const transformed = await server.transformRequest(mod.id);
|
|
3711
|
-
const code = transformed?.code || '';
|
|
3712
|
-
upsertGraphModule((mod.id || '').replace(/\?.*$/, ''), code, deps, {
|
|
3713
|
-
emitDeltaOnInsert: true,
|
|
3714
|
-
broadcastDelta: ACTIVE_STRATEGY.flavor !== 'angular',
|
|
3715
|
-
});
|
|
3716
|
-
}
|
|
3717
|
-
catch (error) {
|
|
3718
|
-
if (verbose)
|
|
3719
|
-
console.warn('[hmr-ws][v2] failed graph update target', mod.id, error);
|
|
3720
|
-
}
|
|
3721
|
-
}
|
|
3722
|
-
}
|
|
3723
|
-
}
|
|
3724
|
-
catch (e) {
|
|
3725
|
-
if (verbose)
|
|
3726
|
-
console.warn('[hmr-ws][v2] failed graph update', e);
|
|
3727
|
-
}
|
|
3728
|
-
const root = server.config.root || process.cwd();
|
|
3729
|
-
const srcDir = `${root}/src`;
|
|
3730
|
-
const coreDir = `${root}/core`;
|
|
3731
|
-
const appDir = `${root}/${APP_ROOT_DIR}`;
|
|
3732
|
-
const normalizedFile = file.split(path.sep).join('/');
|
|
3733
|
-
const inSrcOrCore = normalizedFile.includes(srcDir) || normalizedFile.includes(coreDir);
|
|
3734
|
-
const inApp = normalizedFile.includes(appDir);
|
|
3735
|
-
const shouldIgnore = !(inSrcOrCore || inApp);
|
|
3736
|
-
if (shouldIgnore)
|
|
3737
|
-
return;
|
|
3738
|
-
if (verbose)
|
|
3739
|
-
console.log(`[hmr-ws] Hot update for: ${file}`);
|
|
3740
|
-
// Handle CSS updates
|
|
3741
|
-
if (file.endsWith('.css')) {
|
|
3742
|
-
try {
|
|
3743
|
-
let rel = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
3744
|
-
const origin = getServerOrigin(server);
|
|
3745
|
-
const msg = {
|
|
3746
|
-
type: 'ns:css-updates',
|
|
3747
|
-
origin,
|
|
3748
|
-
updates: [
|
|
3749
|
-
{
|
|
3750
|
-
type: 'css-update',
|
|
3751
|
-
path: rel,
|
|
3752
|
-
acceptedPath: rel,
|
|
3753
|
-
timestamp: Date.now(),
|
|
3754
|
-
},
|
|
3755
|
-
],
|
|
3756
|
-
};
|
|
3757
|
-
wss.clients.forEach((client) => {
|
|
3758
|
-
if (isSocketClientOpen(client)) {
|
|
3759
|
-
client.send(JSON.stringify(msg));
|
|
3760
|
-
}
|
|
3761
|
-
});
|
|
3762
|
-
}
|
|
3763
|
-
catch (error) {
|
|
3764
|
-
console.warn('[hmr-ws] CSS update failed:', error);
|
|
3765
|
-
}
|
|
3766
|
-
return;
|
|
3767
|
-
}
|
|
3768
|
-
// Framework-specific hot update handling
|
|
3769
|
-
if (ACTIVE_STRATEGY.flavor === 'angular') {
|
|
3770
|
-
// For Angular, react to component TS or external template HTML changes under /src
|
|
3771
|
-
const isHtml = file.endsWith('.html');
|
|
3772
|
-
const isTs = file.endsWith('.ts');
|
|
3773
|
-
const angularHotUpdateRoots = collectAngularHotUpdateRoots({
|
|
3774
|
-
file,
|
|
3775
|
-
modules: ctx.modules,
|
|
3776
|
-
getModuleById: (id) => server.moduleGraph.getModuleById(id),
|
|
3777
|
-
getModulesByFile: (targetFile) => server.moduleGraph.getModulesByFile?.(targetFile),
|
|
3778
|
-
});
|
|
3779
|
-
if (!(isHtml || isTs))
|
|
3780
|
-
return;
|
|
3781
|
-
if (angularHotUpdateRoots.length) {
|
|
3782
|
-
for (const mod of angularHotUpdateRoots) {
|
|
3783
|
-
try {
|
|
3784
|
-
server.moduleGraph.invalidateModule(mod);
|
|
3785
|
-
}
|
|
3786
|
-
catch (invalidationError) {
|
|
3787
|
-
if (verbose) {
|
|
3788
|
-
console.warn('[hmr-ws][angular] hot-update root invalidation failed', mod?.id, invalidationError);
|
|
3789
|
-
}
|
|
3790
|
-
}
|
|
3791
|
-
}
|
|
3792
|
-
if (verbose) {
|
|
3793
|
-
console.log('[hmr-ws][angular] invalidated hot-update root modules:', angularHotUpdateRoots.length);
|
|
3794
|
-
}
|
|
3795
|
-
}
|
|
3796
|
-
const angularTransitiveInvalidationRoots = (angularHotUpdateRoots.length ? angularHotUpdateRoots : ctx.modules);
|
|
3797
|
-
if (shouldInvalidateAngularTransitiveImporters({ flavor: ACTIVE_STRATEGY.flavor, file })) {
|
|
3798
|
-
try {
|
|
3799
|
-
const transitiveImporters = collectAngularTransitiveImportersForInvalidation({
|
|
3800
|
-
modules: angularTransitiveInvalidationRoots,
|
|
3801
|
-
isExcluded: (id) => id.includes('/node_modules/'),
|
|
3802
|
-
maxDepth: 16,
|
|
3803
|
-
});
|
|
3804
|
-
for (const mod of transitiveImporters) {
|
|
3805
|
-
try {
|
|
3806
|
-
server.moduleGraph.invalidateModule(mod);
|
|
3807
|
-
}
|
|
3808
|
-
catch (invalidationError) {
|
|
3809
|
-
if (verbose) {
|
|
3810
|
-
console.warn('[hmr-ws][angular] transitive importer invalidation failed', mod?.id, invalidationError);
|
|
3811
|
-
}
|
|
3812
|
-
}
|
|
3813
|
-
}
|
|
3814
|
-
if (verbose && transitiveImporters.length) {
|
|
3815
|
-
console.log('[hmr-ws][angular] invalidated transitive importers:', transitiveImporters.length);
|
|
3816
|
-
}
|
|
3817
|
-
}
|
|
3818
|
-
catch (error) {
|
|
3819
|
-
if (verbose)
|
|
3820
|
-
console.warn('[hmr-ws][angular] transitive importer collection failed', error);
|
|
3821
|
-
}
|
|
3822
|
-
}
|
|
3823
|
-
try {
|
|
3824
|
-
const transitiveImporters = shouldInvalidateAngularTransitiveImporters({ flavor: ACTIVE_STRATEGY.flavor, file })
|
|
3825
|
-
? collectAngularTransitiveImportersForInvalidation({
|
|
3826
|
-
modules: angularTransitiveInvalidationRoots,
|
|
3827
|
-
isExcluded: (id) => id.includes('/node_modules/'),
|
|
3828
|
-
maxDepth: 16,
|
|
3829
|
-
})
|
|
3830
|
-
: [];
|
|
3831
|
-
const transformCacheInvalidationUrls = new Set(collectAngularTransformCacheInvalidationUrls({
|
|
3832
|
-
file,
|
|
3833
|
-
isTs,
|
|
3834
|
-
hotUpdateRoots: angularHotUpdateRoots,
|
|
3835
|
-
transitiveImporters,
|
|
3836
|
-
projectRoot: server.config.root || process.cwd(),
|
|
3837
|
-
}));
|
|
3838
|
-
if (transformCacheInvalidationUrls.size) {
|
|
3839
|
-
sharedTransformRequest.invalidateMany(transformCacheInvalidationUrls);
|
|
3840
|
-
if (verbose) {
|
|
3841
|
-
console.log('[hmr-ws][angular] purged shared transform cache entries:', transformCacheInvalidationUrls.size);
|
|
3842
|
-
}
|
|
3843
|
-
}
|
|
3844
|
-
}
|
|
3845
|
-
catch (error) {
|
|
3846
|
-
if (verbose)
|
|
3847
|
-
console.warn('[hmr-ws][angular] shared transform cache purge failed', error);
|
|
3848
|
-
}
|
|
3849
|
-
try {
|
|
3850
|
-
const root = server.config.root || process.cwd();
|
|
3851
|
-
const rel = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
3852
|
-
rememberAngularReloadSuppression(root, file);
|
|
3853
|
-
const origin = getServerOrigin(server);
|
|
3854
|
-
const msg = {
|
|
3855
|
-
type: 'ns:angular-update',
|
|
3856
|
-
origin,
|
|
3857
|
-
path: rel,
|
|
3858
|
-
version: graphVersion,
|
|
3859
|
-
timestamp: Date.now(),
|
|
3860
|
-
};
|
|
3861
|
-
if (verbose) {
|
|
3862
|
-
console.log('[hmr-ws][angular] broadcasting update', Array.from(wss.clients || []).map((client) => ({
|
|
3863
|
-
role: getHmrSocketRole(client),
|
|
3864
|
-
readyState: client.readyState,
|
|
3865
|
-
openState: client.OPEN,
|
|
3866
|
-
})));
|
|
3867
|
-
}
|
|
3868
|
-
wss.clients.forEach((client) => {
|
|
3869
|
-
if (isSocketClientOpen(client)) {
|
|
3870
|
-
client.send(JSON.stringify(msg));
|
|
3871
|
-
}
|
|
3872
|
-
});
|
|
3873
|
-
}
|
|
3874
|
-
catch (error) {
|
|
3875
|
-
console.warn('[hmr-ws][angular] update failed:', error);
|
|
3876
|
-
}
|
|
3877
|
-
if (shouldSuppressDefaultViteHotUpdate({ flavor: ACTIVE_STRATEGY.flavor, file })) {
|
|
3878
|
-
return [];
|
|
3879
|
-
}
|
|
3880
|
-
return;
|
|
3881
|
-
}
|
|
3882
|
-
// TypeScript flavor: emit generic graph delta for app XML/TS/style changes
|
|
3883
|
-
if (ACTIVE_STRATEGY.flavor === 'typescript') {
|
|
3884
|
-
try {
|
|
3885
|
-
const rel = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
3886
|
-
if (verbose)
|
|
3887
|
-
console.log('[hmr-ws][ts] app file hot update', { file, rel });
|
|
3888
|
-
// Treat the changed file itself as a graph module with no deps. We only
|
|
3889
|
-
// care that its hash/identity changes so the client sees a delta and can
|
|
3890
|
-
// perform a TS root reset. Code is not used for execution here.
|
|
3891
|
-
upsertGraphModule(rel, '', [], { emitDeltaOnInsert: true });
|
|
3892
|
-
}
|
|
3893
|
-
catch (e) {
|
|
3894
|
-
if (verbose)
|
|
3895
|
-
console.warn('[hmr-ws][ts] failed to emit delta for', file, e);
|
|
3896
|
-
}
|
|
3897
|
-
return;
|
|
3898
|
-
}
|
|
3899
|
-
// Solid flavor: emit graph delta for app TSX/TS/JSX file changes.
|
|
3900
|
-
// The common graph-update block above (moduleGraph lookup) may have
|
|
3901
|
-
// already emitted a delta if the file was in Vite's module graph.
|
|
3902
|
-
// This handler ensures a delta is emitted even if the module wasn't
|
|
3903
|
-
// found (e.g. new file, or moduleGraph mismatch), and provides
|
|
3904
|
-
// Solid-specific logging. The client-side processQueue handles
|
|
3905
|
-
// propagation from non-component .ts files to .tsx component boundaries.
|
|
3906
|
-
if (ACTIVE_STRATEGY.flavor === 'solid') {
|
|
3907
|
-
const isSolidFile = /\.(tsx?|jsx?)$/i.test(file);
|
|
3908
|
-
if (!isSolidFile)
|
|
3909
|
-
return;
|
|
3910
|
-
try {
|
|
3911
|
-
const rel = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
3912
|
-
if (verbose)
|
|
3913
|
-
console.log('[hmr-ws][solid] app file hot update', { file, rel });
|
|
3914
|
-
// If the common block already upserted (hash changed), this will
|
|
3915
|
-
// detect unchanged hash and no-op. If the common block missed it
|
|
3916
|
-
// (module not in Vite's graph), this forces the delta emission.
|
|
3917
|
-
const normalizedId = normalizeGraphId(rel);
|
|
3918
|
-
const existing = graph.get(normalizedId);
|
|
3919
|
-
if (!existing) {
|
|
3920
|
-
// Module not in graph yet — force upsert with timestamp-based
|
|
3921
|
-
// hash so the client sees a change.
|
|
3922
|
-
upsertGraphModule(rel, `/* solid-hmr ${Date.now()} */`, [], { emitDeltaOnInsert: true });
|
|
3923
|
-
}
|
|
3924
|
-
// Log what we're sending so devs can trace the flow on the server side.
|
|
3925
|
-
if (verbose) {
|
|
3926
|
-
const gm = graph.get(normalizedId);
|
|
3927
|
-
console.log('[hmr-ws][solid] delta module', { id: gm?.id, hash: gm?.hash });
|
|
3928
|
-
}
|
|
3929
|
-
}
|
|
3930
|
-
catch (e) {
|
|
3931
|
-
if (verbose)
|
|
3932
|
-
console.warn('[hmr-ws][solid] failed to handle hot update for', file, e);
|
|
3933
|
-
}
|
|
3934
|
-
return;
|
|
3935
|
-
}
|
|
3936
|
-
// Handle .vue file updates
|
|
3937
|
-
if (!file.endsWith('.vue')) {
|
|
3938
|
-
if (verbose)
|
|
3939
|
-
console.log('[hmr-ws] Not a .vue file, skipping');
|
|
3940
|
-
return;
|
|
3941
|
-
}
|
|
3942
|
-
console.log('[hmr-ws] Processing .vue file update...');
|
|
3943
|
-
try {
|
|
3944
|
-
const root = server.config.root || process.cwd();
|
|
3945
|
-
let rel = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
3946
|
-
// Transform the .vue file
|
|
3947
|
-
const transformed = await server.transformRequest(rel);
|
|
3948
|
-
if (!transformed?.code)
|
|
3949
|
-
return;
|
|
3950
|
-
let code = transformed.code;
|
|
3951
|
-
// Clean and process
|
|
3952
|
-
code = cleanCode(code);
|
|
3953
|
-
// Process dependencies
|
|
3954
|
-
const visitedPaths = new Set();
|
|
3955
|
-
const importerDir = path.posix.dirname(rel);
|
|
3956
|
-
// Collect dependencies from this file
|
|
3957
|
-
const deps = new Set();
|
|
3958
|
-
const collectDeps = (pattern) => {
|
|
3959
|
-
let match;
|
|
3960
|
-
while ((match = pattern.exec(code)) !== null) {
|
|
3961
|
-
const spec = match[2];
|
|
3962
|
-
if (!spec || PAT.VUE_FILE_PATTERN.test(spec) || !shouldRemapImport(spec)) {
|
|
3963
|
-
continue;
|
|
3964
|
-
}
|
|
3965
|
-
let key;
|
|
3966
|
-
if (spec.startsWith('/')) {
|
|
3967
|
-
key = spec;
|
|
3968
|
-
}
|
|
3969
|
-
else if (spec.startsWith('./') || spec.startsWith('../')) {
|
|
3970
|
-
key = path.posix.normalize(path.posix.join(importerDir, spec));
|
|
3971
|
-
if (!key.startsWith('/'))
|
|
3972
|
-
key = '/' + key;
|
|
3973
|
-
}
|
|
3974
|
-
else {
|
|
3975
|
-
continue;
|
|
3976
|
-
}
|
|
3977
|
-
key = key.replace(PAT.QUERY_PATTERN, '');
|
|
3978
|
-
deps.add(key);
|
|
3979
|
-
}
|
|
3980
|
-
};
|
|
3981
|
-
collectDeps(PAT.IMPORT_PATTERN_1);
|
|
3982
|
-
collectDeps(PAT.IMPORT_PATTERN_2);
|
|
3983
|
-
collectDeps(PAT.EXPORT_PATTERN);
|
|
3984
|
-
collectDeps(PAT.IMPORT_PATTERN_3);
|
|
3985
|
-
// CRITICAL: Collect .vue file imports separately
|
|
3986
|
-
// Use matchAll() to avoid regex state issues
|
|
3987
|
-
const vueDeps = new Set();
|
|
3988
|
-
const vueImportMatches = [...code.matchAll(PAT.IMPORT_PATTERN_1), ...code.matchAll(PAT.VUE_FILE_IMPORT)];
|
|
3989
|
-
for (const match of vueImportMatches) {
|
|
3990
|
-
const spec = match[2];
|
|
3991
|
-
if (!spec || !PAT.VUE_FILE_PATTERN.test(spec)) {
|
|
3992
|
-
continue;
|
|
3993
|
-
}
|
|
3994
|
-
let key;
|
|
3995
|
-
if (spec.startsWith('/')) {
|
|
3996
|
-
key = spec.replace(PAT.QUERY_PATTERN, '');
|
|
3997
|
-
}
|
|
3998
|
-
else if (spec.startsWith('./') || spec.startsWith('../')) {
|
|
3999
|
-
key = path.posix.normalize(path.posix.join(importerDir, spec.replace(PAT.QUERY_PATTERN, '')));
|
|
4000
|
-
if (!key.startsWith('/'))
|
|
4001
|
-
key = '/' + key;
|
|
4002
|
-
}
|
|
4003
|
-
else {
|
|
4004
|
-
continue;
|
|
4005
|
-
}
|
|
4006
|
-
// Ensure this .vue file is registered in sfcFileMap
|
|
4007
|
-
if (!sfcFileMap.has(key)) {
|
|
4008
|
-
const hash = createHash('md5').update(key).digest('hex').slice(0, 8);
|
|
4009
|
-
sfcFileMap.set(key, `sfc-${hash}.mjs`);
|
|
4010
|
-
if (verbose) {
|
|
4011
|
-
console.log(`[hmr-ws] Registered .vue import: ${key} → sfc-${hash}.mjs`);
|
|
4012
|
-
}
|
|
4013
|
-
}
|
|
4014
|
-
// Add to vueDeps for separate processing
|
|
4015
|
-
vueDeps.add(key);
|
|
4016
|
-
}
|
|
4017
|
-
// Process .vue dependencies (they stay as sfc-*.mjs imports)
|
|
4018
|
-
for (const vueDep of vueDeps) {
|
|
4019
|
-
await ACTIVE_STRATEGY.processFile({
|
|
4020
|
-
filePath: vueDep,
|
|
4021
|
-
server,
|
|
4022
|
-
sfcFileMap,
|
|
4023
|
-
depFileMap,
|
|
4024
|
-
visitedPaths,
|
|
4025
|
-
wss,
|
|
4026
|
-
verbose,
|
|
4027
|
-
helpers: {
|
|
4028
|
-
cleanCode,
|
|
4029
|
-
collectImportDependencies,
|
|
4030
|
-
isCoreGlobalsReference,
|
|
4031
|
-
isNativeScriptCoreModule,
|
|
4032
|
-
isNativeScriptPluginModule,
|
|
4033
|
-
resolveVendorFromCandidate,
|
|
4034
|
-
createHash: (value) => createHash('md5').update(value).digest('hex'),
|
|
4035
|
-
},
|
|
4036
|
-
});
|
|
4037
|
-
}
|
|
4038
|
-
// Process with consistent SFC processor (removes non-.vue imports)
|
|
4039
|
-
code = processSfcCode(code);
|
|
4040
|
-
// Rewrite ONLY .vue imports (everything else is now inlined)
|
|
4041
|
-
const projectRoot = server.config.root || process.cwd();
|
|
4042
|
-
code = rewriteImports(code, rel, sfcFileMap, depFileMap, projectRoot, opts.verbose, undefined);
|
|
4043
|
-
upsertGraphModule(rel, code, [...deps, ...vueDeps]);
|
|
4044
|
-
// Add HMR runtime prelude (CRITICAL for runtime)
|
|
4045
|
-
const hmrPrelude = `
|
|
4046
|
-
// Embedded HMR Runtime for NativeScript runtime
|
|
4047
|
-
const createHotContext = (id) => ({
|
|
4048
|
-
on: (event, handler) => {
|
|
4049
|
-
if (!globalThis.__NS_HMR_HANDLERS__) globalThis.__NS_HMR_HANDLERS__ = new Map();
|
|
4050
|
-
if (!globalThis.__NS_HMR_HANDLERS__.has(id)) globalThis.__NS_HMR_HANDLERS__.set(id, []);
|
|
4051
|
-
globalThis.__NS_HMR_HANDLERS__.get(id).push({ event, handler });
|
|
4052
|
-
},
|
|
4053
|
-
accept: (handler) => {
|
|
4054
|
-
if (!globalThis.__NS_HMR_ACCEPTS__) globalThis.__NS_HMR_ACCEPTS__ = new Map();
|
|
4055
|
-
globalThis.__NS_HMR_ACCEPTS__.set(id, handler);
|
|
4056
|
-
}
|
|
4057
|
-
});
|
|
4058
|
-
|
|
4059
|
-
if (typeof import.meta === 'undefined') {
|
|
4060
|
-
globalThis.importMeta = { hot: null };
|
|
4061
|
-
} else if (!import.meta.hot) {
|
|
4062
|
-
import.meta.hot = null;
|
|
4063
|
-
}
|
|
4064
|
-
|
|
4065
|
-
const __vite__createHotContext = createHotContext;
|
|
4066
|
-
|
|
4067
|
-
if (typeof __VUE_HMR_RUNTIME__ === 'undefined') {
|
|
4068
|
-
globalThis.__VUE_HMR_RUNTIME__ = {
|
|
4069
|
-
createRecord: () => true,
|
|
4070
|
-
reload: () => {},
|
|
4071
|
-
rerender: () => {},
|
|
4072
|
-
};
|
|
4073
|
-
}
|
|
4074
|
-
|
|
4075
|
-
// Install a lightweight guard to capture require('http(s)://...') attempts with stack traces
|
|
4076
|
-
(() => {
|
|
4077
|
-
try {
|
|
4078
|
-
const g = globalThis;
|
|
4079
|
-
if (g.__NS_REQUIRE_GUARD_INSTALLED__) return;
|
|
4080
|
-
const makeGuard = (orig, label) => function () {
|
|
4081
|
-
try {
|
|
4082
|
-
const spec = arguments[0];
|
|
4083
|
-
if (typeof spec === 'string' && /^(?:https?:)\/\//.test(spec)) {
|
|
4084
|
-
const err = new Error('[ns-hmr][require-guard] require of URL: ' + spec + ' via ' + label);
|
|
4085
|
-
const stack = err.stack || '';
|
|
4086
|
-
try { console.error(err.message + '\n' + stack); } catch {}
|
|
4087
|
-
try { g.__NS_REQUIRE_GUARD_LAST__ = { spec, stack, label, ts: Date.now() }; } catch {}
|
|
4088
|
-
}
|
|
4089
|
-
} catch {}
|
|
4090
|
-
return orig.apply(this, arguments);
|
|
4091
|
-
};
|
|
4092
|
-
if (typeof g.require === 'function' && !g.require.__NS_REQ_GUARDED__) {
|
|
4093
|
-
const orig = g.require; g.require = makeGuard(orig, 'require'); g.require.__NS_REQ_GUARDED__ = true;
|
|
4094
|
-
}
|
|
4095
|
-
if (typeof g.__nsRequire === 'function' && !g.__nsRequire.__NS_REQ_GUARDED__) {
|
|
4096
|
-
const orig = g.__nsRequire; g.__nsRequire = makeGuard(orig, '__nsRequire'); g.__nsRequire.__NS_REQ_GUARDED__ = true;
|
|
4097
|
-
}
|
|
4098
|
-
g.__NS_REQUIRE_GUARD_INSTALLED__ = true;
|
|
4099
|
-
} catch {}
|
|
4100
|
-
})();
|
|
4101
|
-
`;
|
|
4102
|
-
code = hmrPrelude + '\n' + code;
|
|
4103
|
-
// Update SFC registry
|
|
4104
|
-
const hash = createHash('md5').update(rel).digest('hex').slice(0, 8);
|
|
4105
|
-
const hmrId = hash;
|
|
4106
|
-
const fileName = sfcFileMap.get(rel) || `sfc-${hash}.mjs`;
|
|
4107
|
-
sfcFileMap.set(rel, fileName);
|
|
4108
|
-
const ts = Date.now();
|
|
4109
|
-
const absolutePath = `file://${path.resolve(file)}`;
|
|
4110
|
-
// FIRST: Send mapping-only registry update (no code)
|
|
4111
|
-
const registryUpdateMsg = {
|
|
4112
|
-
type: 'ns:vue-sfc-registry-update',
|
|
4113
|
-
path: rel,
|
|
4114
|
-
fileName,
|
|
4115
|
-
ts,
|
|
4116
|
-
version: graphVersion,
|
|
4117
|
-
};
|
|
4118
|
-
wss.clients.forEach((client) => {
|
|
4119
|
-
if (isSocketClientOpen(client)) {
|
|
4120
|
-
client.send(JSON.stringify(registryUpdateMsg));
|
|
4121
|
-
}
|
|
4122
|
-
});
|
|
4123
|
-
// SECOND/THIRD: Removed WS code-push and template URL emissions in HTTP-only mode.
|
|
4124
|
-
// The device loads SFC artifacts via HTTP endpoints directly; WS remains metadata-only.
|
|
4125
|
-
const id = path
|
|
4126
|
-
.basename(file)
|
|
4127
|
-
.replace(/\.vue$/i, '')
|
|
4128
|
-
.toLowerCase();
|
|
4129
|
-
// placeholder source for any legacy dynamic module shapes that may still reference it
|
|
4130
|
-
const source = '';
|
|
4131
|
-
// FOURTH: Send dynamic module message (CRITICAL - this is what triggers the actual HMR!)
|
|
4132
|
-
const moduleId = `hmr-${id}-${ts}`;
|
|
4133
|
-
const modulePath = `/${rel}?hmr=${ts}`;
|
|
4134
|
-
let appDeps;
|
|
4135
|
-
try {
|
|
4136
|
-
// Enhanced dependency harvesting for pre-await:
|
|
4137
|
-
// * Preserve .mjs extension when present so client can await exact filesystem module
|
|
4138
|
-
// * Recognize rewritten __NSDOC__/foo/bar.mjs and convert to /foo/bar.mjs base form
|
|
4139
|
-
// * Convert absolute app paths to /app-style references (with extension) for uniformity
|
|
4140
|
-
// * Exclude vendor runtime/plugin modules and synthetic dep-* & sfc-* artifacts as before
|
|
4141
|
-
const raw = collectImportDependencies(code, rel);
|
|
4142
|
-
const filtered = new Set();
|
|
4143
|
-
const addCandidate = (orig) => {
|
|
4144
|
-
if (!orig)
|
|
4145
|
-
return;
|
|
4146
|
-
let cleaned = orig.replace(PAT.QUERY_PATTERN, '');
|
|
4147
|
-
if (isCoreGlobalsReference(cleaned))
|
|
4148
|
-
return;
|
|
4149
|
-
if (isNativeScriptCoreModule(cleaned))
|
|
4150
|
-
return;
|
|
4151
|
-
if (isNativeScriptPluginModule(cleaned))
|
|
4152
|
-
return;
|
|
4153
|
-
if (resolveVendorFromCandidate(cleaned))
|
|
4154
|
-
return;
|
|
4155
|
-
if (/\bdep-[a-f0-9]{8}\.mjs$/i.test(cleaned))
|
|
4156
|
-
return;
|
|
4157
|
-
if (/\bsfc-[a-f0-9]{8}\.mjs$/i.test(cleaned))
|
|
4158
|
-
return;
|
|
4159
|
-
// Normalize __NSDOC__/ prefix
|
|
4160
|
-
if (cleaned.startsWith('__NSDOC__/')) {
|
|
4161
|
-
cleaned = cleaned.substring('__NSDOC__/'.length);
|
|
4162
|
-
if (!cleaned.startsWith('/'))
|
|
4163
|
-
cleaned = '/' + cleaned;
|
|
4164
|
-
}
|
|
4165
|
-
// Relative path (./ or ../) → resolve to absolute /path relative to SFC file
|
|
4166
|
-
if (cleaned.startsWith('./') || cleaned.startsWith('../')) {
|
|
4167
|
-
const importerDir = path.posix.dirname(rel);
|
|
4168
|
-
let abs = path.posix.normalize(path.posix.join(importerDir, cleaned));
|
|
4169
|
-
if (!abs.startsWith('/'))
|
|
4170
|
-
abs = '/' + abs;
|
|
4171
|
-
cleaned = abs;
|
|
4172
|
-
}
|
|
4173
|
-
if (!cleaned.startsWith('/'))
|
|
4174
|
-
return; // still not absolute app path
|
|
4175
|
-
cleaned = cleaned.replace(/\.(ts|js|tsx|jsx|mts|cts)$/i, '.mjs');
|
|
4176
|
-
if (!/\.mjs$/i.test(cleaned))
|
|
4177
|
-
return;
|
|
4178
|
-
filtered.add(cleaned);
|
|
4179
|
-
};
|
|
4180
|
-
for (const spec of raw) {
|
|
4181
|
-
addCandidate(spec);
|
|
4182
|
-
}
|
|
4183
|
-
// Additional scan: after rewrites, application imports may appear only as string literals
|
|
4184
|
-
// with the canonical placeholder __NSDOC__/ – collect them directly.
|
|
4185
|
-
const NSDOC_IMPORT_PATTERN = /__NSDOC__\/([A-Za-z0-9_\-./]+?\.mjs)\b/g;
|
|
4186
|
-
{
|
|
4187
|
-
let m;
|
|
4188
|
-
while ((m = NSDOC_IMPORT_PATTERN.exec(code)) !== null) {
|
|
4189
|
-
const relSpec = m[1]; // path relative to documents root
|
|
4190
|
-
if (relSpec) {
|
|
4191
|
-
const normalized = '/' + relSpec.replace(/^\/+/, '');
|
|
4192
|
-
addCandidate(normalized);
|
|
4193
|
-
}
|
|
4194
|
-
}
|
|
4195
|
-
}
|
|
4196
|
-
// Heuristic for barrel index modules that might not have explicit .mjs import strings
|
|
4197
|
-
const utilsIndexCandidate = `${APP_VIRTUAL_WITH_SLASH}utils/index.mjs`;
|
|
4198
|
-
const hasUtilsIndex = Array.from(filtered).some((p) => p.toLowerCase() === utilsIndexCandidate.toLowerCase());
|
|
4199
|
-
if (!hasUtilsIndex) {
|
|
4200
|
-
const utilsMarker = `${APP_VIRTUAL_WITH_SLASH}utils/`;
|
|
4201
|
-
if (code.includes(utilsMarker)) {
|
|
4202
|
-
addCandidate(utilsIndexCandidate);
|
|
4203
|
-
}
|
|
4204
|
-
}
|
|
4205
|
-
if (filtered.size) {
|
|
4206
|
-
appDeps = Array.from(filtered);
|
|
4207
|
-
}
|
|
4208
|
-
}
|
|
4209
|
-
catch {
|
|
4210
|
-
// Silently ignore errors – dependency pre-await is an optimization only
|
|
4211
|
-
}
|
|
4212
|
-
// After computing appDeps: no WS push. Client discovers deps via HTTP imports on demand.
|
|
4213
|
-
// Legacy dynamic module protocol removed in v2 graph system.
|
|
4214
|
-
}
|
|
4215
|
-
catch (error) {
|
|
4216
|
-
console.warn('[hmr-ws] HMR update failed:', error);
|
|
4217
|
-
console.error(error);
|
|
4218
|
-
}
|
|
4219
|
-
// CRITICAL: Return empty array to prevent Vite's default HMR
|
|
4220
|
-
return [];
|
|
741
|
+
// Every flavor owns its `handleHotUpdate` (shared prologue + its tail);
|
|
742
|
+
// call the active strategy's hook directly with the injected deps.
|
|
743
|
+
return strategy.handleHotUpdate?.(ctx, {
|
|
744
|
+
wss,
|
|
745
|
+
moduleGraph,
|
|
746
|
+
strategy,
|
|
747
|
+
verbose,
|
|
748
|
+
sfcFileMap,
|
|
749
|
+
depFileMap,
|
|
750
|
+
sharedTransformRequest,
|
|
751
|
+
getHmrSourceRootsCached,
|
|
752
|
+
getBootstrapEntryRelPath,
|
|
753
|
+
isSocketClientOpen,
|
|
754
|
+
getHmrSocketRole,
|
|
755
|
+
shouldRemapImport,
|
|
756
|
+
rememberAngularReloadSuppression,
|
|
757
|
+
getRootComponentIdentity,
|
|
758
|
+
getGraphInitialPopulationPromise: () => graphInitialPopulationPromise,
|
|
759
|
+
appRootDir: APP_ROOT_DIR,
|
|
760
|
+
});
|
|
4221
761
|
},
|
|
4222
762
|
};
|
|
4223
763
|
}
|
|
4224
|
-
// ----------
|
|
4225
|
-
// Framework-specific HMR WebSocket plugins
|
|
4226
|
-
// ----------
|
|
4227
|
-
export function hmrWebSocketVue(opts) {
|
|
4228
|
-
ACTIVE_STRATEGY = resolveFrameworkStrategy('vue');
|
|
4229
|
-
return createHmrWebSocketPlugin(opts);
|
|
4230
|
-
}
|
|
4231
|
-
export function hmrWebSocketAngular(opts) {
|
|
4232
|
-
ACTIVE_STRATEGY = resolveFrameworkStrategy('angular');
|
|
4233
|
-
return createHmrWebSocketPlugin(opts);
|
|
4234
|
-
}
|
|
4235
|
-
export function hmrWebSocketSolid(opts) {
|
|
4236
|
-
ACTIVE_STRATEGY = resolveFrameworkStrategy('solid');
|
|
4237
|
-
return createHmrWebSocketPlugin(opts);
|
|
4238
|
-
}
|
|
4239
|
-
export function hmrWebSocketTypescript(opts) {
|
|
4240
|
-
ACTIVE_STRATEGY = resolveFrameworkStrategy('typescript');
|
|
4241
|
-
return createHmrWebSocketPlugin(opts);
|
|
4242
|
-
}
|
|
4243
764
|
/**
|
|
4244
|
-
*
|
|
765
|
+
* Build the server-side HMR WebSocket plugin for `flavor`, or `undefined` when
|
|
766
|
+
* the flavor has no registered server strategy (e.g. `react`, which ships only
|
|
767
|
+
* the client plugin today). Driven off `STRATEGY_REGISTRY`, so adding a flavor
|
|
768
|
+
* is a one-line registry change — no per-flavor wrapper and no `getHMRPlugins`
|
|
769
|
+
* switch arm. Replaces the former hmrWebSocket{Vue,Angular,Solid,Typescript}
|
|
770
|
+
* wrappers and the explicit `case 'react': // no-op`.
|
|
4245
771
|
*/
|
|
4246
|
-
function
|
|
4247
|
-
const
|
|
4248
|
-
|
|
4249
|
-
if (urls?.network?.length) {
|
|
4250
|
-
try {
|
|
4251
|
-
const u = new URL(String(urls.network[0]));
|
|
4252
|
-
const origin = `${u.protocol}//${u.host}`;
|
|
4253
|
-
if (!/^https?:\/\/[\w\-.:\[\]]+$/.test(origin)) {
|
|
4254
|
-
console.warn('[hmr][origin] invariant failed for resolvedUrls.network:', urls.network[0], '→', origin);
|
|
4255
|
-
}
|
|
4256
|
-
return origin;
|
|
4257
|
-
}
|
|
4258
|
-
catch {
|
|
4259
|
-
// Fallthrough to local below if network parse fails
|
|
4260
|
-
}
|
|
4261
|
-
}
|
|
4262
|
-
if (urls?.local?.length) {
|
|
4263
|
-
try {
|
|
4264
|
-
const u = new URL(String(urls.local[0]));
|
|
4265
|
-
const origin = `${u.protocol}//${u.host}`;
|
|
4266
|
-
if (!/^https?:\/\/[\w\-.:\[\]]+$/.test(origin)) {
|
|
4267
|
-
console.warn('[hmr][origin] invariant failed for resolvedUrls.local:', urls.local[0], '→', origin);
|
|
4268
|
-
}
|
|
4269
|
-
return origin;
|
|
4270
|
-
}
|
|
4271
|
-
catch {
|
|
4272
|
-
// Fallthrough to manual construction
|
|
4273
|
-
}
|
|
4274
|
-
}
|
|
4275
|
-
const isHttps = !!server.config.server?.https;
|
|
4276
|
-
const httpServer = server.httpServer;
|
|
4277
|
-
const addr = httpServer?.address?.();
|
|
4278
|
-
const port = Number(server.config.server?.port || addr?.port || 5173);
|
|
4279
|
-
const hostCfg = server.config.server?.host;
|
|
4280
|
-
const host = typeof hostCfg === 'string' && hostCfg !== '0.0.0.0' ? hostCfg : '127.0.0.1';
|
|
4281
|
-
const origin = `${isHttps ? 'https' : 'http'}://${host}:${port}`;
|
|
4282
|
-
if (!/^https?:\/\/[\w\-.:\[\]]+$/.test(origin)) {
|
|
4283
|
-
console.warn('[hmr][origin] invariant failed for constructed origin:', origin);
|
|
4284
|
-
}
|
|
4285
|
-
return origin;
|
|
772
|
+
export function hmrWebSocketPluginForFlavor(flavor, opts) {
|
|
773
|
+
const strategy = STRATEGY_REGISTRY.get(flavor);
|
|
774
|
+
return strategy ? createHmrWebSocketPlugin(opts, strategy) : undefined;
|
|
4286
775
|
}
|
|
4287
|
-
// Test-only export: allow unit tests to run the sanitizer on snippets without booting a server
|
|
4288
|
-
// Safe in production builds; this is a named export that tests can import explicitly.
|
|
4289
|
-
export const __test_processCodeForDevice = processCodeForDevice;
|
|
4290
|
-
export const __test_resolveVendorRouting = resolveVendorRouting;
|
|
4291
|
-
export const __test_getBlockedDeviceNodeModulesReason = getBlockedDeviceNodeModulesReason;
|
|
4292
776
|
//# sourceMappingURL=websocket.js.map
|