@analogjs/vite-plugin-angular 3.0.0-alpha.9 → 3.0.0-alpha.91

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 (184) hide show
  1. package/README.md +121 -0
  2. package/migrations/migrate-setup-vitest/migrate-setup-vitest.d.ts +2 -0
  3. package/migrations/migrate-setup-vitest/migrate-setup-vitest.js +49 -0
  4. package/migrations/migrate-setup-vitest/migrate-setup-vitest.js.map +1 -0
  5. package/migrations/migration.json +7 -1
  6. package/migrations/migrations.json +9 -0
  7. package/migrations/update-3-0-0/migrate-setup-vitest.d.ts +2 -0
  8. package/migrations/update-3-0-0/migrate-setup-vitest.js +36 -0
  9. package/migrations/update-3-0-0/migrate-setup-vitest.js.map +1 -0
  10. package/package.json +49 -8
  11. package/src/index.d.ts +7 -2
  12. package/src/index.js +3 -1
  13. package/src/index.js.map +1 -1
  14. package/src/lib/analog-plugin-interop.d.ts +78 -0
  15. package/src/lib/analog-plugin-interop.js +97 -0
  16. package/src/lib/analog-plugin-interop.js.map +1 -0
  17. package/src/lib/angular-build-optimizer-plugin.d.ts +8 -4
  18. package/src/lib/angular-build-optimizer-plugin.js +66 -31
  19. package/src/lib/angular-build-optimizer-plugin.js.map +1 -1
  20. package/src/lib/angular-jit-plugin.d.ts +3 -3
  21. package/src/lib/angular-jit-plugin.js +28 -6
  22. package/src/lib/angular-jit-plugin.js.map +1 -1
  23. package/src/lib/angular-pending-tasks.plugin.d.ts +7 -7
  24. package/src/lib/angular-pending-tasks.plugin.js.map +1 -1
  25. package/src/lib/angular-vite-plugin.d.ts +79 -69
  26. package/src/lib/angular-vite-plugin.js +775 -352
  27. package/src/lib/angular-vite-plugin.js.map +1 -1
  28. package/src/lib/angular-vitest-plugin.d.ts +26 -21
  29. package/src/lib/angular-vitest-plugin.js +34 -25
  30. package/src/lib/angular-vitest-plugin.js.map +1 -1
  31. package/src/lib/compilation-api/compilation-api-plugin.d.ts +24 -0
  32. package/src/lib/compilation-api/compilation-api-plugin.js +548 -0
  33. package/src/lib/compilation-api/compilation-api-plugin.js.map +1 -0
  34. package/src/lib/compilation-api/index.d.ts +1 -0
  35. package/src/lib/compilation-api/index.js +1 -0
  36. package/src/lib/compiler/angular-version.d.ts +19 -0
  37. package/src/lib/compiler/angular-version.js +31 -0
  38. package/src/lib/compiler/angular-version.js.map +1 -0
  39. package/src/lib/compiler/class-field-lowering.d.ts +23 -0
  40. package/src/lib/compiler/class-field-lowering.js +131 -0
  41. package/src/lib/compiler/class-field-lowering.js.map +1 -0
  42. package/src/lib/compiler/compile.d.ts +50 -0
  43. package/src/lib/compiler/compile.js +814 -0
  44. package/src/lib/compiler/compile.js.map +1 -0
  45. package/src/lib/compiler/constants.d.ts +24 -0
  46. package/src/lib/compiler/constants.js +59 -0
  47. package/src/lib/compiler/constants.js.map +1 -0
  48. package/src/lib/compiler/debug.d.ts +22 -0
  49. package/src/lib/compiler/debug.js +20 -0
  50. package/src/lib/compiler/debug.js.map +1 -0
  51. package/src/lib/compiler/defer.d.ts +57 -0
  52. package/src/lib/compiler/defer.js +154 -0
  53. package/src/lib/compiler/defer.js.map +1 -0
  54. package/src/lib/compiler/dts-reader.d.ts +45 -0
  55. package/src/lib/compiler/dts-reader.js +395 -0
  56. package/src/lib/compiler/dts-reader.js.map +1 -0
  57. package/src/lib/compiler/hmr.d.ts +16 -0
  58. package/src/lib/compiler/hmr.js +69 -0
  59. package/src/lib/compiler/hmr.js.map +1 -0
  60. package/src/lib/compiler/index.d.ts +8 -0
  61. package/src/lib/compiler/index.js +8 -0
  62. package/src/lib/compiler/jit-metadata.d.ts +14 -0
  63. package/src/lib/compiler/jit-metadata.js +168 -0
  64. package/src/lib/compiler/jit-metadata.js.map +1 -0
  65. package/src/lib/compiler/jit-transform.d.ts +24 -0
  66. package/src/lib/compiler/jit-transform.js +204 -0
  67. package/src/lib/compiler/jit-transform.js.map +1 -0
  68. package/src/lib/compiler/js-emitter.d.ts +10 -0
  69. package/src/lib/compiler/js-emitter.js +446 -0
  70. package/src/lib/compiler/js-emitter.js.map +1 -0
  71. package/src/lib/compiler/metadata.d.ts +36 -0
  72. package/src/lib/compiler/metadata.js +710 -0
  73. package/src/lib/compiler/metadata.js.map +1 -0
  74. package/src/lib/compiler/registry.d.ts +56 -0
  75. package/src/lib/compiler/registry.js +176 -0
  76. package/src/lib/compiler/registry.js.map +1 -0
  77. package/src/lib/compiler/resource-inliner.d.ts +43 -0
  78. package/src/lib/compiler/resource-inliner.js +230 -0
  79. package/src/lib/compiler/resource-inliner.js.map +1 -0
  80. package/src/lib/compiler/style-ast.d.ts +9 -0
  81. package/src/lib/compiler/style-ast.js +54 -0
  82. package/src/lib/compiler/style-ast.js.map +1 -0
  83. package/src/lib/compiler/styles.d.ts +13 -0
  84. package/src/lib/compiler/test-helpers.d.ts +7 -0
  85. package/src/lib/compiler/type-elision.d.ts +28 -0
  86. package/src/lib/compiler/type-elision.js +211 -0
  87. package/src/lib/compiler/type-elision.js.map +1 -0
  88. package/src/lib/compiler/utils.d.ts +40 -0
  89. package/src/lib/compiler/utils.js +76 -0
  90. package/src/lib/compiler/utils.js.map +1 -0
  91. package/src/lib/compiler-plugin.d.ts +13 -12
  92. package/src/lib/compiler-plugin.js +18 -18
  93. package/src/lib/compiler-plugin.js.map +1 -1
  94. package/src/lib/component-resolvers.d.ts +20 -4
  95. package/src/lib/component-resolvers.js +34 -20
  96. package/src/lib/component-resolvers.js.map +1 -1
  97. package/src/lib/encapsulation-plugin.d.ts +23 -0
  98. package/src/lib/encapsulation-plugin.js +68 -0
  99. package/src/lib/encapsulation-plugin.js.map +1 -0
  100. package/src/lib/fast-compile-plugin.d.ts +23 -0
  101. package/src/lib/fast-compile-plugin.js +434 -0
  102. package/src/lib/fast-compile-plugin.js.map +1 -0
  103. package/src/lib/host.d.ts +10 -8
  104. package/src/lib/host.js +59 -17
  105. package/src/lib/host.js.map +1 -1
  106. package/src/lib/live-reload-plugin.d.ts +5 -5
  107. package/src/lib/live-reload-plugin.js +10 -0
  108. package/src/lib/live-reload-plugin.js.map +1 -1
  109. package/src/lib/models.d.ts +9 -9
  110. package/src/lib/nx-folder-plugin.d.ts +5 -5
  111. package/src/lib/nx-folder-plugin.js.map +1 -1
  112. package/src/lib/plugins/file-replacements.plugin.d.ts +4 -4
  113. package/src/lib/plugins/file-replacements.plugin.js +6 -1
  114. package/src/lib/plugins/file-replacements.plugin.js.map +1 -1
  115. package/src/lib/router-plugin.d.ts +1 -1
  116. package/src/lib/router-plugin.js +14 -2
  117. package/src/lib/router-plugin.js.map +1 -1
  118. package/src/lib/style-preprocessor.d.ts +35 -0
  119. package/src/lib/style-preprocessor.js +35 -0
  120. package/src/lib/style-preprocessor.js.map +1 -0
  121. package/src/lib/stylesheet-registry.d.ts +74 -0
  122. package/src/lib/stylesheet-registry.js +174 -0
  123. package/src/lib/stylesheet-registry.js.map +1 -0
  124. package/src/lib/tools/package.json +1 -3
  125. package/src/lib/tools/src/builders/vite/vite-build.impl.js +7 -5
  126. package/src/lib/tools/src/builders/vite/vite-build.impl.js.map +1 -1
  127. package/src/lib/tools/src/builders/vite-dev-server/dev-server.impl.js +16 -19
  128. package/src/lib/tools/src/builders/vite-dev-server/dev-server.impl.js.map +1 -1
  129. package/src/lib/utils/compilation-shared.d.ts +62 -0
  130. package/src/lib/utils/compilation-shared.js +138 -0
  131. package/src/lib/utils/compilation-shared.js.map +1 -0
  132. package/src/lib/utils/compiler-plugin-options.d.ts +11 -11
  133. package/src/lib/utils/css-extension-resolver.d.ts +32 -0
  134. package/src/lib/utils/css-extension-resolver.js +73 -0
  135. package/src/lib/utils/css-extension-resolver.js.map +1 -0
  136. package/src/lib/utils/css-preprocessor-workers.d.ts +6 -0
  137. package/src/lib/utils/css-preprocessor-workers.js +29 -0
  138. package/src/lib/utils/css-preprocessor-workers.js.map +1 -0
  139. package/src/lib/utils/debug-harness.d.ts +23 -0
  140. package/src/lib/utils/debug-harness.js +88 -0
  141. package/src/lib/utils/debug-harness.js.map +1 -0
  142. package/src/lib/utils/debug-log-file.d.ts +5 -0
  143. package/src/lib/utils/debug-log-file.js +56 -0
  144. package/src/lib/utils/debug-log-file.js.map +1 -0
  145. package/src/lib/utils/debug.d.ts +26 -0
  146. package/src/lib/utils/debug.js +35 -0
  147. package/src/lib/utils/debug.js.map +1 -0
  148. package/src/lib/utils/devkit.d.ts +14 -6
  149. package/src/lib/utils/devkit.js +40 -2
  150. package/src/lib/utils/devkit.js.map +1 -1
  151. package/src/lib/utils/hmr-candidates.d.ts +28 -28
  152. package/src/lib/utils/jit-inline-styles.d.ts +3 -0
  153. package/src/lib/utils/jit-inline-styles.js +16 -0
  154. package/src/lib/utils/jit-inline-styles.js.map +1 -0
  155. package/src/lib/utils/plugin-config.d.ts +41 -0
  156. package/src/lib/utils/plugin-config.js +85 -0
  157. package/src/lib/utils/plugin-config.js.map +1 -0
  158. package/src/lib/utils/rolldown.d.ts +1 -1
  159. package/src/lib/utils/rolldown.js.map +1 -1
  160. package/src/lib/utils/safe-module-paths.d.ts +16 -0
  161. package/src/lib/utils/safe-module-paths.js +29 -0
  162. package/src/lib/utils/safe-module-paths.js.map +1 -0
  163. package/src/lib/utils/source-file-cache.d.ts +8 -15
  164. package/src/lib/utils/source-file-cache.js.map +1 -1
  165. package/src/lib/utils/transform-cache.d.ts +43 -0
  166. package/src/lib/utils/transform-cache.js +97 -0
  167. package/src/lib/utils/transform-cache.js.map +1 -0
  168. package/src/lib/utils/tsconfig-resolver.d.ts +34 -0
  169. package/src/lib/utils/tsconfig-resolver.js +205 -0
  170. package/src/lib/utils/tsconfig-resolver.js.map +1 -0
  171. package/src/lib/utils/virtual-ids.d.ts +4 -0
  172. package/src/lib/utils/virtual-ids.js +23 -0
  173. package/src/lib/utils/virtual-ids.js.map +1 -0
  174. package/src/lib/utils/virtual-resources.d.ts +19 -0
  175. package/src/lib/utils/virtual-resources.js +38 -0
  176. package/src/lib/utils/virtual-resources.js.map +1 -0
  177. package/src/lib/virtual-modules-plugin.d.ts +5 -0
  178. package/src/lib/virtual-modules-plugin.js +23 -0
  179. package/src/lib/virtual-modules-plugin.js.map +1 -0
  180. package/src/test-setup.d.ts +2 -0
  181. package/setup-vitest.d.ts +0 -4
  182. package/setup-vitest.js +0 -176
  183. package/setup-vitest.js.map +0 -1
  184. package/src/lib/tools/src/index.d.ts +0 -0
@@ -1,87 +1,126 @@
1
- import { angularFullVersion, cjt, createAngularCompilation, sourceFileCache } from "./utils/devkit.js";
1
+ import { angularFullVersion, cjt, sourceFileCache } from "./utils/devkit.js";
2
+ import { activateDeferredDebug, applyDebugOption, debugCompiler, debugCompilerV, debugEmit, debugEmitV, debugHmr, debugHmrV, debugStyles, debugStylesV } from "./utils/debug.js";
3
+ import { createCompilerPlugin, createRolldownCompilerPlugin } from "./compiler-plugin.js";
4
+ import { TS_EXT_REGEX, createTsConfigGetter, getTsConfigPath, isProdMode } from "./utils/plugin-config.js";
5
+ import { TsconfigResolver } from "./utils/tsconfig-resolver.js";
2
6
  import { getJsTransformConfigKey, isRolldown } from "./utils/rolldown.js";
7
+ import { AnalogStylesheetRegistry, preprocessStylesheet, rewriteRelativeCssImports } from "./stylesheet-registry.js";
8
+ import { discoverAnalogIntegrations } from "./analog-plugin-interop.js";
9
+ import { describeStylesheetContent, injectViteIgnoreForHmrMetadata, isIgnoredHmrFile, isTestWatchMode } from "./utils/compilation-shared.js";
10
+ import { angularVitestPlugins } from "./angular-vitest-plugin.js";
3
11
  import { buildOptimizerPlugin } from "./angular-build-optimizer-plugin.js";
12
+ import { toJitInlineStyleId } from "./utils/jit-inline-styles.js";
13
+ import { releaseCssPreprocessorWorkers } from "./utils/css-preprocessor-workers.js";
4
14
  import { jitPlugin } from "./angular-jit-plugin.js";
5
- import { createCompilerPlugin, createRolldownCompilerPlugin } from "./compiler-plugin.js";
6
- import { StyleUrlsResolver, TemplateUrlsResolver } from "./component-resolvers.js";
7
- import { augmentHostWithCaching, augmentHostWithResources, augmentProgramWithVersioning, mergeTransformers } from "./host.js";
8
- import { angularVitestPlugins } from "./angular-vitest-plugin.js";
9
- import { pendingTasksPlugin } from "./angular-pending-tasks.plugin.js";
10
15
  import { liveReloadPlugin } from "./live-reload-plugin.js";
16
+ import { toVirtualRawId } from "./utils/virtual-ids.js";
17
+ import { virtualModulesPlugin } from "./virtual-modules-plugin.js";
18
+ import { encapsulationPlugin, getComponentStyleSheetMeta, isComponentStyleSheet } from "./encapsulation-plugin.js";
11
19
  import { nxFolderPlugin } from "./nx-folder-plugin.js";
12
- import { replaceFiles } from "./plugins/file-replacements.plugin.js";
13
20
  import { routerPlugin } from "./router-plugin.js";
14
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
21
+ import { replaceFiles } from "./plugins/file-replacements.plugin.js";
22
+ import { cssExtensionStyleResolverPlugin } from "./utils/css-extension-resolver.js";
23
+ import { loadVirtualRawModule, rewriteHtmlRawImport } from "./utils/virtual-resources.js";
24
+ import { StyleUrlsResolver, TemplateUrlsResolver } from "./component-resolvers.js";
25
+ import { augmentHostWithCaching, augmentHostWithResources, augmentProgramWithVersioning, mergeTransformers } from "./host.js";
26
+ import { markStylePathSafe } from "./utils/safe-module-paths.js";
27
+ import { fastCompilePlugin } from "./fast-compile-plugin.js";
28
+ import { pendingTasksPlugin } from "./angular-pending-tasks.plugin.js";
29
+ import { union } from "es-toolkit";
30
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
15
31
  import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
16
- import * as compilerCli from "@angular/compiler-cli";
17
32
  import { createRequire } from "node:module";
18
- import * as ngCompiler from "@angular/compiler";
19
- import { globSync } from "tinyglobby";
20
- import { defaultClientConditions, normalizePath, preprocessCSS } from "vite";
21
- import { createHash } from "node:crypto";
33
+ import { normalizePath, preprocessCSS } from "vite";
34
+ import * as compilerCli from "@angular/compiler-cli";
22
35
  //#region packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts
23
36
  var require = createRequire(import.meta.url);
24
- var DiagnosticModes = /* @__PURE__ */ function(DiagnosticModes) {
25
- DiagnosticModes[DiagnosticModes["None"] = 0] = "None";
26
- DiagnosticModes[DiagnosticModes["Option"] = 1] = "Option";
27
- DiagnosticModes[DiagnosticModes["Syntactic"] = 2] = "Syntactic";
28
- DiagnosticModes[DiagnosticModes["Semantic"] = 4] = "Semantic";
29
- DiagnosticModes[DiagnosticModes["All"] = 7] = "All";
30
- return DiagnosticModes;
31
- }({});
32
- /**
33
- * TypeScript file extension regex
34
- * Match .(c or m)ts, .ts extensions with an optional ? for query params
35
- * Ignore .tsx extensions
36
- */
37
- var TS_EXT_REGEX = /\.[cm]?(ts)[^x]?\??/;
38
37
  var classNames = /* @__PURE__ */ new Map();
38
+ function evictDeletedFileMetadata(file, { classNamesMap, fileTransformMap }) {
39
+ const normalizedFile = normalizePath(file.split("?")[0]);
40
+ classNamesMap.delete(normalizedFile);
41
+ fileTransformMap.delete(normalizedFile);
42
+ }
39
43
  function angular(options) {
44
+ applyDebugOption(options?.debug, options?.workspaceRoot);
45
+ const liveReload = options?.liveReload ?? true;
46
+ let externalizeStylesRequested = false;
47
+ let configureStylesheetRegistry;
48
+ let transformFilter;
40
49
  /**
41
50
  * Normalize plugin options so defaults
42
51
  * are used for values not provided.
43
52
  */
44
53
  const pluginOptions = {
45
54
  tsconfigGetter: createTsConfigGetter(options?.tsconfig),
46
- workspaceRoot: options?.workspaceRoot ?? process.cwd(),
55
+ workspaceRoot: options?.workspaceRoot ?? process.env["NX_WORKSPACE_ROOT"] ?? process.cwd(),
47
56
  inlineStylesExtension: options?.inlineStylesExtension ?? "css",
48
- advanced: { tsTransformers: {
49
- before: options?.advanced?.tsTransformers?.before ?? [],
50
- after: options?.advanced?.tsTransformers?.after ?? [],
51
- afterDeclarations: options?.advanced?.tsTransformers?.afterDeclarations ?? []
52
- } },
53
57
  supportedBrowsers: options?.supportedBrowsers ?? ["safari 15"],
54
58
  jit: options?.jit,
55
59
  include: options?.include ?? [],
56
- additionalContentDirs: options?.additionalContentDirs ?? [],
57
- liveReload: options?.liveReload ?? false,
60
+ liveReload,
58
61
  disableTypeChecking: options?.disableTypeChecking ?? true,
59
62
  fileReplacements: options?.fileReplacements ?? [],
60
- useAngularCompilationAPI: options?.experimental?.useAngularCompilationAPI ?? false
63
+ fastCompile: options?.fastCompile ?? false,
64
+ fastCompileMode: options?.fastCompileMode ?? "full",
65
+ stylePreprocessor: void 0
61
66
  };
62
67
  let resolvedConfig;
63
68
  let tsConfigResolutionContext = null;
64
69
  const ts = require("typescript");
65
70
  let builder;
66
71
  let nextProgram;
67
- const tsconfigOptionsCache = /* @__PURE__ */ new Map();
68
72
  let cachedHost;
69
73
  let cachedHostKey;
70
- let includeCache = [];
74
+ const isTest = process.env.NODE_ENV === "test" || !!process.env["VITEST"];
75
+ const tsconfigResolver = new TsconfigResolver({
76
+ workspaceRoot: pluginOptions.workspaceRoot,
77
+ include: pluginOptions.include,
78
+ liveReload: pluginOptions.liveReload,
79
+ isTest
80
+ });
71
81
  function invalidateFsCaches() {
72
- includeCache = [];
82
+ tsconfigResolver.invalidateIncludeCache();
73
83
  }
74
84
  function invalidateTsconfigCaches() {
75
- tsconfigOptionsCache.clear();
85
+ tsconfigResolver.invalidateTsconfigCaches();
76
86
  cachedHost = void 0;
77
87
  cachedHostKey = void 0;
78
88
  }
79
89
  let watchMode = false;
80
90
  let testWatchMode = isTestWatchMode();
81
- let inlineComponentStyles;
82
- let externalComponentStyles;
91
+ function hasViteHmrTransport() {
92
+ return resolvedConfig ? resolvedConfig.server.hmr !== false : true;
93
+ }
94
+ function shouldEnableLiveReload() {
95
+ return !!((isTest ? testWatchMode : watchMode) && pluginOptions.liveReload && hasViteHmrTransport());
96
+ }
97
+ /**
98
+ * Determines whether Angular should externalize component styles.
99
+ *
100
+ * When true, Angular emits style references (hash-based IDs) instead of
101
+ * inlining CSS strings. Vite's resolveId → load → transform pipeline
102
+ * then serves these virtual modules, allowing @tailwindcss/vite to
103
+ * process @reference directives.
104
+ *
105
+ * Required for TWO independent use-cases:
106
+ * 1. HMR — Vite needs external modules for hot replacement
107
+ * 2. A Vite plugin asked for it through `analog.setup()` so its styles
108
+ * pass through Vite's CSS pipeline (e.g. @tailwindcss/vite)
109
+ *
110
+ * In production builds (!watchMode), styles are NOT externalized — they
111
+ * are inlined after preprocessCSS runs eagerly in transformStylesheet.
112
+ */
113
+ function shouldExternalizeStyles() {
114
+ if (!(isTest ? testWatchMode : watchMode)) return false;
115
+ return !!(shouldEnableLiveReload() || externalizeStylesRequested);
116
+ }
117
+ let stylesheetRegistry;
83
118
  const sourceFileCache$1 = new sourceFileCache();
84
- const isTest = process.env.NODE_ENV === "test" || !!process.env["VITEST"];
119
+ const tsSourceFileCache = /* @__PURE__ */ new Map();
120
+ function invalidateSourceFiles(files) {
121
+ for (const file of files) tsSourceFileCache.delete(normalizePath(file));
122
+ sourceFileCache$1.invalidate(files);
123
+ }
85
124
  const isVitestVscode = !!process.env["VITEST_VSCODE"];
86
125
  const isStackBlitz = !!process.versions["webcontainer"];
87
126
  const isAstroIntegration = process.env["ANALOG_ASTRO"] === "true";
@@ -91,9 +130,36 @@ function angular(options) {
91
130
  const templateUrlsResolver = new TemplateUrlsResolver();
92
131
  let outputFile;
93
132
  const outputFiles = /* @__PURE__ */ new Map();
133
+ const normalizeEmitterLookupId = (file) => {
134
+ const normalizedFile = normalizePath(file);
135
+ if (!normalizedFile.startsWith("/@fs/")) return normalizedFile;
136
+ return normalizePath(normalizedFile.slice(4).replace(/^\/([A-Za-z]:\/)/, "$1"));
137
+ };
138
+ const describeEmitMarkers = (content) => ({
139
+ contentLength: content.length,
140
+ hasCmp: content.includes("ɵcmp"),
141
+ hasFac: content.includes("ɵfac"),
142
+ hasProv: content.includes("ɵprov"),
143
+ hasDecorate: content.includes("__decorate"),
144
+ hasMetadata: content.includes("__metadata")
145
+ });
94
146
  const fileEmitter = (file) => {
95
- outputFile?.(file);
96
- return outputFiles.get(normalizePath(file));
147
+ const normalizedFile = normalizeEmitterLookupId(file);
148
+ const hadCachedEmit = outputFiles.has(normalizedFile);
149
+ outputFile?.(normalizedFile);
150
+ const emittedResult = outputFiles.get(normalizedFile);
151
+ debugEmitV("fileEmitter lookup", {
152
+ requestFile: file,
153
+ normalizedFile,
154
+ hadCachedEmit,
155
+ hasOutputFileHook: !!outputFile,
156
+ emitted: !!emittedResult,
157
+ knownOutputCount: outputFiles.size,
158
+ contentLength: emittedResult?.content?.length ?? 0,
159
+ errorCount: emittedResult?.errors?.length ?? 0,
160
+ warningCount: emittedResult?.warnings?.length ?? 0
161
+ });
162
+ return emittedResult;
97
163
  };
98
164
  let initialCompilation = false;
99
165
  const declarationFiles = [];
@@ -101,29 +167,38 @@ function angular(options) {
101
167
  let styleTransform;
102
168
  let pendingCompilation;
103
169
  let compilationLock = Promise.resolve();
104
- let angularCompilation;
105
170
  function angularPlugin() {
106
171
  let isProd = false;
107
- if (angularFullVersion < 19e4 || isTest) pluginOptions.liveReload = false;
108
- if (pluginOptions.useAngularCompilationAPI) {
109
- if (angularFullVersion < 200100) {
110
- pluginOptions.useAngularCompilationAPI = false;
111
- console.warn("[@analogjs/vite-plugin-angular]: The Angular Compilation API is only available with Angular v20.1 and later");
112
- }
172
+ if (angularFullVersion < 19e4 && pluginOptions.liveReload) {
173
+ debugHmr("hmr disabled: Angular version does not support HMR APIs", {
174
+ angularVersion: angularFullVersion,
175
+ isTest
176
+ });
177
+ console.warn("[@analogjs/vite-plugin-angular]: HMR was disabled because Angular v19+ is required for externalRuntimeStyles/_enableHmr support. Detected Angular version: %s.", angularFullVersion);
178
+ pluginOptions.liveReload = false;
179
+ }
180
+ if (isTest) {
181
+ pluginOptions.liveReload = false;
182
+ debugHmr("hmr disabled", {
183
+ angularVersion: angularFullVersion,
184
+ isTest
185
+ });
113
186
  }
114
187
  return {
115
188
  name: "@analogjs/vite-plugin-angular",
189
+ api: { getTsConfigPath: resolveTsConfigPath },
116
190
  async config(config, { command }) {
191
+ activateDeferredDebug(command);
117
192
  watchMode = command === "serve";
118
- isProd = config.mode === "production" || process.env.NODE_ENV === "production";
193
+ isProd = isProdMode(config.mode);
119
194
  tsConfigResolutionContext = {
120
195
  root: config.root || ".",
121
196
  isProd,
122
197
  isLib: !!config?.build?.lib
123
198
  };
124
199
  const preliminaryTsConfigPath = resolveTsConfigPath();
125
- const esbuild = pluginOptions.useAngularCompilationAPI ? void 0 : config.esbuild ?? false;
126
- const oxc = pluginOptions.useAngularCompilationAPI ? void 0 : config.oxc ?? false;
200
+ const esbuild = config.esbuild ?? false;
201
+ const oxc = config.oxc ?? false;
127
202
  const defineOptions = {
128
203
  ngJitMode: "false",
129
204
  ngI18nClosureMode: "false",
@@ -138,7 +213,7 @@ function angular(options) {
138
213
  advancedOptimizations: isProd,
139
214
  jit,
140
215
  incremental: watchMode
141
- })] };
216
+ }, isTest, !isAstroIntegration)] };
142
217
  const esbuildOptions = {
143
218
  plugins: [createCompilerPlugin({
144
219
  tsconfig: preliminaryTsConfigPath,
@@ -152,18 +227,24 @@ function angular(options) {
152
227
  return {
153
228
  [jsTransformConfigKey]: jsTransformConfigValue,
154
229
  optimizeDeps: {
155
- include: ["rxjs/operators", "rxjs"],
230
+ include: [
231
+ "rxjs/operators",
232
+ "rxjs",
233
+ "tslib"
234
+ ],
156
235
  exclude: ["@angular/platform-server"],
157
236
  ...useRolldown ? { rolldownOptions } : { esbuildOptions }
158
- },
159
- resolve: { conditions: ["style", ...config.resolve?.conditions || defaultClientConditions] }
237
+ }
160
238
  };
161
239
  },
162
240
  configResolved(config) {
163
241
  resolvedConfig = config;
164
- if (pluginOptions.useAngularCompilationAPI) {
165
- externalComponentStyles = /* @__PURE__ */ new Map();
166
- inlineComponentStyles = /* @__PURE__ */ new Map();
242
+ if (config.logger?.warnOnce) {
243
+ const originalWarnOnce = config.logger.warnOnce;
244
+ config.logger.warnOnce = (msg, options) => {
245
+ if (typeof msg === "string" && msg.includes("Sourcemap") && msg.includes("node_modules")) return;
246
+ originalWarnOnce(msg, options);
247
+ };
167
248
  }
168
249
  if (!jit) styleTransform = (code, filename) => preprocessCSS(code, filename, config);
169
250
  if (isTest) testWatchMode = !(config.server.watch === null) || config.test?.watch === true || testWatchMode;
@@ -172,30 +253,90 @@ function angular(options) {
172
253
  viteServer = server;
173
254
  const invalidateCompilationOnFsChange = createFsWatcherCacheInvalidator(invalidateFsCaches, invalidateTsconfigCaches, () => performCompilation(resolvedConfig));
174
255
  server.watcher.on("add", invalidateCompilationOnFsChange);
175
- server.watcher.on("unlink", invalidateCompilationOnFsChange);
256
+ server.watcher.on("unlink", (file) => {
257
+ const id = normalizePath(file);
258
+ outputFiles.delete(id);
259
+ tsSourceFileCache.delete(id);
260
+ sourceFileCache$1.invalidate(new Set([id]));
261
+ evictDeletedFileMetadata(file, {
262
+ classNamesMap: classNames,
263
+ fileTransformMap
264
+ });
265
+ return invalidateCompilationOnFsChange();
266
+ });
176
267
  server.watcher.on("change", (file) => {
177
268
  if (file.includes("tsconfig")) invalidateTsconfigCaches();
178
269
  });
179
270
  },
180
271
  async buildStart() {
181
272
  if (!isVitestVscode) {
182
- await performCompilation(resolvedConfig);
273
+ pendingCompilation = performCompilation(this.environment?.config ?? resolvedConfig);
274
+ await pendingCompilation;
183
275
  pendingCompilation = null;
184
276
  initialCompilation = true;
185
277
  }
186
278
  },
279
+ buildEnd() {
280
+ if (watchMode) return;
281
+ nextProgram = void 0;
282
+ cachedHost = void 0;
283
+ cachedHostKey = void 0;
284
+ outputFile = void 0;
285
+ outputFiles.clear();
286
+ fileTransformMap.clear();
287
+ tsSourceFileCache.clear();
288
+ sourceFileCache$1.clear();
289
+ sourceFileCache$1.modifiedFiles.clear();
290
+ sourceFileCache$1.babelFileCache?.clear();
291
+ sourceFileCache$1.typeScriptFileCache?.clear();
292
+ sourceFileCache$1.referencedFiles = void 0;
293
+ tsconfigResolver.invalidateAll();
294
+ releaseCssPreprocessorWorkers();
295
+ },
187
296
  async handleHotUpdate(ctx) {
297
+ if (isIgnoredHmrFile(ctx.file)) {
298
+ debugHmr("ignored file change", { file: ctx.file });
299
+ return [];
300
+ }
188
301
  if (TS_EXT_REGEX.test(ctx.file)) {
189
302
  const [fileId] = ctx.file.split("?");
303
+ debugHmr("TS file changed", {
304
+ file: ctx.file,
305
+ fileId
306
+ });
190
307
  pendingCompilation = performCompilation(resolvedConfig, [fileId]);
191
308
  let result;
192
- if (pluginOptions.liveReload) {
309
+ if (shouldEnableLiveReload()) {
193
310
  await pendingCompilation;
194
311
  pendingCompilation = null;
195
312
  result = fileEmitter(fileId);
313
+ debugHmr("TS file emitted", {
314
+ fileId,
315
+ hmrEligible: !!result?.hmrEligible,
316
+ hasClassName: !!classNames.get(fileId)
317
+ });
318
+ debugHmrV("ts hmr evaluation", {
319
+ file: ctx.file,
320
+ fileId,
321
+ hasResult: !!result,
322
+ hmrEligible: !!result?.hmrEligible,
323
+ hasClassName: !!classNames.get(fileId),
324
+ className: classNames.get(fileId),
325
+ updateCode: result?.hmrUpdateCode ? describeStylesheetContent(result.hmrUpdateCode) : void 0,
326
+ errors: result?.errors?.length ?? 0,
327
+ warnings: result?.warnings?.length ?? 0,
328
+ hint: result?.hmrEligible ? "A TS-side component change, including inline template edits, produced an Angular HMR payload." : "No Angular HMR payload was emitted for this TS change; the change may not affect component template state."
329
+ });
196
330
  }
197
- if (pluginOptions.liveReload && result?.hmrEligible && classNames.get(fileId)) {
331
+ if (shouldEnableLiveReload() && result?.hmrEligible && classNames.get(fileId)) {
198
332
  const relativeFileId = `${normalizePath(relative(process.cwd(), fileId))}@${classNames.get(fileId)}`;
333
+ debugHmr("sending component update", { relativeFileId });
334
+ debugHmrV("ts hmr component update payload", {
335
+ file: ctx.file,
336
+ fileId,
337
+ relativeFileId,
338
+ className: classNames.get(fileId)
339
+ });
199
340
  sendHMRComponentUpdate(ctx.server, relativeFileId);
200
341
  return ctx.modules.map((mod) => {
201
342
  if (mod.id === ctx.file) return markModuleSelfAccepting(mod);
@@ -204,79 +345,260 @@ function angular(options) {
204
345
  }
205
346
  }
206
347
  if (/\.(html|htm|css|less|sass|scss)$/.test(ctx.file)) {
348
+ debugHmr("resource file changed", { file: ctx.file });
207
349
  fileTransformMap.delete(ctx.file.split("?")[0]);
350
+ const fileModules = await getModulesForChangedFile(ctx.server, ctx.file, ctx.modules, stylesheetRegistry);
208
351
  /**
209
352
  * Check to see if this was a direct request
210
353
  * for an external resource (styles, html).
211
354
  */
212
- const isDirect = ctx.modules.find((mod) => ctx.file === mod.file && mod.id?.includes("?direct"));
213
- const isInline = ctx.modules.find((mod) => ctx.file === mod.file && mod.id?.includes("?inline"));
355
+ const isDirect = fileModules.find((mod) => !!mod.id && mod.id.includes("?direct") && isModuleForChangedResource(mod, ctx.file, stylesheetRegistry));
356
+ const isInline = fileModules.find((mod) => !!mod.id && mod.id.includes("?inline") && isModuleForChangedResource(mod, ctx.file, stylesheetRegistry));
357
+ debugHmrV("resource direct/inline detection", {
358
+ file: ctx.file,
359
+ hasDirect: !!isDirect,
360
+ directId: isDirect?.id,
361
+ hasInline: !!isInline,
362
+ inlineId: isInline?.id
363
+ });
214
364
  if (isDirect || isInline) {
215
- if (pluginOptions.liveReload && isDirect?.id && isDirect.file) {
216
- if (isDirect.type === "css" && isComponentStyleSheet(isDirect.id)) {
365
+ if (shouldExternalizeStyles() && isDirect?.id && isDirect.file) {
366
+ const isComponentStyle = isDirect.type === "css" && isComponentStyleSheet(isDirect.id);
367
+ debugHmrV("resource direct branch", {
368
+ file: ctx.file,
369
+ directId: isDirect.id,
370
+ directType: isDirect.type,
371
+ shouldExternalize: shouldExternalizeStyles(),
372
+ isComponentStyle
373
+ });
374
+ if (isComponentStyle) {
217
375
  const { encapsulation } = getComponentStyleSheetMeta(isDirect.id);
218
- if (encapsulation !== "shadow") {
219
- ctx.server.ws.send({
220
- type: "update",
221
- updates: [{
222
- type: "css-update",
223
- timestamp: Date.now(),
224
- path: isDirect.url,
225
- acceptedPath: isDirect.file
226
- }]
376
+ const wrapperModules = await findComponentStylesheetWrapperModules(ctx.server, ctx.file, isDirect, fileModules, stylesheetRegistry);
377
+ const stylesheetDiagnosis = diagnoseComponentStylesheetPipeline(ctx.file, isDirect, stylesheetRegistry, wrapperModules, pluginOptions.stylePreprocessor);
378
+ debugStylesV("HMR: component stylesheet changed", {
379
+ file: isDirect.file,
380
+ encapsulation
381
+ });
382
+ debugHmrV("component stylesheet wrapper modules", {
383
+ file: ctx.file,
384
+ wrapperCount: wrapperModules.length,
385
+ wrapperIds: wrapperModules.map((mod) => mod.id),
386
+ availableModuleIds: fileModules.map((mod) => mod.id)
387
+ });
388
+ debugHmrV("component stylesheet pipeline diagnosis", stylesheetDiagnosis);
389
+ ctx.server.moduleGraph.invalidateModule(isDirect);
390
+ debugHmrV("component stylesheet direct module invalidated", {
391
+ file: ctx.file,
392
+ directModuleId: isDirect.id,
393
+ directModuleUrl: isDirect.url,
394
+ reason: "Ensure Vite drops stale direct CSS transform results before wrapper or fallback handling continues."
395
+ });
396
+ const trackedWrapperRequestIds = stylesheetDiagnosis.trackedRequestIds.filter((id) => id.includes("?ngcomp="));
397
+ if (encapsulation !== "shadow" && (wrapperModules.length > 0 || trackedWrapperRequestIds.length > 0)) {
398
+ wrapperModules.forEach((mod) => ctx.server.moduleGraph.invalidateModule(mod));
399
+ debugHmrV("sending css-update for component stylesheet", {
400
+ file: ctx.file,
401
+ path: isDirect.url,
402
+ acceptedPath: isDirect.file,
403
+ wrapperCount: wrapperModules.length,
404
+ trackedWrapperRequestIds,
405
+ hint: wrapperModules.length > 0 ? "Live wrapper modules were found and invalidated before sending the CSS update." : "No live wrapper ModuleNode was available, but the wrapper request id is already tracked, so Analog is trusting the browser-visible wrapper identity and patching the direct stylesheet instead of forcing a reload."
227
406
  });
228
- return ctx.modules.filter((mod) => {
407
+ sendCssUpdate(ctx.server, {
408
+ path: isDirect.url,
409
+ acceptedPath: isDirect.file
410
+ });
411
+ logComponentStylesheetHmrOutcome({
412
+ file: ctx.file,
413
+ encapsulation,
414
+ diagnosis: stylesheetDiagnosis,
415
+ outcome: "css-update",
416
+ directModuleId: isDirect.id,
417
+ wrapperIds: wrapperModules.map((mod) => mod.id)
418
+ });
419
+ return union(fileModules.filter((mod) => {
229
420
  return mod.file !== ctx.file || mod.id !== isDirect.id;
230
421
  }).map((mod) => {
231
422
  if (mod.file === ctx.file) return markModuleSelfAccepting(mod);
232
423
  return mod;
233
- });
424
+ }), wrapperModules.map((mod) => markModuleSelfAccepting(mod)));
234
425
  }
426
+ debugHmrV("component stylesheet hmr fallback: full reload", {
427
+ file: ctx.file,
428
+ encapsulation,
429
+ reason: trackedWrapperRequestIds.length === 0 ? "missing-wrapper-module" : encapsulation === "shadow" ? "shadow-encapsulation" : "tracked-wrapper-still-not-patchable",
430
+ directId: isDirect.id,
431
+ trackedRequestIds: stylesheetRegistry?.getRequestIdsForSource(ctx.file) ?? []
432
+ });
433
+ logComponentStylesheetHmrOutcome({
434
+ file: ctx.file,
435
+ encapsulation,
436
+ diagnosis: stylesheetDiagnosis,
437
+ outcome: "full-reload",
438
+ directModuleId: isDirect.id,
439
+ wrapperIds: wrapperModules.map((mod) => mod.id)
440
+ });
441
+ sendFullReload(ctx.server, {
442
+ file: ctx.file,
443
+ encapsulation,
444
+ reason: wrapperModules.length === 0 ? "missing-wrapper-module" : "shadow-encapsulation",
445
+ directId: isDirect.id,
446
+ trackedRequestIds: stylesheetRegistry?.getRequestIdsForSource(ctx.file) ?? []
447
+ });
448
+ return [];
449
+ }
450
+ }
451
+ return fileModules;
452
+ }
453
+ if (shouldEnableLiveReload() && /\.(html|htm)$/.test(ctx.file) && fileModules.length === 0) {
454
+ const ownerModules = findTemplateOwnerModules(ctx.server, ctx.file);
455
+ debugHmrV("template owner lookup", {
456
+ file: ctx.file,
457
+ ownerCount: ownerModules.length,
458
+ ownerIds: ownerModules.map((mod) => mod.id),
459
+ hint: ownerModules.length > 0 ? "The external template has candidate TS owner modules that can be recompiled for HMR." : "No TS owner modules were visible for this external template change; HMR will fall through to the generic importer path."
460
+ });
461
+ if (ownerModules.length > 0) {
462
+ const ownerIds = ownerModules.map((mod) => mod.id).filter(Boolean);
463
+ ownerModules.forEach((mod) => ctx.server.moduleGraph.invalidateModule(mod));
464
+ pendingCompilation = performCompilation(resolvedConfig, ownerIds);
465
+ await pendingCompilation;
466
+ pendingCompilation = null;
467
+ const updates = ownerIds.filter((id) => classNames.get(id));
468
+ debugHmrV("template owner recompilation result", {
469
+ file: ctx.file,
470
+ ownerIds,
471
+ updates,
472
+ updateClassNames: updates.map((id) => ({
473
+ id,
474
+ className: classNames.get(id)
475
+ })),
476
+ hint: updates.length > 0 ? "External template recompilation produced Angular component update targets." : "External template recompilation completed, but no Angular component update targets were surfaced."
477
+ });
478
+ if (updates.length > 0) {
479
+ debugHmr("template owner module invalidation", {
480
+ file: ctx.file,
481
+ ownerIds,
482
+ updateCount: updates.length
483
+ });
484
+ updates.forEach((updateId) => {
485
+ const relativeFileId = `${normalizePath(relative(process.cwd(), updateId))}@${classNames.get(updateId)}`;
486
+ sendHMRComponentUpdate(ctx.server, relativeFileId);
487
+ });
488
+ return ownerModules.map((mod) => markModuleSelfAccepting(mod));
235
489
  }
236
490
  }
237
- return ctx.modules;
238
491
  }
239
492
  const mods = [];
240
493
  const updates = [];
241
- ctx.modules.forEach((mod) => {
494
+ fileModules.forEach((mod) => {
242
495
  mod.importers.forEach((imp) => {
243
496
  ctx.server.moduleGraph.invalidateModule(imp);
244
- if (pluginOptions.liveReload && classNames.get(imp.id)) updates.push(imp.id);
497
+ if (shouldExternalizeStyles() && classNames.get(imp.id)) updates.push(imp.id);
245
498
  else mods.push(imp);
246
499
  });
247
500
  });
248
- pendingCompilation = performCompilation(resolvedConfig, [...mods.map((mod) => mod.id), ...updates]);
501
+ debugHmrV("resource importer analysis", {
502
+ file: ctx.file,
503
+ fileModuleCount: fileModules.length,
504
+ importerCount: fileModules.reduce((count, mod) => count + mod.importers.size, 0),
505
+ updates,
506
+ mods: mods.map((mod) => mod.id)
507
+ });
508
+ pendingCompilation = performCompilation(resolvedConfig, [...mods.map((mod) => mod.id).filter(Boolean), ...updates]);
249
509
  if (updates.length > 0) {
250
510
  await pendingCompilation;
251
511
  pendingCompilation = null;
512
+ debugHmr("resource importer component updates", {
513
+ file: ctx.file,
514
+ updateCount: updates.length
515
+ });
252
516
  updates.forEach((updateId) => {
253
517
  const impRelativeFileId = `${normalizePath(relative(process.cwd(), updateId))}@${classNames.get(updateId)}`;
254
518
  sendHMRComponentUpdate(ctx.server, impRelativeFileId);
255
519
  });
256
- return ctx.modules.map((mod) => {
520
+ return fileModules.map((mod) => {
257
521
  if (mod.id === ctx.file) return markModuleSelfAccepting(mod);
258
522
  return mod;
259
523
  });
260
524
  }
261
525
  return mods;
262
526
  }
527
+ debugHmr("full reload — unrecognized file type", { file: ctx.file });
263
528
  classNames.clear();
264
529
  return ctx.modules;
265
530
  },
266
531
  resolveId(id, importer) {
532
+ if (id.startsWith("virtual:@analogjs/vite-plugin-angular:raw:")) return `\0${id}`;
267
533
  if (jit && id.startsWith("angular:jit:")) {
268
- const path = id.split(";")[1];
269
- return `${normalizePath(resolve(dirname(importer), path))}?${id.includes(":style") ? "inline" : "raw"}`;
534
+ const filePath = normalizePath(resolve(dirname(importer), id.split(";")[1]));
535
+ if (id.includes(":style")) {
536
+ markStylePathSafe(resolvedConfig, filePath);
537
+ return filePath + "?inline";
538
+ }
539
+ return toVirtualRawId(filePath);
540
+ }
541
+ const rawRewrite = rewriteHtmlRawImport(id, importer);
542
+ if (rawRewrite) return rawRewrite;
543
+ if (/\.(css|scss|sass|less)\?inline$/.test(id) && importer) {
544
+ const filePath = id.split("?")[0];
545
+ const resolved = isAbsolute(filePath) ? normalizePath(filePath) : normalizePath(resolve(dirname(importer), filePath));
546
+ markStylePathSafe(resolvedConfig, resolved);
547
+ return resolved + "?inline";
270
548
  }
271
549
  if (isComponentStyleSheet(id)) {
272
- const componentStyles = externalComponentStyles?.get(getFilenameFromPath(id));
273
- if (componentStyles) return componentStyles + new URL(id, "http://localhost").search;
550
+ const filename = getFilenameFromPath(id);
551
+ const search = new URL(id, "http://localhost").search;
552
+ const servedSourcePath = stylesheetRegistry?.getServedSourcePath(filename);
553
+ if (servedSourcePath) {
554
+ debugStylesV("resolveId: mapped served stylesheet to source", {
555
+ filename,
556
+ resolvedPath: servedSourcePath
557
+ });
558
+ return servedSourcePath + search;
559
+ }
560
+ if (stylesheetRegistry?.hasServed(filename)) {
561
+ debugStylesV("resolveId: kept preprocessed ID", { filename });
562
+ return id;
563
+ }
564
+ const componentStyles = stylesheetRegistry?.resolveExternalSource(filename);
565
+ if (componentStyles) {
566
+ debugStylesV("resolveId: mapped external stylesheet", {
567
+ filename,
568
+ resolvedPath: componentStyles
569
+ });
570
+ return componentStyles + new URL(id, "http://localhost").search;
571
+ }
572
+ debugStyles("resolveId: component stylesheet NOT FOUND in either map", {
573
+ filename,
574
+ inlineMapSize: stylesheetRegistry?.servedCount ?? 0,
575
+ externalMapSize: stylesheetRegistry?.externalCount ?? 0
576
+ });
274
577
  }
275
578
  },
276
579
  async load(id) {
580
+ const rawModule = await loadVirtualRawModule(this, id);
581
+ if (rawModule !== void 0) return rawModule;
582
+ if (/\.(css|scss|sass|less)\?inline$/.test(id)) markStylePathSafe(resolvedConfig, id.split("?")[0]);
277
583
  if (isComponentStyleSheet(id)) {
278
- const componentStyles = inlineComponentStyles?.get(getFilenameFromPath(id));
279
- if (componentStyles) return componentStyles;
584
+ const filename = getFilenameFromPath(id);
585
+ const componentStyles = stylesheetRegistry?.getServedContent(filename);
586
+ if (componentStyles) {
587
+ stylesheetRegistry?.registerActiveRequest(id);
588
+ debugHmrV("stylesheet active request registered", {
589
+ requestId: id,
590
+ filename,
591
+ sourcePath: stylesheetRegistry?.resolveExternalSource(filename) ?? stylesheetRegistry?.resolveExternalSource(filename.replace(/^\//, "")) ?? stylesheetRegistry?.getServedSourcePath(filename) ?? stylesheetRegistry?.getServedSourcePath(filename.replace(/^\//, "")),
592
+ trackedRequestIds: stylesheetRegistry?.getRequestIdsForSource(stylesheetRegistry?.resolveExternalSource(filename) ?? stylesheetRegistry?.resolveExternalSource(filename.replace(/^\//, "")) ?? stylesheetRegistry?.getServedSourcePath(filename) ?? stylesheetRegistry?.getServedSourcePath(filename.replace(/^\//, "")) ?? "") ?? []
593
+ });
594
+ debugStylesV("load: served inline component stylesheet", {
595
+ filename,
596
+ length: componentStyles.length,
597
+ requestId: id,
598
+ ...describeStylesheetContent(componentStyles)
599
+ });
600
+ return componentStyles;
601
+ }
280
602
  }
281
603
  },
282
604
  transform: {
@@ -289,27 +611,7 @@ function angular(options) {
289
611
  ]
290
612
  } },
291
613
  async handler(code, id) {
292
- /**
293
- * Check for options.transformFilter
294
- */
295
- if (options?.transformFilter && !(options?.transformFilter(code, id) ?? true)) return;
296
- if (pluginOptions.useAngularCompilationAPI) {
297
- if (!/(Component|Directive|Pipe|Injectable|NgModule)\(/.test(code)) return;
298
- }
299
- /**
300
- * Skip transforming content files
301
- */
302
- if (id.includes("?") && id.includes("analog-content-")) return;
303
- /**
304
- * Encapsulate component stylesheets that use emulated encapsulation
305
- */
306
- if (pluginOptions.liveReload && isComponentStyleSheet(id)) {
307
- const { encapsulation, componentId } = getComponentStyleSheetMeta(id);
308
- if (encapsulation === "emulated" && componentId) return {
309
- code: ngCompiler.encapsulateStyle(code, componentId),
310
- map: null
311
- };
312
- }
614
+ if (transformFilter && !transformFilter(code, id)) return;
313
615
  if (id.includes(".ts?")) id = id.replace(/\?(.*)/, "");
314
616
  fileTransformMap.set(id, code);
315
617
  /**
@@ -318,19 +620,25 @@ function angular(options) {
318
620
  */
319
621
  if (isTest) {
320
622
  if (isVitestVscode && !initialCompilation) {
321
- pendingCompilation = performCompilation(resolvedConfig);
623
+ pendingCompilation = performCompilation(this.environment?.config ?? resolvedConfig);
322
624
  initialCompilation = true;
323
625
  }
324
626
  const tsMod = viteServer?.moduleGraph.getModuleById(id);
325
627
  if (tsMod) {
326
628
  const invalidated = tsMod.lastInvalidationTimestamp;
327
- if (testWatchMode && invalidated) pendingCompilation = performCompilation(resolvedConfig, [id]);
629
+ if (testWatchMode && invalidated) pendingCompilation = performCompilation(this.environment?.config ?? resolvedConfig, [id]);
328
630
  }
329
631
  }
330
632
  const hasComponent = code.includes("@Component");
331
- const templateUrls = hasComponent ? templateUrlsResolver.resolve(code, id) : [];
332
- const styleUrls = hasComponent ? styleUrlsResolver.resolve(code, id) : [];
333
- if (hasComponent && watchMode) for (const urlSet of [...templateUrls, ...styleUrls]) {
633
+ debugCompilerV("transform", {
634
+ id,
635
+ codeLength: code.length,
636
+ hasComponent
637
+ });
638
+ const resolveResourceUrls = hasComponent && (watchMode || jit);
639
+ const templateUrls = resolveResourceUrls ? templateUrlsResolver.resolve(code, id) : [];
640
+ const styleUrls = resolveResourceUrls ? styleUrlsResolver.resolve(code, id) : [];
641
+ if (resolveResourceUrls && watchMode) for (const urlSet of [...templateUrls, ...styleUrls]) {
334
642
  const [, absoluteFileUrl] = urlSet.split("|");
335
643
  this.addWatchFile(absoluteFileUrl);
336
644
  }
@@ -339,23 +647,58 @@ function angular(options) {
339
647
  pendingCompilation = null;
340
648
  }
341
649
  const typescriptResult = fileEmitter(id);
342
- if (typescriptResult?.warnings && typescriptResult?.warnings.length > 0) this.warn(`${typescriptResult.warnings.join("\n")}`);
343
- if (typescriptResult?.errors && typescriptResult?.errors.length > 0) this.error(`${typescriptResult.errors.join("\n")}`);
344
- let data = typescriptResult?.content ?? "";
650
+ if (!typescriptResult) {
651
+ debugCompilerV("transform skip (file not emitted by Angular)", { id });
652
+ const isAngular = !id.includes("@ng/component") && /(Component|Directive|Pipe|Injectable|NgModule)\(/.test(code);
653
+ debugEmit("transform emit miss", {
654
+ id,
655
+ normalizedId: normalizeEmitterLookupId(id),
656
+ knownOutputCount: outputFiles.size,
657
+ hasOutputFileHook: !!outputFile,
658
+ isAngular
659
+ });
660
+ if (isAngular) this.warn(`[@analogjs/vite-plugin-angular]: "${id}" contains Angular decorators but is not in the TypeScript program. Ensure it is included in your tsconfig.`);
661
+ return;
662
+ }
663
+ if (typescriptResult.warnings && typescriptResult.warnings.length > 0) this.warn(`${typescriptResult.warnings.join("\n")}`);
664
+ if (typescriptResult.errors && typescriptResult.errors.length > 0) this.error(`${typescriptResult.errors.join("\n")}`);
665
+ let data = typescriptResult.content ?? "";
666
+ debugEmitV("transform emit hit", {
667
+ id,
668
+ normalizedId: normalizeEmitterLookupId(id),
669
+ ...describeEmitMarkers(data),
670
+ errorCount: typescriptResult.errors?.length ?? 0,
671
+ warningCount: typescriptResult.warnings?.length ?? 0
672
+ });
345
673
  if (jit && data.includes("angular:jit:")) {
346
- data = data.replace(/angular:jit:style:inline;/g, "virtual:angular:jit:style:inline;");
674
+ data = data.replace(/angular:jit:style:inline;([A-Za-z0-9+/=]*)/g, (_match, encodedStyles) => toJitInlineStyleId(encodedStyles));
347
675
  templateUrls.forEach((templateUrlSet) => {
348
676
  const [templateFile, resolvedTemplateUrl] = templateUrlSet.split("|");
349
- data = data.replace(`angular:jit:template:file;${templateFile}`, `${resolvedTemplateUrl}?raw`);
677
+ data = data.replace(`angular:jit:template:file;${templateFile}`, toVirtualRawId(resolvedTemplateUrl));
350
678
  });
351
679
  styleUrls.forEach((styleUrlSet) => {
352
680
  const [styleFile, resolvedStyleUrl] = styleUrlSet.split("|");
353
- data = data.replace(`angular:jit:style:file;${styleFile}`, `${resolvedStyleUrl}?inline`);
681
+ markStylePathSafe(resolvedConfig, resolvedStyleUrl);
682
+ data = data.replace(`angular:jit:style:file;${styleFile}`, resolvedStyleUrl + "?inline");
354
683
  });
355
684
  }
685
+ if (data.includes("HmrLoad")) {
686
+ const hasMetaUrl = data.includes("getReplaceMetadataURL");
687
+ debugHmrV("vite-ignore injection", {
688
+ id,
689
+ dataLength: data.length,
690
+ hasMetaUrl
691
+ });
692
+ if (hasMetaUrl) {
693
+ const patched = injectViteIgnoreForHmrMetadata(data);
694
+ if (patched !== data && !patched.includes("@vite-ignore")) debugHmrV("vite-ignore regex fallback", { id });
695
+ data = patched;
696
+ }
697
+ }
698
+ if (typescriptResult.map) data = data.replace(/\s*\/\/# sourceMappingURL=[^\r\n]*\s*$/, "");
356
699
  return {
357
700
  code: data,
358
- map: null
701
+ map: typescriptResult.map ? normalizeSourceMapSources(typescriptResult.map, id) : null
359
702
  };
360
703
  }
361
704
  },
@@ -364,19 +707,47 @@ function angular(options) {
364
707
  mkdirSync(declarationFileDir, { recursive: true });
365
708
  writeFileSync(declarationPath, data, "utf-8");
366
709
  });
367
- angularCompilation?.close?.();
368
- angularCompilation = void 0;
710
+ declarationFiles.length = 0;
711
+ if (!watchMode) releaseCssPreprocessorWorkers();
369
712
  }
370
713
  };
371
714
  }
715
+ function normalizeSourceMapSources(map, id) {
716
+ const sourceMap = JSON.parse(map);
717
+ const sourceDirectory = dirname(id);
718
+ const sourceRoot = sourceMap.sourceRoot ?? "";
719
+ sourceMap.sources = sourceMap.sources?.map((source) => {
720
+ if (isAbsolute(source) || /^[a-z][a-z\d+.-]*:/i.test(source)) return normalizePath(source);
721
+ return normalizePath(resolve(sourceDirectory, sourceRoot, source));
722
+ });
723
+ delete sourceMap.sourceRoot;
724
+ return JSON.stringify(sourceMap);
725
+ }
726
+ const compilationPlugin = pluginOptions.fastCompile ? fastCompilePlugin({
727
+ tsconfigGetter: pluginOptions.tsconfigGetter,
728
+ workspaceRoot: pluginOptions.workspaceRoot,
729
+ inlineStylesExtension: pluginOptions.inlineStylesExtension,
730
+ jit,
731
+ liveReload: pluginOptions.liveReload,
732
+ supportedBrowsers: pluginOptions.supportedBrowsers,
733
+ isTest,
734
+ isAstroIntegration,
735
+ fastCompileMode: pluginOptions.fastCompileMode
736
+ }) : angularPlugin();
372
737
  return [
738
+ cssExtensionStyleResolverPlugin(),
373
739
  replaceFiles(pluginOptions.fileReplacements, pluginOptions.workspaceRoot),
374
- angularPlugin(),
740
+ virtualModulesPlugin({ jit }),
375
741
  pluginOptions.liveReload && liveReloadPlugin({
376
742
  classNames,
377
743
  fileEmitter
378
744
  }),
379
- ...isTest && !isStackBlitz ? angularVitestPlugins() : [],
745
+ compilationPlugin,
746
+ !pluginOptions.fastCompile && pluginOptions.liveReload && liveReloadPlugin({
747
+ classNames,
748
+ fileEmitter
749
+ }),
750
+ ...isTest && !isStackBlitz ? angularVitestPlugins((id) => outputFiles.get(normalizePath(id))?.map) : [],
380
751
  jit && jitPlugin({ inlineStylesExtension: pluginOptions.inlineStylesExtension }),
381
752
  buildOptimizerPlugin({
382
753
  supportedBrowsers: pluginOptions.supportedBrowsers,
@@ -384,116 +755,12 @@ function angular(options) {
384
755
  }),
385
756
  routerPlugin(),
386
757
  angularFullVersion < 190004 && pendingTasksPlugin(),
387
- nxFolderPlugin()
758
+ nxFolderPlugin(),
759
+ encapsulationPlugin()
388
760
  ].filter(Boolean);
389
- function findIncludes() {
390
- const workspaceRoot = normalizePath(resolve(pluginOptions.workspaceRoot));
391
- return globSync([...pluginOptions.include.map((glob) => `${workspaceRoot}${glob}`)], {
392
- dot: true,
393
- absolute: true
394
- });
395
- }
396
- function createTsConfigGetter(tsconfigOrGetter) {
397
- if (typeof tsconfigOrGetter === "function") return tsconfigOrGetter;
398
- return () => tsconfigOrGetter || "";
399
- }
400
- function getTsConfigPath(root, tsconfig, isProd, isTest, isLib) {
401
- if (tsconfig && isAbsolute(tsconfig)) {
402
- if (!existsSync(tsconfig)) console.error(`[@analogjs/vite-plugin-angular]: Unable to resolve tsconfig at ${tsconfig}. This causes compilation issues. Check the path or set the "tsconfig" property with an absolute path.`);
403
- return tsconfig;
404
- }
405
- let tsconfigFilePath = "./tsconfig.app.json";
406
- if (isLib) tsconfigFilePath = isProd ? "./tsconfig.lib.prod.json" : "./tsconfig.lib.json";
407
- if (isTest) tsconfigFilePath = "./tsconfig.spec.json";
408
- if (tsconfig) tsconfigFilePath = tsconfig;
409
- const resolvedPath = resolve(root, tsconfigFilePath);
410
- if (!existsSync(resolvedPath)) console.error(`[@analogjs/vite-plugin-angular]: Unable to resolve tsconfig at ${resolvedPath}. This causes compilation issues. Check the path or set the "tsconfig" property with an absolute path.`);
411
- return resolvedPath;
412
- }
413
761
  function resolveTsConfigPath() {
414
762
  const tsconfigValue = pluginOptions.tsconfigGetter();
415
- return getTsConfigPath(tsConfigResolutionContext.root, tsconfigValue, tsConfigResolutionContext.isProd, isTest, tsConfigResolutionContext.isLib);
416
- }
417
- /**
418
- * Perform compilation using Angular's private Compilation API.
419
- *
420
- * Key differences from the standard `performCompilation` path:
421
- * 1. The compilation instance is reused across rebuilds (nullish-coalescing
422
- * assignment below) so Angular retains prior state and can diff it to
423
- * produce `templateUpdates` for HMR.
424
- * 2. `ids` (modified files) are forwarded to both the source-file cache and
425
- * `angularCompilation.update()` so that incremental re-analysis is
426
- * scoped to what actually changed.
427
- * 3. `fileReplacements` are converted and passed into Angular's host via
428
- * `toAngularCompilationFileReplacements`.
429
- * 4. `templateUpdates` from the compilation result are mapped back to
430
- * file-level HMR metadata (`hmrUpdateCode`, `hmrEligible`, `classNames`).
431
- */
432
- async function performAngularCompilation(config, ids) {
433
- angularCompilation ??= await createAngularCompilation(!!pluginOptions.jit, false);
434
- const modifiedFiles = ids?.length ? new Set(ids.map((file) => normalizePath(file))) : void 0;
435
- if (modifiedFiles?.size) sourceFileCache$1.invalidate(modifiedFiles);
436
- if (modifiedFiles?.size && angularCompilation.update) await angularCompilation.update(modifiedFiles);
437
- const resolvedTsConfigPath = resolveTsConfigPath();
438
- const compilationResult = await angularCompilation.initialize(resolvedTsConfigPath, {
439
- fileReplacements: toAngularCompilationFileReplacements(pluginOptions.fileReplacements, pluginOptions.workspaceRoot),
440
- modifiedFiles,
441
- async transformStylesheet(data, containingFile, resourceFile, order, className) {
442
- if (pluginOptions.liveReload) {
443
- const filename = createHash("sha256").update(containingFile).update(className).update(String(order)).update(data).digest("hex") + "." + pluginOptions.inlineStylesExtension;
444
- inlineComponentStyles.set(filename, data);
445
- return filename;
446
- }
447
- const filename = resourceFile ?? containingFile.replace(".ts", `.${options?.inlineStylesExtension}`);
448
- let stylesheetResult;
449
- try {
450
- stylesheetResult = await preprocessCSS(data, `${filename}?direct`, resolvedConfig);
451
- } catch (e) {
452
- console.error(`${e}`);
453
- }
454
- return stylesheetResult?.code || "";
455
- },
456
- processWebWorker(workerFile, containingFile) {
457
- return "";
458
- }
459
- }, (tsCompilerOptions) => {
460
- if (pluginOptions.liveReload && watchMode) {
461
- tsCompilerOptions["_enableHmr"] = true;
462
- tsCompilerOptions["externalRuntimeStyles"] = true;
463
- tsCompilerOptions["supportTestBed"] = true;
464
- }
465
- if (tsCompilerOptions.compilationMode === "partial") {
466
- tsCompilerOptions["supportTestBed"] = true;
467
- tsCompilerOptions["supportJitMode"] = true;
468
- }
469
- if (!isTest && config.build?.lib) {
470
- tsCompilerOptions["declaration"] = true;
471
- tsCompilerOptions["declarationMap"] = watchMode;
472
- tsCompilerOptions["inlineSources"] = true;
473
- }
474
- if (isTest) tsCompilerOptions["supportTestBed"] = true;
475
- return tsCompilerOptions;
476
- });
477
- compilationResult.externalStylesheets?.forEach((value, key) => {
478
- externalComponentStyles?.set(`${value}.css`, key);
479
- });
480
- const diagnostics = await angularCompilation.diagnoseFiles(pluginOptions.disableTypeChecking ? DiagnosticModes.All & ~DiagnosticModes.Semantic : DiagnosticModes.All);
481
- const errors = diagnostics.errors?.length ? diagnostics.errors : [];
482
- const warnings = diagnostics.warnings?.length ? diagnostics.warnings : [];
483
- const templateUpdates = mapTemplateUpdatesToFiles(compilationResult.templateUpdates);
484
- for (const file of await angularCompilation.emitAffectedFiles()) {
485
- const normalizedFilename = normalizePath(file.filename);
486
- const templateUpdate = templateUpdates.get(normalizedFilename);
487
- if (templateUpdate) classNames.set(normalizedFilename, templateUpdate.className);
488
- outputFiles.set(normalizedFilename, {
489
- content: file.contents,
490
- dependencies: [],
491
- errors: errors.map((error) => error.text || ""),
492
- warnings: warnings.map((warning) => warning.text || ""),
493
- hmrUpdateCode: templateUpdate?.code,
494
- hmrEligible: !!templateUpdate?.code
495
- });
496
- }
763
+ return getTsConfigPath(tsConfigResolutionContext.root, tsconfigValue, tsConfigResolutionContext.isProd, isTest, tsConfigResolutionContext.isLib, pluginOptions.workspaceRoot);
497
764
  }
498
765
  async function performCompilation(config, ids) {
499
766
  let resolve;
@@ -503,6 +770,12 @@ function angular(options) {
503
770
  });
504
771
  try {
505
772
  await previousLock;
773
+ const integrations = await discoverAnalogIntegrations(config);
774
+ pluginOptions.stylePreprocessor = integrations.stylePreprocessor;
775
+ externalizeStylesRequested = integrations.externalizeStyles;
776
+ configureStylesheetRegistry = integrations.configureStylesheetRegistry;
777
+ transformFilter = integrations.transformFilter;
778
+ tsconfigResolver.setIntegrationIncludes(integrations.include);
506
779
  await _doPerformCompilation(config, ids);
507
780
  } finally {
508
781
  resolve();
@@ -513,54 +786,27 @@ function angular(options) {
513
786
  * It should not be called concurrently. Use `performCompilation` which wraps this method in a lock to ensure only one compilation runs at a time.
514
787
  */
515
788
  async function _doPerformCompilation(config, ids) {
516
- if (pluginOptions.useAngularCompilationAPI) {
517
- await performAngularCompilation(config, ids);
518
- return;
519
- }
789
+ if (!jit) styleTransform = (code, filename) => preprocessCSS(code, filename, config);
520
790
  const isProd = config.mode === "production";
521
- const modifiedFiles = new Set(ids ?? []);
522
- sourceFileCache$1.invalidate(modifiedFiles);
791
+ invalidateSourceFiles(new Set(ids ?? []));
523
792
  if (ids?.length) for (const id of ids || []) fileTransformMap.delete(id);
524
- if (pluginOptions.include.length > 0 && includeCache.length === 0) includeCache = findIncludes();
525
793
  const resolvedTsConfigPath = resolveTsConfigPath();
526
- const tsconfigKey = [
527
- resolvedTsConfigPath,
528
- isProd ? "prod" : "dev",
529
- isTest ? "test" : "app",
530
- config.build?.lib ? "lib" : "nolib"
531
- ].join("|");
532
- let cached = tsconfigOptionsCache.get(tsconfigKey);
533
- if (!cached) {
534
- const read = compilerCli.readConfiguration(resolvedTsConfigPath, {
535
- suppressOutputPathCheck: true,
536
- outDir: void 0,
537
- sourceMap: false,
538
- inlineSourceMap: !isProd,
539
- inlineSources: !isProd,
540
- declaration: false,
541
- declarationMap: false,
542
- allowEmptyCodegenFiles: false,
543
- annotationsAs: "decorators",
544
- enableResourceInlining: false,
545
- noEmitOnError: false,
546
- mapRoot: void 0,
547
- sourceRoot: void 0,
548
- supportTestBed: false,
549
- supportJitMode: false
550
- });
551
- cached = {
552
- options: read.options,
553
- rootNames: read.rootNames
554
- };
555
- tsconfigOptionsCache.set(tsconfigKey, cached);
556
- }
794
+ const cached = tsconfigResolver.getCachedTsconfigOptions(resolvedTsConfigPath, config);
557
795
  const tsCompilerOptions = { ...cached.options };
558
796
  let rootNames = [...cached.rootNames];
559
- if (pluginOptions.liveReload && watchMode) {
797
+ if (shouldExternalizeStyles()) tsCompilerOptions["externalRuntimeStyles"] = true;
798
+ if (shouldEnableLiveReload()) {
560
799
  tsCompilerOptions["_enableHmr"] = true;
561
- tsCompilerOptions["externalRuntimeStyles"] = true;
562
800
  tsCompilerOptions["supportTestBed"] = true;
563
801
  }
802
+ debugCompiler("tsCompilerOptions (NgtscProgram path)", {
803
+ liveReload: pluginOptions.liveReload,
804
+ viteHmr: hasViteHmrTransport(),
805
+ externalizeStylesRequested,
806
+ shouldExternalize: shouldExternalizeStyles(),
807
+ externalRuntimeStyles: !!tsCompilerOptions["externalRuntimeStyles"],
808
+ hmrEnabled: !!tsCompilerOptions["_enableHmr"]
809
+ });
564
810
  if (tsCompilerOptions["compilationMode"] === "partial") {
565
811
  tsCompilerOptions["supportTestBed"] = true;
566
812
  tsCompilerOptions["supportJitMode"] = true;
@@ -572,11 +818,7 @@ function angular(options) {
572
818
  }
573
819
  if (isTest) tsCompilerOptions["supportTestBed"] = true;
574
820
  const replacements = pluginOptions.fileReplacements.map((rp) => join(pluginOptions.workspaceRoot, rp.ssr || rp.with));
575
- rootNames = [...new Set([
576
- ...rootNames,
577
- ...includeCache,
578
- ...replacements
579
- ])];
821
+ rootNames = union(rootNames, tsconfigResolver.ensureIncludeCache(), replacements);
580
822
  const hostKey = JSON.stringify(tsCompilerOptions);
581
823
  let host;
582
824
  if (cachedHost && cachedHostKey === hostKey) host = cachedHost;
@@ -592,17 +834,19 @@ function angular(options) {
592
834
  });
593
835
  cachedHost = host;
594
836
  cachedHostKey = hostKey;
595
- if (watchMode) augmentHostWithCaching(host, sourceFileCache$1);
837
+ if (watchMode) augmentHostWithCaching(host, tsSourceFileCache);
596
838
  }
597
839
  if (!jit) {
598
- inlineComponentStyles = tsCompilerOptions["externalRuntimeStyles"] ? /* @__PURE__ */ new Map() : void 0;
599
- externalComponentStyles = tsCompilerOptions["externalRuntimeStyles"] ? /* @__PURE__ */ new Map() : void 0;
840
+ const externalizeStyles = !!tsCompilerOptions["externalRuntimeStyles"];
841
+ stylesheetRegistry = externalizeStyles ? new AnalogStylesheetRegistry() : void 0;
842
+ if (stylesheetRegistry) configureStylesheetRegistry?.(stylesheetRegistry, { workspaceRoot: pluginOptions.workspaceRoot });
843
+ debugStyles("stylesheet registry initialized (NgtscProgram path)", { externalizeStyles });
600
844
  augmentHostWithResources(host, styleTransform, {
601
845
  inlineStylesExtension: pluginOptions.inlineStylesExtension,
602
846
  isProd,
603
- inlineComponentStyles,
604
- externalComponentStyles,
605
- sourceFileCache: sourceFileCache$1
847
+ stylesheetRegistry,
848
+ sourceFileCache: sourceFileCache$1,
849
+ stylePreprocessor: pluginOptions.stylePreprocessor
606
850
  });
607
851
  }
608
852
  /**
@@ -631,9 +875,19 @@ function angular(options) {
631
875
  const writeFileCallback = (_filename, content, _a, _b, sourceFiles) => {
632
876
  if (!sourceFiles?.length) return;
633
877
  const filename = normalizePath(sourceFiles[0].fileName);
634
- if (filename.includes("ngtypecheck.ts") || filename.includes(".d.")) return;
878
+ if (filename.includes("ngtypecheck.ts") || filename.includes(".d.") || _filename.endsWith(".d.ts") || _filename.endsWith(".d.mts") || _filename.endsWith(".d.cts")) return;
879
+ if (/\.[cm]?js\.map$/.test(_filename)) {
880
+ const existing = outputFiles.get(filename);
881
+ outputFiles.set(filename, {
882
+ ...existing ?? { dependencies: [] },
883
+ map: content
884
+ });
885
+ return;
886
+ }
635
887
  const metadata = watchMode ? fileMetadata(filename) : {};
888
+ const existing = outputFiles.get(filename);
636
889
  outputFiles.set(filename, {
890
+ ...existing ?? {},
637
891
  content,
638
892
  dependencies: [],
639
893
  errors: metadata.errors,
@@ -641,13 +895,27 @@ function angular(options) {
641
895
  hmrUpdateCode: metadata.hmrUpdateCode,
642
896
  hmrEligible: metadata.hmrEligible
643
897
  });
898
+ debugEmitV("registered ngtsc output", {
899
+ filename,
900
+ ...describeEmitMarkers(content),
901
+ errorCount: metadata.errors?.length ?? 0,
902
+ warningCount: metadata.warnings?.length ?? 0,
903
+ hmrEligible: !!metadata.hmrEligible,
904
+ knownOutputCount: outputFiles.size
905
+ });
644
906
  };
645
907
  const writeOutputFile = (id) => {
646
908
  const sourceFile = builder.getSourceFile(id);
647
909
  if (!sourceFile) return;
648
910
  let content = "";
911
+ let map;
912
+ let mapFilename;
649
913
  builder.emit(sourceFile, (filename, data) => {
650
914
  if (/\.[cm]?js$/.test(filename)) content = data;
915
+ if (/\.[cm]?js\.map$/.test(filename)) {
916
+ map = data;
917
+ mapFilename = filename;
918
+ }
651
919
  if (!watchMode && !isTest && /\.d\.ts/.test(filename) && !filename.includes(".ngtypecheck.")) {
652
920
  const declarationPath = resolve(config.root, config.build.outDir, relative(config.root, filename)).replace("/src/", "/");
653
921
  const declarationFileDir = declarationPath.replace(basename(filename), "").replace("/src/", "/");
@@ -659,6 +927,7 @@ function angular(options) {
659
927
  }
660
928
  }, void 0, void 0, transformers);
661
929
  writeFileCallback(id, content, false, void 0, [sourceFile]);
930
+ if (map !== void 0 && mapFilename !== void 0) writeFileCallback(mapFilename, map, false, void 0, [sourceFile]);
662
931
  if (angularCompiler) angularCompiler.incrementalCompilation.recordSuccessfulEmit(sourceFile);
663
932
  };
664
933
  if (watchMode) {
@@ -681,54 +950,225 @@ function createFsWatcherCacheInvalidator(invalidateFsCaches, invalidateTsconfigC
681
950
  };
682
951
  }
683
952
  /**
684
- * Convert Analog/Angular CLI-style file replacements into the flat record
685
- * expected by `AngularHostOptions.fileReplacements`.
953
+ * Returns every live Vite module that can legitimately represent a changed
954
+ * Angular resource file.
686
955
  *
687
- * Only browser replacements (`{ replace, with }`) are converted. SSR-only
688
- * replacements (`{ replace, ssr }`) are left for the Vite runtime plugin to
689
- * handle — they should not be baked into the Angular compilation host because
690
- * that would apply them to both browser and server builds.
691
- *
692
- * Relative paths are resolved against `workspaceRoot` so that the host
693
- * receives the same absolute paths it would get from the Angular CLI.
956
+ * For normal files, `getModulesByFile()` is enough. For Angular component
957
+ * stylesheets, it is not: the browser often holds virtual hashed requests
958
+ * (`/abc123.css?direct&ngcomp=...` and `/abc123.css?ngcomp=...`) that are no
959
+ * longer discoverable from the original source path alone. We therefore merge:
960
+ * - watcher event modules
961
+ * - module-graph modules by source file
962
+ * - registry-tracked live request ids resolved back through the module graph
694
963
  */
695
- function toAngularCompilationFileReplacements(replacements, workspaceRoot) {
696
- const mappedReplacements = replacements.flatMap((replacement) => {
697
- if (!("with" in replacement)) return [];
698
- return [[isAbsolute(replacement.replace) ? replacement.replace : resolve(workspaceRoot, replacement.replace), isAbsolute(replacement.with) ? replacement.with : resolve(workspaceRoot, replacement.with)]];
964
+ async function getModulesForChangedFile(server, file, eventModules = [], stylesheetRegistry) {
965
+ const normalizedFile = normalizePath(file.split("?")[0]);
966
+ const modules = /* @__PURE__ */ new Map();
967
+ for (const mod of eventModules) if (mod.id) modules.set(mod.id, mod);
968
+ server.moduleGraph.getModulesByFile(normalizedFile)?.forEach((mod) => {
969
+ if (mod.id) modules.set(mod.id, mod);
970
+ });
971
+ const stylesheetRequestIds = stylesheetRegistry?.getRequestIdsForSource(normalizedFile) ?? [];
972
+ const requestIdHits = [];
973
+ for (const requestId of stylesheetRequestIds) {
974
+ const candidates = [requestId, requestId.startsWith("/") ? requestId : `/${requestId}`];
975
+ for (const candidate of candidates) {
976
+ const mod = await server.moduleGraph.getModuleByUrl(candidate) ?? server.moduleGraph.getModuleById(candidate);
977
+ requestIdHits.push({
978
+ requestId,
979
+ candidate,
980
+ via: mod?.url === candidate ? "url" : "id",
981
+ moduleId: mod?.id
982
+ });
983
+ if (mod?.id) modules.set(mod.id, mod);
984
+ }
985
+ }
986
+ debugHmrV("getModulesForChangedFile registry lookup", {
987
+ file: normalizedFile,
988
+ stylesheetRequestIds,
989
+ requestIdHits,
990
+ resolvedModuleIds: [...modules.keys()]
699
991
  });
700
- return mappedReplacements.length ? Object.fromEntries(mappedReplacements) : void 0;
992
+ return [...modules.values()];
701
993
  }
702
- /**
703
- * Map Angular's `templateUpdates` (keyed by `encodedFilePath@ClassName`)
704
- * back to absolute file paths with their associated HMR code and component
705
- * class name.
706
- *
707
- * Angular's private Compilation API emits template update keys in the form
708
- * `encodeURIComponent(relativePath + '@' + className)`. We decode and resolve
709
- * them so the caller can look up updates by the same normalized absolute path
710
- * used elsewhere in the plugin (`outputFiles`, `classNames`, etc.).
711
- */
712
- function mapTemplateUpdatesToFiles(templateUpdates) {
713
- const updatesByFile = /* @__PURE__ */ new Map();
714
- templateUpdates?.forEach((code, encodedUpdateId) => {
715
- const [file, className = ""] = decodeURIComponent(encodedUpdateId).split("@");
716
- const resolvedFile = normalizePath(resolve(process.cwd(), file));
717
- updatesByFile.set(resolvedFile, {
718
- className,
719
- code
994
+ function isModuleForChangedResource(mod, changedFile, stylesheetRegistry) {
995
+ const normalizedChangedFile = normalizePath(changedFile.split("?")[0]);
996
+ if (normalizePath((mod.file ?? "").split("?")[0]) === normalizedChangedFile) return true;
997
+ if (!mod.id) return false;
998
+ const requestPath = getFilenameFromPath(mod.id);
999
+ return normalizePath((stylesheetRegistry?.resolveExternalSource(requestPath) ?? stylesheetRegistry?.resolveExternalSource(requestPath.replace(/^\//, "")) ?? stylesheetRegistry?.getServedSourcePath(requestPath) ?? stylesheetRegistry?.getServedSourcePath(requestPath.replace(/^\//, "")) ?? "").split("?")[0]) === normalizedChangedFile;
1000
+ }
1001
+ function diagnoseComponentStylesheetPipeline(changedFile, directModule, stylesheetRegistry, wrapperModules, stylePreprocessor) {
1002
+ const normalizedFile = normalizePath(changedFile.split("?")[0]);
1003
+ const sourceExists = existsSync(normalizedFile);
1004
+ const sourceCode = sourceExists ? readFileSync(normalizedFile, "utf-8") : void 0;
1005
+ const directRequestPath = directModule.id ? getFilenameFromPath(directModule.id) : void 0;
1006
+ const sourcePath = directRequestPath ? stylesheetRegistry?.resolveExternalSource(directRequestPath) ?? stylesheetRegistry?.resolveExternalSource(directRequestPath.replace(/^\//, "")) ?? stylesheetRegistry?.getServedSourcePath(directRequestPath) ?? stylesheetRegistry?.getServedSourcePath(directRequestPath.replace(/^\//, "")) : normalizedFile;
1007
+ const registryCode = directRequestPath ? stylesheetRegistry?.getServedContent(directRequestPath) : void 0;
1008
+ const trackedRequestIds = stylesheetRegistry?.getRequestIdsForSource(sourcePath ?? "") ?? [];
1009
+ const dependencies = stylesheetRegistry?.getDependenciesForSource(sourcePath ?? "") ?? [];
1010
+ const diagnostics = stylesheetRegistry?.getDiagnosticsForSource(sourcePath ?? "") ?? [];
1011
+ const tags = stylesheetRegistry?.getTagsForSource(sourcePath ?? "") ?? [];
1012
+ const anomalies = [];
1013
+ const hints = [];
1014
+ if (!sourceExists) {
1015
+ anomalies.push("source_file_missing");
1016
+ hints.push("The stylesheet watcher fired for a file that no longer exists on disk.");
1017
+ }
1018
+ if (!registryCode) {
1019
+ anomalies.push("registry_content_missing");
1020
+ hints.push("The stylesheet registry has no served content for the direct module request path.");
1021
+ }
1022
+ if (sourceCode && registryCode) {
1023
+ let expectedRegistryCode = preprocessStylesheet(sourceCode, normalizedFile, stylePreprocessor);
1024
+ expectedRegistryCode = rewriteRelativeCssImports(expectedRegistryCode, normalizedFile);
1025
+ if (describeStylesheetContent(expectedRegistryCode).digest !== describeStylesheetContent(registryCode).digest) {
1026
+ anomalies.push("source_registry_mismatch");
1027
+ hints.push("The source file changed, but the served stylesheet content in the registry is still stale.");
1028
+ }
1029
+ }
1030
+ if (trackedRequestIds.length === 0) {
1031
+ anomalies.push("no_tracked_requests");
1032
+ hints.push("No live stylesheet requests are tracked for this source file, so HMR has no browser-facing target.");
1033
+ }
1034
+ if (trackedRequestIds.some((id) => id.includes("?ngcomp=")) && wrapperModules.length === 0) {
1035
+ anomalies.push("tracked_wrapper_missing_from_module_graph");
1036
+ hints.push("A wrapper request id is known, but Vite did not expose a live wrapper module during this HMR pass.");
1037
+ }
1038
+ if (trackedRequestIds.every((id) => !id.includes("?ngcomp=")) && wrapperModules.length === 0) {
1039
+ anomalies.push("wrapper_not_yet_tracked");
1040
+ hints.push("Only direct stylesheet requests were tracked during this HMR pass; the wrapper request may be appearing too late.");
1041
+ }
1042
+ return {
1043
+ file: changedFile,
1044
+ sourcePath,
1045
+ source: sourceCode ? describeStylesheetContent(rewriteRelativeCssImports(preprocessStylesheet(sourceCode, normalizedFile, stylePreprocessor), normalizedFile)) : void 0,
1046
+ registry: registryCode ? describeStylesheetContent(registryCode) : void 0,
1047
+ dependencies,
1048
+ diagnostics,
1049
+ tags,
1050
+ directModuleId: directModule.id,
1051
+ directModuleUrl: directModule.url,
1052
+ trackedRequestIds,
1053
+ wrapperCount: wrapperModules.length,
1054
+ anomalies,
1055
+ hints
1056
+ };
1057
+ }
1058
+ async function findComponentStylesheetWrapperModules(server, changedFile, directModule, fileModules, stylesheetRegistry) {
1059
+ const wrapperModules = /* @__PURE__ */ new Map();
1060
+ for (const mod of fileModules) if (mod.id && mod.type === "js" && isComponentStyleSheet(mod.id) && isModuleForChangedResource(mod, changedFile, stylesheetRegistry)) wrapperModules.set(mod.id, mod);
1061
+ const directRequestIds = /* @__PURE__ */ new Set();
1062
+ if (directModule.id) directRequestIds.add(directModule.id);
1063
+ if (directModule.url) directRequestIds.add(directModule.url);
1064
+ const requestPath = directModule.id ? getFilenameFromPath(directModule.id) : void 0;
1065
+ const sourcePath = requestPath ? stylesheetRegistry?.resolveExternalSource(requestPath) ?? stylesheetRegistry?.resolveExternalSource(requestPath.replace(/^\//, "")) ?? stylesheetRegistry?.getServedSourcePath(requestPath) ?? stylesheetRegistry?.getServedSourcePath(requestPath.replace(/^\//, "")) : void 0;
1066
+ for (const requestId of stylesheetRegistry?.getRequestIdsForSource(sourcePath ?? "") ?? []) if (requestId.includes("?ngcomp=")) directRequestIds.add(requestId);
1067
+ const candidateWrapperIds = [...directRequestIds].filter((id) => id.includes("?direct&ngcomp=")).map((id) => id.replace("?direct&ngcomp=", "?ngcomp="));
1068
+ const lookupHits = [];
1069
+ for (const candidate of candidateWrapperIds) {
1070
+ const mod = await server.moduleGraph.getModuleByUrl(candidate) ?? server.moduleGraph.getModuleById(candidate);
1071
+ lookupHits.push({
1072
+ candidate,
1073
+ via: mod?.url === candidate ? "url" : mod ? "id" : void 0,
1074
+ moduleId: mod?.id,
1075
+ moduleType: mod?.type
720
1076
  });
1077
+ if (mod?.id && mod.type === "js" && isComponentStyleSheet(mod.id) && isModuleForChangedResource(mod, changedFile, stylesheetRegistry)) wrapperModules.set(mod.id, mod);
1078
+ }
1079
+ debugHmrV("component stylesheet wrapper lookup", {
1080
+ file: changedFile,
1081
+ sourcePath,
1082
+ directModuleId: directModule.id,
1083
+ directModuleUrl: directModule.url,
1084
+ candidateWrapperIds,
1085
+ lookupHits
721
1086
  });
722
- return updatesByFile;
1087
+ if (wrapperModules.size === 0) debugHmrV("component stylesheet wrapper lookup empty", {
1088
+ file: changedFile,
1089
+ sourcePath,
1090
+ directModuleId: directModule.id,
1091
+ directModuleUrl: directModule.url,
1092
+ candidateWrapperIds
1093
+ });
1094
+ return [...wrapperModules.values()];
723
1095
  }
724
1096
  function sendHMRComponentUpdate(server, id) {
1097
+ debugHmrV("ws send: angular component update", {
1098
+ id,
1099
+ timestamp: Date.now()
1100
+ });
725
1101
  server.ws.send("angular:component-update", {
726
1102
  id: encodeURIComponent(id),
727
1103
  timestamp: Date.now()
728
1104
  });
729
1105
  classNames.delete(id);
730
1106
  }
731
- function getFileMetadata(program, angularCompiler, liveReload, disableTypeChecking) {
1107
+ function sendCssUpdate(server, update) {
1108
+ const timestamp = Date.now();
1109
+ debugHmrV("ws send: css-update", {
1110
+ ...update,
1111
+ timestamp
1112
+ });
1113
+ server.ws.send({
1114
+ type: "update",
1115
+ updates: [{
1116
+ type: "css-update",
1117
+ timestamp,
1118
+ path: update.path,
1119
+ acceptedPath: update.acceptedPath
1120
+ }]
1121
+ });
1122
+ }
1123
+ function sendFullReload(server, details) {
1124
+ debugHmrV("ws send: full-reload", details);
1125
+ server.ws.send("analog:debug-full-reload", details);
1126
+ server.ws.send({ type: "full-reload" });
1127
+ }
1128
+ function logComponentStylesheetHmrOutcome(details) {
1129
+ const pitfalls = [];
1130
+ const rejectedPreferredPaths = [];
1131
+ const hints = [];
1132
+ if (details.encapsulation === "shadow") {
1133
+ pitfalls.push("shadow-encapsulation");
1134
+ rejectedPreferredPaths.push("css-update");
1135
+ hints.push("Shadow DOM styles cannot rely on Vite CSS patching because Angular applies them inside a shadow root.");
1136
+ }
1137
+ if (details.diagnosis.anomalies.includes("wrapper_not_yet_tracked")) {
1138
+ pitfalls.push("wrapper-not-yet-tracked");
1139
+ rejectedPreferredPaths.push("css-update");
1140
+ hints.push("The direct stylesheet module exists, but the browser-visible Angular wrapper module was not available in the live graph during this HMR pass.");
1141
+ }
1142
+ if (details.diagnosis.anomalies.includes("tracked_wrapper_missing_from_module_graph")) {
1143
+ pitfalls.push("tracked-wrapper-missing-from-module-graph");
1144
+ rejectedPreferredPaths.push("css-update");
1145
+ hints.push("A wrapper request id is known, but Vite could not resolve a live wrapper module for targeted CSS HMR.");
1146
+ }
1147
+ debugHmrV("component stylesheet hmr outcome", {
1148
+ file: details.file,
1149
+ outcome: details.outcome,
1150
+ encapsulation: details.encapsulation,
1151
+ directModuleId: details.directModuleId,
1152
+ wrapperIds: details.wrapperIds ?? [],
1153
+ preferredPath: details.encapsulation === "shadow" ? "full-reload" : "css-update",
1154
+ rejectedPreferredPaths: [...new Set(rejectedPreferredPaths)],
1155
+ pitfalls: [...new Set(pitfalls)],
1156
+ anomalies: details.diagnosis.anomalies,
1157
+ hints: [...new Set([...details.diagnosis.hints, ...hints])]
1158
+ });
1159
+ }
1160
+ function findTemplateOwnerModules(server, resourceFile) {
1161
+ const candidateTsFiles = [normalizePath(resourceFile.split("?")[0]).replace(/\.(html|htm)$/i, ".ts")];
1162
+ const modules = /* @__PURE__ */ new Map();
1163
+ for (const candidate of candidateTsFiles) server.moduleGraph.getModulesByFile(candidate)?.forEach((mod) => {
1164
+ if (mod.id) modules.set(mod.id, mod);
1165
+ });
1166
+ return [...modules.values()];
1167
+ }
1168
+ /**
1169
+ * @internal
1170
+ */
1171
+ function getFileMetadata(program, angularCompiler, hmrEnabled, disableTypeChecking) {
732
1172
  const ts = require("typescript");
733
1173
  return (file) => {
734
1174
  const sourceFile = program.getSourceFile(file);
@@ -738,12 +1178,17 @@ function getFileMetadata(program, angularCompiler, liveReload, disableTypeChecki
738
1178
  const warnings = diagnostics.filter((d) => d.category === ts.DiagnosticCategory?.Warning).map((d) => d.messageText);
739
1179
  let hmrUpdateCode = void 0;
740
1180
  let hmrEligible = false;
741
- if (liveReload) {
1181
+ if (hmrEnabled) {
742
1182
  for (const node of sourceFile.statements) if (ts.isClassDeclaration(node) && node.name != null) {
743
1183
  hmrUpdateCode = angularCompiler?.emitHmrUpdateModule(node);
744
1184
  if (hmrUpdateCode) {
745
- classNames.set(file, node.name.getText());
1185
+ const className = node.name.getText();
1186
+ classNames.set(file, className);
746
1187
  hmrEligible = true;
1188
+ debugHmr("NgtscProgram emitHmrUpdateModule", {
1189
+ file,
1190
+ className
1191
+ });
747
1192
  }
748
1193
  }
749
1194
  }
@@ -773,40 +1218,18 @@ function markModuleSelfAccepting(mod) {
773
1218
  isSelfAccepting: true
774
1219
  };
775
1220
  }
776
- function isComponentStyleSheet(id) {
777
- return id.includes("ngcomp=");
778
- }
779
- function getComponentStyleSheetMeta(id) {
780
- const params = new URL(id, "http://localhost").searchParams;
781
- return {
782
- componentId: params.get("ngcomp"),
783
- encapsulation: {
784
- "0": "emulated",
785
- "2": "none",
786
- "3": "shadow"
787
- }[params.get("e")]
788
- };
789
- }
790
1221
  /**
791
1222
  * Removes leading / and query string from a url path
792
1223
  * e.g. /foo.scss?direct&ngcomp=ng-c3153525609&e=0 returns foo.scss
793
1224
  * @param id
794
1225
  */
795
1226
  function getFilenameFromPath(id) {
796
- return new URL(id, "http://localhost").pathname.replace(/^\//, "");
797
- }
798
- /**
799
- * Checks for vitest run from the command line
800
- * @returns boolean
801
- */
802
- function isTestWatchMode(args = process.argv) {
803
- if (args.find((arg) => arg.includes("--run"))) return false;
804
- if (args.find((arg) => arg.includes("--no-run"))) return true;
805
- const hasWatch = args.find((arg) => arg.includes("watch"));
806
- if (hasWatch && ["false", "no"].some((neg) => hasWatch.includes(neg))) return false;
807
- const watchArg = args[args.findIndex((arg) => arg.includes("watch")) + 1];
808
- if (watchArg && watchArg === "false") return false;
809
- return true;
1227
+ try {
1228
+ return new URL(id, "http://localhost").pathname.replace(/^\//, "");
1229
+ } catch {
1230
+ const queryIndex = id.indexOf("?");
1231
+ return (queryIndex >= 0 ? id.slice(0, queryIndex) : id).replace(/^\//, "");
1232
+ }
810
1233
  }
811
1234
  //#endregion
812
1235
  export { angular };