@nativescript/vite 8.0.0-alpha.2 → 8.0.0-alpha.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/configuration/angular.d.ts +34 -1
  2. package/configuration/angular.js +380 -34
  3. package/configuration/angular.js.map +1 -1
  4. package/configuration/base.js +171 -7
  5. package/configuration/base.js.map +1 -1
  6. package/configuration/solid.js +27 -1
  7. package/configuration/solid.js.map +1 -1
  8. package/configuration/typescript.js +1 -1
  9. package/configuration/typescript.js.map +1 -1
  10. package/helpers/angular/angular-linker.js +3 -12
  11. package/helpers/angular/angular-linker.js.map +1 -1
  12. package/helpers/angular/inject-component-hmr-registration.d.ts +112 -0
  13. package/helpers/angular/inject-component-hmr-registration.js +359 -0
  14. package/helpers/angular/inject-component-hmr-registration.js.map +1 -0
  15. package/helpers/angular/inject-hmr-vite-ignore.d.ts +75 -0
  16. package/helpers/angular/inject-hmr-vite-ignore.js +288 -0
  17. package/helpers/angular/inject-hmr-vite-ignore.js.map +1 -0
  18. package/helpers/angular/util.d.ts +1 -0
  19. package/helpers/angular/util.js +88 -0
  20. package/helpers/angular/util.js.map +1 -1
  21. package/helpers/commonjs-plugins.d.ts +5 -2
  22. package/helpers/commonjs-plugins.js +126 -0
  23. package/helpers/commonjs-plugins.js.map +1 -1
  24. package/helpers/config-as-json.js +10 -0
  25. package/helpers/config-as-json.js.map +1 -1
  26. package/helpers/dev-host.d.ts +274 -0
  27. package/helpers/dev-host.js +491 -0
  28. package/helpers/dev-host.js.map +1 -0
  29. package/helpers/global-defines.d.ts +51 -0
  30. package/helpers/global-defines.js +77 -0
  31. package/helpers/global-defines.js.map +1 -1
  32. package/helpers/logging.d.ts +1 -0
  33. package/helpers/logging.js +63 -3
  34. package/helpers/logging.js.map +1 -1
  35. package/helpers/main-entry.d.ts +3 -1
  36. package/helpers/main-entry.js +450 -125
  37. package/helpers/main-entry.js.map +1 -1
  38. package/helpers/nativeclass-transformer-plugin.d.ts +9 -2
  39. package/helpers/nativeclass-transformer-plugin.js +157 -14
  40. package/helpers/nativeclass-transformer-plugin.js.map +1 -1
  41. package/helpers/ns-core-url.d.ts +88 -0
  42. package/helpers/ns-core-url.js +191 -0
  43. package/helpers/ns-core-url.js.map +1 -0
  44. package/helpers/prelink-angular.js +1 -4
  45. package/helpers/prelink-angular.js.map +1 -1
  46. package/helpers/project.d.ts +35 -0
  47. package/helpers/project.js +120 -2
  48. package/helpers/project.js.map +1 -1
  49. package/helpers/resolver.js +9 -1
  50. package/helpers/resolver.js.map +1 -1
  51. package/helpers/solid-jsx-deps.d.ts +15 -0
  52. package/helpers/solid-jsx-deps.js +178 -0
  53. package/helpers/solid-jsx-deps.js.map +1 -0
  54. package/helpers/ts-config-paths.js +50 -2
  55. package/helpers/ts-config-paths.js.map +1 -1
  56. package/helpers/workers.d.ts +20 -19
  57. package/helpers/workers.js +620 -3
  58. package/helpers/workers.js.map +1 -1
  59. package/hmr/client/css-handler.d.ts +1 -0
  60. package/hmr/client/css-handler.js +34 -5
  61. package/hmr/client/css-handler.js.map +1 -1
  62. package/hmr/client/css-update-overlay.d.ts +18 -0
  63. package/hmr/client/css-update-overlay.js +27 -0
  64. package/hmr/client/css-update-overlay.js.map +1 -0
  65. package/hmr/client/hmr-pending-overlay.d.ts +27 -0
  66. package/hmr/client/hmr-pending-overlay.js +50 -0
  67. package/hmr/client/hmr-pending-overlay.js.map +1 -0
  68. package/hmr/client/index.js +483 -33
  69. package/hmr/client/index.js.map +1 -1
  70. package/hmr/client/utils.d.ts +5 -0
  71. package/hmr/client/utils.js +283 -12
  72. package/hmr/client/utils.js.map +1 -1
  73. package/hmr/client/vue-sfc-update-overlay.d.ts +82 -0
  74. package/hmr/client/vue-sfc-update-overlay.js +133 -0
  75. package/hmr/client/vue-sfc-update-overlay.js.map +1 -0
  76. package/hmr/entry-runtime.d.ts +2 -1
  77. package/hmr/entry-runtime.js +253 -66
  78. package/hmr/entry-runtime.js.map +1 -1
  79. package/hmr/frameworks/angular/client/index.d.ts +3 -1
  80. package/hmr/frameworks/angular/client/index.js +802 -10
  81. package/hmr/frameworks/angular/client/index.js.map +1 -1
  82. package/hmr/frameworks/angular/server/linker.js +1 -4
  83. package/hmr/frameworks/angular/server/linker.js.map +1 -1
  84. package/hmr/frameworks/angular/server/strategy.js +30 -6
  85. package/hmr/frameworks/angular/server/strategy.js.map +1 -1
  86. package/hmr/frameworks/typescript/server/strategy.js +8 -2
  87. package/hmr/frameworks/typescript/server/strategy.js.map +1 -1
  88. package/hmr/frameworks/vue/client/index.js +18 -42
  89. package/hmr/frameworks/vue/client/index.js.map +1 -1
  90. package/hmr/helpers/ast-normalizer.js +52 -5
  91. package/hmr/helpers/ast-normalizer.js.map +1 -1
  92. package/hmr/helpers/cjs-named-exports.d.ts +23 -0
  93. package/hmr/helpers/cjs-named-exports.js +152 -0
  94. package/hmr/helpers/cjs-named-exports.js.map +1 -0
  95. package/hmr/helpers/package-exports.d.ts +16 -0
  96. package/hmr/helpers/package-exports.js +396 -0
  97. package/hmr/helpers/package-exports.js.map +1 -0
  98. package/hmr/server/constants.js +13 -4
  99. package/hmr/server/constants.js.map +1 -1
  100. package/hmr/server/core-sanitize.d.ts +93 -8
  101. package/hmr/server/core-sanitize.js +222 -49
  102. package/hmr/server/core-sanitize.js.map +1 -1
  103. package/hmr/server/import-map.js +80 -22
  104. package/hmr/server/import-map.js.map +1 -1
  105. package/hmr/server/index.d.ts +2 -1
  106. package/hmr/server/index.js.map +1 -1
  107. package/hmr/server/ns-core-cjs-shape.d.ts +204 -0
  108. package/hmr/server/ns-core-cjs-shape.js +271 -0
  109. package/hmr/server/ns-core-cjs-shape.js.map +1 -0
  110. package/hmr/server/ns-rt-bridge.d.ts +51 -0
  111. package/hmr/server/ns-rt-bridge.js +131 -0
  112. package/hmr/server/ns-rt-bridge.js.map +1 -0
  113. package/hmr/server/perf-instrumentation.d.ts +114 -0
  114. package/hmr/server/perf-instrumentation.js +195 -0
  115. package/hmr/server/perf-instrumentation.js.map +1 -0
  116. package/hmr/server/runtime-graph-filter.d.ts +5 -0
  117. package/hmr/server/runtime-graph-filter.js +21 -0
  118. package/hmr/server/runtime-graph-filter.js.map +1 -0
  119. package/hmr/server/shared-transform-request.d.ts +12 -0
  120. package/hmr/server/shared-transform-request.js +144 -0
  121. package/hmr/server/shared-transform-request.js.map +1 -0
  122. package/hmr/server/vite-plugin.d.ts +21 -1
  123. package/hmr/server/vite-plugin.js +497 -58
  124. package/hmr/server/vite-plugin.js.map +1 -1
  125. package/hmr/server/websocket-angular-entry.d.ts +2 -0
  126. package/hmr/server/websocket-angular-entry.js +68 -0
  127. package/hmr/server/websocket-angular-entry.js.map +1 -0
  128. package/hmr/server/websocket-angular-hot-update.d.ts +78 -0
  129. package/hmr/server/websocket-angular-hot-update.js +413 -0
  130. package/hmr/server/websocket-angular-hot-update.js.map +1 -0
  131. package/hmr/server/websocket-core-bridge.d.ts +58 -0
  132. package/hmr/server/websocket-core-bridge.js +368 -0
  133. package/hmr/server/websocket-core-bridge.js.map +1 -0
  134. package/hmr/server/websocket-css-hot-update.d.ts +33 -0
  135. package/hmr/server/websocket-css-hot-update.js +65 -0
  136. package/hmr/server/websocket-css-hot-update.js.map +1 -0
  137. package/hmr/server/websocket-graph-upsert.d.ts +21 -0
  138. package/hmr/server/websocket-graph-upsert.js +33 -0
  139. package/hmr/server/websocket-graph-upsert.js.map +1 -0
  140. package/hmr/server/websocket-hmr-pending.d.ts +43 -0
  141. package/hmr/server/websocket-hmr-pending.js +55 -0
  142. package/hmr/server/websocket-hmr-pending.js.map +1 -0
  143. package/hmr/server/websocket-module-bindings.d.ts +6 -0
  144. package/hmr/server/websocket-module-bindings.js +471 -0
  145. package/hmr/server/websocket-module-bindings.js.map +1 -0
  146. package/hmr/server/websocket-module-specifiers.d.ts +101 -0
  147. package/hmr/server/websocket-module-specifiers.js +820 -0
  148. package/hmr/server/websocket-module-specifiers.js.map +1 -0
  149. package/hmr/server/websocket-ns-m-finalize.d.ts +22 -0
  150. package/hmr/server/websocket-ns-m-finalize.js +88 -0
  151. package/hmr/server/websocket-ns-m-finalize.js.map +1 -0
  152. package/hmr/server/websocket-ns-m-paths.d.ts +3 -0
  153. package/hmr/server/websocket-ns-m-paths.js +92 -0
  154. package/hmr/server/websocket-ns-m-paths.js.map +1 -0
  155. package/hmr/server/websocket-ns-m-request.d.ts +45 -0
  156. package/hmr/server/websocket-ns-m-request.js +196 -0
  157. package/hmr/server/websocket-ns-m-request.js.map +1 -0
  158. package/hmr/server/websocket-served-module-helpers.d.ts +36 -0
  159. package/hmr/server/websocket-served-module-helpers.js +644 -0
  160. package/hmr/server/websocket-served-module-helpers.js.map +1 -0
  161. package/hmr/server/websocket-txn.d.ts +6 -0
  162. package/hmr/server/websocket-txn.js +45 -0
  163. package/hmr/server/websocket-txn.js.map +1 -0
  164. package/hmr/server/websocket-vendor-unifier.d.ts +10 -0
  165. package/hmr/server/websocket-vendor-unifier.js +51 -0
  166. package/hmr/server/websocket-vendor-unifier.js.map +1 -0
  167. package/hmr/server/websocket-vue-sfc.d.ts +26 -0
  168. package/hmr/server/websocket-vue-sfc.js +1053 -0
  169. package/hmr/server/websocket-vue-sfc.js.map +1 -0
  170. package/hmr/server/websocket.d.ts +58 -75
  171. package/hmr/server/websocket.js +2230 -1802
  172. package/hmr/server/websocket.js.map +1 -1
  173. package/hmr/shared/package-classifier.d.ts +9 -0
  174. package/hmr/shared/package-classifier.js +58 -0
  175. package/hmr/shared/package-classifier.js.map +1 -0
  176. package/hmr/shared/runtime/boot-placeholder-ui.d.ts +69 -0
  177. package/hmr/shared/runtime/boot-placeholder-ui.js +101 -0
  178. package/hmr/shared/runtime/boot-placeholder-ui.js.map +1 -0
  179. package/hmr/shared/runtime/boot-progress.d.ts +40 -0
  180. package/hmr/shared/runtime/boot-progress.js +128 -0
  181. package/hmr/shared/runtime/boot-progress.js.map +1 -0
  182. package/hmr/shared/runtime/boot-timeline.d.ts +18 -0
  183. package/hmr/shared/runtime/boot-timeline.js +52 -0
  184. package/hmr/shared/runtime/boot-timeline.js.map +1 -0
  185. package/hmr/shared/runtime/browser-runtime-contract.d.ts +64 -0
  186. package/hmr/shared/runtime/browser-runtime-contract.js +54 -0
  187. package/hmr/shared/runtime/browser-runtime-contract.js.map +1 -0
  188. package/hmr/shared/runtime/dev-overlay.d.ts +78 -3
  189. package/hmr/shared/runtime/dev-overlay.js +1094 -26
  190. package/hmr/shared/runtime/dev-overlay.js.map +1 -1
  191. package/hmr/shared/runtime/module-provenance.js +1 -4
  192. package/hmr/shared/runtime/module-provenance.js.map +1 -1
  193. package/hmr/shared/runtime/root-placeholder.d.ts +1 -0
  194. package/hmr/shared/runtime/root-placeholder.js +1019 -151
  195. package/hmr/shared/runtime/root-placeholder.js.map +1 -1
  196. package/hmr/shared/runtime/session-bootstrap.d.ts +1 -0
  197. package/hmr/shared/runtime/session-bootstrap.js +309 -0
  198. package/hmr/shared/runtime/session-bootstrap.js.map +1 -0
  199. package/hmr/shared/runtime/vendor-bootstrap.js +1 -9
  200. package/hmr/shared/runtime/vendor-bootstrap.js.map +1 -1
  201. package/hmr/shared/vendor/manifest.d.ts +32 -0
  202. package/hmr/shared/vendor/manifest.js +411 -46
  203. package/hmr/shared/vendor/manifest.js.map +1 -1
  204. package/index.d.ts +1 -0
  205. package/index.js +5 -0
  206. package/index.js.map +1 -1
  207. package/package.json +9 -1
  208. package/runtime/core-aliases-early.js +94 -67
  209. package/runtime/core-aliases-early.js.map +1 -1
@@ -1,4 +1,37 @@
1
1
  import { type UserConfig } from 'vite';
2
- export declare const angularConfig: ({ mode }: {
2
+ /**
3
+ * File replacement entry forwarded to `@analogjs/vite-plugin-angular`.
4
+ * `replace` and `with` may be absolute paths or paths relative to
5
+ * `workspaceRoot` (when supplied). The Analog plugin matches against the
6
+ * resolved id with `endsWith(replace)`, so absolute paths Just Work in Nx
7
+ * / pnpm / Rush layouts where the source tree may live above the project
8
+ * root.
9
+ */
10
+ export interface AngularFileReplacement {
11
+ replace: string;
12
+ with: string;
13
+ ssr?: string;
14
+ }
15
+ export declare const angularConfig: ({ mode, fileReplacements, workspaceRoot, }: {
3
16
  mode: string;
17
+ /**
18
+ * Angular CLI–style file replacements (e.g. `environment.ts` →
19
+ * `environment.stg.ts`) forwarded to `@analogjs/vite-plugin-angular`'s
20
+ * `replaceFiles` plugin AND to the Angular TypeScript CompilerHost.
21
+ *
22
+ * Required for monorepo apps that need configuration-specific
23
+ * environment files: the Angular host reads source files via its own
24
+ * file-system layer, so a Vite `resolveId`/`load` plugin alone cannot
25
+ * swap a `.ts` file for `vite serve` / HMR — the host will still
26
+ * compile the original source from disk. Passing the list here ensures
27
+ * the host sees the replacement before TypeScript ever parses the
28
+ * file, matching how Angular CLI's `fileReplacements` works.
29
+ */
30
+ fileReplacements?: AngularFileReplacement[];
31
+ /**
32
+ * Workspace root used by `@analogjs/vite-plugin-angular` to resolve
33
+ * relative `fileReplacements` paths. Absolute paths bypass this.
34
+ * Defaults to `process.cwd()`.
35
+ */
36
+ workspaceRoot?: string;
4
37
  }) => UserConfig;
@@ -7,11 +7,14 @@ import { angularLinkerVitePlugin, angularLinkerVitePluginPost } from '../helpers
7
7
  import { synthesizeMissingInjectableFactories } from '../helpers/angular/synthesize-injectable-factories.js';
8
8
  import { ensureSharedAngularLinker, resolveAngularFileSystem } from '../helpers/angular/shared-linker.js';
9
9
  import { inlineDecoratorComponentTemplates } from '../helpers/angular/inline-decorator-component-templates.js';
10
+ import { appendComponentHmrRegistration, findComponentClassNames, INJECTION_MARKER as HMR_REGISTER_MARKER } from '../helpers/angular/inject-component-hmr-registration.js';
11
+ import { injectAngularHmrViteIgnore } from '../helpers/angular/inject-hmr-vite-ignore.js';
10
12
  import { synthesizeDecoratorCtorParameters } from '../helpers/angular/synthesize-decorator-ctor-parameters.js';
11
- import { containsRealNgDeclare } from '../helpers/angular/util.js';
13
+ import { containsRealNgDeclare, stripJsComments } from '../helpers/angular/util.js';
12
14
  import { baseConfig } from './base.js';
13
15
  import { getCliFlags } from '../helpers/cli-flags.js';
14
16
  import { resolveRelativeToImportMeta } from '../helpers/import-meta-path.js';
17
+ import { resolveVerboseFlag } from '../helpers/logging.js';
15
18
  // Lazily import the Angular linker factory function. Used by chunk-level linkers
16
19
  // to create FRESH plugin instances per invocation (avoiding stale state in watch mode).
17
20
  let _cachedLinkerFactory = null;
@@ -87,10 +90,7 @@ function angularRollupLinker(projectRoot) {
87
90
  return null;
88
91
  const plugin = createLinker({ sourceMapping: false, fileSystem: angularFileSystem });
89
92
  if (debug) {
90
- try {
91
- console.log('[ns-angular-linker][rollup-load] linking', cleanId);
92
- }
93
- catch { }
93
+ console.log('[ns-angular-linker][rollup-load] linking', cleanId);
94
94
  }
95
95
  const result = await babel.transformAsync(code, {
96
96
  filename: cleanId,
@@ -123,10 +123,7 @@ function angularRollupLinker(projectRoot) {
123
123
  try {
124
124
  const plugin = createLinker({ sourceMapping: false, fileSystem: angularFileSystem });
125
125
  if (debug) {
126
- try {
127
- console.log('[ns-angular-linker][rollup] linking', cleanId);
128
- }
129
- catch { }
126
+ console.log('[ns-angular-linker][rollup] linking', cleanId);
130
127
  }
131
128
  const result = await babel.transformAsync(code, {
132
129
  filename: cleanId,
@@ -148,6 +145,37 @@ function angularRollupLinker(projectRoot) {
148
145
  const cliFlags = getCliFlags();
149
146
  const isDevEnv = process.env.NODE_ENV !== 'production';
150
147
  const hmrActive = isDevEnv && !!cliFlags.hmr;
148
+ /**
149
+ * Web-style template HMR opt-out.
150
+ *
151
+ * When `hmrActive` is true we default to the in-place template-replacement
152
+ * pipeline (`liveReload: true` on Analog → `_enableHmr: true` on the Angular
153
+ * TS compiler → `angular:component-update` events served via the
154
+ * `/@ng/component` middleware → `ɵɵreplaceMetadata` on the live class).
155
+ * Setting `_enableHmr: true` also forces Analog to set
156
+ * `externalRuntimeStyles: true`, changing how component styles are emitted
157
+ * (URLs fetched lazily instead of inlined). On the web that's fine; for
158
+ * NativeScript it's a new code path that touches the SCSS / Tailwind
159
+ * pipeline and the iOS runtime's HTTP module loader.
160
+ *
161
+ * If a project hits a regression on day one (broken styling, unresolved
162
+ * `?ngcomp=` imports, etc.) the user can roll back to the legacy reboot
163
+ * pipeline without an upstream patch by setting the env flag. We honour
164
+ * `0`, `false`, `off`, and `no` (case-insensitive) as "off" — anything
165
+ * else (including unset) keeps the new path on.
166
+ *
167
+ * The flag is read once at module load, mirroring how `hmrActive` is
168
+ * computed, so a project can flip it via `cross-env` in their dev script
169
+ * and never look back.
170
+ */
171
+ const angularLiveReloadDisabledByEnv = (() => {
172
+ const raw = process.env.NS_VITE_ANGULAR_LIVE_RELOAD;
173
+ if (typeof raw !== 'string')
174
+ return false;
175
+ const v = raw.trim().toLowerCase();
176
+ return v === '0' || v === 'false' || v === 'off' || v === 'no';
177
+ })();
178
+ const hmrAngularLiveReload = hmrActive && !angularLiveReloadDisabledByEnv;
151
179
  const projectRoot = process.cwd();
152
180
  const tsConfigAppPath = path.resolve(projectRoot, 'tsconfig.app.json');
153
181
  const tsConfigPath = path.resolve(projectRoot, 'tsconfig.json');
@@ -156,21 +184,61 @@ if (!fs.existsSync(tsConfigAppPath) && fs.existsSync(tsConfigPath)) {
156
184
  tsConfig = tsConfigPath;
157
185
  }
158
186
  function normalizeAngularWatchPath(filePath) {
159
- return filePath.split('?', 1)[0].replace(/\\/g, '/');
187
+ return filePath
188
+ .split('?', 1)[0]
189
+ .replace(/\\/g, '/')
190
+ .replace(/^file:\/\//, '');
191
+ }
192
+ function normalizeAngularWatchKey(filePath) {
193
+ const normalizedPath = normalizeAngularWatchPath(filePath);
194
+ const fileSystemPath = normalizedPath.startsWith('/@fs/') ? normalizedPath.slice('/@fs'.length) : normalizedPath;
195
+ const normalizedProjectRoot = projectRoot.replace(/\\/g, '/').replace(/\/$/, '');
196
+ if (normalizedProjectRoot && fileSystemPath.startsWith(normalizedProjectRoot)) {
197
+ const relative = fileSystemPath.slice(normalizedProjectRoot.length);
198
+ return relative.startsWith('/') ? relative : `/${relative}`;
199
+ }
200
+ return fileSystemPath;
201
+ }
202
+ function getAngularWatchKeys(filePath) {
203
+ const normalizedPath = normalizeAngularWatchPath(filePath);
204
+ const keys = new Set();
205
+ keys.add(normalizedPath);
206
+ keys.add(normalizeAngularWatchKey(normalizedPath));
207
+ return Array.from(keys).filter(Boolean);
208
+ }
209
+ function resolveAngularWatchFilePath(filePath) {
210
+ const normalizedPath = normalizeAngularWatchPath(filePath);
211
+ const fileSystemPath = normalizedPath.startsWith('/@fs/') ? normalizedPath.slice('/@fs'.length) : normalizedPath;
212
+ if (path.isAbsolute(fileSystemPath) && fs.existsSync(fileSystemPath)) {
213
+ return fileSystemPath;
214
+ }
215
+ if (fileSystemPath.startsWith('/')) {
216
+ return path.resolve(projectRoot, `.${fileSystemPath}`);
217
+ }
218
+ return path.resolve(projectRoot, fileSystemPath);
160
219
  }
161
220
  function extractComponentAssetPaths(code, componentId) {
162
221
  const componentPath = normalizeAngularWatchPath(componentId);
163
222
  const assetPaths = new Set();
164
223
  const resolveAssetPath = (assetPath) => normalizeAngularWatchPath(path.resolve(path.dirname(componentPath), assetPath));
165
- const templateUrlMatch = code.match(/templateUrl\s*:\s*['"](.+?\.(?:html|htm))['"]/);
224
+ // Blank out `//` and `/* */` comments before scanning. The regexes below
225
+ // are intentionally simple (no JS parser) so they would otherwise match
226
+ // commented-out `templateUrl` / `styleUrls` declarations and register
227
+ // phantom asset deps via `addWatchFile`. In current Rolldown-Vite that
228
+ // also enrolls them as `_addedImports`, which `vite:import-analysis`
229
+ // then tries to resolve — surfacing as a misleading
230
+ // `Failed to resolve import "<file>" from "<importer>". Does the file
231
+ // exist?` pre-transform error if the file (predictably) doesn't exist.
232
+ const scanCode = stripJsComments(code);
233
+ const templateUrlMatch = scanCode.match(/templateUrl\s*:\s*['"](.+?\.(?:html|htm))['"]/);
166
234
  if (templateUrlMatch) {
167
235
  assetPaths.add(resolveAssetPath(templateUrlMatch[1]));
168
236
  }
169
- const styleUrlMatch = code.match(/styleUrl\s*:\s*['"](.+?\.(?:css|less|sass|scss))['"]/);
237
+ const styleUrlMatch = scanCode.match(/styleUrl\s*:\s*['"](.+?\.(?:css|less|sass|scss))['"]/);
170
238
  if (styleUrlMatch) {
171
239
  assetPaths.add(resolveAssetPath(styleUrlMatch[1]));
172
240
  }
173
- const styleUrlsMatch = code.match(/styleUrls\s*:\s*\[([\s\S]*?)\]/m);
241
+ const styleUrlsMatch = scanCode.match(/styleUrls\s*:\s*\[([\s\S]*?)\]/m);
174
242
  if (styleUrlsMatch) {
175
243
  for (const match of styleUrlsMatch[1].matchAll(/['"](.+?\.(?:css|less|sass|scss))['"]/g)) {
176
244
  assetPaths.add(resolveAssetPath(match[1]));
@@ -179,9 +247,18 @@ function extractComponentAssetPaths(code, componentId) {
179
247
  return Array.from(assetPaths);
180
248
  }
181
249
  function createAngularPlugins(opts) {
250
+ const verbose = resolveVerboseFlag();
182
251
  const assetToComponents = new Map();
183
252
  const componentToAssets = new Map();
184
253
  const pendingComponentInvalidations = new Set();
254
+ // Shared state between the `enforce: 'pre'` discovery plugin and the
255
+ // `enforce: 'post'` injection plugin. Maps a clean (no-querystring)
256
+ // .ts file id to the list of `@Component`-decorated class names found
257
+ // in its RAW TypeScript source. The pre plugin populates this map;
258
+ // the post plugin reads it to know which class names to register
259
+ // against the compiled output. Cleared on each pre-plugin invocation
260
+ // so renames or `@Component` removals don't leave stale entries.
261
+ const componentsByCleanId = new Map();
185
262
  const untrackComponentAssets = (componentPath) => {
186
263
  const previousAssets = componentToAssets.get(componentPath);
187
264
  if (!previousAssets)
@@ -201,7 +278,7 @@ function createAngularPlugins(opts) {
201
278
  untrackComponentAssets(componentPath);
202
279
  if (assetPaths.length === 0)
203
280
  return;
204
- const normalizedAssets = new Set(assetPaths.map((assetPath) => normalizeAngularWatchPath(assetPath)));
281
+ const normalizedAssets = new Set(assetPaths.flatMap((assetPath) => getAngularWatchKeys(assetPath)));
205
282
  componentToAssets.set(componentPath, normalizedAssets);
206
283
  for (const assetPath of normalizedAssets) {
207
284
  const components = assetToComponents.get(assetPath) || new Set();
@@ -209,43 +286,190 @@ function createAngularPlugins(opts) {
209
286
  assetToComponents.set(assetPath, components);
210
287
  }
211
288
  };
289
+ const isCandidateComponentTs = (cleanId) => {
290
+ if (!cleanId.endsWith('.ts'))
291
+ return false;
292
+ if (cleanId.includes('/node_modules/'))
293
+ return false;
294
+ if (cleanId.endsWith('.d.ts'))
295
+ return false;
296
+ if (cleanId.endsWith('.spec.ts') || cleanId.endsWith('.test.ts'))
297
+ return false;
298
+ return true;
299
+ };
212
300
  return [
301
+ // HMR self-registration runs in two phases:
302
+ //
303
+ // 1. (this plugin, `enforce: 'pre'`) Walk the raw TypeScript
304
+ // source for each user `.ts` file and record the names of
305
+ // any `@Component`-decorated classes into the shared
306
+ // `componentsByCleanId` map. Discovery has to happen on the
307
+ // raw source because the Analog Angular plugin rewrites
308
+ // `@Component(...)` into static metadata calls and removes
309
+ // the textual decorator pattern.
310
+ //
311
+ // 2. (`ns-component-hmr-register-post`, `enforce: 'post'`)
312
+ // After the Analog Angular plugin has compiled the file,
313
+ // append the global `__NS_HMR_REGISTER_COMPONENT__`
314
+ // registration calls keyed by the names recorded in step 1.
315
+ //
316
+ // Why the two-phase split: the Analog Angular plugin's `transform`
317
+ // returns its OWN regenerated compiled output (from its internal
318
+ // `outputFiles` cache populated at `buildStart`), discarding any
319
+ // code modifications applied earlier in the pipeline. We
320
+ // previously appended the registration snippet here, in the pre
321
+ // plugin, and the snippet was silently dropped — leaving the
322
+ // HMR class registry empty and `getFreshComponentClass` returning
323
+ // `found=false reason=no-registry` after every reboot.
324
+ //
325
+ // Placement notes that still apply:
326
+ // - `apply: 'serve'`: the registry runtime hook is dev-only;
327
+ // production builds never need self-registration.
328
+ // - Intentionally NOT gated on `hmrActive`. The injected
329
+ // snippet self-guards with
330
+ // `typeof globalThis.__NS_HMR_REGISTER_COMPONENT__ === 'function'`,
331
+ // so it's a no-op when the runtime hook isn't installed
332
+ // (e.g. `--no-hmr` users still serving modules through
333
+ // Vite). Gating the transform itself on `hmrActive` produced
334
+ // a silent failure mode where `--no-hmr` users got HMR
335
+ // machinery up but never got the registration calls
336
+ // injected, leaving the registry empty.
337
+ {
338
+ name: 'ns-component-hmr-register',
339
+ enforce: 'pre',
340
+ apply: 'serve',
341
+ transform(code, id) {
342
+ const cleanId = id.split('?', 1)[0];
343
+ if (!isCandidateComponentTs(cleanId))
344
+ return null;
345
+ const componentNames = findComponentClassNames(code);
346
+ if (componentNames.length === 0) {
347
+ // Drop any stale entry from a previous transform
348
+ // pass; the file may have lost its `@Component`
349
+ // decorator across a rename/refactor.
350
+ componentsByCleanId.delete(cleanId);
351
+ return null;
352
+ }
353
+ componentsByCleanId.set(cleanId, componentNames);
354
+ if (verbose) {
355
+ console.info(`[ns-hmr][ns-component-hmr-register] discovered ${componentNames.length} component(s) in ${cleanId} (${componentNames.join(', ')})`);
356
+ }
357
+ // Discovery only — never modify the raw TS source. Any
358
+ // modification here is discarded by the Analog Angular
359
+ // plugin downstream; the actual snippet append happens
360
+ // in `ns-component-hmr-register-post`.
361
+ return null;
362
+ },
363
+ },
364
+ // Phase 2: append the HMR registration snippet to the compiled
365
+ // JS output produced by `@analogjs/vite-plugin-angular`. Runs
366
+ // `enforce: 'post'` so we see the post-Angular code (where the
367
+ // pre plugin's work would otherwise be discarded). Reads the
368
+ // component names recorded by the pre plugin via
369
+ // `componentsByCleanId`.
370
+ {
371
+ name: 'ns-component-hmr-register-post',
372
+ enforce: 'post',
373
+ apply: 'serve',
374
+ transform(code, id) {
375
+ const cleanId = id.split('?', 1)[0];
376
+ if (!isCandidateComponentTs(cleanId))
377
+ return null;
378
+ const componentNames = componentsByCleanId.get(cleanId);
379
+ if (!componentNames || componentNames.length === 0)
380
+ return null;
381
+ // Idempotency: the Vite cache may replay the transform
382
+ // pipeline on cached modules. The marker comment is
383
+ // inserted by `appendComponentHmrRegistration` and
384
+ // guards against double-injection. We also defensively
385
+ // short-circuit here so we don't have to allocate the
386
+ // suffix string on every cached re-run.
387
+ if (code.includes(HMR_REGISTER_MARKER))
388
+ return null;
389
+ const result = appendComponentHmrRegistration(code, componentNames);
390
+ if (!result.code)
391
+ return null;
392
+ if (verbose) {
393
+ console.info(`[ns-hmr][ns-component-hmr-register-post] appended registrations for ${result.componentNames.length} component(s) in ${cleanId} (${result.componentNames.join(', ')})`);
394
+ }
395
+ // Returning `null` for the source map is acceptable for
396
+ // dev: lines 1..N (the original compiled body) keep
397
+ // the upstream Angular source map; the appended snippet
398
+ // is invisible to debuggers but harmless. For
399
+ // production-grade source maps a MagicString-based
400
+ // pass-through could be used; not required for HMR.
401
+ return { code: result.code, map: null };
402
+ },
403
+ },
213
404
  // Allow external html template changes to trigger hot reload: Make .ts files depend on their .html templates
214
405
  {
215
406
  name: 'angular-template-deps',
216
407
  enforce: 'pre',
217
408
  transform(code, id) {
218
409
  const componentPath = normalizeAngularWatchPath(id);
219
- if (!componentPath.endsWith('.ts'))
410
+ const componentKey = normalizeAngularWatchKey(id);
411
+ if (!componentKey.endsWith('.ts'))
220
412
  return null;
221
413
  const assetPaths = extractComponentAssetPaths(code, componentPath);
222
- trackComponentAssets(componentPath, assetPaths);
414
+ trackComponentAssets(componentKey, assetPaths);
223
415
  for (const assetPath of assetPaths) {
224
416
  this.addWatchFile(assetPath);
225
417
  }
418
+ // Diagnostic: surface which .ts files we've registered
419
+ // asset (template/styleUrls) dependencies for. This is
420
+ // the first fence the HTML→TS invalidation pipeline must
421
+ // pass — if we never see a [tracking] log for the
422
+ // component we're editing, the watcher will never fire
423
+ // and `pendingComponentInvalidations` stays empty.
424
+ if (verbose) {
425
+ console.info(`[ns-hmr][angular-template-deps] [tracking] componentKey=${componentKey} assets=${assetPaths.length} (${assetPaths.slice(0, 4).join(', ')})`);
426
+ }
226
427
  return null;
227
428
  },
228
429
  watchChange(id) {
229
430
  const changedPath = normalizeAngularWatchPath(id);
230
- const components = assetToComponents.get(changedPath);
431
+ const components = new Set();
432
+ for (const assetKey of getAngularWatchKeys(changedPath)) {
433
+ for (const componentPath of assetToComponents.get(assetKey) || []) {
434
+ components.add(componentPath);
435
+ }
436
+ }
231
437
  if (components?.size) {
232
438
  for (const componentPath of components) {
233
439
  pendingComponentInvalidations.add(componentPath);
234
440
  }
441
+ if (verbose) {
442
+ console.info(`[ns-hmr][angular-template-deps] watchChange [via assetToComponents] changed=${changedPath} → invalidating ${components.size} component(s):`, Array.from(components));
443
+ }
235
444
  return;
236
445
  }
237
446
  if (/\.(html|htm)$/i.test(changedPath)) {
238
447
  const componentPath = changedPath.replace(/\.(html|htm)$/i, '.ts');
239
- if (fs.existsSync(componentPath)) {
240
- pendingComponentInvalidations.add(componentPath);
448
+ const exists = fs.existsSync(resolveAngularWatchFilePath(componentPath));
449
+ if (exists) {
450
+ const componentKey = normalizeAngularWatchKey(componentPath);
451
+ pendingComponentInvalidations.add(componentKey);
452
+ if (verbose) {
453
+ console.info(`[ns-hmr][angular-template-deps] watchChange [via fallback .html→.ts] changed=${changedPath} componentKey=${componentKey}`);
454
+ }
455
+ }
456
+ else {
457
+ // Truly anomalous: a watched template/style asset has no companion
458
+ // `.ts` file, so we cannot route the edit through the Angular
459
+ // HMR pipeline. Always-on warning so it surfaces in non-verbose
460
+ // runs — silent fallback would hide a real wiring break.
461
+ console.warn(`[ns-hmr][angular-template-deps] watchChange [no companion .ts found] changed=${changedPath} expectedTs=${componentPath}`);
241
462
  }
242
463
  }
243
464
  },
244
465
  shouldTransformCachedModule({ id }) {
245
- const componentPath = normalizeAngularWatchPath(id);
466
+ const componentPath = normalizeAngularWatchKey(id);
246
467
  if (!pendingComponentInvalidations.has(componentPath))
247
468
  return null;
248
469
  pendingComponentInvalidations.delete(componentPath);
470
+ if (verbose) {
471
+ console.info(`[ns-hmr][angular-template-deps] shouldTransformCachedModule → re-transform componentKey=${componentPath}`);
472
+ }
249
473
  return true;
250
474
  },
251
475
  },
@@ -258,11 +482,140 @@ function createAngularPlugins(opts) {
258
482
  experimental: {
259
483
  useAngularCompilationAPI: opts.useAngularCompilationAPI,
260
484
  },
261
- liveReload: false, // Disable live reload in favor of HMR
485
+ // `liveReload` is Analog's flag for Angular's web-style template HMR
486
+ // pipeline. When ON, Analog:
487
+ // 1. Sets `_enableHmr = true` on the TS compiler so each compiled
488
+ // component `.mjs` emits `<ClassName>_HmrLoad` plus an
489
+ // `import.meta.hot.on('angular:component-update', ...)` listener.
490
+ // 2. Registers a `/@ng/component?c=<id>` middleware that serves the
491
+ // recompiled template's `_UpdateMetadata` source on demand.
492
+ // 3. In `handleHotUpdate` for `.html` / `.css` / `.scss` edits, sends
493
+ // `server.ws.send('angular:component-update', { id, timestamp })`
494
+ // so the runtime can call `ɵɵreplaceMetadata` on the live class —
495
+ // swapping the template definition AND walking live LViews to
496
+ // recreate matching views in-place. NO Angular reboot, NO route
497
+ // navigation.
498
+ //
499
+ // Previously this was `false` because the NativeScript HMR pipeline
500
+ // rebuilt every save through `__reboot_ng_modules__`. That works but
501
+ // has two big downsides on mobile: every save triggers a full app
502
+ // reboot AND the captured route-history replay (see
503
+ // `@nativescript/angular`'s `hmr-route-replay.ts`), which produces 2-3
504
+ // re-navigations per save and re-instantiates the page component
505
+ // multiple times. For pure template/style edits — the common case —
506
+ // the web-style component-replacement path keeps the page mounted and
507
+ // only swaps the changed bits.
508
+ //
509
+ // We only enable this when HMR itself is active (`hmrActive` already
510
+ // gates on `--hmr` and `NODE_ENV !== 'production'`). With HMR off the
511
+ // behaviour is unchanged: production builds and `--no-hmr` dev still
512
+ // see `liveReload: false`, the compiler skips the HMR initializers,
513
+ // and the middleware is not registered.
514
+ //
515
+ // Important interactions to be aware of:
516
+ // - When `_enableHmr` is true, Analog also sets
517
+ // `externalRuntimeStyles = true`, changing how component styles
518
+ // are emitted (URLs fetched at runtime instead of inlined). For
519
+ // NativeScript, the existing CSS pipeline expects inlined styles;
520
+ // `ns-component-hmr-style-overrides` (below) restores the
521
+ // pre-HMR style-emission strategy so Tailwind/global SCSS
522
+ // packaging keeps working.
523
+ // - The runtime dynamic-import resolves the metadata URL relative
524
+ // to `import.meta.url`, e.g. `http://host:port/ns/m/<componentDir>/@ng/component?c=...`.
525
+ // Analog's middleware uses `req.url.includes('/@ng/component')`
526
+ // (substring match), so the request still matches even with the
527
+ // `/ns/m/` prefix in the path.
528
+ // - The NS HMR client (`packages/vite/hmr/client/index.ts`)
529
+ // forwards Vite's standard `{ type: 'custom', event, data }`
530
+ // payloads to `import.meta.hot.on` listeners via
531
+ // `__NS_DISPATCH_HOT_EVENT__`, and short-circuits before the
532
+ // reboot path for `angular:component-update`.
533
+ // - The NS server-side hot-update handler in
534
+ // `packages/vite/hmr/server/websocket.ts` skips its own
535
+ // `ns:angular-update` broadcast for `.html` / component-style
536
+ // edits so we don't double-fire (the reboot path stays for `.ts`
537
+ // edits).
538
+ // - To roll back to the legacy reboot-only pipeline (e.g. while
539
+ // debugging an `externalRuntimeStyles` regression), set
540
+ // `NS_VITE_ANGULAR_LIVE_RELOAD=0` in the dev environment.
541
+ // `hmrAngularLiveReload` collapses both gates above.
542
+ liveReload: hmrAngularLiveReload,
543
+ // NativeScript can't consume Angular's `externalRuntimeStyles`
544
+ // mode — that emits component styles as runtime-loaded
545
+ // `<hash>.css` URL references which only a browser CSSOM/`<link>`
546
+ // pipeline can resolve. We tell our patched Analog plugin (see
547
+ // `patches/@analogjs+vite-plugin-angular+2.3.1.patch` in
548
+ // downstream apps; upstream PR pending) to keep the legacy
549
+ // behavior of inlining preprocessed CSS strings into the
550
+ // component metadata's `styles: [...]` array, which the NS
551
+ // renderer's CSS-bundle pipeline already knows how to apply.
552
+ // The option is independent from `liveReload` (`_enableHmr`
553
+ // still wires up `ɵɵreplaceMetadata` for in-place template
554
+ // HMR) — we keep HMR ON, just opt out of the URL-style
555
+ // emission. Note the option lands as a no-op on stock
556
+ // Analog releases that haven't merged the patch; once
557
+ // merged, this will switch the compiler off external styles
558
+ // for NativeScript without affecting web builds.
559
+ //
560
+ // `@ts-expect-error` because the option is not yet in
561
+ // `@analogjs/vite-plugin-angular`'s published `PluginOptions`
562
+ // type. When the upstream PR (https://github.com/analogjs/analog)
563
+ // adds it, this `@ts-expect-error` will itself become an
564
+ // "unused suppression" error — that's the signal to remove
565
+ // this comment AND the surrounding explanation, and bump
566
+ // the Analog peer dep to the version that ships the type.
567
+ // @ts-expect-error -- pending upstream Analog type publish
568
+ externalRuntimeStyles: false,
262
569
  tsconfig: tsConfig,
570
+ // Forward Angular-style file replacements (e.g. `environment.ts`
571
+ // → `environment.stg.ts`) directly into the Analog plugin so the
572
+ // Angular TypeScript host (which reads source files via its own
573
+ // CompilerHost, bypassing Vite's load chain) sees the swap. This
574
+ // is the same hook Angular CLI uses for `fileReplacements` in
575
+ // `angular.json` build configurations.
576
+ fileReplacements: opts.fileReplacements ?? [],
577
+ workspaceRoot: opts.workspaceRoot ?? process.cwd(),
263
578
  }),
264
579
  // Post-phase linker to catch any declarations introduced after other transforms (including project code)
265
580
  angularLinkerVitePluginPost(process.cwd()),
581
+ // Re-inject the `/* @vite-ignore */` annotation onto Angular's HMR
582
+ // initializer dynamic imports.
583
+ //
584
+ // Angular's compiler emits each component's HMR loader as
585
+ // `import(/* @vite-ignore */ i0.ɵɵgetReplaceMetadataURL(...))` so
586
+ // Vite leaves the runtime-computed URL alone. The annotation goes
587
+ // missing somewhere in the post-Angular pipeline (empirically the
588
+ // linker's `compact: false` Babel pass loses it on some files),
589
+ // causing Vite's static analyzer to flag the import and rewrite
590
+ // the call site through its runtime resolver — which then throws
591
+ // `TypeError at ɵɵgetReplaceMetadataURL` on the iOS device because
592
+ // the resolver expects a statically known specifier.
593
+ //
594
+ // Running `enforce: 'post'` and `apply: 'serve'` here ensures we
595
+ // see the file AFTER every other transform has had its chance to
596
+ // strip comments, AND only in dev (the HMR initializer is gated
597
+ // behind `ngDevMode` and never runs in a production build, so the
598
+ // fix would be wasted work outside `serve`). The helper is
599
+ // idempotent: if the annotation is already present, the file is
600
+ // returned unchanged.
601
+ {
602
+ name: 'ns-angular-hmr-vite-ignore',
603
+ enforce: 'post',
604
+ apply: 'serve',
605
+ transform(code, id) {
606
+ if (!hmrAngularLiveReload)
607
+ return null;
608
+ const cleanId = id.split('?', 1)[0];
609
+ if (!cleanId.endsWith('.ts') && !cleanId.endsWith('.mjs') && !cleanId.endsWith('.js'))
610
+ return null;
611
+ if (cleanId.includes('/node_modules/'))
612
+ return null;
613
+ const next = injectAngularHmrViteIgnore(code);
614
+ if (next === code)
615
+ return null;
616
+ return { code: next, map: null };
617
+ },
618
+ },
266
619
  // Enforce: fully disable dependency optimization during serve to avoid rxjs esm5 crawling and OOM
267
620
  {
268
621
  name: 'ns-disable-optimize-deps',
@@ -301,7 +654,7 @@ function createAngularPlugins(opts) {
301
654
  },
302
655
  ];
303
656
  }
304
- export const angularConfig = ({ mode }) => {
657
+ export const angularConfig = ({ mode, fileReplacements, workspaceRoot, }) => {
305
658
  const useSingleBundleDevOutput = mode === 'development' && !hmrActive;
306
659
  const plugins = createAngularPlugins({
307
660
  // Vite build --watch with the legacy Analog compilation path can regress
@@ -309,6 +662,8 @@ export const angularConfig = ({ mode }) => {
309
662
  // Restrict the newer compilation API to NativeScript's development no-HMR
310
663
  // flow, which is where the unstable rebuilds occur today.
311
664
  useAngularCompilationAPI: useSingleBundleDevOutput,
665
+ fileReplacements,
666
+ workspaceRoot,
312
667
  });
313
668
  const disableAnimations = true;
314
669
  //process.env.NS_DISABLE_NG_ANIMATIONS === "1" ||
@@ -375,10 +730,7 @@ export const angularConfig = ({ mode }) => {
375
730
  if (finalCode !== code) {
376
731
  chunk.code = finalCode;
377
732
  if (debug) {
378
- try {
379
- console.log('[ns-angular-linker][post] linked', fileName, isNsPolyfills ? '(polyfills)' : '');
380
- }
381
- catch { }
733
+ console.log('[ns-angular-linker][post] linked', fileName, isNsPolyfills ? '(polyfills)' : '');
382
734
  }
383
735
  }
384
736
  if (strict && !isNsPolyfills && containsRealNgDeclare(finalCode)) {
@@ -424,10 +776,7 @@ export const angularConfig = ({ mode }) => {
424
776
  throw new Error(message);
425
777
  }
426
778
  else {
427
- try {
428
- console.warn(`[ns-angular-linker-post] ${message}`);
429
- }
430
- catch { }
779
+ console.warn(`[ns-angular-linker-post] ${message}`);
431
780
  }
432
781
  }
433
782
  },
@@ -474,10 +823,7 @@ export const angularConfig = ({ mode }) => {
474
823
  transformed = applyAngularChunkPostProcessing(transformed);
475
824
  if (transformed !== code) {
476
825
  if (debug) {
477
- try {
478
- console.log('[ns-angular-linker][render] linked', filename);
479
- }
480
- catch { }
826
+ console.log('[ns-angular-linker][render] linked', filename);
481
827
  }
482
828
  return { code: transformed, map: null };
483
829
  }