@analogjs/vite-plugin-angular 2.5.0-beta.5 → 2.5.0-beta.50

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 (82) hide show
  1. package/README.md +24 -0
  2. package/package.json +23 -5
  3. package/src/lib/angular-vite-plugin.d.ts +12 -1
  4. package/src/lib/angular-vite-plugin.js +105 -282
  5. package/src/lib/angular-vite-plugin.js.map +1 -1
  6. package/src/lib/angular-vitest-plugin.js +2 -2
  7. package/src/lib/angular-vitest-plugin.js.map +1 -1
  8. package/src/lib/compiler/angular-version.d.ts +19 -0
  9. package/src/lib/compiler/angular-version.js +42 -0
  10. package/src/lib/compiler/angular-version.js.map +1 -0
  11. package/src/lib/compiler/class-field-lowering.d.ts +23 -0
  12. package/src/lib/compiler/class-field-lowering.js +213 -0
  13. package/src/lib/compiler/class-field-lowering.js.map +1 -0
  14. package/src/lib/compiler/compile.d.ts +44 -0
  15. package/src/lib/compiler/compile.js +1160 -0
  16. package/src/lib/compiler/compile.js.map +1 -0
  17. package/src/lib/compiler/constants.d.ts +18 -0
  18. package/src/lib/compiler/constants.js +48 -0
  19. package/src/lib/compiler/constants.js.map +1 -0
  20. package/src/lib/compiler/debug.d.ts +22 -0
  21. package/src/lib/compiler/debug.js +35 -0
  22. package/src/lib/compiler/debug.js.map +1 -0
  23. package/src/lib/compiler/defer.d.ts +47 -0
  24. package/src/lib/compiler/defer.js +203 -0
  25. package/src/lib/compiler/defer.js.map +1 -0
  26. package/src/lib/compiler/dts-reader.d.ts +35 -0
  27. package/src/lib/compiler/dts-reader.js +526 -0
  28. package/src/lib/compiler/dts-reader.js.map +1 -0
  29. package/src/lib/compiler/hmr.d.ts +16 -0
  30. package/src/lib/compiler/hmr.js +80 -0
  31. package/src/lib/compiler/hmr.js.map +1 -0
  32. package/src/lib/compiler/index.d.ts +7 -0
  33. package/src/lib/compiler/index.js +8 -0
  34. package/src/lib/compiler/index.js.map +1 -0
  35. package/src/lib/compiler/jit-metadata.d.ts +14 -0
  36. package/src/lib/compiler/jit-metadata.js +224 -0
  37. package/src/lib/compiler/jit-metadata.js.map +1 -0
  38. package/src/lib/compiler/jit-transform.d.ts +24 -0
  39. package/src/lib/compiler/jit-transform.js +269 -0
  40. package/src/lib/compiler/jit-transform.js.map +1 -0
  41. package/src/lib/compiler/js-emitter.d.ts +10 -0
  42. package/src/lib/compiler/js-emitter.js +502 -0
  43. package/src/lib/compiler/js-emitter.js.map +1 -0
  44. package/src/lib/compiler/metadata.d.ts +57 -0
  45. package/src/lib/compiler/metadata.js +894 -0
  46. package/src/lib/compiler/metadata.js.map +1 -0
  47. package/src/lib/compiler/registry.d.ts +49 -0
  48. package/src/lib/compiler/registry.js +273 -0
  49. package/src/lib/compiler/registry.js.map +1 -0
  50. package/src/lib/compiler/resource-inliner.d.ts +21 -0
  51. package/src/lib/compiler/resource-inliner.js +200 -0
  52. package/src/lib/compiler/resource-inliner.js.map +1 -0
  53. package/src/lib/compiler/style-ast.d.ts +8 -0
  54. package/src/lib/compiler/style-ast.js +110 -0
  55. package/src/lib/compiler/style-ast.js.map +1 -0
  56. package/src/lib/compiler/styles.d.ts +13 -0
  57. package/src/lib/compiler/styles.js +60 -0
  58. package/src/lib/compiler/styles.js.map +1 -0
  59. package/src/lib/compiler/test-helpers.d.ts +7 -0
  60. package/src/lib/compiler/test-helpers.js +28 -0
  61. package/src/lib/compiler/test-helpers.js.map +1 -0
  62. package/src/lib/compiler/type-elision.d.ts +26 -0
  63. package/src/lib/compiler/type-elision.js +313 -0
  64. package/src/lib/compiler/type-elision.js.map +1 -0
  65. package/src/lib/compiler/utils.d.ts +10 -0
  66. package/src/lib/compiler/utils.js +95 -0
  67. package/src/lib/compiler/utils.js.map +1 -0
  68. package/src/lib/fast-compile-plugin.d.ts +28 -0
  69. package/src/lib/fast-compile-plugin.js +404 -0
  70. package/src/lib/fast-compile-plugin.js.map +1 -0
  71. package/src/lib/utils/plugin-config.d.ts +45 -0
  72. package/src/lib/utils/plugin-config.js +89 -0
  73. package/src/lib/utils/plugin-config.js.map +1 -0
  74. package/src/lib/utils/safe-module-paths.d.ts +16 -0
  75. package/src/lib/utils/safe-module-paths.js +26 -0
  76. package/src/lib/utils/safe-module-paths.js.map +1 -0
  77. package/src/lib/utils/virtual-ids.d.ts +4 -0
  78. package/src/lib/utils/virtual-ids.js +30 -0
  79. package/src/lib/utils/virtual-ids.js.map +1 -0
  80. package/src/lib/utils/virtual-resources.d.ts +19 -0
  81. package/src/lib/utils/virtual-resources.js +47 -0
  82. package/src/lib/utils/virtual-resources.js.map +1 -0
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @analogjs/vite-plugin-angular
2
2
 
3
+ [![Vite Plugin Registry](https://img.shields.io/badge/vite-plugin--registry-blue?logo=vite)](https://registry.vite.dev/plugin/@analogjs/vite-plugin-angular)
4
+
3
5
  A Vite plugin for building Angular applications
4
6
 
5
7
  ## Install
@@ -48,6 +50,28 @@ export default defineConfig({
48
50
 
49
51
  > The `angular` plugin should be listed **first** in the plugins array.
50
52
 
53
+ ## Fast Compile Mode
54
+
55
+ `fastCompile` opts the plugin into a single-pass compilation path that emits Ivy instructions directly and skips Angular's template type-checking. It's intended for content-focused apps and faster dev iteration where build throughput matters more than inline type-safety feedback.
56
+
57
+ ```ts
58
+ export default defineConfig({
59
+ plugins: [angular({ fastCompile: true })],
60
+ });
61
+ ```
62
+
63
+ When `fastCompile` is enabled, template and input type errors will not surface during compilation — run `ngc -p tsconfig.app.json --noEmit` as a separate step in your build script to keep full type safety:
64
+
65
+ ```json
66
+ {
67
+ "scripts": {
68
+ "build": "ngc -p tsconfig.app.json --noEmit && vite build"
69
+ }
70
+ }
71
+ ```
72
+
73
+ The fast compile path currently passes ~91% of Angular's conformance suite. Behavior and output may change between minor releases.
74
+
51
75
  ## Setting up the TypeScript config
52
76
 
53
77
  The integration needs a `tsconfig.app.json` at the root of the project for compilation.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@analogjs/vite-plugin-angular",
3
- "version": "2.5.0-beta.5",
3
+ "version": "2.5.0-beta.50",
4
4
  "description": "Vite Plugin for Angular",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -25,7 +25,8 @@
25
25
  },
26
26
  "peerDependencies": {
27
27
  "@angular-devkit/build-angular": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0",
28
- "@angular/build": "^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0"
28
+ "@angular/build": "^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0",
29
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
29
30
  },
30
31
  "peerDependenciesMeta": {
31
32
  "@angular-devkit/build-angular": {
@@ -33,10 +34,28 @@
33
34
  },
34
35
  "@angular/build": {
35
36
  "optional": true
37
+ },
38
+ "vite": {
39
+ "optional": true
36
40
  }
37
41
  },
42
+ "compatiblePackages": {
43
+ "vite": [
44
+ "^6.0.0",
45
+ "^7.0.0",
46
+ "^8.0.0"
47
+ ],
48
+ "rollup": [
49
+ "^4.0.0"
50
+ ],
51
+ "rolldown": [
52
+ "^1.0.0"
53
+ ]
54
+ },
38
55
  "dependencies": {
39
- "@analogjs/angular-compiler": "^2.5.0-beta.5",
56
+ "magic-string": "^0.30.21",
57
+ "obug": "^2.1.1",
58
+ "oxc-parser": "^0.121.0",
40
59
  "tinyglobby": "^0.2.14",
41
60
  "ts-morph": "^21.0.0"
42
61
  },
@@ -49,8 +68,7 @@
49
68
  "@analogjs/storybook-angular",
50
69
  "@analogjs/vite-plugin-angular",
51
70
  "@analogjs/vite-plugin-nitro",
52
- "@analogjs/vitest-angular",
53
- "@analogjs/angular-compiler"
71
+ "@analogjs/vitest-angular"
54
72
  ],
55
73
  "migrations": "./migrations/migration.json"
56
74
  },
@@ -30,9 +30,20 @@ export interface PluginOptions {
30
30
  liveReload?: boolean;
31
31
  disableTypeChecking?: boolean;
32
32
  fileReplacements?: FileReplacement[];
33
+ /**
34
+ * Opt into the fast compile path. Skips Angular's template type-checking
35
+ * and routes compilation through an internal single-pass transform.
36
+ * Defaults to `false`.
37
+ */
38
+ fastCompile?: boolean;
39
+ /**
40
+ * Compilation output mode used when `fastCompile` is enabled.
41
+ * - `'full'` (default): Emit final Ivy definitions for application builds.
42
+ * - `'partial'`: Emit partial declarations for library publishing.
43
+ */
44
+ fastCompileMode?: 'full' | 'partial';
33
45
  experimental?: {
34
46
  useAngularCompilationAPI?: boolean;
35
- useAnalogCompiler?: boolean;
36
47
  };
37
48
  }
38
49
  export declare function angular(options?: PluginOptions): Plugin[];
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, writeFileSync, promises as fsPromises, } from 'node:fs';
1
+ import { mkdirSync, writeFileSync } from 'node:fs';
2
2
  import { basename, dirname, isAbsolute, join, relative, resolve, } from 'node:path';
3
3
  import * as vite from 'vite';
4
4
  import * as compilerCli from '@angular/compiler-cli';
@@ -8,7 +8,6 @@ import { globSync } from 'tinyglobby';
8
8
  import { defaultClientConditions, normalizePath, preprocessCSS, } from 'vite';
9
9
  import { buildOptimizerPlugin } from './angular-build-optimizer-plugin.js';
10
10
  import { jitPlugin } from './angular-jit-plugin.js';
11
- import { createCompilerPlugin, createRolldownCompilerPlugin, } from './compiler-plugin.js';
12
11
  import { StyleUrlsResolver, TemplateUrlsResolver, } from './component-resolvers.js';
13
12
  import { augmentHostWithCaching, augmentHostWithResources, augmentProgramWithVersioning, mergeTransformers, } from './host.js';
14
13
  import { angularVitestPlugins } from './angular-vitest-plugin.js';
@@ -20,7 +19,11 @@ import { nxFolderPlugin } from './nx-folder-plugin.js';
20
19
  import { replaceFiles, } from './plugins/file-replacements.plugin.js';
21
20
  import { routerPlugin } from './router-plugin.js';
22
21
  import { createHash } from 'node:crypto';
23
- import { compile as analogCompile, scanFile as analogScanFile, scanPackageDts as analogScanPackageDts, collectImportedPackages as analogCollectImportedPackages, jitTransform as analogJitTransform, inlineResourceUrls, extractInlineStyles as extractInlineStylesOxc, generateHmrCode, } from '@analogjs/angular-compiler';
22
+ import { fastCompilePlugin } from './fast-compile-plugin.js';
23
+ import { TS_EXT_REGEX, createTsConfigGetter, getTsConfigPath, createDepOptimizerConfig, } from './utils/plugin-config.js';
24
+ import { VIRTUAL_RAW_PREFIX, toVirtualRawId } from './utils/virtual-ids.js';
25
+ import { loadVirtualRawModule, rewriteHtmlRawImport, } from './utils/virtual-resources.js';
26
+ import { markStylePathSafe } from './utils/safe-module-paths.js';
24
27
  export var DiagnosticModes;
25
28
  (function (DiagnosticModes) {
26
29
  DiagnosticModes[DiagnosticModes["None"] = 0] = "None";
@@ -29,12 +32,6 @@ export var DiagnosticModes;
29
32
  DiagnosticModes[DiagnosticModes["Semantic"] = 4] = "Semantic";
30
33
  DiagnosticModes[DiagnosticModes["All"] = 7] = "All";
31
34
  })(DiagnosticModes || (DiagnosticModes = {}));
32
- /**
33
- * TypeScript file extension regex
34
- * Match .(c or m)ts, .ts extensions with an optional ? for query params
35
- * Ignore .tsx extensions
36
- */
37
- const TS_EXT_REGEX = /\.[cm]?(ts)[^x]?\??/;
38
35
  const classNames = new Map();
39
36
  export function angular(options) {
40
37
  /**
@@ -60,10 +57,10 @@ export function angular(options) {
60
57
  disableTypeChecking: options?.disableTypeChecking ?? true,
61
58
  fileReplacements: options?.fileReplacements ?? [],
62
59
  useAngularCompilationAPI: options?.experimental?.useAngularCompilationAPI ?? false,
63
- useAnalogCompiler: options?.experimental?.useAnalogCompiler ?? false,
60
+ fastCompile: options?.fastCompile ?? false,
61
+ fastCompileMode: options?.fastCompileMode ?? 'full',
64
62
  };
65
63
  let resolvedConfig;
66
- // Store config context needed for getTsConfigPath resolution
67
64
  let tsConfigResolutionContext = null;
68
65
  const ts = require('typescript');
69
66
  let builder;
@@ -113,126 +110,6 @@ export function angular(options) {
113
110
  // Previously the compilation was recreated on every pass, which meant Angular
114
111
  // never had prior state and could never produce HMR payloads.
115
112
  let angularCompilation;
116
- // Analog compiler state (used when experimental.useAnalogCompiler is true)
117
- const analogRegistry = new Map();
118
- const analogResourceToSource = new Map();
119
- /** Tracks which npm packages have already had their .d.ts files scanned. */
120
- const scannedDtsPackages = new Set();
121
- let analogProjectRoot = '';
122
- async function initAnalogCompiler() {
123
- if (jit)
124
- return; // JIT: no registry scan needed
125
- // Scan all source files to build the registry
126
- analogRegistry.clear();
127
- scannedDtsPackages.clear();
128
- const resolvedTsConfigPath = resolveTsConfigPath();
129
- analogProjectRoot = dirname(resolvedTsConfigPath);
130
- const config = compilerCli.readConfiguration(resolvedTsConfigPath);
131
- const results = await Promise.all(config.rootNames.map(async (file) => {
132
- try {
133
- const code = await fsPromises.readFile(file, 'utf-8');
134
- return analogScanFile(code, file);
135
- }
136
- catch {
137
- return []; // Skip unreadable files
138
- }
139
- }));
140
- for (const entries of results) {
141
- for (const entry of entries) {
142
- analogRegistry.set(entry.className, entry);
143
- }
144
- }
145
- }
146
- /**
147
- * Lazily scan .d.ts files for external packages imported by the given source.
148
- * Each package is scanned at most once and the results are cached in the registry.
149
- */
150
- function ensureDtsRegistryForSource(code, id) {
151
- for (const pkg of analogCollectImportedPackages(code, id)) {
152
- if (scannedDtsPackages.has(pkg))
153
- continue;
154
- scannedDtsPackages.add(pkg);
155
- try {
156
- const dtsEntries = analogScanPackageDts(pkg, analogProjectRoot);
157
- for (const entry of dtsEntries) {
158
- if (!analogRegistry.has(entry.className)) {
159
- analogRegistry.set(entry.className, entry);
160
- }
161
- }
162
- }
163
- catch {
164
- // Package may not have .d.ts files or may not be Angular
165
- }
166
- }
167
- }
168
- async function handleAnalogCompilerTransform(code, id) {
169
- if (!/(Component|Directive|Pipe|Injectable|NgModule)\(/.test(code)) {
170
- return undefined;
171
- }
172
- // JIT mode
173
- if (jit) {
174
- const result = analogJitTransform(code, id);
175
- return { code: result.code, map: result.map };
176
- }
177
- // Inline external templateUrl/styleUrl(s) into the source before compilation
178
- // using OXC parser for precise AST-based rewriting.
179
- code = inlineResourceUrls(code, id);
180
- // Pre-resolve inline styles that need preprocessing (SCSS/Sass/Less)
181
- let resolvedStyles;
182
- let resolvedInlineStyles;
183
- if (pluginOptions.inlineStylesExtension !== 'css') {
184
- const styleStrings = extractInlineStylesOxc(code, id);
185
- if (styleStrings.length > 0) {
186
- resolvedInlineStyles = new Map();
187
- for (let i = 0; i < styleStrings.length; i++) {
188
- try {
189
- const fakePath = id.replace(/\.ts$/, `.inline-${i}.${pluginOptions.inlineStylesExtension}`);
190
- const processed = await preprocessCSS(styleStrings[i], fakePath, resolvedConfig);
191
- resolvedInlineStyles.set(i, processed.code);
192
- }
193
- catch {
194
- // Skip styles that can't be preprocessed
195
- }
196
- }
197
- if (resolvedInlineStyles.size === 0)
198
- resolvedInlineStyles = undefined;
199
- }
200
- }
201
- // Lazily scan .d.ts files for any external packages this file imports,
202
- // so pre-compiled directives (e.g. RouterLinkActive) are in the registry
203
- // before template compilation needs them.
204
- ensureDtsRegistryForSource(code, id);
205
- const result = analogCompile(code, id, {
206
- registry: analogRegistry,
207
- resolvedStyles,
208
- resolvedInlineStyles,
209
- });
210
- // Track resource dependencies for HMR
211
- for (const dep of result.resourceDependencies) {
212
- analogResourceToSource.set(dep, id);
213
- }
214
- // Strip TypeScript-only syntax that the analog compiler preserves.
215
- // Use OXC to reliably strip all TS syntax (type annotations, generics,
216
- // interfaces, etc.) so the output is valid JavaScript for both client
217
- // and SSR environments.
218
- const stripped = await vite.transformWithOxc(result.code, id, {
219
- lang: 'ts',
220
- sourcemap: false,
221
- decorator: { legacy: false, emitDecoratorMetadata: false },
222
- });
223
- let outputCode = stripped.code;
224
- // Append HMR code in dev mode
225
- if (watchMode && pluginOptions.liveReload) {
226
- const fileDeclarations = [...analogRegistry.values()].filter((e) => e.fileName === id);
227
- if (fileDeclarations.length > 0) {
228
- // Local deps: other Angular classes in the same file that components
229
- // may reference as template dependencies.
230
- const localDepClassNames = fileDeclarations.map((e) => e.className);
231
- outputCode += generateHmrCode(fileDeclarations, localDepClassNames);
232
- }
233
- }
234
- return { code: outputCode, map: result.map };
235
- }
236
113
  function angularPlugin() {
237
114
  let isProd = false;
238
115
  if (angularFullVersion < 190000 || isTest) {
@@ -254,7 +131,6 @@ export function angular(options) {
254
131
  }
255
132
  return {
256
133
  name: '@analogjs/vite-plugin-angular',
257
- ...(pluginOptions.useAnalogCompiler ? { enforce: 'pre' } : {}),
258
134
  async config(config, { command }) {
259
135
  watchMode = command === 'serve';
260
136
  isProd =
@@ -268,62 +144,26 @@ export function angular(options) {
268
144
  };
269
145
  // Do a preliminary resolution for esbuild plugin (before configResolved)
270
146
  const preliminaryTsConfigPath = resolveTsConfigPath();
271
- // When useAnalogCompiler is true, configure the built-in OXC transform
272
- // to strip TypeScript but NOT lower decorators. The analog compiler
273
- // handles decorator processing in its transform hook. This avoids the
274
- // ordering conflict where lowered decorators can't be found by the
275
- // compiler, while still ensuring all .ts files get TypeScript stripped
276
- // (including those excluded from the analog compiler's transform filter).
277
147
  const esbuild = pluginOptions.useAngularCompilationAPI
278
148
  ? undefined
279
- : pluginOptions.useAnalogCompiler
280
- ? false
281
- : (config.esbuild ?? false);
149
+ : (config.esbuild ?? false);
282
150
  const oxc = pluginOptions.useAngularCompilationAPI
283
151
  ? undefined
284
- : pluginOptions.useAnalogCompiler
285
- ? {}
286
- : (config.oxc ?? false);
287
- const defineOptions = {
288
- ngJitMode: 'false',
289
- ngI18nClosureMode: 'false',
290
- ...(watchMode ? {} : { ngDevMode: 'false' }),
291
- };
292
- const rolldownOptions = {
293
- plugins: [
294
- createRolldownCompilerPlugin({
295
- tsconfig: preliminaryTsConfigPath,
296
- sourcemap: !isProd,
297
- advancedOptimizations: isProd,
298
- jit,
299
- incremental: watchMode,
300
- }),
301
- ],
302
- };
303
- const esbuildOptions = {
304
- plugins: [
305
- createCompilerPlugin({
306
- tsconfig: preliminaryTsConfigPath,
307
- sourcemap: !isProd,
308
- advancedOptimizations: isProd,
309
- jit,
310
- incremental: watchMode,
311
- }, isTest, !isAstroIntegration),
312
- ],
313
- define: defineOptions,
314
- };
152
+ : (config.oxc ?? false);
153
+ const depOptimizer = createDepOptimizerConfig({
154
+ tsconfig: preliminaryTsConfigPath,
155
+ isProd,
156
+ jit,
157
+ watchMode,
158
+ isTest,
159
+ isAstroIntegration,
160
+ });
315
161
  return {
316
162
  ...(vite.rolldownVersion ? { oxc } : { esbuild }),
317
- optimizeDeps: {
318
- include: ['rxjs/operators', 'rxjs'],
319
- exclude: ['@angular/platform-server'],
320
- ...(vite.rolldownVersion
321
- ? { rolldownOptions }
322
- : { esbuildOptions }),
323
- },
163
+ ...depOptimizer,
324
164
  resolve: {
325
165
  conditions: [
326
- 'style',
166
+ ...depOptimizer.resolve.conditions,
327
167
  ...(config.resolve?.conditions || defaultClientConditions),
328
168
  ],
329
169
  },
@@ -352,26 +192,6 @@ export function angular(options) {
352
192
  },
353
193
  configureServer(server) {
354
194
  viteServer = server;
355
- if (pluginOptions.useAnalogCompiler) {
356
- // Watch for new .ts files and scan them into the registry
357
- server.watcher.on('add', (filePath) => {
358
- if (filePath.endsWith('.ts') &&
359
- !filePath.endsWith('.spec.ts') &&
360
- !filePath.endsWith('.d.ts')) {
361
- try {
362
- const code = require('fs').readFileSync(filePath, 'utf-8');
363
- const entries = analogScanFile(code, filePath);
364
- for (const entry of entries) {
365
- analogRegistry.set(entry.className, entry);
366
- }
367
- }
368
- catch {
369
- // Skip unreadable files
370
- }
371
- }
372
- });
373
- return;
374
- }
375
195
  // Add/unlink changes the TypeScript program shape, not just file
376
196
  // contents, so we need to invalidate both include discovery and the
377
197
  // cached tsconfig root names before recompiling.
@@ -385,10 +205,6 @@ export function angular(options) {
385
205
  });
386
206
  },
387
207
  async buildStart() {
388
- if (pluginOptions.useAnalogCompiler) {
389
- await initAnalogCompiler();
390
- return;
391
- }
392
208
  // Defer the first compilation in test mode
393
209
  if (!isVitestVscode) {
394
210
  await performCompilation(resolvedConfig);
@@ -397,35 +213,6 @@ export function angular(options) {
397
213
  }
398
214
  },
399
215
  async handleHotUpdate(ctx) {
400
- // Analog compiler HMR path
401
- if (pluginOptions.useAnalogCompiler) {
402
- // Resource file changes → invalidate parent .ts module
403
- if (analogResourceToSource.has(ctx.file)) {
404
- const parentSource = analogResourceToSource.get(ctx.file);
405
- const parentModule = ctx.server.moduleGraph.getModuleById(parentSource);
406
- if (parentModule) {
407
- return [parentModule];
408
- }
409
- }
410
- if (TS_EXT_REGEX.test(ctx.file)) {
411
- const [fileId] = ctx.file.split('?');
412
- const code = require('fs').readFileSync(fileId, 'utf-8');
413
- // Remove old entries from this file
414
- const oldEntries = [...analogRegistry.entries()]
415
- .filter(([_, v]) => v.fileName === fileId)
416
- .map(([k]) => k);
417
- for (const key of oldEntries) {
418
- analogRegistry.delete(key);
419
- }
420
- // Rescan the changed file
421
- const newEntries = analogScanFile(code, fileId);
422
- for (const entry of newEntries) {
423
- analogRegistry.set(entry.className, entry);
424
- }
425
- }
426
- // Let Vite handle the rest — the transform hook will recompile
427
- return ctx.modules;
428
- }
429
216
  if (TS_EXT_REGEX.test(ctx.file)) {
430
217
  let [fileId] = ctx.file.split('?');
431
218
  pendingCompilation = performCompilation(resolvedConfig, [fileId]);
@@ -531,9 +318,34 @@ export function angular(options) {
531
318
  return ctx.modules;
532
319
  },
533
320
  resolveId(id, importer) {
321
+ if (id.startsWith(VIRTUAL_RAW_PREFIX)) {
322
+ return `\0${id}`;
323
+ }
534
324
  if (jit && id.startsWith('angular:jit:')) {
535
- const path = id.split(';')[1];
536
- return `${normalizePath(resolve(dirname(importer), path))}?${id.includes(':style') ? 'inline' : 'raw'}`;
325
+ const filePath = normalizePath(resolve(dirname(importer), id.split(';')[1]));
326
+ if (id.includes(':style')) {
327
+ // Mark the style path as safe so Vite's Denied ID check
328
+ // passes, then let Vite's native CSS pipeline handle the
329
+ // ?inline import (preprocessing, test.css, etc.).
330
+ markStylePathSafe(resolvedConfig, filePath);
331
+ return filePath + '?inline';
332
+ }
333
+ return toVirtualRawId(filePath);
334
+ }
335
+ // User `.html?raw` imports get rewritten to virtual ids so
336
+ // Vite's server.fs Denied ID check stays out of the way.
337
+ const rawRewrite = rewriteHtmlRawImport(id, importer);
338
+ if (rawRewrite)
339
+ return rawRewrite;
340
+ // User `.scss?inline` / `.css?inline` imports: resolve and mark
341
+ // safe so Vite's native CSS pipeline handles them.
342
+ if (/\.(css|scss|sass|less)\?inline$/.test(id) && importer) {
343
+ const filePath = id.split('?')[0];
344
+ const resolved = isAbsolute(filePath)
345
+ ? normalizePath(filePath)
346
+ : normalizePath(resolve(dirname(importer), filePath));
347
+ markStylePathSafe(resolvedConfig, resolved);
348
+ return resolved + '?inline';
537
349
  }
538
350
  // Map angular external styleUrls to the source file
539
351
  if (isComponentStyleSheet(id)) {
@@ -545,6 +357,20 @@ export function angular(options) {
545
357
  return undefined;
546
358
  },
547
359
  async load(id) {
360
+ // Virtual raw ids (templates) come from the transform-time
361
+ // substitution below and the resolveId rewrite for user
362
+ // `.html?raw` imports. Style ?inline imports now flow through
363
+ // Vite's native CSS pipeline via safeModulePaths.
364
+ const rawModule = await loadVirtualRawModule(this, id);
365
+ if (rawModule !== undefined)
366
+ return rawModule;
367
+ // Vitest fallback: the module-runner calls ensureEntryFromUrl
368
+ // before transformRequest, which can skip resolveId. Mark the
369
+ // path safe here so the Denied ID check passes, then let Vite's
370
+ // CSS pipeline handle the rest.
371
+ if (/\.(css|scss|sass|less)\?inline$/.test(id)) {
372
+ markStylePathSafe(resolvedConfig, id.split('?')[0]);
373
+ }
548
374
  // Map angular inline styles to the source text
549
375
  if (isComponentStyleSheet(id)) {
550
376
  const componentStyles = inlineComponentStyles?.get(getFilenameFromPath(id));
@@ -569,13 +395,6 @@ export function angular(options) {
569
395
  !(options?.transformFilter(code, id) ?? true)) {
570
396
  return;
571
397
  }
572
- // Analog compiler transform path
573
- if (pluginOptions.useAnalogCompiler) {
574
- if (id.includes('.ts?')) {
575
- id = id.replace(/\?(.*)/, '');
576
- }
577
- return handleAnalogCompilerTransform(code, id);
578
- }
579
398
  if (pluginOptions.useAngularCompilationAPI) {
580
399
  const isAngular = /(Component|Directive|Pipe|Injectable|NgModule)\(/.test(code);
581
400
  if (!isAngular) {
@@ -646,24 +465,43 @@ export function angular(options) {
646
465
  pendingCompilation = null;
647
466
  }
648
467
  const typescriptResult = fileEmitter(id);
649
- if (typescriptResult?.warnings &&
650
- typescriptResult?.warnings.length > 0) {
468
+ // File not in the Angular program — skip and let other plugins
469
+ // or Vite's built-in transform handle it. Warn if it looks like
470
+ // an Angular file that should have been compiled.
471
+ if (!typescriptResult) {
472
+ const isAngular = !id.includes('@ng/component') &&
473
+ /(Component|Directive|Pipe|Injectable|NgModule)\(/.test(code);
474
+ if (isAngular) {
475
+ this.warn(`[@analogjs/vite-plugin-angular]: "${id}" contains Angular decorators but is not in the TypeScript program. ` +
476
+ `Ensure it is included in your tsconfig.`);
477
+ }
478
+ return;
479
+ }
480
+ if (typescriptResult.warnings &&
481
+ typescriptResult.warnings.length > 0) {
651
482
  this.warn(`${typescriptResult.warnings.join('\n')}`);
652
483
  }
653
- if (typescriptResult?.errors && typescriptResult?.errors.length > 0) {
484
+ if (typescriptResult.errors && typescriptResult.errors.length > 0) {
654
485
  this.error(`${typescriptResult.errors.join('\n')}`);
655
486
  }
656
- // return fileEmitter
657
- let data = typescriptResult?.content ?? '';
487
+ let data = typescriptResult.content ?? '';
658
488
  if (jit && data.includes('angular:jit:')) {
659
489
  data = data.replace(/angular:jit:style:inline;/g, 'virtual:angular:jit:style:inline;');
490
+ // Templates use virtual ids (no extension) so Vite's asset/CSS
491
+ // plugins don't interfere. (#2263)
660
492
  templateUrls.forEach((templateUrlSet) => {
661
493
  const [templateFile, resolvedTemplateUrl] = templateUrlSet.split('|');
662
- data = data.replace(`angular:jit:template:file;${templateFile}`, `${resolvedTemplateUrl}?raw`);
494
+ data = data.replace(`angular:jit:template:file;${templateFile}`, toVirtualRawId(resolvedTemplateUrl));
663
495
  });
496
+ // External styles use native ?inline imports. We mark each
497
+ // path as safe in Vite's safeModulePaths so the Denied ID
498
+ // security check passes, and Vite's CSS pipeline handles
499
+ // preprocessing, test.css, and browser/node differences
500
+ // natively. (#2263, #2310)
664
501
  styleUrls.forEach((styleUrlSet) => {
665
502
  const [styleFile, resolvedStyleUrl] = styleUrlSet.split('|');
666
- data = data.replace(`angular:jit:style:file;${styleFile}`, `${resolvedStyleUrl}?inline`);
503
+ markStylePathSafe(resolvedConfig, resolvedStyleUrl);
504
+ data = data.replace(`angular:jit:style:file;${styleFile}`, resolvedStyleUrl + '?inline');
667
505
  });
668
506
  }
669
507
  return {
@@ -684,10 +522,26 @@ export function angular(options) {
684
522
  },
685
523
  };
686
524
  }
525
+ const compilationPlugin = pluginOptions.fastCompile
526
+ ? fastCompilePlugin({
527
+ tsconfigGetter: pluginOptions.tsconfigGetter,
528
+ workspaceRoot: pluginOptions.workspaceRoot,
529
+ inlineStylesExtension: pluginOptions.inlineStylesExtension,
530
+ jit,
531
+ liveReload: pluginOptions.liveReload,
532
+ supportedBrowsers: pluginOptions.supportedBrowsers,
533
+ transformFilter: options?.transformFilter,
534
+ isTest,
535
+ isAstroIntegration,
536
+ fastCompileMode: pluginOptions.fastCompileMode,
537
+ })
538
+ : angularPlugin();
687
539
  return [
688
540
  replaceFiles(pluginOptions.fileReplacements, pluginOptions.workspaceRoot),
689
- angularPlugin(),
690
- pluginOptions.liveReload && liveReloadPlugin({ classNames, fileEmitter }),
541
+ compilationPlugin,
542
+ !pluginOptions.fastCompile &&
543
+ pluginOptions.liveReload &&
544
+ liveReloadPlugin({ classNames, fileEmitter }),
691
545
  ...(isTest && !isStackBlitz ? angularVitestPlugins() : []),
692
546
  (jit &&
693
547
  jitPlugin({
@@ -713,37 +567,6 @@ export function angular(options) {
713
567
  absolute: true,
714
568
  });
715
569
  }
716
- function createTsConfigGetter(tsconfigOrGetter) {
717
- if (typeof tsconfigOrGetter === 'function') {
718
- return tsconfigOrGetter;
719
- }
720
- return () => tsconfigOrGetter || '';
721
- }
722
- function getTsConfigPath(root, tsconfig, isProd, isTest, isLib) {
723
- if (tsconfig && isAbsolute(tsconfig)) {
724
- if (!existsSync(tsconfig)) {
725
- 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.`);
726
- }
727
- return tsconfig;
728
- }
729
- let tsconfigFilePath = './tsconfig.app.json';
730
- if (isLib) {
731
- tsconfigFilePath = isProd
732
- ? './tsconfig.lib.prod.json'
733
- : './tsconfig.lib.json';
734
- }
735
- if (isTest) {
736
- tsconfigFilePath = './tsconfig.spec.json';
737
- }
738
- if (tsconfig) {
739
- tsconfigFilePath = tsconfig;
740
- }
741
- const resolvedPath = resolve(root, tsconfigFilePath);
742
- if (!existsSync(resolvedPath)) {
743
- 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.`);
744
- }
745
- return resolvedPath;
746
- }
747
570
  function resolveTsConfigPath() {
748
571
  const tsconfigValue = pluginOptions.tsconfigGetter();
749
572
  return getTsConfigPath(tsConfigResolutionContext.root, tsconfigValue, tsConfigResolutionContext.isProd, isTest, tsConfigResolutionContext.isLib);