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

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 (51) hide show
  1. package/package.json +3 -7
  2. package/setup-vitest.js +154 -193
  3. package/setup-vitest.js.map +1 -1
  4. package/src/index.d.ts +1 -1
  5. package/src/index.js +6 -2
  6. package/src/index.js.map +1 -1
  7. package/src/lib/angular-build-optimizer-plugin.js +48 -63
  8. package/src/lib/angular-build-optimizer-plugin.js.map +1 -1
  9. package/src/lib/angular-jit-plugin.js +31 -37
  10. package/src/lib/angular-jit-plugin.js.map +1 -1
  11. package/src/lib/angular-pending-tasks.plugin.js +17 -18
  12. package/src/lib/angular-pending-tasks.plugin.js.map +1 -1
  13. package/src/lib/angular-vite-plugin.js +790 -1077
  14. package/src/lib/angular-vite-plugin.js.map +1 -1
  15. package/src/lib/angular-vitest-plugin.js +97 -135
  16. package/src/lib/angular-vitest-plugin.js.map +1 -1
  17. package/src/lib/compiler-plugin.js +40 -44
  18. package/src/lib/compiler-plugin.js.map +1 -1
  19. package/src/lib/component-resolvers.js +87 -120
  20. package/src/lib/component-resolvers.js.map +1 -1
  21. package/src/lib/host.js +69 -101
  22. package/src/lib/host.js.map +1 -1
  23. package/src/lib/live-reload-plugin.js +51 -63
  24. package/src/lib/live-reload-plugin.js.map +1 -1
  25. package/src/lib/nx-folder-plugin.js +18 -16
  26. package/src/lib/nx-folder-plugin.js.map +1 -1
  27. package/src/lib/plugins/file-replacements.plugin.js +35 -62
  28. package/src/lib/plugins/file-replacements.plugin.js.map +1 -1
  29. package/src/lib/router-plugin.js +23 -23
  30. package/src/lib/router-plugin.js.map +1 -1
  31. package/src/lib/tools/package.json +2 -5
  32. package/src/lib/tools/src/builders/vite/vite-build.impl.js +31 -38
  33. package/src/lib/tools/src/builders/vite/vite-build.impl.js.map +1 -1
  34. package/src/lib/tools/src/builders/vite-dev-server/dev-server.impl.js +52 -60
  35. package/src/lib/tools/src/builders/vite-dev-server/dev-server.impl.js.map +1 -1
  36. package/src/lib/tools/src/index.js +0 -2
  37. package/src/lib/utils/devkit.js +34 -38
  38. package/src/lib/utils/devkit.js.map +1 -1
  39. package/src/lib/utils/rolldown.js +9 -5
  40. package/src/lib/utils/rolldown.js.map +1 -1
  41. package/src/lib/utils/source-file-cache.js +35 -37
  42. package/src/lib/utils/source-file-cache.js.map +1 -1
  43. package/README.md +0 -91
  44. package/src/lib/models.js +0 -1
  45. package/src/lib/models.js.map +0 -1
  46. package/src/lib/tools/README.md +0 -3
  47. package/src/lib/tools/src/index.js.map +0 -1
  48. package/src/lib/utils/compiler-plugin-options.js +0 -1
  49. package/src/lib/utils/compiler-plugin-options.js.map +0 -1
  50. package/src/lib/utils/hmr-candidates.js +0 -272
  51. package/src/lib/utils/hmr-candidates.js.map +0 -1
@@ -1,1101 +1,814 @@
1
- import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
- import { basename, dirname, isAbsolute, join, relative, resolve, } from 'node:path';
3
- import * as compilerCli from '@angular/compiler-cli';
4
- import { createRequire } from 'node:module';
5
- import * as ngCompiler from '@angular/compiler';
6
- import { globSync } from 'tinyglobby';
7
- import { defaultClientConditions, normalizePath, preprocessCSS, } from 'vite';
8
- import { buildOptimizerPlugin } from './angular-build-optimizer-plugin.js';
9
- import { jitPlugin } from './angular-jit-plugin.js';
10
- import { createCompilerPlugin, createRolldownCompilerPlugin, } from './compiler-plugin.js';
11
- import { StyleUrlsResolver, TemplateUrlsResolver, } from './component-resolvers.js';
12
- import { augmentHostWithCaching, augmentHostWithResources, augmentProgramWithVersioning, mergeTransformers, } from './host.js';
13
- import { angularVitestPlugins } from './angular-vitest-plugin.js';
14
- import { createAngularCompilation, createJitResourceTransformer, SourceFileCache, angularFullVersion, } from './utils/devkit.js';
15
- import { getJsTransformConfigKey, isRolldown } from './utils/rolldown.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 = {}));
1
+ import { angularFullVersion, cjt, createAngularCompilation, sourceFileCache } from "./utils/devkit.js";
2
+ import { getJsTransformConfigKey, isRolldown } from "./utils/rolldown.js";
3
+ import { buildOptimizerPlugin } from "./angular-build-optimizer-plugin.js";
4
+ import { jitPlugin } from "./angular-jit-plugin.js";
5
+ import { createCompilerPlugin, createRolldownCompilerPlugin } from "./compiler-plugin.js";
6
+ import { StyleUrlsResolver, TemplateUrlsResolver } from "./component-resolvers.js";
7
+ import { augmentHostWithCaching, augmentHostWithResources, augmentProgramWithVersioning, mergeTransformers } from "./host.js";
8
+ import { angularVitestPlugins } from "./angular-vitest-plugin.js";
9
+ import { pendingTasksPlugin } from "./angular-pending-tasks.plugin.js";
10
+ import { liveReloadPlugin } from "./live-reload-plugin.js";
11
+ import { nxFolderPlugin } from "./nx-folder-plugin.js";
12
+ import { replaceFiles } from "./plugins/file-replacements.plugin.js";
13
+ import { routerPlugin } from "./router-plugin.js";
14
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
15
+ import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
16
+ import * as compilerCli from "@angular/compiler-cli";
17
+ import { createRequire } from "node:module";
18
+ import * as ngCompiler from "@angular/compiler";
19
+ import { globSync } from "tinyglobby";
20
+ import { defaultClientConditions, normalizePath, preprocessCSS } from "vite";
21
+ import { createHash } from "node:crypto";
22
+ //#region packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts
23
+ var require = createRequire(import.meta.url);
24
+ var DiagnosticModes = /* @__PURE__ */ function(DiagnosticModes) {
25
+ DiagnosticModes[DiagnosticModes["None"] = 0] = "None";
26
+ DiagnosticModes[DiagnosticModes["Option"] = 1] = "Option";
27
+ DiagnosticModes[DiagnosticModes["Syntactic"] = 2] = "Syntactic";
28
+ DiagnosticModes[DiagnosticModes["Semantic"] = 4] = "Semantic";
29
+ DiagnosticModes[DiagnosticModes["All"] = 7] = "All";
30
+ return DiagnosticModes;
31
+ }({});
31
32
  /**
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 useRolldown = isRolldown();
160
- const jsTransformConfigKey = getJsTransformConfigKey();
161
- const jsTransformConfigValue = jsTransformConfigKey === 'oxc' ? oxc : esbuild;
162
- const rolldownOptions = {
163
- plugins: [
164
- createRolldownCompilerPlugin({
165
- tsconfig: preliminaryTsConfigPath,
166
- sourcemap: !isProd,
167
- advancedOptimizations: isProd,
168
- jit,
169
- incremental: watchMode,
170
- }),
171
- ],
172
- };
173
- const esbuildOptions = {
174
- plugins: [
175
- createCompilerPlugin({
176
- tsconfig: preliminaryTsConfigPath,
177
- sourcemap: !isProd,
178
- advancedOptimizations: isProd,
179
- jit,
180
- incremental: watchMode,
181
- }, isTest, !isAstroIntegration),
182
- ],
183
- define: defineOptions,
184
- };
185
- return {
186
- [jsTransformConfigKey]: jsTransformConfigValue,
187
- optimizeDeps: {
188
- include: ['rxjs/operators', 'rxjs'],
189
- exclude: ['@angular/platform-server'],
190
- ...(useRolldown ? { rolldownOptions } : { esbuildOptions }),
191
- },
192
- resolve: {
193
- conditions: [
194
- 'style',
195
- ...(config.resolve?.conditions || defaultClientConditions),
196
- ],
197
- },
198
- };
199
- },
200
- configResolved(config) {
201
- resolvedConfig = config;
202
- if (pluginOptions.useAngularCompilationAPI) {
203
- externalComponentStyles = new Map();
204
- inlineComponentStyles = new Map();
205
- }
206
- if (!jit) {
207
- styleTransform = (code, filename) => preprocessCSS(code, filename, config);
208
- }
209
- if (isTest) {
210
- // set test watch mode
211
- // - vite override from vitest-angular
212
- // - @nx/vite executor set server.watch explicitly to undefined (watch)/null (watch=false)
213
- // - vite config for test.watch variable
214
- // - vitest watch mode detected from the command line
215
- testWatchMode =
216
- !(config.server.watch === null) ||
217
- config.test?.watch === true ||
218
- testWatchMode;
219
- }
220
- },
221
- configureServer(server) {
222
- viteServer = server;
223
- // Add/unlink changes the TypeScript program shape, not just file
224
- // contents, so we need to invalidate both include discovery and the
225
- // cached tsconfig root names before recompiling.
226
- const invalidateCompilationOnFsChange = createFsWatcherCacheInvalidator(invalidateFsCaches, invalidateTsconfigCaches, () => performCompilation(resolvedConfig));
227
- server.watcher.on('add', invalidateCompilationOnFsChange);
228
- server.watcher.on('unlink', invalidateCompilationOnFsChange);
229
- server.watcher.on('change', (file) => {
230
- if (file.includes('tsconfig')) {
231
- invalidateTsconfigCaches();
232
- }
233
- });
234
- },
235
- async buildStart() {
236
- // Defer the first compilation in test mode
237
- if (!isVitestVscode) {
238
- await performCompilation(resolvedConfig);
239
- pendingCompilation = null;
240
- initialCompilation = true;
241
- }
242
- },
243
- async handleHotUpdate(ctx) {
244
- if (TS_EXT_REGEX.test(ctx.file)) {
245
- const [fileId] = ctx.file.split('?');
246
- pendingCompilation = performCompilation(resolvedConfig, [fileId]);
247
- let result;
248
- if (pluginOptions.liveReload) {
249
- await pendingCompilation;
250
- pendingCompilation = null;
251
- result = fileEmitter(fileId);
252
- }
253
- if (pluginOptions.liveReload &&
254
- result?.hmrEligible &&
255
- classNames.get(fileId)) {
256
- const relativeFileId = `${normalizePath(relative(process.cwd(), fileId))}@${classNames.get(fileId)}`;
257
- sendHMRComponentUpdate(ctx.server, relativeFileId);
258
- return ctx.modules.map((mod) => {
259
- if (mod.id === ctx.file) {
260
- return markModuleSelfAccepting(mod);
261
- }
262
- return mod;
263
- });
264
- }
265
- }
266
- if (/\.(html|htm|css|less|sass|scss)$/.test(ctx.file)) {
267
- fileTransformMap.delete(ctx.file.split('?')[0]);
268
- /**
269
- * Check to see if this was a direct request
270
- * for an external resource (styles, html).
271
- */
272
- const isDirect = ctx.modules.find((mod) => ctx.file === mod.file && mod.id?.includes('?direct'));
273
- const isInline = ctx.modules.find((mod) => ctx.file === mod.file && mod.id?.includes('?inline'));
274
- if (isDirect || isInline) {
275
- if (pluginOptions.liveReload && isDirect?.id && isDirect.file) {
276
- const isComponentStyle = isDirect.type === 'css' && isComponentStyleSheet(isDirect.id);
277
- if (isComponentStyle) {
278
- const { encapsulation } = getComponentStyleSheetMeta(isDirect.id);
279
- // Track if the component uses ShadowDOM encapsulation
280
- // Shadow DOM components currently require a full reload.
281
- // Vite's CSS hot replacement does not support shadow root searching.
282
- if (encapsulation !== 'shadow') {
283
- ctx.server.ws.send({
284
- type: 'update',
285
- updates: [
286
- {
287
- type: 'css-update',
288
- timestamp: Date.now(),
289
- path: isDirect.url,
290
- acceptedPath: isDirect.file,
291
- },
292
- ],
293
- });
294
- return ctx.modules
295
- .filter((mod) => {
296
- // Component stylesheets will have 2 modules (*.component.scss and *.component.scss?direct&ngcomp=xyz&e=x)
297
- // 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"
298
- return mod.file !== ctx.file || mod.id !== isDirect.id;
299
- })
300
- .map((mod) => {
301
- if (mod.file === ctx.file) {
302
- return markModuleSelfAccepting(mod);
303
- }
304
- return mod;
305
- });
306
- }
307
- }
308
- }
309
- return ctx.modules;
310
- }
311
- const mods = [];
312
- const updates = [];
313
- ctx.modules.forEach((mod) => {
314
- mod.importers.forEach((imp) => {
315
- ctx.server.moduleGraph.invalidateModule(imp);
316
- if (pluginOptions.liveReload && classNames.get(imp.id)) {
317
- updates.push(imp.id);
318
- }
319
- else {
320
- mods.push(imp);
321
- }
322
- });
323
- });
324
- pendingCompilation = performCompilation(resolvedConfig, [
325
- ...mods.map((mod) => mod.id),
326
- ...updates,
327
- ]);
328
- if (updates.length > 0) {
329
- await pendingCompilation;
330
- pendingCompilation = null;
331
- updates.forEach((updateId) => {
332
- const impRelativeFileId = `${normalizePath(relative(process.cwd(), updateId))}@${classNames.get(updateId)}`;
333
- sendHMRComponentUpdate(ctx.server, impRelativeFileId);
334
- });
335
- return ctx.modules.map((mod) => {
336
- if (mod.id === ctx.file) {
337
- return markModuleSelfAccepting(mod);
338
- }
339
- return mod;
340
- });
341
- }
342
- return mods;
343
- }
344
- // clear HMR updates with a full reload
345
- classNames.clear();
346
- return ctx.modules;
347
- },
348
- resolveId(id, importer) {
349
- if (jit && id.startsWith('angular:jit:')) {
350
- const path = id.split(';')[1];
351
- return `${normalizePath(resolve(dirname(importer), path))}?${id.includes(':style') ? 'inline' : 'raw'}`;
352
- }
353
- // Map angular external styleUrls to the source file
354
- if (isComponentStyleSheet(id)) {
355
- const componentStyles = externalComponentStyles?.get(getFilenameFromPath(id));
356
- if (componentStyles) {
357
- return componentStyles + new URL(id, 'http://localhost').search;
358
- }
359
- }
360
- return undefined;
361
- },
362
- async load(id) {
363
- // Map angular inline styles to the source text
364
- if (isComponentStyleSheet(id)) {
365
- const componentStyles = inlineComponentStyles?.get(getFilenameFromPath(id));
366
- if (componentStyles) {
367
- return componentStyles;
368
- }
369
- }
370
- return;
371
- },
372
- transform: {
373
- filter: {
374
- id: {
375
- include: [TS_EXT_REGEX],
376
- exclude: [/node_modules/, 'type=script', '@ng/component'],
377
- },
378
- },
379
- async handler(code, id) {
380
- /**
381
- * Check for options.transformFilter
382
- */
383
- if (options?.transformFilter &&
384
- !(options?.transformFilter(code, id) ?? true)) {
385
- return;
386
- }
387
- if (pluginOptions.useAngularCompilationAPI) {
388
- const isAngular = /(Component|Directive|Pipe|Injectable|NgModule)\(/.test(code);
389
- if (!isAngular) {
390
- return;
391
- }
392
- }
393
- /**
394
- * Skip transforming content files
395
- */
396
- if (id.includes('?') && id.includes('analog-content-')) {
397
- return;
398
- }
399
- /**
400
- * Encapsulate component stylesheets that use emulated encapsulation
401
- */
402
- if (pluginOptions.liveReload && isComponentStyleSheet(id)) {
403
- const { encapsulation, componentId } = getComponentStyleSheetMeta(id);
404
- if (encapsulation === 'emulated' && componentId) {
405
- const encapsulated = ngCompiler.encapsulateStyle(code, componentId);
406
- return {
407
- code: encapsulated,
408
- map: null,
409
- };
410
- }
411
- }
412
- if (id.includes('.ts?')) {
413
- // Strip the query string off the ID
414
- // in case of a dynamically loaded file
415
- id = id.replace(/\?(.*)/, '');
416
- }
417
- fileTransformMap.set(id, code);
418
- /**
419
- * Re-analyze on each transform
420
- * for test(Vitest)
421
- */
422
- if (isTest) {
423
- if (isVitestVscode && !initialCompilation) {
424
- // Do full initial compilation
425
- pendingCompilation = performCompilation(resolvedConfig);
426
- initialCompilation = true;
427
- }
428
- const tsMod = viteServer?.moduleGraph.getModuleById(id);
429
- if (tsMod) {
430
- const invalidated = tsMod.lastInvalidationTimestamp;
431
- if (testWatchMode && invalidated) {
432
- pendingCompilation = performCompilation(resolvedConfig, [id]);
433
- }
434
- }
435
- }
436
- const hasComponent = code.includes('@Component');
437
- const templateUrls = hasComponent
438
- ? templateUrlsResolver.resolve(code, id)
439
- : [];
440
- const styleUrls = hasComponent
441
- ? styleUrlsResolver.resolve(code, id)
442
- : [];
443
- if (hasComponent && watchMode) {
444
- for (const urlSet of [...templateUrls, ...styleUrls]) {
445
- // `urlSet` is a string where a relative path is joined with an
446
- // absolute path using the `|` symbol.
447
- // For example: `./app.component.html|/home/projects/analog/src/app/app.component.html`.
448
- const [, absoluteFileUrl] = urlSet.split('|');
449
- this.addWatchFile(absoluteFileUrl);
450
- }
451
- }
452
- if (pendingCompilation) {
453
- await pendingCompilation;
454
- pendingCompilation = null;
455
- }
456
- const typescriptResult = fileEmitter(id);
457
- if (typescriptResult?.warnings &&
458
- typescriptResult?.warnings.length > 0) {
459
- this.warn(`${typescriptResult.warnings.join('\n')}`);
460
- }
461
- if (typescriptResult?.errors && typescriptResult?.errors.length > 0) {
462
- this.error(`${typescriptResult.errors.join('\n')}`);
463
- }
464
- // return fileEmitter
465
- let data = typescriptResult?.content ?? '';
466
- if (jit && data.includes('angular:jit:')) {
467
- data = data.replace(/angular:jit:style:inline;/g, 'virtual:angular:jit:style:inline;');
468
- templateUrls.forEach((templateUrlSet) => {
469
- const [templateFile, resolvedTemplateUrl] = templateUrlSet.split('|');
470
- data = data.replace(`angular:jit:template:file;${templateFile}`, `${resolvedTemplateUrl}?raw`);
471
- });
472
- styleUrls.forEach((styleUrlSet) => {
473
- const [styleFile, resolvedStyleUrl] = styleUrlSet.split('|');
474
- data = data.replace(`angular:jit:style:file;${styleFile}`, `${resolvedStyleUrl}?inline`);
475
- });
476
- }
477
- return {
478
- code: data,
479
- map: null,
480
- };
481
- },
482
- },
483
- closeBundle() {
484
- declarationFiles.forEach(({ declarationFileDir, declarationPath, data }) => {
485
- mkdirSync(declarationFileDir, { recursive: true });
486
- writeFileSync(declarationPath, data, 'utf-8');
487
- });
488
- // Tear down the persistent compilation instance at end of build so it
489
- // does not leak memory across unrelated Vite invocations.
490
- angularCompilation?.close?.();
491
- angularCompilation = undefined;
492
- },
493
- };
494
- }
495
- return [
496
- replaceFiles(pluginOptions.fileReplacements, pluginOptions.workspaceRoot),
497
- angularPlugin(),
498
- pluginOptions.liveReload && liveReloadPlugin({ classNames, fileEmitter }),
499
- ...(isTest && !isStackBlitz ? angularVitestPlugins() : []),
500
- (jit &&
501
- jitPlugin({
502
- inlineStylesExtension: pluginOptions.inlineStylesExtension,
503
- })),
504
- buildOptimizerPlugin({
505
- supportedBrowsers: pluginOptions.supportedBrowsers,
506
- jit,
507
- }),
508
- routerPlugin(),
509
- angularFullVersion < 190004 && pendingTasksPlugin(),
510
- nxFolderPlugin(),
511
- ].filter(Boolean);
512
- function findIncludes() {
513
- const workspaceRoot = normalizePath(resolve(pluginOptions.workspaceRoot));
514
- // Map include patterns to absolute workspace paths
515
- const globs = [
516
- ...pluginOptions.include.map((glob) => `${workspaceRoot}${glob}`),
517
- ];
518
- // Discover TypeScript files using tinyglobby
519
- return globSync(globs, {
520
- dot: true,
521
- absolute: true,
522
- });
523
- }
524
- function createTsConfigGetter(tsconfigOrGetter) {
525
- if (typeof tsconfigOrGetter === 'function') {
526
- return tsconfigOrGetter;
527
- }
528
- return () => tsconfigOrGetter || '';
529
- }
530
- function getTsConfigPath(root, tsconfig, isProd, isTest, isLib) {
531
- if (tsconfig && isAbsolute(tsconfig)) {
532
- if (!existsSync(tsconfig)) {
533
- 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.`);
534
- }
535
- return tsconfig;
536
- }
537
- let tsconfigFilePath = './tsconfig.app.json';
538
- if (isLib) {
539
- tsconfigFilePath = isProd
540
- ? './tsconfig.lib.prod.json'
541
- : './tsconfig.lib.json';
542
- }
543
- if (isTest) {
544
- tsconfigFilePath = './tsconfig.spec.json';
545
- }
546
- if (tsconfig) {
547
- tsconfigFilePath = tsconfig;
548
- }
549
- const resolvedPath = resolve(root, tsconfigFilePath);
550
- if (!existsSync(resolvedPath)) {
551
- 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.`);
552
- }
553
- return resolvedPath;
554
- }
555
- function resolveTsConfigPath() {
556
- const tsconfigValue = pluginOptions.tsconfigGetter();
557
- return getTsConfigPath(tsConfigResolutionContext.root, tsconfigValue, tsConfigResolutionContext.isProd, isTest, tsConfigResolutionContext.isLib);
558
- }
559
- /**
560
- * Perform compilation using Angular's private Compilation API.
561
- *
562
- * Key differences from the standard `performCompilation` path:
563
- * 1. The compilation instance is reused across rebuilds (nullish-coalescing
564
- * assignment below) so Angular retains prior state and can diff it to
565
- * produce `templateUpdates` for HMR.
566
- * 2. `ids` (modified files) are forwarded to both the source-file cache and
567
- * `angularCompilation.update()` so that incremental re-analysis is
568
- * scoped to what actually changed.
569
- * 3. `fileReplacements` are converted and passed into Angular's host via
570
- * `toAngularCompilationFileReplacements`.
571
- * 4. `templateUpdates` from the compilation result are mapped back to
572
- * file-level HMR metadata (`hmrUpdateCode`, `hmrEligible`, `classNames`).
573
- */
574
- async function performAngularCompilation(config, ids) {
575
- // Reuse the existing instance so Angular can diff against prior state.
576
- angularCompilation ??= await createAngularCompilation(!!pluginOptions.jit, false);
577
- const modifiedFiles = ids?.length
578
- ? new Set(ids.map((file) => normalizePath(file)))
579
- : undefined;
580
- if (modifiedFiles?.size) {
581
- sourceFileCache.invalidate(modifiedFiles);
582
- }
583
- // Notify Angular of modified files before re-initialization so it can
584
- // scope its incremental analysis.
585
- if (modifiedFiles?.size && angularCompilation.update) {
586
- await angularCompilation.update(modifiedFiles);
587
- }
588
- const resolvedTsConfigPath = resolveTsConfigPath();
589
- const compilationResult = await angularCompilation.initialize(resolvedTsConfigPath, {
590
- // Convert Analog's browser-style `{ replace, with }` entries into the
591
- // `Record<string, string>` shape that Angular's AngularHostOptions
592
- // expects. SSR-only replacements (`{ replace, ssr }`) are intentionally
593
- // excluded — they stay on the Vite runtime side.
594
- fileReplacements: toAngularCompilationFileReplacements(pluginOptions.fileReplacements, pluginOptions.workspaceRoot),
595
- modifiedFiles,
596
- async transformStylesheet(data, containingFile, resourceFile, order, className) {
597
- if (pluginOptions.liveReload) {
598
- const id = createHash('sha256')
599
- .update(containingFile)
600
- .update(className)
601
- .update(String(order))
602
- .update(data)
603
- .digest('hex');
604
- const filename = id + '.' + pluginOptions.inlineStylesExtension;
605
- inlineComponentStyles.set(filename, data);
606
- return filename;
607
- }
608
- const filename = resourceFile ??
609
- containingFile.replace('.ts', `.${options?.inlineStylesExtension}`);
610
- let stylesheetResult;
611
- try {
612
- stylesheetResult = await preprocessCSS(data, `${filename}?direct`, resolvedConfig);
613
- }
614
- catch (e) {
615
- console.error(`${e}`);
616
- }
617
- return stylesheetResult?.code || '';
618
- },
619
- processWebWorker(workerFile, containingFile) {
620
- return '';
621
- },
622
- }, (tsCompilerOptions) => {
623
- if (pluginOptions.liveReload && watchMode) {
624
- tsCompilerOptions['_enableHmr'] = true;
625
- tsCompilerOptions['externalRuntimeStyles'] = true;
626
- // Workaround for https://github.com/angular/angular/issues/59310
627
- // Force extra instructions to be generated for HMR w/defer
628
- tsCompilerOptions['supportTestBed'] = true;
629
- }
630
- if (tsCompilerOptions.compilationMode === 'partial') {
631
- // These options can't be false in partial mode
632
- tsCompilerOptions['supportTestBed'] = true;
633
- tsCompilerOptions['supportJitMode'] = true;
634
- }
635
- if (!isTest && config.build?.lib) {
636
- tsCompilerOptions['declaration'] = true;
637
- tsCompilerOptions['declarationMap'] = watchMode;
638
- tsCompilerOptions['inlineSources'] = true;
639
- }
640
- if (isTest) {
641
- // Allow `TestBed.overrideXXX()` APIs.
642
- tsCompilerOptions['supportTestBed'] = true;
643
- }
644
- return tsCompilerOptions;
645
- });
646
- compilationResult.externalStylesheets?.forEach((value, key) => {
647
- externalComponentStyles?.set(`${value}.css`, key);
648
- });
649
- const diagnostics = await angularCompilation.diagnoseFiles(pluginOptions.disableTypeChecking
650
- ? DiagnosticModes.All & ~DiagnosticModes.Semantic
651
- : DiagnosticModes.All);
652
- const errors = diagnostics.errors?.length ? diagnostics.errors : [];
653
- const warnings = diagnostics.warnings?.length ? diagnostics.warnings : [];
654
- // Angular encodes template updates as `encodedFilePath@ClassName` keys.
655
- // `mapTemplateUpdatesToFiles` decodes them back to absolute file paths so
656
- // we can attach HMR metadata to the correct `EmitFileResult` below.
657
- const templateUpdates = mapTemplateUpdatesToFiles(compilationResult.templateUpdates);
658
- for (const file of await angularCompilation.emitAffectedFiles()) {
659
- const normalizedFilename = normalizePath(file.filename);
660
- const templateUpdate = templateUpdates.get(normalizedFilename);
661
- if (templateUpdate) {
662
- classNames.set(normalizedFilename, templateUpdate.className);
663
- }
664
- // Surface Angular's HMR payloads into Analog's existing live-reload
665
- // flow via the `hmrUpdateCode` / `hmrEligible` fields.
666
- outputFiles.set(normalizedFilename, {
667
- content: file.contents,
668
- dependencies: [],
669
- errors: errors.map((error) => error.text || ''),
670
- warnings: warnings.map((warning) => warning.text || ''),
671
- hmrUpdateCode: templateUpdate?.code,
672
- hmrEligible: !!templateUpdate?.code,
673
- });
674
- }
675
- }
676
- async function performCompilation(config, ids) {
677
- let resolve;
678
- const previousLock = compilationLock;
679
- compilationLock = new Promise((r) => {
680
- resolve = r;
681
- });
682
- try {
683
- await previousLock;
684
- await _doPerformCompilation(config, ids);
685
- }
686
- finally {
687
- resolve();
688
- }
689
- }
690
- /**
691
- * This method share mutable state and performs the actual compilation work.
692
- * It should not be called concurrently. Use `performCompilation` which wraps this method in a lock to ensure only one compilation runs at a time.
693
- */
694
- async function _doPerformCompilation(config, ids) {
695
- // Forward `ids` (modified files) so the Compilation API path can do
696
- // incremental re-analysis instead of a full recompile on every change.
697
- if (pluginOptions.useAngularCompilationAPI) {
698
- await performAngularCompilation(config, ids);
699
- return;
700
- }
701
- const isProd = config.mode === 'production';
702
- const modifiedFiles = new Set(ids ?? []);
703
- sourceFileCache.invalidate(modifiedFiles);
704
- if (ids?.length) {
705
- for (const id of ids || []) {
706
- fileTransformMap.delete(id);
707
- }
708
- }
709
- // Cached include discovery (invalidated only on FS events)
710
- if (pluginOptions.include.length > 0 && includeCache.length === 0) {
711
- includeCache = findIncludes();
712
- }
713
- const resolvedTsConfigPath = resolveTsConfigPath();
714
- const tsconfigKey = [
715
- resolvedTsConfigPath,
716
- isProd ? 'prod' : 'dev',
717
- isTest ? 'test' : 'app',
718
- config.build?.lib ? 'lib' : 'nolib',
719
- ].join('|');
720
- let cached = tsconfigOptionsCache.get(tsconfigKey);
721
- if (!cached) {
722
- const read = compilerCli.readConfiguration(resolvedTsConfigPath, {
723
- suppressOutputPathCheck: true,
724
- outDir: undefined,
725
- sourceMap: false,
726
- inlineSourceMap: !isProd,
727
- inlineSources: !isProd,
728
- declaration: false,
729
- declarationMap: false,
730
- allowEmptyCodegenFiles: false,
731
- annotationsAs: 'decorators',
732
- enableResourceInlining: false,
733
- noEmitOnError: false,
734
- mapRoot: undefined,
735
- sourceRoot: undefined,
736
- supportTestBed: false,
737
- supportJitMode: false,
738
- });
739
- cached = { options: read.options, rootNames: read.rootNames };
740
- tsconfigOptionsCache.set(tsconfigKey, cached);
741
- }
742
- // Clone options before mutation (preserve cache purity)
743
- const tsCompilerOptions = { ...cached.options };
744
- let rootNames = [...cached.rootNames];
745
- if (pluginOptions.liveReload && watchMode) {
746
- tsCompilerOptions['_enableHmr'] = true;
747
- tsCompilerOptions['externalRuntimeStyles'] = true;
748
- // Workaround for https://github.com/angular/angular/issues/59310
749
- // Force extra instructions to be generated for HMR w/defer
750
- tsCompilerOptions['supportTestBed'] = true;
751
- }
752
- if (tsCompilerOptions['compilationMode'] === 'partial') {
753
- // These options can't be false in partial mode
754
- tsCompilerOptions['supportTestBed'] = true;
755
- tsCompilerOptions['supportJitMode'] = true;
756
- }
757
- if (!isTest && config.build?.lib) {
758
- tsCompilerOptions['declaration'] = true;
759
- tsCompilerOptions['declarationMap'] = watchMode;
760
- tsCompilerOptions['inlineSources'] = true;
761
- }
762
- if (isTest) {
763
- // Allow `TestBed.overrideXXX()` APIs.
764
- tsCompilerOptions['supportTestBed'] = true;
765
- }
766
- const replacements = pluginOptions.fileReplacements.map((rp) => join(pluginOptions.workspaceRoot, rp.ssr || rp.with));
767
- // Merge + dedupe root names
768
- rootNames = [...new Set([...rootNames, ...includeCache, ...replacements])];
769
- const hostKey = JSON.stringify(tsCompilerOptions);
770
- let host;
771
- if (cachedHost && cachedHostKey === hostKey) {
772
- host = cachedHost;
773
- }
774
- else {
775
- host = ts.createIncrementalCompilerHost(tsCompilerOptions, {
776
- ...ts.sys,
777
- readFile(path, encoding) {
778
- if (fileTransformMap.has(path)) {
779
- return fileTransformMap.get(path);
780
- }
781
- const file = ts.sys.readFile.call(null, path, encoding);
782
- if (file) {
783
- fileTransformMap.set(path, file);
784
- }
785
- return file;
786
- },
787
- });
788
- cachedHost = host;
789
- cachedHostKey = hostKey;
790
- // Only store cache if in watch mode
791
- if (watchMode) {
792
- augmentHostWithCaching(host, sourceFileCache);
793
- }
794
- }
795
- if (!jit) {
796
- inlineComponentStyles = tsCompilerOptions['externalRuntimeStyles']
797
- ? new Map()
798
- : undefined;
799
- externalComponentStyles = tsCompilerOptions['externalRuntimeStyles']
800
- ? new Map()
801
- : undefined;
802
- augmentHostWithResources(host, styleTransform, {
803
- inlineStylesExtension: pluginOptions.inlineStylesExtension,
804
- isProd,
805
- inlineComponentStyles,
806
- externalComponentStyles,
807
- sourceFileCache,
808
- });
809
- }
810
- /**
811
- * Creates a new NgtscProgram to analyze/re-analyze
812
- * the source files and create a file emitter.
813
- * This is shared between an initial build and a hot update.
814
- */
815
- let typeScriptProgram;
816
- let angularCompiler;
817
- const oldBuilder = builder ?? ts.readBuilderProgram(tsCompilerOptions, host);
818
- if (!jit) {
819
- // Create the Angular specific program that contains the Angular compiler
820
- const angularProgram = new compilerCli.NgtscProgram(rootNames, tsCompilerOptions, host, nextProgram);
821
- angularCompiler = angularProgram.compiler;
822
- typeScriptProgram = angularProgram.compiler.getCurrentProgram();
823
- augmentProgramWithVersioning(typeScriptProgram);
824
- builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(typeScriptProgram, host, oldBuilder);
825
- nextProgram = angularProgram;
826
- }
827
- else {
828
- builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(rootNames, tsCompilerOptions, host, oldBuilder);
829
- typeScriptProgram = builder.getProgram();
830
- }
831
- if (!watchMode) {
832
- // When not in watch mode, the startup cost of the incremental analysis can be avoided by
833
- // using an abstract builder that only wraps a TypeScript program.
834
- builder = ts.createAbstractBuilder(typeScriptProgram, host, oldBuilder);
835
- }
836
- if (angularCompiler) {
837
- await angularCompiler.analyzeAsync();
838
- }
839
- const beforeTransformers = jit
840
- ? [
841
- compilerCli.constructorParametersDownlevelTransform(builder.getProgram()),
842
- createJitResourceTransformer(() => builder.getProgram().getTypeChecker()),
843
- ]
844
- : [];
845
- const transformers = mergeTransformers({ before: beforeTransformers }, jit ? {} : angularCompiler.prepareEmit().transformers);
846
- const fileMetadata = getFileMetadata(builder, angularCompiler, pluginOptions.liveReload, pluginOptions.disableTypeChecking);
847
- const writeFileCallback = (_filename, content, _a, _b, sourceFiles) => {
848
- if (!sourceFiles?.length) {
849
- return;
850
- }
851
- const filename = normalizePath(sourceFiles[0].fileName);
852
- if (filename.includes('ngtypecheck.ts') || filename.includes('.d.')) {
853
- return;
854
- }
855
- const metadata = watchMode ? fileMetadata(filename) : {};
856
- outputFiles.set(filename, {
857
- content,
858
- dependencies: [],
859
- errors: metadata.errors,
860
- warnings: metadata.warnings,
861
- hmrUpdateCode: metadata.hmrUpdateCode,
862
- hmrEligible: metadata.hmrEligible,
863
- });
864
- };
865
- const writeOutputFile = (id) => {
866
- const sourceFile = builder.getSourceFile(id);
867
- if (!sourceFile) {
868
- return;
869
- }
870
- let content = '';
871
- builder.emit(sourceFile, (filename, data) => {
872
- if (/\.[cm]?js$/.test(filename)) {
873
- content = data;
874
- }
875
- if (!watchMode &&
876
- !isTest &&
877
- /\.d\.ts/.test(filename) &&
878
- !filename.includes('.ngtypecheck.')) {
879
- // output to library root instead /src
880
- const declarationPath = resolve(config.root, config.build.outDir, relative(config.root, filename)).replace('/src/', '/');
881
- const declarationFileDir = declarationPath
882
- .replace(basename(filename), '')
883
- .replace('/src/', '/');
884
- declarationFiles.push({
885
- declarationFileDir,
886
- declarationPath,
887
- data,
888
- });
889
- }
890
- }, undefined /* cancellationToken */, undefined /* emitOnlyDtsFiles */, transformers);
891
- writeFileCallback(id, content, false, undefined, [sourceFile]);
892
- if (angularCompiler) {
893
- angularCompiler.incrementalCompilation.recordSuccessfulEmit(sourceFile);
894
- }
895
- };
896
- if (watchMode) {
897
- if (ids && ids.length > 0) {
898
- ids.forEach((id) => writeOutputFile(id));
899
- }
900
- else {
901
- /**
902
- * Only block the server from starting up
903
- * during testing.
904
- */
905
- if (isTest) {
906
- // TypeScript will loop until there are no more affected files in the program
907
- while (builder.emitNextAffectedFile(writeFileCallback, undefined, undefined, transformers)) {
908
- /* empty */
909
- }
910
- }
911
- }
912
- }
913
- if (!isTest) {
914
- /**
915
- * Perf: Output files on demand so the dev server
916
- * isn't blocked when emitting files.
917
- */
918
- outputFile = writeOutputFile;
919
- }
920
- }
33
+ * TypeScript file extension regex
34
+ * Match .(c or m)ts, .ts extensions with an optional ? for query params
35
+ * Ignore .tsx extensions
36
+ */
37
+ var TS_EXT_REGEX = /\.[cm]?(ts)[^x]?\??/;
38
+ var classNames = /* @__PURE__ */ new Map();
39
+ function angular(options) {
40
+ /**
41
+ * Normalize plugin options so defaults
42
+ * are used for values not provided.
43
+ */
44
+ const pluginOptions = {
45
+ tsconfigGetter: createTsConfigGetter(options?.tsconfig),
46
+ workspaceRoot: options?.workspaceRoot ?? process.cwd(),
47
+ inlineStylesExtension: options?.inlineStylesExtension ?? "css",
48
+ advanced: { tsTransformers: {
49
+ before: options?.advanced?.tsTransformers?.before ?? [],
50
+ after: options?.advanced?.tsTransformers?.after ?? [],
51
+ afterDeclarations: options?.advanced?.tsTransformers?.afterDeclarations ?? []
52
+ } },
53
+ supportedBrowsers: options?.supportedBrowsers ?? ["safari 15"],
54
+ jit: options?.jit,
55
+ include: options?.include ?? [],
56
+ additionalContentDirs: options?.additionalContentDirs ?? [],
57
+ liveReload: options?.liveReload ?? false,
58
+ disableTypeChecking: options?.disableTypeChecking ?? true,
59
+ fileReplacements: options?.fileReplacements ?? [],
60
+ useAngularCompilationAPI: options?.experimental?.useAngularCompilationAPI ?? false
61
+ };
62
+ let resolvedConfig;
63
+ let tsConfigResolutionContext = null;
64
+ const ts = require("typescript");
65
+ let builder;
66
+ let nextProgram;
67
+ const tsconfigOptionsCache = /* @__PURE__ */ new Map();
68
+ let cachedHost;
69
+ let cachedHostKey;
70
+ let includeCache = [];
71
+ function invalidateFsCaches() {
72
+ includeCache = [];
73
+ }
74
+ function invalidateTsconfigCaches() {
75
+ tsconfigOptionsCache.clear();
76
+ cachedHost = void 0;
77
+ cachedHostKey = void 0;
78
+ }
79
+ let watchMode = false;
80
+ let testWatchMode = isTestWatchMode();
81
+ let inlineComponentStyles;
82
+ let externalComponentStyles;
83
+ const sourceFileCache$1 = new sourceFileCache();
84
+ const isTest = process.env.NODE_ENV === "test" || !!process.env["VITEST"];
85
+ const isVitestVscode = !!process.env["VITEST_VSCODE"];
86
+ const isStackBlitz = !!process.versions["webcontainer"];
87
+ const isAstroIntegration = process.env["ANALOG_ASTRO"] === "true";
88
+ const jit = typeof pluginOptions?.jit !== "undefined" ? pluginOptions.jit : isTest;
89
+ let viteServer;
90
+ const styleUrlsResolver = new StyleUrlsResolver();
91
+ const templateUrlsResolver = new TemplateUrlsResolver();
92
+ let outputFile;
93
+ const outputFiles = /* @__PURE__ */ new Map();
94
+ const fileEmitter = (file) => {
95
+ outputFile?.(file);
96
+ return outputFiles.get(normalizePath(file));
97
+ };
98
+ let initialCompilation = false;
99
+ const declarationFiles = [];
100
+ const fileTransformMap = /* @__PURE__ */ new Map();
101
+ let styleTransform;
102
+ let pendingCompilation;
103
+ let compilationLock = Promise.resolve();
104
+ let angularCompilation;
105
+ function angularPlugin() {
106
+ let isProd = false;
107
+ if (angularFullVersion < 19e4 || isTest) pluginOptions.liveReload = false;
108
+ if (pluginOptions.useAngularCompilationAPI) {
109
+ if (angularFullVersion < 200100) {
110
+ pluginOptions.useAngularCompilationAPI = false;
111
+ console.warn("[@analogjs/vite-plugin-angular]: The Angular Compilation API is only available with Angular v20.1 and later");
112
+ }
113
+ }
114
+ return {
115
+ name: "@analogjs/vite-plugin-angular",
116
+ async config(config, { command }) {
117
+ watchMode = command === "serve";
118
+ isProd = config.mode === "production" || process.env.NODE_ENV === "production";
119
+ tsConfigResolutionContext = {
120
+ root: config.root || ".",
121
+ isProd,
122
+ isLib: !!config?.build?.lib
123
+ };
124
+ const preliminaryTsConfigPath = resolveTsConfigPath();
125
+ const esbuild = pluginOptions.useAngularCompilationAPI ? void 0 : config.esbuild ?? false;
126
+ const oxc = pluginOptions.useAngularCompilationAPI ? void 0 : config.oxc ?? false;
127
+ const defineOptions = {
128
+ ngJitMode: "false",
129
+ ngI18nClosureMode: "false",
130
+ ...watchMode ? {} : { ngDevMode: "false" }
131
+ };
132
+ const useRolldown = isRolldown();
133
+ const jsTransformConfigKey = getJsTransformConfigKey();
134
+ const jsTransformConfigValue = jsTransformConfigKey === "oxc" ? oxc : esbuild;
135
+ const rolldownOptions = { plugins: [createRolldownCompilerPlugin({
136
+ tsconfig: preliminaryTsConfigPath,
137
+ sourcemap: !isProd,
138
+ advancedOptimizations: isProd,
139
+ jit,
140
+ incremental: watchMode
141
+ })] };
142
+ const esbuildOptions = {
143
+ plugins: [createCompilerPlugin({
144
+ tsconfig: preliminaryTsConfigPath,
145
+ sourcemap: !isProd,
146
+ advancedOptimizations: isProd,
147
+ jit,
148
+ incremental: watchMode
149
+ }, isTest, !isAstroIntegration)],
150
+ define: defineOptions
151
+ };
152
+ return {
153
+ [jsTransformConfigKey]: jsTransformConfigValue,
154
+ optimizeDeps: {
155
+ include: ["rxjs/operators", "rxjs"],
156
+ exclude: ["@angular/platform-server"],
157
+ ...useRolldown ? { rolldownOptions } : { esbuildOptions }
158
+ },
159
+ resolve: { conditions: ["style", ...config.resolve?.conditions || defaultClientConditions] }
160
+ };
161
+ },
162
+ configResolved(config) {
163
+ resolvedConfig = config;
164
+ if (pluginOptions.useAngularCompilationAPI) {
165
+ externalComponentStyles = /* @__PURE__ */ new Map();
166
+ inlineComponentStyles = /* @__PURE__ */ new Map();
167
+ }
168
+ if (!jit) styleTransform = (code, filename) => preprocessCSS(code, filename, config);
169
+ if (isTest) testWatchMode = !(config.server.watch === null) || config.test?.watch === true || testWatchMode;
170
+ },
171
+ configureServer(server) {
172
+ viteServer = server;
173
+ const invalidateCompilationOnFsChange = createFsWatcherCacheInvalidator(invalidateFsCaches, invalidateTsconfigCaches, () => performCompilation(resolvedConfig));
174
+ server.watcher.on("add", invalidateCompilationOnFsChange);
175
+ server.watcher.on("unlink", invalidateCompilationOnFsChange);
176
+ server.watcher.on("change", (file) => {
177
+ if (file.includes("tsconfig")) invalidateTsconfigCaches();
178
+ });
179
+ },
180
+ async buildStart() {
181
+ if (!isVitestVscode) {
182
+ await performCompilation(resolvedConfig);
183
+ pendingCompilation = null;
184
+ initialCompilation = true;
185
+ }
186
+ },
187
+ async handleHotUpdate(ctx) {
188
+ if (TS_EXT_REGEX.test(ctx.file)) {
189
+ const [fileId] = ctx.file.split("?");
190
+ pendingCompilation = performCompilation(resolvedConfig, [fileId]);
191
+ let result;
192
+ if (pluginOptions.liveReload) {
193
+ await pendingCompilation;
194
+ pendingCompilation = null;
195
+ result = fileEmitter(fileId);
196
+ }
197
+ if (pluginOptions.liveReload && result?.hmrEligible && classNames.get(fileId)) {
198
+ const relativeFileId = `${normalizePath(relative(process.cwd(), fileId))}@${classNames.get(fileId)}`;
199
+ sendHMRComponentUpdate(ctx.server, relativeFileId);
200
+ return ctx.modules.map((mod) => {
201
+ if (mod.id === ctx.file) return markModuleSelfAccepting(mod);
202
+ return mod;
203
+ });
204
+ }
205
+ }
206
+ if (/\.(html|htm|css|less|sass|scss)$/.test(ctx.file)) {
207
+ fileTransformMap.delete(ctx.file.split("?")[0]);
208
+ /**
209
+ * Check to see if this was a direct request
210
+ * for an external resource (styles, html).
211
+ */
212
+ const isDirect = ctx.modules.find((mod) => ctx.file === mod.file && mod.id?.includes("?direct"));
213
+ const isInline = ctx.modules.find((mod) => ctx.file === mod.file && mod.id?.includes("?inline"));
214
+ if (isDirect || isInline) {
215
+ if (pluginOptions.liveReload && isDirect?.id && isDirect.file) {
216
+ if (isDirect.type === "css" && isComponentStyleSheet(isDirect.id)) {
217
+ const { encapsulation } = getComponentStyleSheetMeta(isDirect.id);
218
+ if (encapsulation !== "shadow") {
219
+ ctx.server.ws.send({
220
+ type: "update",
221
+ updates: [{
222
+ type: "css-update",
223
+ timestamp: Date.now(),
224
+ path: isDirect.url,
225
+ acceptedPath: isDirect.file
226
+ }]
227
+ });
228
+ return ctx.modules.filter((mod) => {
229
+ return mod.file !== ctx.file || mod.id !== isDirect.id;
230
+ }).map((mod) => {
231
+ if (mod.file === ctx.file) return markModuleSelfAccepting(mod);
232
+ return mod;
233
+ });
234
+ }
235
+ }
236
+ }
237
+ return ctx.modules;
238
+ }
239
+ const mods = [];
240
+ const updates = [];
241
+ ctx.modules.forEach((mod) => {
242
+ mod.importers.forEach((imp) => {
243
+ ctx.server.moduleGraph.invalidateModule(imp);
244
+ if (pluginOptions.liveReload && classNames.get(imp.id)) updates.push(imp.id);
245
+ else mods.push(imp);
246
+ });
247
+ });
248
+ pendingCompilation = performCompilation(resolvedConfig, [...mods.map((mod) => mod.id), ...updates]);
249
+ if (updates.length > 0) {
250
+ await pendingCompilation;
251
+ pendingCompilation = null;
252
+ updates.forEach((updateId) => {
253
+ const impRelativeFileId = `${normalizePath(relative(process.cwd(), updateId))}@${classNames.get(updateId)}`;
254
+ sendHMRComponentUpdate(ctx.server, impRelativeFileId);
255
+ });
256
+ return ctx.modules.map((mod) => {
257
+ if (mod.id === ctx.file) return markModuleSelfAccepting(mod);
258
+ return mod;
259
+ });
260
+ }
261
+ return mods;
262
+ }
263
+ classNames.clear();
264
+ return ctx.modules;
265
+ },
266
+ resolveId(id, importer) {
267
+ if (jit && id.startsWith("angular:jit:")) {
268
+ const path = id.split(";")[1];
269
+ return `${normalizePath(resolve(dirname(importer), path))}?${id.includes(":style") ? "inline" : "raw"}`;
270
+ }
271
+ if (isComponentStyleSheet(id)) {
272
+ const componentStyles = externalComponentStyles?.get(getFilenameFromPath(id));
273
+ if (componentStyles) return componentStyles + new URL(id, "http://localhost").search;
274
+ }
275
+ },
276
+ async load(id) {
277
+ if (isComponentStyleSheet(id)) {
278
+ const componentStyles = inlineComponentStyles?.get(getFilenameFromPath(id));
279
+ if (componentStyles) return componentStyles;
280
+ }
281
+ },
282
+ transform: {
283
+ filter: { id: {
284
+ include: [TS_EXT_REGEX],
285
+ exclude: [
286
+ /node_modules/,
287
+ "type=script",
288
+ "@ng/component"
289
+ ]
290
+ } },
291
+ async handler(code, id) {
292
+ /**
293
+ * Check for options.transformFilter
294
+ */
295
+ if (options?.transformFilter && !(options?.transformFilter(code, id) ?? true)) return;
296
+ if (pluginOptions.useAngularCompilationAPI) {
297
+ if (!/(Component|Directive|Pipe|Injectable|NgModule)\(/.test(code)) return;
298
+ }
299
+ /**
300
+ * Skip transforming content files
301
+ */
302
+ if (id.includes("?") && id.includes("analog-content-")) return;
303
+ /**
304
+ * Encapsulate component stylesheets that use emulated encapsulation
305
+ */
306
+ if (pluginOptions.liveReload && isComponentStyleSheet(id)) {
307
+ const { encapsulation, componentId } = getComponentStyleSheetMeta(id);
308
+ if (encapsulation === "emulated" && componentId) return {
309
+ code: ngCompiler.encapsulateStyle(code, componentId),
310
+ map: null
311
+ };
312
+ }
313
+ if (id.includes(".ts?")) id = id.replace(/\?(.*)/, "");
314
+ fileTransformMap.set(id, code);
315
+ /**
316
+ * Re-analyze on each transform
317
+ * for test(Vitest)
318
+ */
319
+ if (isTest) {
320
+ if (isVitestVscode && !initialCompilation) {
321
+ pendingCompilation = performCompilation(resolvedConfig);
322
+ initialCompilation = true;
323
+ }
324
+ const tsMod = viteServer?.moduleGraph.getModuleById(id);
325
+ if (tsMod) {
326
+ const invalidated = tsMod.lastInvalidationTimestamp;
327
+ if (testWatchMode && invalidated) pendingCompilation = performCompilation(resolvedConfig, [id]);
328
+ }
329
+ }
330
+ const hasComponent = code.includes("@Component");
331
+ const templateUrls = hasComponent ? templateUrlsResolver.resolve(code, id) : [];
332
+ const styleUrls = hasComponent ? styleUrlsResolver.resolve(code, id) : [];
333
+ if (hasComponent && watchMode) for (const urlSet of [...templateUrls, ...styleUrls]) {
334
+ const [, absoluteFileUrl] = urlSet.split("|");
335
+ this.addWatchFile(absoluteFileUrl);
336
+ }
337
+ if (pendingCompilation) {
338
+ await pendingCompilation;
339
+ pendingCompilation = null;
340
+ }
341
+ const typescriptResult = fileEmitter(id);
342
+ if (typescriptResult?.warnings && typescriptResult?.warnings.length > 0) this.warn(`${typescriptResult.warnings.join("\n")}`);
343
+ if (typescriptResult?.errors && typescriptResult?.errors.length > 0) this.error(`${typescriptResult.errors.join("\n")}`);
344
+ let data = typescriptResult?.content ?? "";
345
+ if (jit && data.includes("angular:jit:")) {
346
+ data = data.replace(/angular:jit:style:inline;/g, "virtual:angular:jit:style:inline;");
347
+ templateUrls.forEach((templateUrlSet) => {
348
+ const [templateFile, resolvedTemplateUrl] = templateUrlSet.split("|");
349
+ data = data.replace(`angular:jit:template:file;${templateFile}`, `${resolvedTemplateUrl}?raw`);
350
+ });
351
+ styleUrls.forEach((styleUrlSet) => {
352
+ const [styleFile, resolvedStyleUrl] = styleUrlSet.split("|");
353
+ data = data.replace(`angular:jit:style:file;${styleFile}`, `${resolvedStyleUrl}?inline`);
354
+ });
355
+ }
356
+ return {
357
+ code: data,
358
+ map: null
359
+ };
360
+ }
361
+ },
362
+ closeBundle() {
363
+ declarationFiles.forEach(({ declarationFileDir, declarationPath, data }) => {
364
+ mkdirSync(declarationFileDir, { recursive: true });
365
+ writeFileSync(declarationPath, data, "utf-8");
366
+ });
367
+ angularCompilation?.close?.();
368
+ angularCompilation = void 0;
369
+ }
370
+ };
371
+ }
372
+ return [
373
+ replaceFiles(pluginOptions.fileReplacements, pluginOptions.workspaceRoot),
374
+ angularPlugin(),
375
+ pluginOptions.liveReload && liveReloadPlugin({
376
+ classNames,
377
+ fileEmitter
378
+ }),
379
+ ...isTest && !isStackBlitz ? angularVitestPlugins() : [],
380
+ jit && jitPlugin({ inlineStylesExtension: pluginOptions.inlineStylesExtension }),
381
+ buildOptimizerPlugin({
382
+ supportedBrowsers: pluginOptions.supportedBrowsers,
383
+ jit
384
+ }),
385
+ routerPlugin(),
386
+ angularFullVersion < 190004 && pendingTasksPlugin(),
387
+ nxFolderPlugin()
388
+ ].filter(Boolean);
389
+ function findIncludes() {
390
+ const workspaceRoot = normalizePath(resolve(pluginOptions.workspaceRoot));
391
+ return globSync([...pluginOptions.include.map((glob) => `${workspaceRoot}${glob}`)], {
392
+ dot: true,
393
+ absolute: true
394
+ });
395
+ }
396
+ function createTsConfigGetter(tsconfigOrGetter) {
397
+ if (typeof tsconfigOrGetter === "function") return tsconfigOrGetter;
398
+ return () => tsconfigOrGetter || "";
399
+ }
400
+ function getTsConfigPath(root, tsconfig, isProd, isTest, isLib) {
401
+ if (tsconfig && isAbsolute(tsconfig)) {
402
+ if (!existsSync(tsconfig)) console.error(`[@analogjs/vite-plugin-angular]: Unable to resolve tsconfig at ${tsconfig}. This causes compilation issues. Check the path or set the "tsconfig" property with an absolute path.`);
403
+ return tsconfig;
404
+ }
405
+ let tsconfigFilePath = "./tsconfig.app.json";
406
+ if (isLib) tsconfigFilePath = isProd ? "./tsconfig.lib.prod.json" : "./tsconfig.lib.json";
407
+ if (isTest) tsconfigFilePath = "./tsconfig.spec.json";
408
+ if (tsconfig) tsconfigFilePath = tsconfig;
409
+ const resolvedPath = resolve(root, tsconfigFilePath);
410
+ if (!existsSync(resolvedPath)) console.error(`[@analogjs/vite-plugin-angular]: Unable to resolve tsconfig at ${resolvedPath}. This causes compilation issues. Check the path or set the "tsconfig" property with an absolute path.`);
411
+ return resolvedPath;
412
+ }
413
+ function resolveTsConfigPath() {
414
+ const tsconfigValue = pluginOptions.tsconfigGetter();
415
+ return getTsConfigPath(tsConfigResolutionContext.root, tsconfigValue, tsConfigResolutionContext.isProd, isTest, tsConfigResolutionContext.isLib);
416
+ }
417
+ /**
418
+ * Perform compilation using Angular's private Compilation API.
419
+ *
420
+ * Key differences from the standard `performCompilation` path:
421
+ * 1. The compilation instance is reused across rebuilds (nullish-coalescing
422
+ * assignment below) so Angular retains prior state and can diff it to
423
+ * produce `templateUpdates` for HMR.
424
+ * 2. `ids` (modified files) are forwarded to both the source-file cache and
425
+ * `angularCompilation.update()` so that incremental re-analysis is
426
+ * scoped to what actually changed.
427
+ * 3. `fileReplacements` are converted and passed into Angular's host via
428
+ * `toAngularCompilationFileReplacements`.
429
+ * 4. `templateUpdates` from the compilation result are mapped back to
430
+ * file-level HMR metadata (`hmrUpdateCode`, `hmrEligible`, `classNames`).
431
+ */
432
+ async function performAngularCompilation(config, ids) {
433
+ angularCompilation ??= await createAngularCompilation(!!pluginOptions.jit, false);
434
+ const modifiedFiles = ids?.length ? new Set(ids.map((file) => normalizePath(file))) : void 0;
435
+ if (modifiedFiles?.size) sourceFileCache$1.invalidate(modifiedFiles);
436
+ if (modifiedFiles?.size && angularCompilation.update) await angularCompilation.update(modifiedFiles);
437
+ const resolvedTsConfigPath = resolveTsConfigPath();
438
+ const compilationResult = await angularCompilation.initialize(resolvedTsConfigPath, {
439
+ fileReplacements: toAngularCompilationFileReplacements(pluginOptions.fileReplacements, pluginOptions.workspaceRoot),
440
+ modifiedFiles,
441
+ async transformStylesheet(data, containingFile, resourceFile, order, className) {
442
+ if (pluginOptions.liveReload) {
443
+ const filename = createHash("sha256").update(containingFile).update(className).update(String(order)).update(data).digest("hex") + "." + pluginOptions.inlineStylesExtension;
444
+ inlineComponentStyles.set(filename, data);
445
+ return filename;
446
+ }
447
+ const filename = resourceFile ?? containingFile.replace(".ts", `.${options?.inlineStylesExtension}`);
448
+ let stylesheetResult;
449
+ try {
450
+ stylesheetResult = await preprocessCSS(data, `${filename}?direct`, resolvedConfig);
451
+ } catch (e) {
452
+ console.error(`${e}`);
453
+ }
454
+ return stylesheetResult?.code || "";
455
+ },
456
+ processWebWorker(workerFile, containingFile) {
457
+ return "";
458
+ }
459
+ }, (tsCompilerOptions) => {
460
+ if (pluginOptions.liveReload && watchMode) {
461
+ tsCompilerOptions["_enableHmr"] = true;
462
+ tsCompilerOptions["externalRuntimeStyles"] = true;
463
+ tsCompilerOptions["supportTestBed"] = true;
464
+ }
465
+ if (tsCompilerOptions.compilationMode === "partial") {
466
+ tsCompilerOptions["supportTestBed"] = true;
467
+ tsCompilerOptions["supportJitMode"] = true;
468
+ }
469
+ if (!isTest && config.build?.lib) {
470
+ tsCompilerOptions["declaration"] = true;
471
+ tsCompilerOptions["declarationMap"] = watchMode;
472
+ tsCompilerOptions["inlineSources"] = true;
473
+ }
474
+ if (isTest) tsCompilerOptions["supportTestBed"] = true;
475
+ return tsCompilerOptions;
476
+ });
477
+ compilationResult.externalStylesheets?.forEach((value, key) => {
478
+ externalComponentStyles?.set(`${value}.css`, key);
479
+ });
480
+ const diagnostics = await angularCompilation.diagnoseFiles(pluginOptions.disableTypeChecking ? DiagnosticModes.All & ~DiagnosticModes.Semantic : DiagnosticModes.All);
481
+ const errors = diagnostics.errors?.length ? diagnostics.errors : [];
482
+ const warnings = diagnostics.warnings?.length ? diagnostics.warnings : [];
483
+ const templateUpdates = mapTemplateUpdatesToFiles(compilationResult.templateUpdates);
484
+ for (const file of await angularCompilation.emitAffectedFiles()) {
485
+ const normalizedFilename = normalizePath(file.filename);
486
+ const templateUpdate = templateUpdates.get(normalizedFilename);
487
+ if (templateUpdate) classNames.set(normalizedFilename, templateUpdate.className);
488
+ outputFiles.set(normalizedFilename, {
489
+ content: file.contents,
490
+ dependencies: [],
491
+ errors: errors.map((error) => error.text || ""),
492
+ warnings: warnings.map((warning) => warning.text || ""),
493
+ hmrUpdateCode: templateUpdate?.code,
494
+ hmrEligible: !!templateUpdate?.code
495
+ });
496
+ }
497
+ }
498
+ async function performCompilation(config, ids) {
499
+ let resolve;
500
+ const previousLock = compilationLock;
501
+ compilationLock = new Promise((r) => {
502
+ resolve = r;
503
+ });
504
+ try {
505
+ await previousLock;
506
+ await _doPerformCompilation(config, ids);
507
+ } finally {
508
+ resolve();
509
+ }
510
+ }
511
+ /**
512
+ * This method share mutable state and performs the actual compilation work.
513
+ * It should not be called concurrently. Use `performCompilation` which wraps this method in a lock to ensure only one compilation runs at a time.
514
+ */
515
+ async function _doPerformCompilation(config, ids) {
516
+ if (pluginOptions.useAngularCompilationAPI) {
517
+ await performAngularCompilation(config, ids);
518
+ return;
519
+ }
520
+ const isProd = config.mode === "production";
521
+ const modifiedFiles = new Set(ids ?? []);
522
+ sourceFileCache$1.invalidate(modifiedFiles);
523
+ if (ids?.length) for (const id of ids || []) fileTransformMap.delete(id);
524
+ if (pluginOptions.include.length > 0 && includeCache.length === 0) includeCache = findIncludes();
525
+ const resolvedTsConfigPath = resolveTsConfigPath();
526
+ const tsconfigKey = [
527
+ resolvedTsConfigPath,
528
+ isProd ? "prod" : "dev",
529
+ isTest ? "test" : "app",
530
+ config.build?.lib ? "lib" : "nolib"
531
+ ].join("|");
532
+ let cached = tsconfigOptionsCache.get(tsconfigKey);
533
+ if (!cached) {
534
+ const read = compilerCli.readConfiguration(resolvedTsConfigPath, {
535
+ suppressOutputPathCheck: true,
536
+ outDir: void 0,
537
+ sourceMap: false,
538
+ inlineSourceMap: !isProd,
539
+ inlineSources: !isProd,
540
+ declaration: false,
541
+ declarationMap: false,
542
+ allowEmptyCodegenFiles: false,
543
+ annotationsAs: "decorators",
544
+ enableResourceInlining: false,
545
+ noEmitOnError: false,
546
+ mapRoot: void 0,
547
+ sourceRoot: void 0,
548
+ supportTestBed: false,
549
+ supportJitMode: false
550
+ });
551
+ cached = {
552
+ options: read.options,
553
+ rootNames: read.rootNames
554
+ };
555
+ tsconfigOptionsCache.set(tsconfigKey, cached);
556
+ }
557
+ const tsCompilerOptions = { ...cached.options };
558
+ let rootNames = [...cached.rootNames];
559
+ if (pluginOptions.liveReload && watchMode) {
560
+ tsCompilerOptions["_enableHmr"] = true;
561
+ tsCompilerOptions["externalRuntimeStyles"] = true;
562
+ tsCompilerOptions["supportTestBed"] = true;
563
+ }
564
+ if (tsCompilerOptions["compilationMode"] === "partial") {
565
+ tsCompilerOptions["supportTestBed"] = true;
566
+ tsCompilerOptions["supportJitMode"] = true;
567
+ }
568
+ if (!isTest && config.build?.lib) {
569
+ tsCompilerOptions["declaration"] = true;
570
+ tsCompilerOptions["declarationMap"] = watchMode;
571
+ tsCompilerOptions["inlineSources"] = true;
572
+ }
573
+ if (isTest) tsCompilerOptions["supportTestBed"] = true;
574
+ const replacements = pluginOptions.fileReplacements.map((rp) => join(pluginOptions.workspaceRoot, rp.ssr || rp.with));
575
+ rootNames = [...new Set([
576
+ ...rootNames,
577
+ ...includeCache,
578
+ ...replacements
579
+ ])];
580
+ const hostKey = JSON.stringify(tsCompilerOptions);
581
+ let host;
582
+ if (cachedHost && cachedHostKey === hostKey) host = cachedHost;
583
+ else {
584
+ host = ts.createIncrementalCompilerHost(tsCompilerOptions, {
585
+ ...ts.sys,
586
+ readFile(path, encoding) {
587
+ if (fileTransformMap.has(path)) return fileTransformMap.get(path);
588
+ const file = ts.sys.readFile.call(null, path, encoding);
589
+ if (file) fileTransformMap.set(path, file);
590
+ return file;
591
+ }
592
+ });
593
+ cachedHost = host;
594
+ cachedHostKey = hostKey;
595
+ if (watchMode) augmentHostWithCaching(host, sourceFileCache$1);
596
+ }
597
+ if (!jit) {
598
+ inlineComponentStyles = tsCompilerOptions["externalRuntimeStyles"] ? /* @__PURE__ */ new Map() : void 0;
599
+ externalComponentStyles = tsCompilerOptions["externalRuntimeStyles"] ? /* @__PURE__ */ new Map() : void 0;
600
+ augmentHostWithResources(host, styleTransform, {
601
+ inlineStylesExtension: pluginOptions.inlineStylesExtension,
602
+ isProd,
603
+ inlineComponentStyles,
604
+ externalComponentStyles,
605
+ sourceFileCache: sourceFileCache$1
606
+ });
607
+ }
608
+ /**
609
+ * Creates a new NgtscProgram to analyze/re-analyze
610
+ * the source files and create a file emitter.
611
+ * This is shared between an initial build and a hot update.
612
+ */
613
+ let typeScriptProgram;
614
+ let angularCompiler;
615
+ const oldBuilder = builder ?? ts.readBuilderProgram(tsCompilerOptions, host);
616
+ if (!jit) {
617
+ const angularProgram = new compilerCli.NgtscProgram(rootNames, tsCompilerOptions, host, nextProgram);
618
+ angularCompiler = angularProgram.compiler;
619
+ typeScriptProgram = angularProgram.compiler.getCurrentProgram();
620
+ augmentProgramWithVersioning(typeScriptProgram);
621
+ builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(typeScriptProgram, host, oldBuilder);
622
+ nextProgram = angularProgram;
623
+ } else {
624
+ builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(rootNames, tsCompilerOptions, host, oldBuilder);
625
+ typeScriptProgram = builder.getProgram();
626
+ }
627
+ if (!watchMode) builder = ts.createAbstractBuilder(typeScriptProgram, host, oldBuilder);
628
+ if (angularCompiler) await angularCompiler.analyzeAsync();
629
+ const transformers = mergeTransformers({ before: jit ? [compilerCli.constructorParametersDownlevelTransform(builder.getProgram()), cjt(() => builder.getProgram().getTypeChecker())] : [] }, jit ? {} : angularCompiler.prepareEmit().transformers);
630
+ const fileMetadata = getFileMetadata(builder, angularCompiler, pluginOptions.liveReload, pluginOptions.disableTypeChecking);
631
+ const writeFileCallback = (_filename, content, _a, _b, sourceFiles) => {
632
+ if (!sourceFiles?.length) return;
633
+ const filename = normalizePath(sourceFiles[0].fileName);
634
+ if (filename.includes("ngtypecheck.ts") || filename.includes(".d.")) return;
635
+ const metadata = watchMode ? fileMetadata(filename) : {};
636
+ outputFiles.set(filename, {
637
+ content,
638
+ dependencies: [],
639
+ errors: metadata.errors,
640
+ warnings: metadata.warnings,
641
+ hmrUpdateCode: metadata.hmrUpdateCode,
642
+ hmrEligible: metadata.hmrEligible
643
+ });
644
+ };
645
+ const writeOutputFile = (id) => {
646
+ const sourceFile = builder.getSourceFile(id);
647
+ if (!sourceFile) return;
648
+ let content = "";
649
+ builder.emit(sourceFile, (filename, data) => {
650
+ if (/\.[cm]?js$/.test(filename)) content = data;
651
+ if (!watchMode && !isTest && /\.d\.ts/.test(filename) && !filename.includes(".ngtypecheck.")) {
652
+ const declarationPath = resolve(config.root, config.build.outDir, relative(config.root, filename)).replace("/src/", "/");
653
+ const declarationFileDir = declarationPath.replace(basename(filename), "").replace("/src/", "/");
654
+ declarationFiles.push({
655
+ declarationFileDir,
656
+ declarationPath,
657
+ data
658
+ });
659
+ }
660
+ }, void 0, void 0, transformers);
661
+ writeFileCallback(id, content, false, void 0, [sourceFile]);
662
+ if (angularCompiler) angularCompiler.incrementalCompilation.recordSuccessfulEmit(sourceFile);
663
+ };
664
+ if (watchMode) {
665
+ if (ids && ids.length > 0) ids.forEach((id) => writeOutputFile(id));
666
+ else if (isTest) while (builder.emitNextAffectedFile(writeFileCallback, void 0, void 0, transformers));
667
+ }
668
+ if (!isTest)
669
+ /**
670
+ * Perf: Output files on demand so the dev server
671
+ * isn't blocked when emitting files.
672
+ */
673
+ outputFile = writeOutputFile;
674
+ }
921
675
  }
922
- export function createFsWatcherCacheInvalidator(invalidateFsCaches, invalidateTsconfigCaches, performCompilation) {
923
- return async () => {
924
- invalidateFsCaches();
925
- invalidateTsconfigCaches();
926
- await performCompilation();
927
- };
676
+ function createFsWatcherCacheInvalidator(invalidateFsCaches, invalidateTsconfigCaches, performCompilation) {
677
+ return async () => {
678
+ invalidateFsCaches();
679
+ invalidateTsconfigCaches();
680
+ await performCompilation();
681
+ };
928
682
  }
929
683
  /**
930
- * Convert Analog/Angular CLI-style file replacements into the flat record
931
- * expected by `AngularHostOptions.fileReplacements`.
932
- *
933
- * Only browser replacements (`{ replace, with }`) are converted. SSR-only
934
- * replacements (`{ replace, ssr }`) are left for the Vite runtime plugin to
935
- * handle — they should not be baked into the Angular compilation host because
936
- * that would apply them to both browser and server builds.
937
- *
938
- * Relative paths are resolved against `workspaceRoot` so that the host
939
- * receives the same absolute paths it would get from the Angular CLI.
940
- */
941
- export function toAngularCompilationFileReplacements(replacements, workspaceRoot) {
942
- const mappedReplacements = replacements.flatMap((replacement) => {
943
- // Skip SSR-only entries they use `ssr` instead of `with`.
944
- if (!('with' in replacement)) {
945
- return [];
946
- }
947
- return [
948
- [
949
- isAbsolute(replacement.replace)
950
- ? replacement.replace
951
- : resolve(workspaceRoot, replacement.replace),
952
- isAbsolute(replacement.with)
953
- ? replacement.with
954
- : resolve(workspaceRoot, replacement.with),
955
- ],
956
- ];
957
- });
958
- return mappedReplacements.length
959
- ? Object.fromEntries(mappedReplacements)
960
- : undefined;
684
+ * Convert Analog/Angular CLI-style file replacements into the flat record
685
+ * expected by `AngularHostOptions.fileReplacements`.
686
+ *
687
+ * Only browser replacements (`{ replace, with }`) are converted. SSR-only
688
+ * replacements (`{ replace, ssr }`) are left for the Vite runtime plugin to
689
+ * handle — they should not be baked into the Angular compilation host because
690
+ * that would apply them to both browser and server builds.
691
+ *
692
+ * Relative paths are resolved against `workspaceRoot` so that the host
693
+ * receives the same absolute paths it would get from the Angular CLI.
694
+ */
695
+ function toAngularCompilationFileReplacements(replacements, workspaceRoot) {
696
+ const mappedReplacements = replacements.flatMap((replacement) => {
697
+ if (!("with" in replacement)) return [];
698
+ return [[isAbsolute(replacement.replace) ? replacement.replace : resolve(workspaceRoot, replacement.replace), isAbsolute(replacement.with) ? replacement.with : resolve(workspaceRoot, replacement.with)]];
699
+ });
700
+ return mappedReplacements.length ? Object.fromEntries(mappedReplacements) : void 0;
961
701
  }
962
702
  /**
963
- * Map Angular's `templateUpdates` (keyed by `encodedFilePath@ClassName`)
964
- * back to absolute file paths with their associated HMR code and component
965
- * class name.
966
- *
967
- * Angular's private Compilation API emits template update keys in the form
968
- * `encodeURIComponent(relativePath + '@' + className)`. We decode and resolve
969
- * them so the caller can look up updates by the same normalized absolute path
970
- * used elsewhere in the plugin (`outputFiles`, `classNames`, etc.).
971
- */
972
- export function mapTemplateUpdatesToFiles(templateUpdates) {
973
- const updatesByFile = new Map();
974
- templateUpdates?.forEach((code, encodedUpdateId) => {
975
- const [file, className = ''] = decodeURIComponent(encodedUpdateId).split('@');
976
- const resolvedFile = normalizePath(resolve(process.cwd(), file));
977
- updatesByFile.set(resolvedFile, {
978
- className,
979
- code,
980
- });
981
- });
982
- return updatesByFile;
703
+ * Map Angular's `templateUpdates` (keyed by `encodedFilePath@ClassName`)
704
+ * back to absolute file paths with their associated HMR code and component
705
+ * class name.
706
+ *
707
+ * Angular's private Compilation API emits template update keys in the form
708
+ * `encodeURIComponent(relativePath + '@' + className)`. We decode and resolve
709
+ * them so the caller can look up updates by the same normalized absolute path
710
+ * used elsewhere in the plugin (`outputFiles`, `classNames`, etc.).
711
+ */
712
+ function mapTemplateUpdatesToFiles(templateUpdates) {
713
+ const updatesByFile = /* @__PURE__ */ new Map();
714
+ templateUpdates?.forEach((code, encodedUpdateId) => {
715
+ const [file, className = ""] = decodeURIComponent(encodedUpdateId).split("@");
716
+ const resolvedFile = normalizePath(resolve(process.cwd(), file));
717
+ updatesByFile.set(resolvedFile, {
718
+ className,
719
+ code
720
+ });
721
+ });
722
+ return updatesByFile;
983
723
  }
984
724
  function sendHMRComponentUpdate(server, id) {
985
- server.ws.send('angular:component-update', {
986
- id: encodeURIComponent(id),
987
- timestamp: Date.now(),
988
- });
989
- classNames.delete(id);
725
+ server.ws.send("angular:component-update", {
726
+ id: encodeURIComponent(id),
727
+ timestamp: Date.now()
728
+ });
729
+ classNames.delete(id);
990
730
  }
991
- export function getFileMetadata(program, angularCompiler, liveReload, disableTypeChecking) {
992
- const ts = require('typescript');
993
- return (file) => {
994
- const sourceFile = program.getSourceFile(file);
995
- if (!sourceFile) {
996
- return {};
997
- }
998
- const diagnostics = getDiagnosticsForSourceFile(sourceFile, !!disableTypeChecking, program, angularCompiler);
999
- const errors = diagnostics
1000
- .filter((d) => d.category === ts.DiagnosticCategory?.Error)
1001
- .map((d) => typeof d.messageText === 'object'
1002
- ? d.messageText.messageText
1003
- : d.messageText);
1004
- const warnings = diagnostics
1005
- .filter((d) => d.category === ts.DiagnosticCategory?.Warning)
1006
- .map((d) => d.messageText);
1007
- let hmrUpdateCode = undefined;
1008
- let hmrEligible = false;
1009
- if (liveReload) {
1010
- for (const node of sourceFile.statements) {
1011
- if (ts.isClassDeclaration(node) && node.name != null) {
1012
- hmrUpdateCode = angularCompiler?.emitHmrUpdateModule(node);
1013
- if (hmrUpdateCode) {
1014
- classNames.set(file, node.name.getText());
1015
- hmrEligible = true;
1016
- }
1017
- }
1018
- }
1019
- }
1020
- return { errors, warnings, hmrUpdateCode, hmrEligible };
1021
- };
731
+ function getFileMetadata(program, angularCompiler, liveReload, disableTypeChecking) {
732
+ const ts = require("typescript");
733
+ return (file) => {
734
+ const sourceFile = program.getSourceFile(file);
735
+ if (!sourceFile) return {};
736
+ const diagnostics = getDiagnosticsForSourceFile(sourceFile, !!disableTypeChecking, program, angularCompiler);
737
+ const errors = diagnostics.filter((d) => d.category === ts.DiagnosticCategory?.Error).map((d) => typeof d.messageText === "object" ? d.messageText.messageText : d.messageText);
738
+ const warnings = diagnostics.filter((d) => d.category === ts.DiagnosticCategory?.Warning).map((d) => d.messageText);
739
+ let hmrUpdateCode = void 0;
740
+ let hmrEligible = false;
741
+ if (liveReload) {
742
+ for (const node of sourceFile.statements) if (ts.isClassDeclaration(node) && node.name != null) {
743
+ hmrUpdateCode = angularCompiler?.emitHmrUpdateModule(node);
744
+ if (hmrUpdateCode) {
745
+ classNames.set(file, node.name.getText());
746
+ hmrEligible = true;
747
+ }
748
+ }
749
+ }
750
+ return {
751
+ errors,
752
+ warnings,
753
+ hmrUpdateCode,
754
+ hmrEligible
755
+ };
756
+ };
1022
757
  }
1023
758
  function getDiagnosticsForSourceFile(sourceFile, disableTypeChecking, program, angularCompiler) {
1024
- const syntacticDiagnostics = program.getSyntacticDiagnostics(sourceFile);
1025
- if (disableTypeChecking) {
1026
- // Syntax errors are cheap to compute and the app will not run if there are any
1027
- // So always show these types of errors regardless if type checking is disabled
1028
- return syntacticDiagnostics;
1029
- }
1030
- const semanticDiagnostics = program.getSemanticDiagnostics(sourceFile);
1031
- const angularDiagnostics = angularCompiler
1032
- ? angularCompiler.getDiagnosticsForFile(sourceFile, 1)
1033
- : [];
1034
- return [
1035
- ...syntacticDiagnostics,
1036
- ...semanticDiagnostics,
1037
- ...angularDiagnostics,
1038
- ];
759
+ const syntacticDiagnostics = program.getSyntacticDiagnostics(sourceFile);
760
+ if (disableTypeChecking) return syntacticDiagnostics;
761
+ const semanticDiagnostics = program.getSemanticDiagnostics(sourceFile);
762
+ const angularDiagnostics = angularCompiler ? angularCompiler.getDiagnosticsForFile(sourceFile, 1) : [];
763
+ return [
764
+ ...syntacticDiagnostics,
765
+ ...semanticDiagnostics,
766
+ ...angularDiagnostics
767
+ ];
1039
768
  }
1040
769
  function markModuleSelfAccepting(mod) {
1041
- // support Vite 6
1042
- if ('_clientModule' in mod) {
1043
- mod['_clientModule'].isSelfAccepting = true;
1044
- }
1045
- return {
1046
- ...mod,
1047
- isSelfAccepting: true,
1048
- };
770
+ if ("_clientModule" in mod) mod["_clientModule"].isSelfAccepting = true;
771
+ return {
772
+ ...mod,
773
+ isSelfAccepting: true
774
+ };
1049
775
  }
1050
776
  function isComponentStyleSheet(id) {
1051
- return id.includes('ngcomp=');
777
+ return id.includes("ngcomp=");
1052
778
  }
1053
779
  function getComponentStyleSheetMeta(id) {
1054
- const params = new URL(id, 'http://localhost').searchParams;
1055
- const encapsulationMapping = {
1056
- '0': 'emulated',
1057
- '2': 'none',
1058
- '3': 'shadow',
1059
- };
1060
- return {
1061
- componentId: params.get('ngcomp'),
1062
- encapsulation: encapsulationMapping[params.get('e')],
1063
- };
780
+ const params = new URL(id, "http://localhost").searchParams;
781
+ return {
782
+ componentId: params.get("ngcomp"),
783
+ encapsulation: {
784
+ "0": "emulated",
785
+ "2": "none",
786
+ "3": "shadow"
787
+ }[params.get("e")]
788
+ };
1064
789
  }
1065
790
  /**
1066
- * Removes leading / and query string from a url path
1067
- * e.g. /foo.scss?direct&ngcomp=ng-c3153525609&e=0 returns foo.scss
1068
- * @param id
1069
- */
791
+ * Removes leading / and query string from a url path
792
+ * e.g. /foo.scss?direct&ngcomp=ng-c3153525609&e=0 returns foo.scss
793
+ * @param id
794
+ */
1070
795
  function getFilenameFromPath(id) {
1071
- return new URL(id, 'http://localhost').pathname.replace(/^\//, '');
796
+ return new URL(id, "http://localhost").pathname.replace(/^\//, "");
1072
797
  }
1073
798
  /**
1074
- * Checks for vitest run from the command line
1075
- * @returns boolean
1076
- */
1077
- export function isTestWatchMode(args = process.argv) {
1078
- // vitest --run
1079
- const hasRun = args.find((arg) => arg.includes('--run'));
1080
- if (hasRun) {
1081
- return false;
1082
- }
1083
- // vitest --no-run
1084
- const hasNoRun = args.find((arg) => arg.includes('--no-run'));
1085
- if (hasNoRun) {
1086
- return true;
1087
- }
1088
- // check for --watch=false or --no-watch
1089
- const hasWatch = args.find((arg) => arg.includes('watch'));
1090
- if (hasWatch && ['false', 'no'].some((neg) => hasWatch.includes(neg))) {
1091
- return false;
1092
- }
1093
- // check for --watch false
1094
- const watchIndex = args.findIndex((arg) => arg.includes('watch'));
1095
- const watchArg = args[watchIndex + 1];
1096
- if (watchArg && watchArg === 'false') {
1097
- return false;
1098
- }
1099
- return true;
799
+ * Checks for vitest run from the command line
800
+ * @returns boolean
801
+ */
802
+ function isTestWatchMode(args = process.argv) {
803
+ if (args.find((arg) => arg.includes("--run"))) return false;
804
+ if (args.find((arg) => arg.includes("--no-run"))) return true;
805
+ const hasWatch = args.find((arg) => arg.includes("watch"));
806
+ if (hasWatch && ["false", "no"].some((neg) => hasWatch.includes(neg))) return false;
807
+ const watchArg = args[args.findIndex((arg) => arg.includes("watch")) + 1];
808
+ if (watchArg && watchArg === "false") return false;
809
+ return true;
1100
810
  }
811
+ //#endregion
812
+ export { angular };
813
+
1101
814
  //# sourceMappingURL=angular-vite-plugin.js.map