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

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 +774 -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 +547 -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 +433 -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,37 @@ 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",
116
189
  async config(config, { command }) {
190
+ activateDeferredDebug(command);
117
191
  watchMode = command === "serve";
118
- isProd = config.mode === "production" || process.env.NODE_ENV === "production";
192
+ isProd = isProdMode(config.mode);
119
193
  tsConfigResolutionContext = {
120
194
  root: config.root || ".",
121
195
  isProd,
122
196
  isLib: !!config?.build?.lib
123
197
  };
124
198
  const preliminaryTsConfigPath = resolveTsConfigPath();
125
- const esbuild = pluginOptions.useAngularCompilationAPI ? void 0 : config.esbuild ?? false;
126
- const oxc = pluginOptions.useAngularCompilationAPI ? void 0 : config.oxc ?? false;
199
+ const esbuild = config.esbuild ?? false;
200
+ const oxc = config.oxc ?? false;
127
201
  const defineOptions = {
128
202
  ngJitMode: "false",
129
203
  ngI18nClosureMode: "false",
@@ -138,7 +212,7 @@ function angular(options) {
138
212
  advancedOptimizations: isProd,
139
213
  jit,
140
214
  incremental: watchMode
141
- })] };
215
+ }, isTest, !isAstroIntegration)] };
142
216
  const esbuildOptions = {
143
217
  plugins: [createCompilerPlugin({
144
218
  tsconfig: preliminaryTsConfigPath,
@@ -152,18 +226,24 @@ function angular(options) {
152
226
  return {
153
227
  [jsTransformConfigKey]: jsTransformConfigValue,
154
228
  optimizeDeps: {
155
- include: ["rxjs/operators", "rxjs"],
229
+ include: [
230
+ "rxjs/operators",
231
+ "rxjs",
232
+ "tslib"
233
+ ],
156
234
  exclude: ["@angular/platform-server"],
157
235
  ...useRolldown ? { rolldownOptions } : { esbuildOptions }
158
- },
159
- resolve: { conditions: ["style", ...config.resolve?.conditions || defaultClientConditions] }
236
+ }
160
237
  };
161
238
  },
162
239
  configResolved(config) {
163
240
  resolvedConfig = config;
164
- if (pluginOptions.useAngularCompilationAPI) {
165
- externalComponentStyles = /* @__PURE__ */ new Map();
166
- inlineComponentStyles = /* @__PURE__ */ new Map();
241
+ if (config.logger?.warnOnce) {
242
+ const originalWarnOnce = config.logger.warnOnce;
243
+ config.logger.warnOnce = (msg, options) => {
244
+ if (typeof msg === "string" && msg.includes("Sourcemap") && msg.includes("node_modules")) return;
245
+ originalWarnOnce(msg, options);
246
+ };
167
247
  }
168
248
  if (!jit) styleTransform = (code, filename) => preprocessCSS(code, filename, config);
169
249
  if (isTest) testWatchMode = !(config.server.watch === null) || config.test?.watch === true || testWatchMode;
@@ -172,30 +252,90 @@ function angular(options) {
172
252
  viteServer = server;
173
253
  const invalidateCompilationOnFsChange = createFsWatcherCacheInvalidator(invalidateFsCaches, invalidateTsconfigCaches, () => performCompilation(resolvedConfig));
174
254
  server.watcher.on("add", invalidateCompilationOnFsChange);
175
- server.watcher.on("unlink", invalidateCompilationOnFsChange);
255
+ server.watcher.on("unlink", (file) => {
256
+ const id = normalizePath(file);
257
+ outputFiles.delete(id);
258
+ tsSourceFileCache.delete(id);
259
+ sourceFileCache$1.invalidate(new Set([id]));
260
+ evictDeletedFileMetadata(file, {
261
+ classNamesMap: classNames,
262
+ fileTransformMap
263
+ });
264
+ return invalidateCompilationOnFsChange();
265
+ });
176
266
  server.watcher.on("change", (file) => {
177
267
  if (file.includes("tsconfig")) invalidateTsconfigCaches();
178
268
  });
179
269
  },
180
270
  async buildStart() {
181
271
  if (!isVitestVscode) {
182
- await performCompilation(resolvedConfig);
272
+ pendingCompilation = performCompilation(this.environment?.config ?? resolvedConfig);
273
+ await pendingCompilation;
183
274
  pendingCompilation = null;
184
275
  initialCompilation = true;
185
276
  }
186
277
  },
278
+ buildEnd() {
279
+ if (watchMode) return;
280
+ nextProgram = void 0;
281
+ cachedHost = void 0;
282
+ cachedHostKey = void 0;
283
+ outputFile = void 0;
284
+ outputFiles.clear();
285
+ fileTransformMap.clear();
286
+ tsSourceFileCache.clear();
287
+ sourceFileCache$1.clear();
288
+ sourceFileCache$1.modifiedFiles.clear();
289
+ sourceFileCache$1.babelFileCache?.clear();
290
+ sourceFileCache$1.typeScriptFileCache?.clear();
291
+ sourceFileCache$1.referencedFiles = void 0;
292
+ tsconfigResolver.invalidateAll();
293
+ releaseCssPreprocessorWorkers();
294
+ },
187
295
  async handleHotUpdate(ctx) {
296
+ if (isIgnoredHmrFile(ctx.file)) {
297
+ debugHmr("ignored file change", { file: ctx.file });
298
+ return [];
299
+ }
188
300
  if (TS_EXT_REGEX.test(ctx.file)) {
189
301
  const [fileId] = ctx.file.split("?");
302
+ debugHmr("TS file changed", {
303
+ file: ctx.file,
304
+ fileId
305
+ });
190
306
  pendingCompilation = performCompilation(resolvedConfig, [fileId]);
191
307
  let result;
192
- if (pluginOptions.liveReload) {
308
+ if (shouldEnableLiveReload()) {
193
309
  await pendingCompilation;
194
310
  pendingCompilation = null;
195
311
  result = fileEmitter(fileId);
312
+ debugHmr("TS file emitted", {
313
+ fileId,
314
+ hmrEligible: !!result?.hmrEligible,
315
+ hasClassName: !!classNames.get(fileId)
316
+ });
317
+ debugHmrV("ts hmr evaluation", {
318
+ file: ctx.file,
319
+ fileId,
320
+ hasResult: !!result,
321
+ hmrEligible: !!result?.hmrEligible,
322
+ hasClassName: !!classNames.get(fileId),
323
+ className: classNames.get(fileId),
324
+ updateCode: result?.hmrUpdateCode ? describeStylesheetContent(result.hmrUpdateCode) : void 0,
325
+ errors: result?.errors?.length ?? 0,
326
+ warnings: result?.warnings?.length ?? 0,
327
+ 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."
328
+ });
196
329
  }
197
- if (pluginOptions.liveReload && result?.hmrEligible && classNames.get(fileId)) {
330
+ if (shouldEnableLiveReload() && result?.hmrEligible && classNames.get(fileId)) {
198
331
  const relativeFileId = `${normalizePath(relative(process.cwd(), fileId))}@${classNames.get(fileId)}`;
332
+ debugHmr("sending component update", { relativeFileId });
333
+ debugHmrV("ts hmr component update payload", {
334
+ file: ctx.file,
335
+ fileId,
336
+ relativeFileId,
337
+ className: classNames.get(fileId)
338
+ });
199
339
  sendHMRComponentUpdate(ctx.server, relativeFileId);
200
340
  return ctx.modules.map((mod) => {
201
341
  if (mod.id === ctx.file) return markModuleSelfAccepting(mod);
@@ -204,79 +344,260 @@ function angular(options) {
204
344
  }
205
345
  }
206
346
  if (/\.(html|htm|css|less|sass|scss)$/.test(ctx.file)) {
347
+ debugHmr("resource file changed", { file: ctx.file });
207
348
  fileTransformMap.delete(ctx.file.split("?")[0]);
349
+ const fileModules = await getModulesForChangedFile(ctx.server, ctx.file, ctx.modules, stylesheetRegistry);
208
350
  /**
209
351
  * Check to see if this was a direct request
210
352
  * for an external resource (styles, html).
211
353
  */
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"));
354
+ const isDirect = fileModules.find((mod) => !!mod.id && mod.id.includes("?direct") && isModuleForChangedResource(mod, ctx.file, stylesheetRegistry));
355
+ const isInline = fileModules.find((mod) => !!mod.id && mod.id.includes("?inline") && isModuleForChangedResource(mod, ctx.file, stylesheetRegistry));
356
+ debugHmrV("resource direct/inline detection", {
357
+ file: ctx.file,
358
+ hasDirect: !!isDirect,
359
+ directId: isDirect?.id,
360
+ hasInline: !!isInline,
361
+ inlineId: isInline?.id
362
+ });
214
363
  if (isDirect || isInline) {
215
- if (pluginOptions.liveReload && isDirect?.id && isDirect.file) {
216
- if (isDirect.type === "css" && isComponentStyleSheet(isDirect.id)) {
364
+ if (shouldExternalizeStyles() && isDirect?.id && isDirect.file) {
365
+ const isComponentStyle = isDirect.type === "css" && isComponentStyleSheet(isDirect.id);
366
+ debugHmrV("resource direct branch", {
367
+ file: ctx.file,
368
+ directId: isDirect.id,
369
+ directType: isDirect.type,
370
+ shouldExternalize: shouldExternalizeStyles(),
371
+ isComponentStyle
372
+ });
373
+ if (isComponentStyle) {
217
374
  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
- }]
375
+ const wrapperModules = await findComponentStylesheetWrapperModules(ctx.server, ctx.file, isDirect, fileModules, stylesheetRegistry);
376
+ const stylesheetDiagnosis = diagnoseComponentStylesheetPipeline(ctx.file, isDirect, stylesheetRegistry, wrapperModules, pluginOptions.stylePreprocessor);
377
+ debugStylesV("HMR: component stylesheet changed", {
378
+ file: isDirect.file,
379
+ encapsulation
380
+ });
381
+ debugHmrV("component stylesheet wrapper modules", {
382
+ file: ctx.file,
383
+ wrapperCount: wrapperModules.length,
384
+ wrapperIds: wrapperModules.map((mod) => mod.id),
385
+ availableModuleIds: fileModules.map((mod) => mod.id)
386
+ });
387
+ debugHmrV("component stylesheet pipeline diagnosis", stylesheetDiagnosis);
388
+ ctx.server.moduleGraph.invalidateModule(isDirect);
389
+ debugHmrV("component stylesheet direct module invalidated", {
390
+ file: ctx.file,
391
+ directModuleId: isDirect.id,
392
+ directModuleUrl: isDirect.url,
393
+ reason: "Ensure Vite drops stale direct CSS transform results before wrapper or fallback handling continues."
394
+ });
395
+ const trackedWrapperRequestIds = stylesheetDiagnosis.trackedRequestIds.filter((id) => id.includes("?ngcomp="));
396
+ if (encapsulation !== "shadow" && (wrapperModules.length > 0 || trackedWrapperRequestIds.length > 0)) {
397
+ wrapperModules.forEach((mod) => ctx.server.moduleGraph.invalidateModule(mod));
398
+ debugHmrV("sending css-update for component stylesheet", {
399
+ file: ctx.file,
400
+ path: isDirect.url,
401
+ acceptedPath: isDirect.file,
402
+ wrapperCount: wrapperModules.length,
403
+ trackedWrapperRequestIds,
404
+ 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
405
  });
228
- return ctx.modules.filter((mod) => {
406
+ sendCssUpdate(ctx.server, {
407
+ path: isDirect.url,
408
+ acceptedPath: isDirect.file
409
+ });
410
+ logComponentStylesheetHmrOutcome({
411
+ file: ctx.file,
412
+ encapsulation,
413
+ diagnosis: stylesheetDiagnosis,
414
+ outcome: "css-update",
415
+ directModuleId: isDirect.id,
416
+ wrapperIds: wrapperModules.map((mod) => mod.id)
417
+ });
418
+ return union(fileModules.filter((mod) => {
229
419
  return mod.file !== ctx.file || mod.id !== isDirect.id;
230
420
  }).map((mod) => {
231
421
  if (mod.file === ctx.file) return markModuleSelfAccepting(mod);
232
422
  return mod;
233
- });
423
+ }), wrapperModules.map((mod) => markModuleSelfAccepting(mod)));
234
424
  }
425
+ debugHmrV("component stylesheet hmr fallback: full reload", {
426
+ file: ctx.file,
427
+ encapsulation,
428
+ reason: trackedWrapperRequestIds.length === 0 ? "missing-wrapper-module" : encapsulation === "shadow" ? "shadow-encapsulation" : "tracked-wrapper-still-not-patchable",
429
+ directId: isDirect.id,
430
+ trackedRequestIds: stylesheetRegistry?.getRequestIdsForSource(ctx.file) ?? []
431
+ });
432
+ logComponentStylesheetHmrOutcome({
433
+ file: ctx.file,
434
+ encapsulation,
435
+ diagnosis: stylesheetDiagnosis,
436
+ outcome: "full-reload",
437
+ directModuleId: isDirect.id,
438
+ wrapperIds: wrapperModules.map((mod) => mod.id)
439
+ });
440
+ sendFullReload(ctx.server, {
441
+ file: ctx.file,
442
+ encapsulation,
443
+ reason: wrapperModules.length === 0 ? "missing-wrapper-module" : "shadow-encapsulation",
444
+ directId: isDirect.id,
445
+ trackedRequestIds: stylesheetRegistry?.getRequestIdsForSource(ctx.file) ?? []
446
+ });
447
+ return [];
448
+ }
449
+ }
450
+ return fileModules;
451
+ }
452
+ if (shouldEnableLiveReload() && /\.(html|htm)$/.test(ctx.file) && fileModules.length === 0) {
453
+ const ownerModules = findTemplateOwnerModules(ctx.server, ctx.file);
454
+ debugHmrV("template owner lookup", {
455
+ file: ctx.file,
456
+ ownerCount: ownerModules.length,
457
+ ownerIds: ownerModules.map((mod) => mod.id),
458
+ 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."
459
+ });
460
+ if (ownerModules.length > 0) {
461
+ const ownerIds = ownerModules.map((mod) => mod.id).filter(Boolean);
462
+ ownerModules.forEach((mod) => ctx.server.moduleGraph.invalidateModule(mod));
463
+ pendingCompilation = performCompilation(resolvedConfig, ownerIds);
464
+ await pendingCompilation;
465
+ pendingCompilation = null;
466
+ const updates = ownerIds.filter((id) => classNames.get(id));
467
+ debugHmrV("template owner recompilation result", {
468
+ file: ctx.file,
469
+ ownerIds,
470
+ updates,
471
+ updateClassNames: updates.map((id) => ({
472
+ id,
473
+ className: classNames.get(id)
474
+ })),
475
+ hint: updates.length > 0 ? "External template recompilation produced Angular component update targets." : "External template recompilation completed, but no Angular component update targets were surfaced."
476
+ });
477
+ if (updates.length > 0) {
478
+ debugHmr("template owner module invalidation", {
479
+ file: ctx.file,
480
+ ownerIds,
481
+ updateCount: updates.length
482
+ });
483
+ updates.forEach((updateId) => {
484
+ const relativeFileId = `${normalizePath(relative(process.cwd(), updateId))}@${classNames.get(updateId)}`;
485
+ sendHMRComponentUpdate(ctx.server, relativeFileId);
486
+ });
487
+ return ownerModules.map((mod) => markModuleSelfAccepting(mod));
235
488
  }
236
489
  }
237
- return ctx.modules;
238
490
  }
239
491
  const mods = [];
240
492
  const updates = [];
241
- ctx.modules.forEach((mod) => {
493
+ fileModules.forEach((mod) => {
242
494
  mod.importers.forEach((imp) => {
243
495
  ctx.server.moduleGraph.invalidateModule(imp);
244
- if (pluginOptions.liveReload && classNames.get(imp.id)) updates.push(imp.id);
496
+ if (shouldExternalizeStyles() && classNames.get(imp.id)) updates.push(imp.id);
245
497
  else mods.push(imp);
246
498
  });
247
499
  });
248
- pendingCompilation = performCompilation(resolvedConfig, [...mods.map((mod) => mod.id), ...updates]);
500
+ debugHmrV("resource importer analysis", {
501
+ file: ctx.file,
502
+ fileModuleCount: fileModules.length,
503
+ importerCount: fileModules.reduce((count, mod) => count + mod.importers.size, 0),
504
+ updates,
505
+ mods: mods.map((mod) => mod.id)
506
+ });
507
+ pendingCompilation = performCompilation(resolvedConfig, [...mods.map((mod) => mod.id).filter(Boolean), ...updates]);
249
508
  if (updates.length > 0) {
250
509
  await pendingCompilation;
251
510
  pendingCompilation = null;
511
+ debugHmr("resource importer component updates", {
512
+ file: ctx.file,
513
+ updateCount: updates.length
514
+ });
252
515
  updates.forEach((updateId) => {
253
516
  const impRelativeFileId = `${normalizePath(relative(process.cwd(), updateId))}@${classNames.get(updateId)}`;
254
517
  sendHMRComponentUpdate(ctx.server, impRelativeFileId);
255
518
  });
256
- return ctx.modules.map((mod) => {
519
+ return fileModules.map((mod) => {
257
520
  if (mod.id === ctx.file) return markModuleSelfAccepting(mod);
258
521
  return mod;
259
522
  });
260
523
  }
261
524
  return mods;
262
525
  }
526
+ debugHmr("full reload — unrecognized file type", { file: ctx.file });
263
527
  classNames.clear();
264
528
  return ctx.modules;
265
529
  },
266
530
  resolveId(id, importer) {
531
+ if (id.startsWith("virtual:@analogjs/vite-plugin-angular:raw:")) return `\0${id}`;
267
532
  if (jit && id.startsWith("angular:jit:")) {
268
- const path = id.split(";")[1];
269
- return `${normalizePath(resolve(dirname(importer), path))}?${id.includes(":style") ? "inline" : "raw"}`;
533
+ const filePath = normalizePath(resolve(dirname(importer), id.split(";")[1]));
534
+ if (id.includes(":style")) {
535
+ markStylePathSafe(resolvedConfig, filePath);
536
+ return filePath + "?inline";
537
+ }
538
+ return toVirtualRawId(filePath);
539
+ }
540
+ const rawRewrite = rewriteHtmlRawImport(id, importer);
541
+ if (rawRewrite) return rawRewrite;
542
+ if (/\.(css|scss|sass|less)\?inline$/.test(id) && importer) {
543
+ const filePath = id.split("?")[0];
544
+ const resolved = isAbsolute(filePath) ? normalizePath(filePath) : normalizePath(resolve(dirname(importer), filePath));
545
+ markStylePathSafe(resolvedConfig, resolved);
546
+ return resolved + "?inline";
270
547
  }
271
548
  if (isComponentStyleSheet(id)) {
272
- const componentStyles = externalComponentStyles?.get(getFilenameFromPath(id));
273
- if (componentStyles) return componentStyles + new URL(id, "http://localhost").search;
549
+ const filename = getFilenameFromPath(id);
550
+ const search = new URL(id, "http://localhost").search;
551
+ const servedSourcePath = stylesheetRegistry?.getServedSourcePath(filename);
552
+ if (servedSourcePath) {
553
+ debugStylesV("resolveId: mapped served stylesheet to source", {
554
+ filename,
555
+ resolvedPath: servedSourcePath
556
+ });
557
+ return servedSourcePath + search;
558
+ }
559
+ if (stylesheetRegistry?.hasServed(filename)) {
560
+ debugStylesV("resolveId: kept preprocessed ID", { filename });
561
+ return id;
562
+ }
563
+ const componentStyles = stylesheetRegistry?.resolveExternalSource(filename);
564
+ if (componentStyles) {
565
+ debugStylesV("resolveId: mapped external stylesheet", {
566
+ filename,
567
+ resolvedPath: componentStyles
568
+ });
569
+ return componentStyles + new URL(id, "http://localhost").search;
570
+ }
571
+ debugStyles("resolveId: component stylesheet NOT FOUND in either map", {
572
+ filename,
573
+ inlineMapSize: stylesheetRegistry?.servedCount ?? 0,
574
+ externalMapSize: stylesheetRegistry?.externalCount ?? 0
575
+ });
274
576
  }
275
577
  },
276
578
  async load(id) {
579
+ const rawModule = await loadVirtualRawModule(this, id);
580
+ if (rawModule !== void 0) return rawModule;
581
+ if (/\.(css|scss|sass|less)\?inline$/.test(id)) markStylePathSafe(resolvedConfig, id.split("?")[0]);
277
582
  if (isComponentStyleSheet(id)) {
278
- const componentStyles = inlineComponentStyles?.get(getFilenameFromPath(id));
279
- if (componentStyles) return componentStyles;
583
+ const filename = getFilenameFromPath(id);
584
+ const componentStyles = stylesheetRegistry?.getServedContent(filename);
585
+ if (componentStyles) {
586
+ stylesheetRegistry?.registerActiveRequest(id);
587
+ debugHmrV("stylesheet active request registered", {
588
+ requestId: id,
589
+ filename,
590
+ sourcePath: stylesheetRegistry?.resolveExternalSource(filename) ?? stylesheetRegistry?.resolveExternalSource(filename.replace(/^\//, "")) ?? stylesheetRegistry?.getServedSourcePath(filename) ?? stylesheetRegistry?.getServedSourcePath(filename.replace(/^\//, "")),
591
+ trackedRequestIds: stylesheetRegistry?.getRequestIdsForSource(stylesheetRegistry?.resolveExternalSource(filename) ?? stylesheetRegistry?.resolveExternalSource(filename.replace(/^\//, "")) ?? stylesheetRegistry?.getServedSourcePath(filename) ?? stylesheetRegistry?.getServedSourcePath(filename.replace(/^\//, "")) ?? "") ?? []
592
+ });
593
+ debugStylesV("load: served inline component stylesheet", {
594
+ filename,
595
+ length: componentStyles.length,
596
+ requestId: id,
597
+ ...describeStylesheetContent(componentStyles)
598
+ });
599
+ return componentStyles;
600
+ }
280
601
  }
281
602
  },
282
603
  transform: {
@@ -289,27 +610,7 @@ function angular(options) {
289
610
  ]
290
611
  } },
291
612
  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
- }
613
+ if (transformFilter && !transformFilter(code, id)) return;
313
614
  if (id.includes(".ts?")) id = id.replace(/\?(.*)/, "");
314
615
  fileTransformMap.set(id, code);
315
616
  /**
@@ -318,19 +619,25 @@ function angular(options) {
318
619
  */
319
620
  if (isTest) {
320
621
  if (isVitestVscode && !initialCompilation) {
321
- pendingCompilation = performCompilation(resolvedConfig);
622
+ pendingCompilation = performCompilation(this.environment?.config ?? resolvedConfig);
322
623
  initialCompilation = true;
323
624
  }
324
625
  const tsMod = viteServer?.moduleGraph.getModuleById(id);
325
626
  if (tsMod) {
326
627
  const invalidated = tsMod.lastInvalidationTimestamp;
327
- if (testWatchMode && invalidated) pendingCompilation = performCompilation(resolvedConfig, [id]);
628
+ if (testWatchMode && invalidated) pendingCompilation = performCompilation(this.environment?.config ?? resolvedConfig, [id]);
328
629
  }
329
630
  }
330
631
  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]) {
632
+ debugCompilerV("transform", {
633
+ id,
634
+ codeLength: code.length,
635
+ hasComponent
636
+ });
637
+ const resolveResourceUrls = hasComponent && (watchMode || jit);
638
+ const templateUrls = resolveResourceUrls ? templateUrlsResolver.resolve(code, id) : [];
639
+ const styleUrls = resolveResourceUrls ? styleUrlsResolver.resolve(code, id) : [];
640
+ if (resolveResourceUrls && watchMode) for (const urlSet of [...templateUrls, ...styleUrls]) {
334
641
  const [, absoluteFileUrl] = urlSet.split("|");
335
642
  this.addWatchFile(absoluteFileUrl);
336
643
  }
@@ -339,23 +646,58 @@ function angular(options) {
339
646
  pendingCompilation = null;
340
647
  }
341
648
  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 ?? "";
649
+ if (!typescriptResult) {
650
+ debugCompilerV("transform skip (file not emitted by Angular)", { id });
651
+ const isAngular = !id.includes("@ng/component") && /(Component|Directive|Pipe|Injectable|NgModule)\(/.test(code);
652
+ debugEmit("transform emit miss", {
653
+ id,
654
+ normalizedId: normalizeEmitterLookupId(id),
655
+ knownOutputCount: outputFiles.size,
656
+ hasOutputFileHook: !!outputFile,
657
+ isAngular
658
+ });
659
+ 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.`);
660
+ return;
661
+ }
662
+ if (typescriptResult.warnings && typescriptResult.warnings.length > 0) this.warn(`${typescriptResult.warnings.join("\n")}`);
663
+ if (typescriptResult.errors && typescriptResult.errors.length > 0) this.error(`${typescriptResult.errors.join("\n")}`);
664
+ let data = typescriptResult.content ?? "";
665
+ debugEmitV("transform emit hit", {
666
+ id,
667
+ normalizedId: normalizeEmitterLookupId(id),
668
+ ...describeEmitMarkers(data),
669
+ errorCount: typescriptResult.errors?.length ?? 0,
670
+ warningCount: typescriptResult.warnings?.length ?? 0
671
+ });
345
672
  if (jit && data.includes("angular:jit:")) {
346
- data = data.replace(/angular:jit:style:inline;/g, "virtual:angular:jit:style:inline;");
673
+ data = data.replace(/angular:jit:style:inline;([A-Za-z0-9+/=]*)/g, (_match, encodedStyles) => toJitInlineStyleId(encodedStyles));
347
674
  templateUrls.forEach((templateUrlSet) => {
348
675
  const [templateFile, resolvedTemplateUrl] = templateUrlSet.split("|");
349
- data = data.replace(`angular:jit:template:file;${templateFile}`, `${resolvedTemplateUrl}?raw`);
676
+ data = data.replace(`angular:jit:template:file;${templateFile}`, toVirtualRawId(resolvedTemplateUrl));
350
677
  });
351
678
  styleUrls.forEach((styleUrlSet) => {
352
679
  const [styleFile, resolvedStyleUrl] = styleUrlSet.split("|");
353
- data = data.replace(`angular:jit:style:file;${styleFile}`, `${resolvedStyleUrl}?inline`);
680
+ markStylePathSafe(resolvedConfig, resolvedStyleUrl);
681
+ data = data.replace(`angular:jit:style:file;${styleFile}`, resolvedStyleUrl + "?inline");
354
682
  });
355
683
  }
684
+ if (data.includes("HmrLoad")) {
685
+ const hasMetaUrl = data.includes("getReplaceMetadataURL");
686
+ debugHmrV("vite-ignore injection", {
687
+ id,
688
+ dataLength: data.length,
689
+ hasMetaUrl
690
+ });
691
+ if (hasMetaUrl) {
692
+ const patched = injectViteIgnoreForHmrMetadata(data);
693
+ if (patched !== data && !patched.includes("@vite-ignore")) debugHmrV("vite-ignore regex fallback", { id });
694
+ data = patched;
695
+ }
696
+ }
697
+ if (typescriptResult.map) data = data.replace(/\s*\/\/# sourceMappingURL=[^\r\n]*\s*$/, "");
356
698
  return {
357
699
  code: data,
358
- map: null
700
+ map: typescriptResult.map ? normalizeSourceMapSources(typescriptResult.map, id) : null
359
701
  };
360
702
  }
361
703
  },
@@ -364,19 +706,47 @@ function angular(options) {
364
706
  mkdirSync(declarationFileDir, { recursive: true });
365
707
  writeFileSync(declarationPath, data, "utf-8");
366
708
  });
367
- angularCompilation?.close?.();
368
- angularCompilation = void 0;
709
+ declarationFiles.length = 0;
710
+ if (!watchMode) releaseCssPreprocessorWorkers();
369
711
  }
370
712
  };
371
713
  }
714
+ function normalizeSourceMapSources(map, id) {
715
+ const sourceMap = JSON.parse(map);
716
+ const sourceDirectory = dirname(id);
717
+ const sourceRoot = sourceMap.sourceRoot ?? "";
718
+ sourceMap.sources = sourceMap.sources?.map((source) => {
719
+ if (isAbsolute(source) || /^[a-z][a-z\d+.-]*:/i.test(source)) return normalizePath(source);
720
+ return normalizePath(resolve(sourceDirectory, sourceRoot, source));
721
+ });
722
+ delete sourceMap.sourceRoot;
723
+ return JSON.stringify(sourceMap);
724
+ }
725
+ const compilationPlugin = pluginOptions.fastCompile ? fastCompilePlugin({
726
+ tsconfigGetter: pluginOptions.tsconfigGetter,
727
+ workspaceRoot: pluginOptions.workspaceRoot,
728
+ inlineStylesExtension: pluginOptions.inlineStylesExtension,
729
+ jit,
730
+ liveReload: pluginOptions.liveReload,
731
+ supportedBrowsers: pluginOptions.supportedBrowsers,
732
+ isTest,
733
+ isAstroIntegration,
734
+ fastCompileMode: pluginOptions.fastCompileMode
735
+ }) : angularPlugin();
372
736
  return [
737
+ cssExtensionStyleResolverPlugin(),
373
738
  replaceFiles(pluginOptions.fileReplacements, pluginOptions.workspaceRoot),
374
- angularPlugin(),
739
+ virtualModulesPlugin({ jit }),
375
740
  pluginOptions.liveReload && liveReloadPlugin({
376
741
  classNames,
377
742
  fileEmitter
378
743
  }),
379
- ...isTest && !isStackBlitz ? angularVitestPlugins() : [],
744
+ compilationPlugin,
745
+ !pluginOptions.fastCompile && pluginOptions.liveReload && liveReloadPlugin({
746
+ classNames,
747
+ fileEmitter
748
+ }),
749
+ ...isTest && !isStackBlitz ? angularVitestPlugins((id) => outputFiles.get(normalizePath(id))?.map) : [],
380
750
  jit && jitPlugin({ inlineStylesExtension: pluginOptions.inlineStylesExtension }),
381
751
  buildOptimizerPlugin({
382
752
  supportedBrowsers: pluginOptions.supportedBrowsers,
@@ -384,116 +754,12 @@ function angular(options) {
384
754
  }),
385
755
  routerPlugin(),
386
756
  angularFullVersion < 190004 && pendingTasksPlugin(),
387
- nxFolderPlugin()
757
+ nxFolderPlugin(),
758
+ encapsulationPlugin()
388
759
  ].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
760
  function resolveTsConfigPath() {
414
761
  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
- }
762
+ return getTsConfigPath(tsConfigResolutionContext.root, tsconfigValue, tsConfigResolutionContext.isProd, isTest, tsConfigResolutionContext.isLib, pluginOptions.workspaceRoot);
497
763
  }
498
764
  async function performCompilation(config, ids) {
499
765
  let resolve;
@@ -503,6 +769,12 @@ function angular(options) {
503
769
  });
504
770
  try {
505
771
  await previousLock;
772
+ const integrations = await discoverAnalogIntegrations(config);
773
+ pluginOptions.stylePreprocessor = integrations.stylePreprocessor;
774
+ externalizeStylesRequested = integrations.externalizeStyles;
775
+ configureStylesheetRegistry = integrations.configureStylesheetRegistry;
776
+ transformFilter = integrations.transformFilter;
777
+ tsconfigResolver.setIntegrationIncludes(integrations.include);
506
778
  await _doPerformCompilation(config, ids);
507
779
  } finally {
508
780
  resolve();
@@ -513,54 +785,27 @@ function angular(options) {
513
785
  * 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
786
  */
515
787
  async function _doPerformCompilation(config, ids) {
516
- if (pluginOptions.useAngularCompilationAPI) {
517
- await performAngularCompilation(config, ids);
518
- return;
519
- }
788
+ if (!jit) styleTransform = (code, filename) => preprocessCSS(code, filename, config);
520
789
  const isProd = config.mode === "production";
521
- const modifiedFiles = new Set(ids ?? []);
522
- sourceFileCache$1.invalidate(modifiedFiles);
790
+ invalidateSourceFiles(new Set(ids ?? []));
523
791
  if (ids?.length) for (const id of ids || []) fileTransformMap.delete(id);
524
- if (pluginOptions.include.length > 0 && includeCache.length === 0) includeCache = findIncludes();
525
792
  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
- }
793
+ const cached = tsconfigResolver.getCachedTsconfigOptions(resolvedTsConfigPath, config);
557
794
  const tsCompilerOptions = { ...cached.options };
558
795
  let rootNames = [...cached.rootNames];
559
- if (pluginOptions.liveReload && watchMode) {
796
+ if (shouldExternalizeStyles()) tsCompilerOptions["externalRuntimeStyles"] = true;
797
+ if (shouldEnableLiveReload()) {
560
798
  tsCompilerOptions["_enableHmr"] = true;
561
- tsCompilerOptions["externalRuntimeStyles"] = true;
562
799
  tsCompilerOptions["supportTestBed"] = true;
563
800
  }
801
+ debugCompiler("tsCompilerOptions (NgtscProgram path)", {
802
+ liveReload: pluginOptions.liveReload,
803
+ viteHmr: hasViteHmrTransport(),
804
+ externalizeStylesRequested,
805
+ shouldExternalize: shouldExternalizeStyles(),
806
+ externalRuntimeStyles: !!tsCompilerOptions["externalRuntimeStyles"],
807
+ hmrEnabled: !!tsCompilerOptions["_enableHmr"]
808
+ });
564
809
  if (tsCompilerOptions["compilationMode"] === "partial") {
565
810
  tsCompilerOptions["supportTestBed"] = true;
566
811
  tsCompilerOptions["supportJitMode"] = true;
@@ -572,11 +817,7 @@ function angular(options) {
572
817
  }
573
818
  if (isTest) tsCompilerOptions["supportTestBed"] = true;
574
819
  const replacements = pluginOptions.fileReplacements.map((rp) => join(pluginOptions.workspaceRoot, rp.ssr || rp.with));
575
- rootNames = [...new Set([
576
- ...rootNames,
577
- ...includeCache,
578
- ...replacements
579
- ])];
820
+ rootNames = union(rootNames, tsconfigResolver.ensureIncludeCache(), replacements);
580
821
  const hostKey = JSON.stringify(tsCompilerOptions);
581
822
  let host;
582
823
  if (cachedHost && cachedHostKey === hostKey) host = cachedHost;
@@ -592,17 +833,19 @@ function angular(options) {
592
833
  });
593
834
  cachedHost = host;
594
835
  cachedHostKey = hostKey;
595
- if (watchMode) augmentHostWithCaching(host, sourceFileCache$1);
836
+ if (watchMode) augmentHostWithCaching(host, tsSourceFileCache);
596
837
  }
597
838
  if (!jit) {
598
- inlineComponentStyles = tsCompilerOptions["externalRuntimeStyles"] ? /* @__PURE__ */ new Map() : void 0;
599
- externalComponentStyles = tsCompilerOptions["externalRuntimeStyles"] ? /* @__PURE__ */ new Map() : void 0;
839
+ const externalizeStyles = !!tsCompilerOptions["externalRuntimeStyles"];
840
+ stylesheetRegistry = externalizeStyles ? new AnalogStylesheetRegistry() : void 0;
841
+ if (stylesheetRegistry) configureStylesheetRegistry?.(stylesheetRegistry, { workspaceRoot: pluginOptions.workspaceRoot });
842
+ debugStyles("stylesheet registry initialized (NgtscProgram path)", { externalizeStyles });
600
843
  augmentHostWithResources(host, styleTransform, {
601
844
  inlineStylesExtension: pluginOptions.inlineStylesExtension,
602
845
  isProd,
603
- inlineComponentStyles,
604
- externalComponentStyles,
605
- sourceFileCache: sourceFileCache$1
846
+ stylesheetRegistry,
847
+ sourceFileCache: sourceFileCache$1,
848
+ stylePreprocessor: pluginOptions.stylePreprocessor
606
849
  });
607
850
  }
608
851
  /**
@@ -631,9 +874,19 @@ function angular(options) {
631
874
  const writeFileCallback = (_filename, content, _a, _b, sourceFiles) => {
632
875
  if (!sourceFiles?.length) return;
633
876
  const filename = normalizePath(sourceFiles[0].fileName);
634
- if (filename.includes("ngtypecheck.ts") || filename.includes(".d.")) return;
877
+ if (filename.includes("ngtypecheck.ts") || filename.includes(".d.") || _filename.endsWith(".d.ts") || _filename.endsWith(".d.mts") || _filename.endsWith(".d.cts")) return;
878
+ if (/\.[cm]?js\.map$/.test(_filename)) {
879
+ const existing = outputFiles.get(filename);
880
+ outputFiles.set(filename, {
881
+ ...existing ?? { dependencies: [] },
882
+ map: content
883
+ });
884
+ return;
885
+ }
635
886
  const metadata = watchMode ? fileMetadata(filename) : {};
887
+ const existing = outputFiles.get(filename);
636
888
  outputFiles.set(filename, {
889
+ ...existing ?? {},
637
890
  content,
638
891
  dependencies: [],
639
892
  errors: metadata.errors,
@@ -641,13 +894,27 @@ function angular(options) {
641
894
  hmrUpdateCode: metadata.hmrUpdateCode,
642
895
  hmrEligible: metadata.hmrEligible
643
896
  });
897
+ debugEmitV("registered ngtsc output", {
898
+ filename,
899
+ ...describeEmitMarkers(content),
900
+ errorCount: metadata.errors?.length ?? 0,
901
+ warningCount: metadata.warnings?.length ?? 0,
902
+ hmrEligible: !!metadata.hmrEligible,
903
+ knownOutputCount: outputFiles.size
904
+ });
644
905
  };
645
906
  const writeOutputFile = (id) => {
646
907
  const sourceFile = builder.getSourceFile(id);
647
908
  if (!sourceFile) return;
648
909
  let content = "";
910
+ let map;
911
+ let mapFilename;
649
912
  builder.emit(sourceFile, (filename, data) => {
650
913
  if (/\.[cm]?js$/.test(filename)) content = data;
914
+ if (/\.[cm]?js\.map$/.test(filename)) {
915
+ map = data;
916
+ mapFilename = filename;
917
+ }
651
918
  if (!watchMode && !isTest && /\.d\.ts/.test(filename) && !filename.includes(".ngtypecheck.")) {
652
919
  const declarationPath = resolve(config.root, config.build.outDir, relative(config.root, filename)).replace("/src/", "/");
653
920
  const declarationFileDir = declarationPath.replace(basename(filename), "").replace("/src/", "/");
@@ -659,6 +926,7 @@ function angular(options) {
659
926
  }
660
927
  }, void 0, void 0, transformers);
661
928
  writeFileCallback(id, content, false, void 0, [sourceFile]);
929
+ if (map !== void 0 && mapFilename !== void 0) writeFileCallback(mapFilename, map, false, void 0, [sourceFile]);
662
930
  if (angularCompiler) angularCompiler.incrementalCompilation.recordSuccessfulEmit(sourceFile);
663
931
  };
664
932
  if (watchMode) {
@@ -681,54 +949,225 @@ function createFsWatcherCacheInvalidator(invalidateFsCaches, invalidateTsconfigC
681
949
  };
682
950
  }
683
951
  /**
684
- * Convert Analog/Angular CLI-style file replacements into the flat record
685
- * expected by `AngularHostOptions.fileReplacements`.
952
+ * Returns every live Vite module that can legitimately represent a changed
953
+ * Angular resource file.
686
954
  *
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.
955
+ * For normal files, `getModulesByFile()` is enough. For Angular component
956
+ * stylesheets, it is not: the browser often holds virtual hashed requests
957
+ * (`/abc123.css?direct&ngcomp=...` and `/abc123.css?ngcomp=...`) that are no
958
+ * longer discoverable from the original source path alone. We therefore merge:
959
+ * - watcher event modules
960
+ * - module-graph modules by source file
961
+ * - registry-tracked live request ids resolved back through the module graph
694
962
  */
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)]];
963
+ async function getModulesForChangedFile(server, file, eventModules = [], stylesheetRegistry) {
964
+ const normalizedFile = normalizePath(file.split("?")[0]);
965
+ const modules = /* @__PURE__ */ new Map();
966
+ for (const mod of eventModules) if (mod.id) modules.set(mod.id, mod);
967
+ server.moduleGraph.getModulesByFile(normalizedFile)?.forEach((mod) => {
968
+ if (mod.id) modules.set(mod.id, mod);
969
+ });
970
+ const stylesheetRequestIds = stylesheetRegistry?.getRequestIdsForSource(normalizedFile) ?? [];
971
+ const requestIdHits = [];
972
+ for (const requestId of stylesheetRequestIds) {
973
+ const candidates = [requestId, requestId.startsWith("/") ? requestId : `/${requestId}`];
974
+ for (const candidate of candidates) {
975
+ const mod = await server.moduleGraph.getModuleByUrl(candidate) ?? server.moduleGraph.getModuleById(candidate);
976
+ requestIdHits.push({
977
+ requestId,
978
+ candidate,
979
+ via: mod?.url === candidate ? "url" : "id",
980
+ moduleId: mod?.id
981
+ });
982
+ if (mod?.id) modules.set(mod.id, mod);
983
+ }
984
+ }
985
+ debugHmrV("getModulesForChangedFile registry lookup", {
986
+ file: normalizedFile,
987
+ stylesheetRequestIds,
988
+ requestIdHits,
989
+ resolvedModuleIds: [...modules.keys()]
699
990
  });
700
- return mappedReplacements.length ? Object.fromEntries(mappedReplacements) : void 0;
991
+ return [...modules.values()];
701
992
  }
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
993
+ function isModuleForChangedResource(mod, changedFile, stylesheetRegistry) {
994
+ const normalizedChangedFile = normalizePath(changedFile.split("?")[0]);
995
+ if (normalizePath((mod.file ?? "").split("?")[0]) === normalizedChangedFile) return true;
996
+ if (!mod.id) return false;
997
+ const requestPath = getFilenameFromPath(mod.id);
998
+ return normalizePath((stylesheetRegistry?.resolveExternalSource(requestPath) ?? stylesheetRegistry?.resolveExternalSource(requestPath.replace(/^\//, "")) ?? stylesheetRegistry?.getServedSourcePath(requestPath) ?? stylesheetRegistry?.getServedSourcePath(requestPath.replace(/^\//, "")) ?? "").split("?")[0]) === normalizedChangedFile;
999
+ }
1000
+ function diagnoseComponentStylesheetPipeline(changedFile, directModule, stylesheetRegistry, wrapperModules, stylePreprocessor) {
1001
+ const normalizedFile = normalizePath(changedFile.split("?")[0]);
1002
+ const sourceExists = existsSync(normalizedFile);
1003
+ const sourceCode = sourceExists ? readFileSync(normalizedFile, "utf-8") : void 0;
1004
+ const directRequestPath = directModule.id ? getFilenameFromPath(directModule.id) : void 0;
1005
+ const sourcePath = directRequestPath ? stylesheetRegistry?.resolveExternalSource(directRequestPath) ?? stylesheetRegistry?.resolveExternalSource(directRequestPath.replace(/^\//, "")) ?? stylesheetRegistry?.getServedSourcePath(directRequestPath) ?? stylesheetRegistry?.getServedSourcePath(directRequestPath.replace(/^\//, "")) : normalizedFile;
1006
+ const registryCode = directRequestPath ? stylesheetRegistry?.getServedContent(directRequestPath) : void 0;
1007
+ const trackedRequestIds = stylesheetRegistry?.getRequestIdsForSource(sourcePath ?? "") ?? [];
1008
+ const dependencies = stylesheetRegistry?.getDependenciesForSource(sourcePath ?? "") ?? [];
1009
+ const diagnostics = stylesheetRegistry?.getDiagnosticsForSource(sourcePath ?? "") ?? [];
1010
+ const tags = stylesheetRegistry?.getTagsForSource(sourcePath ?? "") ?? [];
1011
+ const anomalies = [];
1012
+ const hints = [];
1013
+ if (!sourceExists) {
1014
+ anomalies.push("source_file_missing");
1015
+ hints.push("The stylesheet watcher fired for a file that no longer exists on disk.");
1016
+ }
1017
+ if (!registryCode) {
1018
+ anomalies.push("registry_content_missing");
1019
+ hints.push("The stylesheet registry has no served content for the direct module request path.");
1020
+ }
1021
+ if (sourceCode && registryCode) {
1022
+ let expectedRegistryCode = preprocessStylesheet(sourceCode, normalizedFile, stylePreprocessor);
1023
+ expectedRegistryCode = rewriteRelativeCssImports(expectedRegistryCode, normalizedFile);
1024
+ if (describeStylesheetContent(expectedRegistryCode).digest !== describeStylesheetContent(registryCode).digest) {
1025
+ anomalies.push("source_registry_mismatch");
1026
+ hints.push("The source file changed, but the served stylesheet content in the registry is still stale.");
1027
+ }
1028
+ }
1029
+ if (trackedRequestIds.length === 0) {
1030
+ anomalies.push("no_tracked_requests");
1031
+ hints.push("No live stylesheet requests are tracked for this source file, so HMR has no browser-facing target.");
1032
+ }
1033
+ if (trackedRequestIds.some((id) => id.includes("?ngcomp=")) && wrapperModules.length === 0) {
1034
+ anomalies.push("tracked_wrapper_missing_from_module_graph");
1035
+ hints.push("A wrapper request id is known, but Vite did not expose a live wrapper module during this HMR pass.");
1036
+ }
1037
+ if (trackedRequestIds.every((id) => !id.includes("?ngcomp=")) && wrapperModules.length === 0) {
1038
+ anomalies.push("wrapper_not_yet_tracked");
1039
+ hints.push("Only direct stylesheet requests were tracked during this HMR pass; the wrapper request may be appearing too late.");
1040
+ }
1041
+ return {
1042
+ file: changedFile,
1043
+ sourcePath,
1044
+ source: sourceCode ? describeStylesheetContent(rewriteRelativeCssImports(preprocessStylesheet(sourceCode, normalizedFile, stylePreprocessor), normalizedFile)) : void 0,
1045
+ registry: registryCode ? describeStylesheetContent(registryCode) : void 0,
1046
+ dependencies,
1047
+ diagnostics,
1048
+ tags,
1049
+ directModuleId: directModule.id,
1050
+ directModuleUrl: directModule.url,
1051
+ trackedRequestIds,
1052
+ wrapperCount: wrapperModules.length,
1053
+ anomalies,
1054
+ hints
1055
+ };
1056
+ }
1057
+ async function findComponentStylesheetWrapperModules(server, changedFile, directModule, fileModules, stylesheetRegistry) {
1058
+ const wrapperModules = /* @__PURE__ */ new Map();
1059
+ for (const mod of fileModules) if (mod.id && mod.type === "js" && isComponentStyleSheet(mod.id) && isModuleForChangedResource(mod, changedFile, stylesheetRegistry)) wrapperModules.set(mod.id, mod);
1060
+ const directRequestIds = /* @__PURE__ */ new Set();
1061
+ if (directModule.id) directRequestIds.add(directModule.id);
1062
+ if (directModule.url) directRequestIds.add(directModule.url);
1063
+ const requestPath = directModule.id ? getFilenameFromPath(directModule.id) : void 0;
1064
+ const sourcePath = requestPath ? stylesheetRegistry?.resolveExternalSource(requestPath) ?? stylesheetRegistry?.resolveExternalSource(requestPath.replace(/^\//, "")) ?? stylesheetRegistry?.getServedSourcePath(requestPath) ?? stylesheetRegistry?.getServedSourcePath(requestPath.replace(/^\//, "")) : void 0;
1065
+ for (const requestId of stylesheetRegistry?.getRequestIdsForSource(sourcePath ?? "") ?? []) if (requestId.includes("?ngcomp=")) directRequestIds.add(requestId);
1066
+ const candidateWrapperIds = [...directRequestIds].filter((id) => id.includes("?direct&ngcomp=")).map((id) => id.replace("?direct&ngcomp=", "?ngcomp="));
1067
+ const lookupHits = [];
1068
+ for (const candidate of candidateWrapperIds) {
1069
+ const mod = await server.moduleGraph.getModuleByUrl(candidate) ?? server.moduleGraph.getModuleById(candidate);
1070
+ lookupHits.push({
1071
+ candidate,
1072
+ via: mod?.url === candidate ? "url" : mod ? "id" : void 0,
1073
+ moduleId: mod?.id,
1074
+ moduleType: mod?.type
720
1075
  });
1076
+ if (mod?.id && mod.type === "js" && isComponentStyleSheet(mod.id) && isModuleForChangedResource(mod, changedFile, stylesheetRegistry)) wrapperModules.set(mod.id, mod);
1077
+ }
1078
+ debugHmrV("component stylesheet wrapper lookup", {
1079
+ file: changedFile,
1080
+ sourcePath,
1081
+ directModuleId: directModule.id,
1082
+ directModuleUrl: directModule.url,
1083
+ candidateWrapperIds,
1084
+ lookupHits
721
1085
  });
722
- return updatesByFile;
1086
+ if (wrapperModules.size === 0) debugHmrV("component stylesheet wrapper lookup empty", {
1087
+ file: changedFile,
1088
+ sourcePath,
1089
+ directModuleId: directModule.id,
1090
+ directModuleUrl: directModule.url,
1091
+ candidateWrapperIds
1092
+ });
1093
+ return [...wrapperModules.values()];
723
1094
  }
724
1095
  function sendHMRComponentUpdate(server, id) {
1096
+ debugHmrV("ws send: angular component update", {
1097
+ id,
1098
+ timestamp: Date.now()
1099
+ });
725
1100
  server.ws.send("angular:component-update", {
726
1101
  id: encodeURIComponent(id),
727
1102
  timestamp: Date.now()
728
1103
  });
729
1104
  classNames.delete(id);
730
1105
  }
731
- function getFileMetadata(program, angularCompiler, liveReload, disableTypeChecking) {
1106
+ function sendCssUpdate(server, update) {
1107
+ const timestamp = Date.now();
1108
+ debugHmrV("ws send: css-update", {
1109
+ ...update,
1110
+ timestamp
1111
+ });
1112
+ server.ws.send({
1113
+ type: "update",
1114
+ updates: [{
1115
+ type: "css-update",
1116
+ timestamp,
1117
+ path: update.path,
1118
+ acceptedPath: update.acceptedPath
1119
+ }]
1120
+ });
1121
+ }
1122
+ function sendFullReload(server, details) {
1123
+ debugHmrV("ws send: full-reload", details);
1124
+ server.ws.send("analog:debug-full-reload", details);
1125
+ server.ws.send({ type: "full-reload" });
1126
+ }
1127
+ function logComponentStylesheetHmrOutcome(details) {
1128
+ const pitfalls = [];
1129
+ const rejectedPreferredPaths = [];
1130
+ const hints = [];
1131
+ if (details.encapsulation === "shadow") {
1132
+ pitfalls.push("shadow-encapsulation");
1133
+ rejectedPreferredPaths.push("css-update");
1134
+ hints.push("Shadow DOM styles cannot rely on Vite CSS patching because Angular applies them inside a shadow root.");
1135
+ }
1136
+ if (details.diagnosis.anomalies.includes("wrapper_not_yet_tracked")) {
1137
+ pitfalls.push("wrapper-not-yet-tracked");
1138
+ rejectedPreferredPaths.push("css-update");
1139
+ 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.");
1140
+ }
1141
+ if (details.diagnosis.anomalies.includes("tracked_wrapper_missing_from_module_graph")) {
1142
+ pitfalls.push("tracked-wrapper-missing-from-module-graph");
1143
+ rejectedPreferredPaths.push("css-update");
1144
+ hints.push("A wrapper request id is known, but Vite could not resolve a live wrapper module for targeted CSS HMR.");
1145
+ }
1146
+ debugHmrV("component stylesheet hmr outcome", {
1147
+ file: details.file,
1148
+ outcome: details.outcome,
1149
+ encapsulation: details.encapsulation,
1150
+ directModuleId: details.directModuleId,
1151
+ wrapperIds: details.wrapperIds ?? [],
1152
+ preferredPath: details.encapsulation === "shadow" ? "full-reload" : "css-update",
1153
+ rejectedPreferredPaths: [...new Set(rejectedPreferredPaths)],
1154
+ pitfalls: [...new Set(pitfalls)],
1155
+ anomalies: details.diagnosis.anomalies,
1156
+ hints: [...new Set([...details.diagnosis.hints, ...hints])]
1157
+ });
1158
+ }
1159
+ function findTemplateOwnerModules(server, resourceFile) {
1160
+ const candidateTsFiles = [normalizePath(resourceFile.split("?")[0]).replace(/\.(html|htm)$/i, ".ts")];
1161
+ const modules = /* @__PURE__ */ new Map();
1162
+ for (const candidate of candidateTsFiles) server.moduleGraph.getModulesByFile(candidate)?.forEach((mod) => {
1163
+ if (mod.id) modules.set(mod.id, mod);
1164
+ });
1165
+ return [...modules.values()];
1166
+ }
1167
+ /**
1168
+ * @internal
1169
+ */
1170
+ function getFileMetadata(program, angularCompiler, hmrEnabled, disableTypeChecking) {
732
1171
  const ts = require("typescript");
733
1172
  return (file) => {
734
1173
  const sourceFile = program.getSourceFile(file);
@@ -738,12 +1177,17 @@ function getFileMetadata(program, angularCompiler, liveReload, disableTypeChecki
738
1177
  const warnings = diagnostics.filter((d) => d.category === ts.DiagnosticCategory?.Warning).map((d) => d.messageText);
739
1178
  let hmrUpdateCode = void 0;
740
1179
  let hmrEligible = false;
741
- if (liveReload) {
1180
+ if (hmrEnabled) {
742
1181
  for (const node of sourceFile.statements) if (ts.isClassDeclaration(node) && node.name != null) {
743
1182
  hmrUpdateCode = angularCompiler?.emitHmrUpdateModule(node);
744
1183
  if (hmrUpdateCode) {
745
- classNames.set(file, node.name.getText());
1184
+ const className = node.name.getText();
1185
+ classNames.set(file, className);
746
1186
  hmrEligible = true;
1187
+ debugHmr("NgtscProgram emitHmrUpdateModule", {
1188
+ file,
1189
+ className
1190
+ });
747
1191
  }
748
1192
  }
749
1193
  }
@@ -773,40 +1217,18 @@ function markModuleSelfAccepting(mod) {
773
1217
  isSelfAccepting: true
774
1218
  };
775
1219
  }
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
1220
  /**
791
1221
  * Removes leading / and query string from a url path
792
1222
  * e.g. /foo.scss?direct&ngcomp=ng-c3153525609&e=0 returns foo.scss
793
1223
  * @param id
794
1224
  */
795
1225
  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;
1226
+ try {
1227
+ return new URL(id, "http://localhost").pathname.replace(/^\//, "");
1228
+ } catch {
1229
+ const queryIndex = id.indexOf("?");
1230
+ return (queryIndex >= 0 ? id.slice(0, queryIndex) : id).replace(/^\//, "");
1231
+ }
810
1232
  }
811
1233
  //#endregion
812
1234
  export { angular };