@analogjs/vite-plugin-angular 3.0.0-alpha.6 → 3.0.0-alpha.61

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