@analogjs/vite-plugin-angular 3.0.0-alpha.23 → 3.0.0-alpha.25
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.
- package/package.json +4 -3
- package/src/lib/angular-jit-plugin.js +8 -2
- package/src/lib/angular-jit-plugin.js.map +1 -1
- package/src/lib/angular-vite-plugin.d.ts +60 -3
- package/src/lib/angular-vite-plugin.js +1046 -88
- package/src/lib/angular-vite-plugin.js.map +1 -1
- package/src/lib/component-resolvers.d.ts +16 -0
- package/src/lib/component-resolvers.js +77 -16
- package/src/lib/component-resolvers.js.map +1 -1
- package/src/lib/host.d.ts +2 -2
- package/src/lib/host.js +26 -16
- package/src/lib/host.js.map +1 -1
- package/src/lib/plugins/file-replacements.plugin.js +6 -1
- package/src/lib/plugins/file-replacements.plugin.js.map +1 -1
- package/src/lib/stylesheet-registry.d.ts +59 -0
- package/src/lib/stylesheet-registry.js +127 -0
- package/src/lib/stylesheet-registry.js.map +1 -0
- package/src/lib/utils/debug-harness.d.ts +23 -0
- package/src/lib/utils/debug-harness.js +88 -0
- package/src/lib/utils/debug-harness.js.map +1 -0
- package/src/lib/utils/debug-log-file.d.ts +5 -0
- package/src/lib/utils/debug-log-file.js +56 -0
- package/src/lib/utils/debug-log-file.js.map +1 -0
- package/src/lib/utils/debug.d.ts +15 -29
- package/src/lib/utils/debug.js +25 -66
- package/src/lib/utils/debug.js.map +1 -1
- package/src/lib/utils/devkit.d.ts +4 -4
- package/src/lib/utils/devkit.js.map +1 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { angularFullVersion, cjt, createAngularCompilation, sourceFileCache } from "./utils/devkit.js";
|
|
2
2
|
import { getJsTransformConfigKey, isRolldown } from "./utils/rolldown.js";
|
|
3
3
|
import { buildOptimizerPlugin } from "./angular-build-optimizer-plugin.js";
|
|
4
|
+
import { activateDeferredDebug, applyDebugOption, debugCompilationApi, debugCompiler, debugCompilerV, debugHmr, debugHmrV, debugStyles, debugStylesV, debugTailwind, debugTailwindV } from "./utils/debug.js";
|
|
4
5
|
import { jitPlugin } from "./angular-jit-plugin.js";
|
|
5
6
|
import { createCompilerPlugin, createRolldownCompilerPlugin } from "./compiler-plugin.js";
|
|
6
|
-
import { StyleUrlsResolver, TemplateUrlsResolver } from "./component-resolvers.js";
|
|
7
|
-
import {
|
|
7
|
+
import { StyleUrlsResolver, TemplateUrlsResolver, getAngularComponentMetadata } from "./component-resolvers.js";
|
|
8
|
+
import { AnalogStylesheetRegistry, preprocessStylesheet, registerStylesheetContent, rewriteRelativeCssImports } from "./stylesheet-registry.js";
|
|
8
9
|
import { augmentHostWithCaching, augmentHostWithResources, augmentProgramWithVersioning, mergeTransformers } from "./host.js";
|
|
9
10
|
import { angularVitestPlugins } from "./angular-vitest-plugin.js";
|
|
10
11
|
import { pendingTasksPlugin } from "./angular-pending-tasks.plugin.js";
|
|
@@ -12,14 +13,15 @@ import { liveReloadPlugin } from "./live-reload-plugin.js";
|
|
|
12
13
|
import { nxFolderPlugin } from "./nx-folder-plugin.js";
|
|
13
14
|
import { replaceFiles } from "./plugins/file-replacements.plugin.js";
|
|
14
15
|
import { routerPlugin } from "./router-plugin.js";
|
|
15
|
-
import {
|
|
16
|
+
import { union } from "es-toolkit";
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
18
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
16
19
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
17
20
|
import * as compilerCli from "@angular/compiler-cli";
|
|
18
21
|
import { createRequire } from "node:module";
|
|
19
22
|
import * as ngCompiler from "@angular/compiler";
|
|
20
23
|
import { globSync } from "tinyglobby";
|
|
21
24
|
import { defaultClientConditions, normalizePath, preprocessCSS } from "vite";
|
|
22
|
-
import { createHash } from "node:crypto";
|
|
23
25
|
//#region packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts
|
|
24
26
|
var require = createRequire(import.meta.url);
|
|
25
27
|
var DiagnosticModes = /* @__PURE__ */ function(DiagnosticModes) {
|
|
@@ -30,6 +32,13 @@ var DiagnosticModes = /* @__PURE__ */ function(DiagnosticModes) {
|
|
|
30
32
|
DiagnosticModes[DiagnosticModes["All"] = 7] = "All";
|
|
31
33
|
return DiagnosticModes;
|
|
32
34
|
}({});
|
|
35
|
+
function normalizeIncludeGlob(workspaceRoot, glob) {
|
|
36
|
+
const normalizedWorkspaceRoot = normalizePath(resolve(workspaceRoot));
|
|
37
|
+
const normalizedGlob = normalizePath(glob);
|
|
38
|
+
if (normalizedGlob === normalizedWorkspaceRoot || normalizedGlob.startsWith(`${normalizedWorkspaceRoot}/`)) return normalizedGlob;
|
|
39
|
+
if (normalizedGlob.startsWith("/")) return `${normalizedWorkspaceRoot}${normalizedGlob}`;
|
|
40
|
+
return normalizePath(resolve(normalizedWorkspaceRoot, normalizedGlob));
|
|
41
|
+
}
|
|
33
42
|
/**
|
|
34
43
|
* TypeScript file extension regex
|
|
35
44
|
* Match .(c or m)ts, .ts extensions with an optional ? for query params
|
|
@@ -37,10 +46,33 @@ var DiagnosticModes = /* @__PURE__ */ function(DiagnosticModes) {
|
|
|
37
46
|
*/
|
|
38
47
|
var TS_EXT_REGEX = /\.[cm]?(ts)[^x]?\??/;
|
|
39
48
|
var classNames = /* @__PURE__ */ new Map();
|
|
49
|
+
function evictDeletedFileMetadata(file, { removeActiveGraphMetadata, removeStyleOwnerMetadata, classNamesMap, fileTransformMap }) {
|
|
50
|
+
const normalizedFile = normalizePath(file.split("?")[0]);
|
|
51
|
+
removeActiveGraphMetadata(normalizedFile);
|
|
52
|
+
removeStyleOwnerMetadata(normalizedFile);
|
|
53
|
+
classNamesMap.delete(normalizedFile);
|
|
54
|
+
fileTransformMap.delete(normalizedFile);
|
|
55
|
+
}
|
|
56
|
+
function injectViteIgnoreForHmrMetadata(code) {
|
|
57
|
+
let patched = code.replace(/\bimport\(([a-zA-Z_$][\w$]*\.\u0275\u0275getReplaceMetadataURL)/g, "import(/* @vite-ignore */ $1");
|
|
58
|
+
if (patched === code) patched = patched.replace(/import\((\S+getReplaceMetadataURL)/g, "import(/* @vite-ignore */ $1");
|
|
59
|
+
return patched;
|
|
60
|
+
}
|
|
61
|
+
function isIgnoredHmrFile(file) {
|
|
62
|
+
return file.endsWith(".tsbuildinfo");
|
|
63
|
+
}
|
|
40
64
|
/**
|
|
41
65
|
* Builds a resolved stylePreprocessor function from plugin options.
|
|
42
|
-
*
|
|
43
|
-
*
|
|
66
|
+
*
|
|
67
|
+
* When `tailwindCss` is configured, creates an injector that prepends
|
|
68
|
+
* `@reference "<rootStylesheet>"` into component CSS that uses Tailwind
|
|
69
|
+
* utilities. Uses absolute paths because Angular's externalRuntimeStyles
|
|
70
|
+
* serves component CSS as virtual modules (hash-based IDs) with no
|
|
71
|
+
* meaningful directory — relative paths can't resolve from a hash.
|
|
72
|
+
*
|
|
73
|
+
* If both `tailwindCss` and `stylePreprocessor` are provided, they are
|
|
74
|
+
* chained: Tailwind reference injection runs first, then the user's
|
|
75
|
+
* custom preprocessor.
|
|
44
76
|
*/
|
|
45
77
|
function buildStylePreprocessor(options) {
|
|
46
78
|
const userPreprocessor = options?.stylePreprocessor;
|
|
@@ -54,17 +86,18 @@ function buildStylePreprocessor(options) {
|
|
|
54
86
|
rootStylesheet,
|
|
55
87
|
prefixes
|
|
56
88
|
});
|
|
89
|
+
if (!existsSync(rootStylesheet)) console.warn(`[@analogjs/vite-plugin-angular] tailwindCss.rootStylesheet not found at "${rootStylesheet}". @reference directives will point to a non-existent file, which will cause Tailwind CSS errors. Ensure the path is absolute and the file exists.`);
|
|
57
90
|
tailwindPreprocessor = (code, filename) => {
|
|
58
91
|
if (code.includes("@reference") || code.includes("@import \"tailwindcss\"") || code.includes("@import 'tailwindcss'")) {
|
|
59
|
-
|
|
92
|
+
debugTailwindV("skip (already has @reference or is root)", { filename });
|
|
60
93
|
return code;
|
|
61
94
|
}
|
|
62
95
|
if (!(prefixes ? prefixes.some((prefix) => code.includes(prefix)) : code.includes("@apply"))) {
|
|
63
|
-
|
|
96
|
+
debugTailwindV("skip (no Tailwind usage detected)", { filename });
|
|
64
97
|
return code;
|
|
65
98
|
}
|
|
66
|
-
debugTailwind("injected @reference", { filename });
|
|
67
|
-
return `@reference "${
|
|
99
|
+
debugTailwind("injected @reference via preprocessor", { filename });
|
|
100
|
+
return `@reference "${rootStylesheet}";\n${code}`;
|
|
68
101
|
};
|
|
69
102
|
}
|
|
70
103
|
if (tailwindPreprocessor && userPreprocessor) {
|
|
@@ -76,7 +109,7 @@ function buildStylePreprocessor(options) {
|
|
|
76
109
|
return tailwindPreprocessor ?? userPreprocessor;
|
|
77
110
|
}
|
|
78
111
|
function angular(options) {
|
|
79
|
-
applyDebugOption(options?.debug);
|
|
112
|
+
applyDebugOption(options?.debug, options?.workspaceRoot);
|
|
80
113
|
/**
|
|
81
114
|
* Normalize plugin options so defaults
|
|
82
115
|
* are used for values not provided.
|
|
@@ -94,10 +127,12 @@ function angular(options) {
|
|
|
94
127
|
jit: options?.jit,
|
|
95
128
|
include: options?.include ?? [],
|
|
96
129
|
additionalContentDirs: options?.additionalContentDirs ?? [],
|
|
97
|
-
|
|
130
|
+
hmr: options?.hmr ?? options?.liveReload ?? true,
|
|
98
131
|
disableTypeChecking: options?.disableTypeChecking ?? true,
|
|
99
132
|
fileReplacements: options?.fileReplacements ?? [],
|
|
100
133
|
useAngularCompilationAPI: options?.experimental?.useAngularCompilationAPI ?? false,
|
|
134
|
+
hasTailwindCss: !!options?.tailwindCss,
|
|
135
|
+
tailwindCss: options?.tailwindCss,
|
|
101
136
|
stylePreprocessor: buildStylePreprocessor(options)
|
|
102
137
|
};
|
|
103
138
|
let resolvedConfig;
|
|
@@ -119,8 +154,134 @@ function angular(options) {
|
|
|
119
154
|
}
|
|
120
155
|
let watchMode = false;
|
|
121
156
|
let testWatchMode = isTestWatchMode();
|
|
122
|
-
|
|
123
|
-
|
|
157
|
+
const activeGraphComponentMetadata = /* @__PURE__ */ new Map();
|
|
158
|
+
const selectorOwners = /* @__PURE__ */ new Map();
|
|
159
|
+
const classNameOwners = /* @__PURE__ */ new Map();
|
|
160
|
+
const transformedStyleOwnerMetadata = /* @__PURE__ */ new Map();
|
|
161
|
+
const styleSourceOwners = /* @__PURE__ */ new Map();
|
|
162
|
+
function shouldEnableHmr() {
|
|
163
|
+
return !!((isTest ? testWatchMode : watchMode) && pluginOptions.hmr);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Determines whether Angular should externalize component styles.
|
|
167
|
+
*
|
|
168
|
+
* When true, Angular emits style references (hash-based IDs) instead of
|
|
169
|
+
* inlining CSS strings. Vite's resolveId → load → transform pipeline
|
|
170
|
+
* then serves these virtual modules, allowing @tailwindcss/vite to
|
|
171
|
+
* process @reference directives.
|
|
172
|
+
*
|
|
173
|
+
* Required for TWO independent use-cases:
|
|
174
|
+
* 1. HMR — Vite needs external modules for hot replacement
|
|
175
|
+
* 2. Tailwind CSS (hasTailwindCss) — styles must pass through Vite's
|
|
176
|
+
* CSS pipeline so @tailwindcss/vite can resolve @apply directives
|
|
177
|
+
*
|
|
178
|
+
* In production builds (!watchMode), styles are NOT externalized — they
|
|
179
|
+
* are inlined after preprocessCSS runs eagerly in transformStylesheet.
|
|
180
|
+
*/
|
|
181
|
+
function shouldExternalizeStyles() {
|
|
182
|
+
if (!(isTest ? testWatchMode : watchMode)) return false;
|
|
183
|
+
return !!(shouldEnableHmr() || pluginOptions.hasTailwindCss);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Validates the Tailwind CSS integration configuration and emits actionable
|
|
187
|
+
* warnings for common misconfigurations that cause silent failures.
|
|
188
|
+
*
|
|
189
|
+
* Called once during `configResolved` when `tailwindCss` is configured.
|
|
190
|
+
*/
|
|
191
|
+
function validateTailwindConfig(config, isWatchMode) {
|
|
192
|
+
const PREFIX = "[@analogjs/vite-plugin-angular]";
|
|
193
|
+
const tw = pluginOptions.tailwindCss;
|
|
194
|
+
if (!tw) return;
|
|
195
|
+
if (!isAbsolute(tw.rootStylesheet)) console.warn(`${PREFIX} tailwindCss.rootStylesheet must be an absolute path. Got: "${tw.rootStylesheet}". Use path.resolve(__dirname, '...') in your vite.config to convert it.`);
|
|
196
|
+
const resolvedPlugins = config.plugins;
|
|
197
|
+
const hasTailwindPlugin = resolvedPlugins.some((p) => p.name.startsWith("@tailwindcss/vite") || p.name.startsWith("tailwindcss"));
|
|
198
|
+
if (isWatchMode && !hasTailwindPlugin) throw new Error(`${PREFIX} tailwindCss is configured but no @tailwindcss/vite plugin was found. Component CSS with @apply directives will not be processed.\n\n Fix: npm install @tailwindcss/vite --save-dev\n Then add tailwindcss() to your vite.config plugins array.\n`);
|
|
199
|
+
if (isWatchMode && tw.rootStylesheet) {
|
|
200
|
+
const projectRoot = config.root;
|
|
201
|
+
if (!tw.rootStylesheet.startsWith(projectRoot)) {
|
|
202
|
+
if (!(config.server?.fs?.allow ?? []).some((allowed) => tw.rootStylesheet.startsWith(allowed))) console.warn(`${PREFIX} tailwindCss.rootStylesheet is outside the Vite project root. The dev server may reject it with 403.\n\n Root: ${projectRoot}\n Stylesheet: ${tw.rootStylesheet}\n\n Fix: server.fs.allow: ['${dirname(tw.rootStylesheet)}']\n`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (tw.prefixes !== void 0 && tw.prefixes.length === 0) console.warn(`${PREFIX} tailwindCss.prefixes is an empty array. No component stylesheets will receive @reference injection. Either remove the prefixes option (to use @apply detection) or specify your prefixes: ['tw:']\n`);
|
|
206
|
+
const analogInstances = resolvedPlugins.filter((p) => p.name === "@analogjs/vite-plugin-angular");
|
|
207
|
+
if (analogInstances.length > 1) throw new Error(`${PREFIX} analog() is registered ${analogInstances.length} times. Each instance creates separate style maps, causing component styles to be lost. Remove duplicate registrations.`);
|
|
208
|
+
if (existsSync(tw.rootStylesheet)) try {
|
|
209
|
+
const rootContent = readFileSync(tw.rootStylesheet, "utf-8");
|
|
210
|
+
if (!rootContent.includes("@import \"tailwindcss\"") && !rootContent.includes("@import 'tailwindcss'")) console.warn(`${PREFIX} tailwindCss.rootStylesheet does not contain @import "tailwindcss". The @reference directive will point to a file without Tailwind configuration.\n\n File: ${tw.rootStylesheet}\n`);
|
|
211
|
+
} catch {}
|
|
212
|
+
}
|
|
213
|
+
function isLikelyPageOnlyComponent(id) {
|
|
214
|
+
return id.includes("/pages/") || /\.page\.[cm]?[jt]sx?$/i.test(id) || /\([^/]+\)\.page\.[cm]?[jt]sx?$/i.test(id);
|
|
215
|
+
}
|
|
216
|
+
function removeActiveGraphMetadata(file) {
|
|
217
|
+
const previous = activeGraphComponentMetadata.get(file);
|
|
218
|
+
if (!previous) return;
|
|
219
|
+
for (const record of previous) {
|
|
220
|
+
const location = `${record.file}#${record.className}`;
|
|
221
|
+
if (record.selector) {
|
|
222
|
+
const selectorSet = selectorOwners.get(record.selector);
|
|
223
|
+
selectorSet?.delete(location);
|
|
224
|
+
if (selectorSet?.size === 0) selectorOwners.delete(record.selector);
|
|
225
|
+
}
|
|
226
|
+
const classNameSet = classNameOwners.get(record.className);
|
|
227
|
+
classNameSet?.delete(location);
|
|
228
|
+
if (classNameSet?.size === 0) classNameOwners.delete(record.className);
|
|
229
|
+
}
|
|
230
|
+
activeGraphComponentMetadata.delete(file);
|
|
231
|
+
}
|
|
232
|
+
function registerActiveGraphMetadata(file, records) {
|
|
233
|
+
removeActiveGraphMetadata(file);
|
|
234
|
+
if (records.length === 0) return;
|
|
235
|
+
activeGraphComponentMetadata.set(file, records);
|
|
236
|
+
for (const record of records) {
|
|
237
|
+
const location = `${record.file}#${record.className}`;
|
|
238
|
+
if (record.selector) {
|
|
239
|
+
let selectorSet = selectorOwners.get(record.selector);
|
|
240
|
+
if (!selectorSet) {
|
|
241
|
+
selectorSet = /* @__PURE__ */ new Set();
|
|
242
|
+
selectorOwners.set(record.selector, selectorSet);
|
|
243
|
+
}
|
|
244
|
+
selectorSet.add(location);
|
|
245
|
+
}
|
|
246
|
+
let classNameSet = classNameOwners.get(record.className);
|
|
247
|
+
if (!classNameSet) {
|
|
248
|
+
classNameSet = /* @__PURE__ */ new Set();
|
|
249
|
+
classNameOwners.set(record.className, classNameSet);
|
|
250
|
+
}
|
|
251
|
+
classNameSet.add(location);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function removeStyleOwnerMetadata(file) {
|
|
255
|
+
const previous = transformedStyleOwnerMetadata.get(file);
|
|
256
|
+
if (!previous) return;
|
|
257
|
+
for (const record of previous) {
|
|
258
|
+
const owners = styleSourceOwners.get(record.sourcePath);
|
|
259
|
+
owners?.delete(record.ownerFile);
|
|
260
|
+
if (owners?.size === 0) styleSourceOwners.delete(record.sourcePath);
|
|
261
|
+
}
|
|
262
|
+
transformedStyleOwnerMetadata.delete(file);
|
|
263
|
+
}
|
|
264
|
+
function registerStyleOwnerMetadata(file, styleUrls) {
|
|
265
|
+
removeStyleOwnerMetadata(file);
|
|
266
|
+
const records = styleUrls.map((urlSet) => {
|
|
267
|
+
const [, absoluteFileUrl] = urlSet.split("|");
|
|
268
|
+
return absoluteFileUrl ? {
|
|
269
|
+
ownerFile: file,
|
|
270
|
+
sourcePath: normalizePath(absoluteFileUrl)
|
|
271
|
+
} : void 0;
|
|
272
|
+
}).filter((record) => !!record);
|
|
273
|
+
if (records.length === 0) return;
|
|
274
|
+
transformedStyleOwnerMetadata.set(file, records);
|
|
275
|
+
for (const record of records) {
|
|
276
|
+
let owners = styleSourceOwners.get(record.sourcePath);
|
|
277
|
+
if (!owners) {
|
|
278
|
+
owners = /* @__PURE__ */ new Set();
|
|
279
|
+
styleSourceOwners.set(record.sourcePath, owners);
|
|
280
|
+
}
|
|
281
|
+
owners.add(record.ownerFile);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
let stylesheetRegistry;
|
|
124
285
|
const sourceFileCache$1 = new sourceFileCache();
|
|
125
286
|
const isTest = process.env.NODE_ENV === "test" || !!process.env["VITEST"];
|
|
126
287
|
const isVitestVscode = !!process.env["VITEST_VSCODE"];
|
|
@@ -145,9 +306,17 @@ function angular(options) {
|
|
|
145
306
|
let angularCompilation;
|
|
146
307
|
function angularPlugin() {
|
|
147
308
|
let isProd = false;
|
|
148
|
-
if (angularFullVersion < 19e4
|
|
149
|
-
|
|
150
|
-
|
|
309
|
+
if (angularFullVersion < 19e4 && pluginOptions.hmr) {
|
|
310
|
+
debugHmr("hmr disabled: Angular version does not support HMR APIs", {
|
|
311
|
+
angularVersion: angularFullVersion,
|
|
312
|
+
isTest
|
|
313
|
+
});
|
|
314
|
+
console.warn("[@analogjs/vite-plugin-angular]: HMR was disabled because Angular v19+ is required for externalRuntimeStyles/_enableHmr support. Detected Angular version: %s.", angularFullVersion);
|
|
315
|
+
pluginOptions.hmr = false;
|
|
316
|
+
}
|
|
317
|
+
if (isTest) {
|
|
318
|
+
pluginOptions.hmr = false;
|
|
319
|
+
debugHmr("hmr disabled", {
|
|
151
320
|
angularVersion: angularFullVersion,
|
|
152
321
|
isTest
|
|
153
322
|
});
|
|
@@ -200,7 +369,11 @@ function angular(options) {
|
|
|
200
369
|
return {
|
|
201
370
|
[jsTransformConfigKey]: jsTransformConfigValue,
|
|
202
371
|
optimizeDeps: {
|
|
203
|
-
include: [
|
|
372
|
+
include: [
|
|
373
|
+
"rxjs/operators",
|
|
374
|
+
"rxjs",
|
|
375
|
+
"tslib"
|
|
376
|
+
],
|
|
204
377
|
exclude: ["@angular/platform-server"],
|
|
205
378
|
...useRolldown ? { rolldownOptions } : { esbuildOptions }
|
|
206
379
|
},
|
|
@@ -209,10 +382,10 @@ function angular(options) {
|
|
|
209
382
|
},
|
|
210
383
|
configResolved(config) {
|
|
211
384
|
resolvedConfig = config;
|
|
385
|
+
if (pluginOptions.hasTailwindCss) validateTailwindConfig(config, watchMode);
|
|
212
386
|
if (pluginOptions.useAngularCompilationAPI) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
debugStyles("style maps initialized (Angular Compilation API)");
|
|
387
|
+
stylesheetRegistry = new AnalogStylesheetRegistry();
|
|
388
|
+
debugStyles("stylesheet registry initialized (Angular Compilation API)");
|
|
216
389
|
}
|
|
217
390
|
if (!jit) styleTransform = (code, filename) => preprocessCSS(code, filename, config);
|
|
218
391
|
if (isTest) testWatchMode = !(config.server.watch === null) || config.test?.watch === true || testWatchMode;
|
|
@@ -221,7 +394,15 @@ function angular(options) {
|
|
|
221
394
|
viteServer = server;
|
|
222
395
|
const invalidateCompilationOnFsChange = createFsWatcherCacheInvalidator(invalidateFsCaches, invalidateTsconfigCaches, () => performCompilation(resolvedConfig));
|
|
223
396
|
server.watcher.on("add", invalidateCompilationOnFsChange);
|
|
224
|
-
server.watcher.on("unlink",
|
|
397
|
+
server.watcher.on("unlink", (file) => {
|
|
398
|
+
evictDeletedFileMetadata(file, {
|
|
399
|
+
removeActiveGraphMetadata,
|
|
400
|
+
removeStyleOwnerMetadata,
|
|
401
|
+
classNamesMap: classNames,
|
|
402
|
+
fileTransformMap
|
|
403
|
+
});
|
|
404
|
+
return invalidateCompilationOnFsChange();
|
|
405
|
+
});
|
|
225
406
|
server.watcher.on("change", (file) => {
|
|
226
407
|
if (file.includes("tsconfig")) invalidateTsconfigCaches();
|
|
227
408
|
});
|
|
@@ -234,6 +415,10 @@ function angular(options) {
|
|
|
234
415
|
}
|
|
235
416
|
},
|
|
236
417
|
async handleHotUpdate(ctx) {
|
|
418
|
+
if (isIgnoredHmrFile(ctx.file)) {
|
|
419
|
+
debugHmr("ignored file change", { file: ctx.file });
|
|
420
|
+
return [];
|
|
421
|
+
}
|
|
237
422
|
if (TS_EXT_REGEX.test(ctx.file)) {
|
|
238
423
|
const [fileId] = ctx.file.split("?");
|
|
239
424
|
debugHmr("TS file changed", {
|
|
@@ -242,7 +427,7 @@ function angular(options) {
|
|
|
242
427
|
});
|
|
243
428
|
pendingCompilation = performCompilation(resolvedConfig, [fileId]);
|
|
244
429
|
let result;
|
|
245
|
-
if (
|
|
430
|
+
if (shouldEnableHmr()) {
|
|
246
431
|
await pendingCompilation;
|
|
247
432
|
pendingCompilation = null;
|
|
248
433
|
result = fileEmitter(fileId);
|
|
@@ -251,10 +436,28 @@ function angular(options) {
|
|
|
251
436
|
hmrEligible: !!result?.hmrEligible,
|
|
252
437
|
hasClassName: !!classNames.get(fileId)
|
|
253
438
|
});
|
|
439
|
+
debugHmrV("ts hmr evaluation", {
|
|
440
|
+
file: ctx.file,
|
|
441
|
+
fileId,
|
|
442
|
+
hasResult: !!result,
|
|
443
|
+
hmrEligible: !!result?.hmrEligible,
|
|
444
|
+
hasClassName: !!classNames.get(fileId),
|
|
445
|
+
className: classNames.get(fileId),
|
|
446
|
+
updateCode: result?.hmrUpdateCode ? describeStylesheetContent(result.hmrUpdateCode) : void 0,
|
|
447
|
+
errors: result?.errors?.length ?? 0,
|
|
448
|
+
warnings: result?.warnings?.length ?? 0,
|
|
449
|
+
hint: result?.hmrEligible ? "A TS-side component change, including inline template edits, produced an Angular HMR payload." : "No Angular HMR payload was emitted for this TS change; the change may not affect component template state."
|
|
450
|
+
});
|
|
254
451
|
}
|
|
255
|
-
if (
|
|
452
|
+
if (shouldEnableHmr() && result?.hmrEligible && classNames.get(fileId)) {
|
|
256
453
|
const relativeFileId = `${normalizePath(relative(process.cwd(), fileId))}@${classNames.get(fileId)}`;
|
|
257
454
|
debugHmr("sending component update", { relativeFileId });
|
|
455
|
+
debugHmrV("ts hmr component update payload", {
|
|
456
|
+
file: ctx.file,
|
|
457
|
+
fileId,
|
|
458
|
+
relativeFileId,
|
|
459
|
+
className: classNames.get(fileId)
|
|
460
|
+
});
|
|
258
461
|
sendHMRComponentUpdate(ctx.server, relativeFileId);
|
|
259
462
|
return ctx.modules.map((mod) => {
|
|
260
463
|
if (mod.id === ctx.file) return markModuleSelfAccepting(mod);
|
|
@@ -265,51 +468,224 @@ function angular(options) {
|
|
|
265
468
|
if (/\.(html|htm|css|less|sass|scss)$/.test(ctx.file)) {
|
|
266
469
|
debugHmr("resource file changed", { file: ctx.file });
|
|
267
470
|
fileTransformMap.delete(ctx.file.split("?")[0]);
|
|
471
|
+
if (/\.(css|less|sass|scss)$/.test(ctx.file)) refreshStylesheetRegistryForFile(ctx.file, stylesheetRegistry, pluginOptions.stylePreprocessor);
|
|
472
|
+
if (/\.(css|less|sass|scss)$/.test(ctx.file) && existsSync(ctx.file)) try {
|
|
473
|
+
const rawResource = readFileSync(ctx.file, "utf-8");
|
|
474
|
+
debugHmrV("resource source snapshot", {
|
|
475
|
+
file: ctx.file,
|
|
476
|
+
mtimeMs: safeStatMtimeMs(ctx.file),
|
|
477
|
+
...describeStylesheetContent(rawResource)
|
|
478
|
+
});
|
|
479
|
+
} catch (error) {
|
|
480
|
+
debugHmrV("resource source snapshot failed", {
|
|
481
|
+
file: ctx.file,
|
|
482
|
+
error: String(error)
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
const fileModules = await getModulesForChangedFile(ctx.server, ctx.file, ctx.modules, stylesheetRegistry);
|
|
486
|
+
debugHmrV("resource modules resolved", {
|
|
487
|
+
file: ctx.file,
|
|
488
|
+
eventModuleCount: ctx.modules.length,
|
|
489
|
+
fileModuleCount: fileModules.length,
|
|
490
|
+
modules: fileModules.map((mod) => ({
|
|
491
|
+
id: mod.id,
|
|
492
|
+
file: mod.file,
|
|
493
|
+
type: mod.type,
|
|
494
|
+
url: mod.url
|
|
495
|
+
}))
|
|
496
|
+
});
|
|
268
497
|
/**
|
|
269
498
|
* Check to see if this was a direct request
|
|
270
499
|
* for an external resource (styles, html).
|
|
271
500
|
*/
|
|
272
|
-
const isDirect =
|
|
273
|
-
const isInline =
|
|
501
|
+
const isDirect = fileModules.find((mod) => !!mod.id && mod.id.includes("?direct") && isModuleForChangedResource(mod, ctx.file, stylesheetRegistry));
|
|
502
|
+
const isInline = fileModules.find((mod) => !!mod.id && mod.id.includes("?inline") && isModuleForChangedResource(mod, ctx.file, stylesheetRegistry));
|
|
503
|
+
debugHmrV("resource direct/inline detection", {
|
|
504
|
+
file: ctx.file,
|
|
505
|
+
hasDirect: !!isDirect,
|
|
506
|
+
directId: isDirect?.id,
|
|
507
|
+
hasInline: !!isInline,
|
|
508
|
+
inlineId: isInline?.id
|
|
509
|
+
});
|
|
274
510
|
if (isDirect || isInline) {
|
|
275
|
-
if (
|
|
276
|
-
|
|
511
|
+
if (shouldExternalizeStyles() && isDirect?.id && isDirect.file) {
|
|
512
|
+
const isComponentStyle = isDirect.type === "css" && isComponentStyleSheet(isDirect.id);
|
|
513
|
+
debugHmrV("resource direct branch", {
|
|
514
|
+
file: ctx.file,
|
|
515
|
+
directId: isDirect.id,
|
|
516
|
+
directType: isDirect.type,
|
|
517
|
+
shouldExternalize: shouldExternalizeStyles(),
|
|
518
|
+
isComponentStyle
|
|
519
|
+
});
|
|
520
|
+
if (isComponentStyle) {
|
|
277
521
|
const { encapsulation } = getComponentStyleSheetMeta(isDirect.id);
|
|
278
|
-
|
|
522
|
+
const wrapperModules = await findComponentStylesheetWrapperModules(ctx.server, ctx.file, isDirect, fileModules, stylesheetRegistry);
|
|
523
|
+
const stylesheetDiagnosis = diagnoseComponentStylesheetPipeline(ctx.file, isDirect, stylesheetRegistry, wrapperModules, pluginOptions.stylePreprocessor);
|
|
524
|
+
debugStylesV("HMR: component stylesheet changed", {
|
|
279
525
|
file: isDirect.file,
|
|
280
526
|
encapsulation
|
|
281
527
|
});
|
|
282
|
-
|
|
283
|
-
ctx.
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
528
|
+
debugHmrV("component stylesheet wrapper modules", {
|
|
529
|
+
file: ctx.file,
|
|
530
|
+
wrapperCount: wrapperModules.length,
|
|
531
|
+
wrapperIds: wrapperModules.map((mod) => mod.id),
|
|
532
|
+
availableModuleIds: fileModules.map((mod) => mod.id)
|
|
533
|
+
});
|
|
534
|
+
debugHmrV("component stylesheet pipeline diagnosis", stylesheetDiagnosis);
|
|
535
|
+
ctx.server.moduleGraph.invalidateModule(isDirect);
|
|
536
|
+
debugHmrV("component stylesheet direct module invalidated", {
|
|
537
|
+
file: ctx.file,
|
|
538
|
+
directModuleId: isDirect.id,
|
|
539
|
+
directModuleUrl: isDirect.url,
|
|
540
|
+
reason: "Ensure Vite drops stale direct CSS transform results before wrapper or fallback handling continues."
|
|
541
|
+
});
|
|
542
|
+
const trackedWrapperRequestIds = stylesheetDiagnosis.trackedRequestIds.filter((id) => id.includes("?ngcomp="));
|
|
543
|
+
if (encapsulation !== "shadow" && (wrapperModules.length > 0 || trackedWrapperRequestIds.length > 0)) {
|
|
544
|
+
wrapperModules.forEach((mod) => ctx.server.moduleGraph.invalidateModule(mod));
|
|
545
|
+
debugHmrV("sending css-update for component stylesheet", {
|
|
546
|
+
file: ctx.file,
|
|
547
|
+
path: isDirect.url,
|
|
548
|
+
acceptedPath: isDirect.file,
|
|
549
|
+
wrapperCount: wrapperModules.length,
|
|
550
|
+
trackedWrapperRequestIds,
|
|
551
|
+
hint: wrapperModules.length > 0 ? "Live wrapper modules were found and invalidated before sending the CSS update." : "No live wrapper ModuleNode was available, but the wrapper request id is already tracked, so Analog is trusting the browser-visible wrapper identity and patching the direct stylesheet instead of forcing a reload."
|
|
552
|
+
});
|
|
553
|
+
sendCssUpdate(ctx.server, {
|
|
554
|
+
path: isDirect.url,
|
|
555
|
+
acceptedPath: isDirect.file
|
|
556
|
+
});
|
|
557
|
+
logComponentStylesheetHmrOutcome({
|
|
558
|
+
file: ctx.file,
|
|
559
|
+
encapsulation,
|
|
560
|
+
diagnosis: stylesheetDiagnosis,
|
|
561
|
+
outcome: "css-update",
|
|
562
|
+
directModuleId: isDirect.id,
|
|
563
|
+
wrapperIds: wrapperModules.map((mod) => mod.id)
|
|
291
564
|
});
|
|
292
|
-
return
|
|
565
|
+
return union(fileModules.filter((mod) => {
|
|
293
566
|
return mod.file !== ctx.file || mod.id !== isDirect.id;
|
|
294
567
|
}).map((mod) => {
|
|
295
568
|
if (mod.file === ctx.file) return markModuleSelfAccepting(mod);
|
|
296
569
|
return mod;
|
|
570
|
+
}), wrapperModules.map((mod) => markModuleSelfAccepting(mod)));
|
|
571
|
+
}
|
|
572
|
+
debugHmrV("component stylesheet hmr fallback: full reload", {
|
|
573
|
+
file: ctx.file,
|
|
574
|
+
encapsulation,
|
|
575
|
+
reason: trackedWrapperRequestIds.length === 0 ? "missing-wrapper-module" : encapsulation === "shadow" ? "shadow-encapsulation" : "tracked-wrapper-still-not-patchable",
|
|
576
|
+
directId: isDirect.id,
|
|
577
|
+
trackedRequestIds: stylesheetRegistry?.getRequestIdsForSource(ctx.file) ?? []
|
|
578
|
+
});
|
|
579
|
+
const ownerModules = findStyleOwnerModules(ctx.server, ctx.file, styleSourceOwners);
|
|
580
|
+
debugHmrV("component stylesheet owner fallback lookup", {
|
|
581
|
+
file: ctx.file,
|
|
582
|
+
ownerCount: ownerModules.length,
|
|
583
|
+
ownerIds: ownerModules.map((mod) => mod.id),
|
|
584
|
+
ownerFiles: [...styleSourceOwners.get(normalizePath(ctx.file)) ?? []]
|
|
585
|
+
});
|
|
586
|
+
if (ownerModules.length > 0) {
|
|
587
|
+
pendingCompilation = performCompilation(resolvedConfig, [...ownerModules.map((mod) => mod.id).filter(Boolean)]);
|
|
588
|
+
await pendingCompilation;
|
|
589
|
+
pendingCompilation = null;
|
|
590
|
+
const updates = ownerModules.map((mod) => mod.id).filter((id) => !!id && !!classNames.get(id));
|
|
591
|
+
const derivedUpdates = ownerModules.map((mod) => mod.id).filter((id) => !!id).flatMap((ownerId) => resolveComponentClassNamesForStyleOwner(ownerId, ctx.file).map((className) => ({
|
|
592
|
+
ownerId,
|
|
593
|
+
className,
|
|
594
|
+
via: "raw-component-metadata"
|
|
595
|
+
})));
|
|
596
|
+
debugHmrV("component stylesheet owner fallback compilation", {
|
|
597
|
+
file: ctx.file,
|
|
598
|
+
ownerIds: ownerModules.map((mod) => mod.id),
|
|
599
|
+
updateIds: updates,
|
|
600
|
+
classNames: updates.map((id) => ({
|
|
601
|
+
id,
|
|
602
|
+
className: classNames.get(id)
|
|
603
|
+
})),
|
|
604
|
+
derivedUpdates
|
|
605
|
+
});
|
|
606
|
+
if (derivedUpdates.length > 0) debugHmrV("component stylesheet owner fallback derived updates", {
|
|
607
|
+
file: ctx.file,
|
|
608
|
+
updates: derivedUpdates,
|
|
609
|
+
hint: "Angular did not repopulate classNames during CSS-only owner recompilation, so Analog derived component identities from raw component metadata."
|
|
297
610
|
});
|
|
298
611
|
}
|
|
612
|
+
logComponentStylesheetHmrOutcome({
|
|
613
|
+
file: ctx.file,
|
|
614
|
+
encapsulation,
|
|
615
|
+
diagnosis: stylesheetDiagnosis,
|
|
616
|
+
outcome: "full-reload",
|
|
617
|
+
directModuleId: isDirect.id,
|
|
618
|
+
wrapperIds: wrapperModules.map((mod) => mod.id),
|
|
619
|
+
ownerIds: ownerModules.map((mod) => mod.id)
|
|
620
|
+
});
|
|
621
|
+
sendFullReload(ctx.server, {
|
|
622
|
+
file: ctx.file,
|
|
623
|
+
encapsulation,
|
|
624
|
+
reason: wrapperModules.length === 0 ? "missing-wrapper-module-and-no-owner-updates" : "shadow-encapsulation",
|
|
625
|
+
directId: isDirect.id,
|
|
626
|
+
trackedRequestIds: stylesheetRegistry?.getRequestIdsForSource(ctx.file) ?? []
|
|
627
|
+
});
|
|
628
|
+
return [];
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return fileModules;
|
|
632
|
+
}
|
|
633
|
+
if (shouldEnableHmr() && /\.(html|htm)$/.test(ctx.file) && fileModules.length === 0) {
|
|
634
|
+
const ownerModules = findTemplateOwnerModules(ctx.server, ctx.file);
|
|
635
|
+
debugHmrV("template owner lookup", {
|
|
636
|
+
file: ctx.file,
|
|
637
|
+
ownerCount: ownerModules.length,
|
|
638
|
+
ownerIds: ownerModules.map((mod) => mod.id),
|
|
639
|
+
hint: ownerModules.length > 0 ? "The external template has candidate TS owner modules that can be recompiled for HMR." : "No TS owner modules were visible for this external template change; HMR will fall through to the generic importer path."
|
|
640
|
+
});
|
|
641
|
+
if (ownerModules.length > 0) {
|
|
642
|
+
const ownerIds = ownerModules.map((mod) => mod.id).filter(Boolean);
|
|
643
|
+
ownerModules.forEach((mod) => ctx.server.moduleGraph.invalidateModule(mod));
|
|
644
|
+
pendingCompilation = performCompilation(resolvedConfig, ownerIds);
|
|
645
|
+
await pendingCompilation;
|
|
646
|
+
pendingCompilation = null;
|
|
647
|
+
const updates = ownerIds.filter((id) => classNames.get(id));
|
|
648
|
+
debugHmrV("template owner recompilation result", {
|
|
649
|
+
file: ctx.file,
|
|
650
|
+
ownerIds,
|
|
651
|
+
updates,
|
|
652
|
+
updateClassNames: updates.map((id) => ({
|
|
653
|
+
id,
|
|
654
|
+
className: classNames.get(id)
|
|
655
|
+
})),
|
|
656
|
+
hint: updates.length > 0 ? "External template recompilation produced Angular component update targets." : "External template recompilation completed, but no Angular component update targets were surfaced."
|
|
657
|
+
});
|
|
658
|
+
if (updates.length > 0) {
|
|
659
|
+
debugHmr("template owner module invalidation", {
|
|
660
|
+
file: ctx.file,
|
|
661
|
+
ownerIds,
|
|
662
|
+
updateCount: updates.length
|
|
663
|
+
});
|
|
664
|
+
updates.forEach((updateId) => {
|
|
665
|
+
const relativeFileId = `${normalizePath(relative(process.cwd(), updateId))}@${classNames.get(updateId)}`;
|
|
666
|
+
sendHMRComponentUpdate(ctx.server, relativeFileId);
|
|
667
|
+
});
|
|
668
|
+
return ownerModules.map((mod) => markModuleSelfAccepting(mod));
|
|
299
669
|
}
|
|
300
670
|
}
|
|
301
|
-
return ctx.modules;
|
|
302
671
|
}
|
|
303
672
|
const mods = [];
|
|
304
673
|
const updates = [];
|
|
305
|
-
|
|
674
|
+
fileModules.forEach((mod) => {
|
|
306
675
|
mod.importers.forEach((imp) => {
|
|
307
676
|
ctx.server.moduleGraph.invalidateModule(imp);
|
|
308
|
-
if (
|
|
677
|
+
if (shouldExternalizeStyles() && classNames.get(imp.id)) updates.push(imp.id);
|
|
309
678
|
else mods.push(imp);
|
|
310
679
|
});
|
|
311
680
|
});
|
|
312
|
-
|
|
681
|
+
debugHmrV("resource importer analysis", {
|
|
682
|
+
file: ctx.file,
|
|
683
|
+
fileModuleCount: fileModules.length,
|
|
684
|
+
importerCount: fileModules.reduce((count, mod) => count + mod.importers.size, 0),
|
|
685
|
+
updates,
|
|
686
|
+
mods: mods.map((mod) => mod.id)
|
|
687
|
+
});
|
|
688
|
+
pendingCompilation = performCompilation(resolvedConfig, [...mods.map((mod) => mod.id).filter(Boolean), ...updates]);
|
|
313
689
|
if (updates.length > 0) {
|
|
314
690
|
await pendingCompilation;
|
|
315
691
|
pendingCompilation = null;
|
|
@@ -321,7 +697,7 @@ function angular(options) {
|
|
|
321
697
|
const impRelativeFileId = `${normalizePath(relative(process.cwd(), updateId))}@${classNames.get(updateId)}`;
|
|
322
698
|
sendHMRComponentUpdate(ctx.server, impRelativeFileId);
|
|
323
699
|
});
|
|
324
|
-
return
|
|
700
|
+
return fileModules.map((mod) => {
|
|
325
701
|
if (mod.id === ctx.file) return markModuleSelfAccepting(mod);
|
|
326
702
|
return mod;
|
|
327
703
|
});
|
|
@@ -339,24 +715,42 @@ function angular(options) {
|
|
|
339
715
|
}
|
|
340
716
|
if (isComponentStyleSheet(id)) {
|
|
341
717
|
const filename = getFilenameFromPath(id);
|
|
342
|
-
|
|
718
|
+
if (stylesheetRegistry?.hasServed(filename)) {
|
|
719
|
+
debugStylesV("resolveId: kept preprocessed ID", { filename });
|
|
720
|
+
return id;
|
|
721
|
+
}
|
|
722
|
+
const componentStyles = stylesheetRegistry?.resolveExternalSource(filename);
|
|
343
723
|
if (componentStyles) {
|
|
344
|
-
|
|
724
|
+
debugStylesV("resolveId: mapped external stylesheet", {
|
|
345
725
|
filename,
|
|
346
726
|
resolvedPath: componentStyles
|
|
347
727
|
});
|
|
348
728
|
return componentStyles + new URL(id, "http://localhost").search;
|
|
349
729
|
}
|
|
730
|
+
debugStyles("resolveId: component stylesheet NOT FOUND in either map", {
|
|
731
|
+
filename,
|
|
732
|
+
inlineMapSize: stylesheetRegistry?.servedCount ?? 0,
|
|
733
|
+
externalMapSize: stylesheetRegistry?.externalCount ?? 0
|
|
734
|
+
});
|
|
350
735
|
}
|
|
351
736
|
},
|
|
352
737
|
async load(id) {
|
|
353
738
|
if (isComponentStyleSheet(id)) {
|
|
354
739
|
const filename = getFilenameFromPath(id);
|
|
355
|
-
const componentStyles =
|
|
740
|
+
const componentStyles = stylesheetRegistry?.getServedContent(filename);
|
|
356
741
|
if (componentStyles) {
|
|
357
|
-
|
|
742
|
+
stylesheetRegistry?.registerActiveRequest(id);
|
|
743
|
+
debugHmrV("stylesheet active request registered", {
|
|
744
|
+
requestId: id,
|
|
358
745
|
filename,
|
|
359
|
-
|
|
746
|
+
sourcePath: stylesheetRegistry?.resolveExternalSource(filename) ?? stylesheetRegistry?.resolveExternalSource(filename.replace(/^\//, "")),
|
|
747
|
+
trackedRequestIds: stylesheetRegistry?.getRequestIdsForSource(stylesheetRegistry?.resolveExternalSource(filename) ?? stylesheetRegistry?.resolveExternalSource(filename.replace(/^\//, "")) ?? "") ?? []
|
|
748
|
+
});
|
|
749
|
+
debugStylesV("load: served inline component stylesheet", {
|
|
750
|
+
filename,
|
|
751
|
+
length: componentStyles.length,
|
|
752
|
+
requestId: id,
|
|
753
|
+
...describeStylesheetContent(componentStyles)
|
|
360
754
|
});
|
|
361
755
|
return componentStyles;
|
|
362
756
|
}
|
|
@@ -387,12 +781,15 @@ function angular(options) {
|
|
|
387
781
|
*/
|
|
388
782
|
if (id.includes("?") && id.includes("analog-content-")) return;
|
|
389
783
|
/**
|
|
390
|
-
* Encapsulate component stylesheets that use emulated encapsulation
|
|
784
|
+
* Encapsulate component stylesheets that use emulated encapsulation.
|
|
785
|
+
* Must run whenever styles are externalized (not just HMR), because
|
|
786
|
+
* Angular's externalRuntimeStyles skips its own encapsulation when
|
|
787
|
+
* styles are external — the build tool is expected to handle it.
|
|
391
788
|
*/
|
|
392
|
-
if (
|
|
789
|
+
if (shouldExternalizeStyles() && isComponentStyleSheet(id)) {
|
|
393
790
|
const { encapsulation, componentId } = getComponentStyleSheetMeta(id);
|
|
394
791
|
if (encapsulation === "emulated" && componentId) {
|
|
395
|
-
|
|
792
|
+
debugStylesV("applying emulated view encapsulation", {
|
|
396
793
|
stylesheet: id.split("?")[0],
|
|
397
794
|
componentId
|
|
398
795
|
});
|
|
@@ -420,7 +817,7 @@ function angular(options) {
|
|
|
420
817
|
}
|
|
421
818
|
}
|
|
422
819
|
const hasComponent = code.includes("@Component");
|
|
423
|
-
|
|
820
|
+
debugCompilerV("transform", {
|
|
424
821
|
id,
|
|
425
822
|
codeLength: code.length,
|
|
426
823
|
hasComponent
|
|
@@ -450,6 +847,19 @@ function angular(options) {
|
|
|
450
847
|
data = data.replace(`angular:jit:style:file;${styleFile}`, `${resolvedStyleUrl}?inline`);
|
|
451
848
|
});
|
|
452
849
|
}
|
|
850
|
+
if (data.includes("HmrLoad")) {
|
|
851
|
+
const hasMetaUrl = data.includes("getReplaceMetadataURL");
|
|
852
|
+
debugHmrV("vite-ignore injection", {
|
|
853
|
+
id,
|
|
854
|
+
dataLength: data.length,
|
|
855
|
+
hasMetaUrl
|
|
856
|
+
});
|
|
857
|
+
if (hasMetaUrl) {
|
|
858
|
+
const patched = injectViteIgnoreForHmrMetadata(data);
|
|
859
|
+
if (patched !== data && !patched.includes("@vite-ignore")) debugHmrV("vite-ignore regex fallback", { id });
|
|
860
|
+
data = patched;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
453
863
|
return {
|
|
454
864
|
code: data,
|
|
455
865
|
map: null
|
|
@@ -468,8 +878,103 @@ function angular(options) {
|
|
|
468
878
|
}
|
|
469
879
|
return [
|
|
470
880
|
replaceFiles(pluginOptions.fileReplacements, pluginOptions.workspaceRoot),
|
|
881
|
+
{
|
|
882
|
+
name: "@analogjs/vite-plugin-angular:template-class-binding-guard",
|
|
883
|
+
enforce: "pre",
|
|
884
|
+
transform(code, id) {
|
|
885
|
+
if (id.includes("node_modules")) return;
|
|
886
|
+
const cleanId = id.split("?")[0];
|
|
887
|
+
if (/\.(html|htm)$/i.test(cleanId)) {
|
|
888
|
+
const staticClassIssue = findStaticClassAndBoundClassConflicts(code)[0];
|
|
889
|
+
if (staticClassIssue) throwTemplateClassBindingConflict(cleanId, staticClassIssue);
|
|
890
|
+
const mixedClassIssue = findBoundClassAndNgClassConflicts(code)[0];
|
|
891
|
+
if (mixedClassIssue) this.warn([
|
|
892
|
+
"[Analog Angular] Conflicting class composition.",
|
|
893
|
+
`File: ${cleanId}:${mixedClassIssue.line}:${mixedClassIssue.column}`,
|
|
894
|
+
"This element mixes `[class]` and `[ngClass]`.",
|
|
895
|
+
"Prefer a single class-binding strategy so class merging stays predictable.",
|
|
896
|
+
"Use one `[ngClass]` expression or explicit `[class.foo]` bindings.",
|
|
897
|
+
`Snippet: ${mixedClassIssue.snippet}`
|
|
898
|
+
].join("\n"));
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
if (TS_EXT_REGEX.test(cleanId)) {
|
|
902
|
+
const rawStyleUrls = styleUrlsResolver.resolve(code, cleanId);
|
|
903
|
+
registerStyleOwnerMetadata(cleanId, rawStyleUrls);
|
|
904
|
+
debugHmrV("component stylesheet owner metadata registered", {
|
|
905
|
+
file: cleanId,
|
|
906
|
+
styleUrlCount: rawStyleUrls.length,
|
|
907
|
+
styleUrls: rawStyleUrls,
|
|
908
|
+
ownerSources: [...transformedStyleOwnerMetadata.get(cleanId)?.map((record) => record.sourcePath) ?? []]
|
|
909
|
+
});
|
|
910
|
+
const components = getAngularComponentMetadata(code);
|
|
911
|
+
const inlineTemplateIssue = components.flatMap((component) => component.inlineTemplates.flatMap((template) => findStaticClassAndBoundClassConflicts(template)))[0];
|
|
912
|
+
if (inlineTemplateIssue) throwTemplateClassBindingConflict(cleanId, inlineTemplateIssue);
|
|
913
|
+
const mixedInlineClassIssue = components.flatMap((component) => component.inlineTemplates.flatMap((template) => findBoundClassAndNgClassConflicts(template)))[0];
|
|
914
|
+
if (mixedInlineClassIssue) this.warn([
|
|
915
|
+
"[Analog Angular] Conflicting class composition.",
|
|
916
|
+
`File: ${cleanId}:${mixedInlineClassIssue.line}:${mixedInlineClassIssue.column}`,
|
|
917
|
+
"This element mixes `[class]` and `[ngClass]`.",
|
|
918
|
+
"Prefer a single class-binding strategy so class merging stays predictable.",
|
|
919
|
+
"Use one `[ngClass]` expression or explicit `[class.foo]` bindings.",
|
|
920
|
+
`Snippet: ${mixedInlineClassIssue.snippet}`
|
|
921
|
+
].join("\n"));
|
|
922
|
+
registerActiveGraphMetadata(cleanId, components.map((component) => ({
|
|
923
|
+
file: cleanId,
|
|
924
|
+
className: component.className,
|
|
925
|
+
selector: component.selector
|
|
926
|
+
})));
|
|
927
|
+
for (const component of components) {
|
|
928
|
+
if (!component.selector && !isLikelyPageOnlyComponent(cleanId)) throw new Error([
|
|
929
|
+
"[Analog Angular] Selectorless component detected.",
|
|
930
|
+
`File: ${cleanId}`,
|
|
931
|
+
`Component: ${component.className}`,
|
|
932
|
+
"This component has no `selector`, so Angular will render it as `ng-component`.",
|
|
933
|
+
"That increases the chance of component ID collisions and makes diagnostics harder to interpret.",
|
|
934
|
+
"Add an explicit selector for reusable components.",
|
|
935
|
+
"Selectorless components are only supported for page and route-only files."
|
|
936
|
+
].join("\n"));
|
|
937
|
+
if (component.selector) {
|
|
938
|
+
const selectorEntries = selectorOwners.get(component.selector);
|
|
939
|
+
if (selectorEntries && selectorEntries.size > 1) throw new Error([
|
|
940
|
+
"[Analog Angular] Duplicate component selector detected.",
|
|
941
|
+
`Selector: ${component.selector}`,
|
|
942
|
+
"Multiple components in the active application graph use the same selector.",
|
|
943
|
+
"Selectors must be unique within the active graph to avoid ambiguous rendering and confusing diagnostics.",
|
|
944
|
+
`Locations:\n${formatActiveGraphLocations(selectorEntries)}`
|
|
945
|
+
].join("\n"));
|
|
946
|
+
}
|
|
947
|
+
const classNameEntries = classNameOwners.get(component.className);
|
|
948
|
+
if (classNameEntries && classNameEntries.size > 1) this.warn([
|
|
949
|
+
"[Analog Angular] Duplicate component class name detected.",
|
|
950
|
+
`Class name: ${component.className}`,
|
|
951
|
+
"Two or more Angular components in the active graph share the same exported class name.",
|
|
952
|
+
"Rename one of them to keep HMR, stack traces, and compiler diagnostics unambiguous.",
|
|
953
|
+
`Locations:\n${formatActiveGraphLocations(classNameEntries)}`
|
|
954
|
+
].join("\n"));
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
},
|
|
959
|
+
pluginOptions.hasTailwindCss && {
|
|
960
|
+
name: "@analogjs/vite-plugin-angular:tailwind-reference",
|
|
961
|
+
enforce: "pre",
|
|
962
|
+
transform(code, id) {
|
|
963
|
+
const tw = pluginOptions.tailwindCss;
|
|
964
|
+
if (!tw || !id.includes(".css")) return;
|
|
965
|
+
if (id.split("?")[0] === tw.rootStylesheet) return;
|
|
966
|
+
if (code.includes("@reference") || code.includes("@import \"tailwindcss\"") || code.includes("@import 'tailwindcss'")) return;
|
|
967
|
+
const rootBasename = basename(tw.rootStylesheet);
|
|
968
|
+
if (code.includes(rootBasename)) return;
|
|
969
|
+
const prefixes = tw.prefixes;
|
|
970
|
+
if (prefixes ? prefixes.some((p) => code.includes(p)) : code.includes("@apply")) {
|
|
971
|
+
debugTailwind("injected @reference via pre-transform", { id: id.split("/").slice(-2).join("/") });
|
|
972
|
+
return `@reference "${tw.rootStylesheet}";\n${code}`;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
},
|
|
471
976
|
angularPlugin(),
|
|
472
|
-
pluginOptions.
|
|
977
|
+
pluginOptions.hmr && liveReloadPlugin({
|
|
473
978
|
classNames,
|
|
474
979
|
fileEmitter
|
|
475
980
|
}),
|
|
@@ -484,8 +989,7 @@ function angular(options) {
|
|
|
484
989
|
nxFolderPlugin()
|
|
485
990
|
].filter(Boolean);
|
|
486
991
|
function findIncludes() {
|
|
487
|
-
|
|
488
|
-
return globSync([...pluginOptions.include.map((glob) => `${workspaceRoot}${glob}`)], {
|
|
992
|
+
return globSync(pluginOptions.include.map((glob) => normalizeIncludeGlob(pluginOptions.workspaceRoot, glob)), {
|
|
489
993
|
dot: true,
|
|
490
994
|
absolute: true
|
|
491
995
|
});
|
|
@@ -540,17 +1044,30 @@ function angular(options) {
|
|
|
540
1044
|
modifiedFiles,
|
|
541
1045
|
async transformStylesheet(data, containingFile, resourceFile, order, className) {
|
|
542
1046
|
const filename = resourceFile ?? containingFile.replace(".ts", `.${pluginOptions.inlineStylesExtension}`);
|
|
543
|
-
const preprocessedData =
|
|
544
|
-
if (
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
1047
|
+
const preprocessedData = preprocessStylesheet(data, filename, pluginOptions.stylePreprocessor);
|
|
1048
|
+
if (shouldEnableHmr() && className && containingFile) classNames.set(normalizePath(containingFile), className);
|
|
1049
|
+
if (shouldExternalizeStyles()) {
|
|
1050
|
+
const stylesheetId = registerStylesheetContent(stylesheetRegistry, {
|
|
1051
|
+
code: preprocessedData,
|
|
1052
|
+
containingFile,
|
|
1053
|
+
className,
|
|
1054
|
+
order,
|
|
1055
|
+
inlineStylesExtension: pluginOptions.inlineStylesExtension,
|
|
1056
|
+
resourceFile: resourceFile ?? void 0
|
|
1057
|
+
});
|
|
1058
|
+
debugStyles("stylesheet deferred to Vite pipeline", {
|
|
548
1059
|
stylesheetId,
|
|
549
1060
|
resourceFile: resourceFile ?? "(inline)"
|
|
550
1061
|
});
|
|
1062
|
+
debugStylesV("stylesheet deferred content snapshot", {
|
|
1063
|
+
stylesheetId,
|
|
1064
|
+
filename,
|
|
1065
|
+
resourceFile: resourceFile ?? "(inline)",
|
|
1066
|
+
...describeStylesheetContent(preprocessedData)
|
|
1067
|
+
});
|
|
551
1068
|
return stylesheetId;
|
|
552
1069
|
}
|
|
553
|
-
debugStyles("stylesheet processed inline via preprocessCSS
|
|
1070
|
+
debugStyles("stylesheet processed inline via preprocessCSS", {
|
|
554
1071
|
filename,
|
|
555
1072
|
resourceFile: resourceFile ?? "(inline)",
|
|
556
1073
|
dataLength: preprocessedData.length
|
|
@@ -559,7 +1076,11 @@ function angular(options) {
|
|
|
559
1076
|
try {
|
|
560
1077
|
stylesheetResult = await preprocessCSS(preprocessedData, `${filename}?direct`, resolvedConfig);
|
|
561
1078
|
} catch (e) {
|
|
562
|
-
|
|
1079
|
+
debugStyles("preprocessCSS error", {
|
|
1080
|
+
filename,
|
|
1081
|
+
resourceFile: resourceFile ?? "(inline)",
|
|
1082
|
+
error: String(e)
|
|
1083
|
+
});
|
|
563
1084
|
}
|
|
564
1085
|
return stylesheetResult?.code || "";
|
|
565
1086
|
},
|
|
@@ -567,21 +1088,24 @@ function angular(options) {
|
|
|
567
1088
|
return "";
|
|
568
1089
|
}
|
|
569
1090
|
}, (tsCompilerOptions) => {
|
|
570
|
-
if (
|
|
1091
|
+
if (shouldExternalizeStyles()) tsCompilerOptions["externalRuntimeStyles"] = true;
|
|
1092
|
+
if (shouldEnableHmr()) {
|
|
571
1093
|
tsCompilerOptions["_enableHmr"] = true;
|
|
572
|
-
tsCompilerOptions["externalRuntimeStyles"] = true;
|
|
573
1094
|
tsCompilerOptions["supportTestBed"] = true;
|
|
574
1095
|
}
|
|
575
1096
|
debugCompiler("tsCompilerOptions (compilation API)", {
|
|
576
|
-
|
|
1097
|
+
hmr: pluginOptions.hmr,
|
|
1098
|
+
hasTailwindCss: pluginOptions.hasTailwindCss,
|
|
577
1099
|
watchMode,
|
|
1100
|
+
shouldExternalize: shouldExternalizeStyles(),
|
|
578
1101
|
externalRuntimeStyles: !!tsCompilerOptions["externalRuntimeStyles"],
|
|
579
|
-
|
|
1102
|
+
hmrEnabled: !!tsCompilerOptions["_enableHmr"]
|
|
580
1103
|
});
|
|
581
1104
|
if (tsCompilerOptions.compilationMode === "partial") {
|
|
582
1105
|
tsCompilerOptions["supportTestBed"] = true;
|
|
583
1106
|
tsCompilerOptions["supportJitMode"] = true;
|
|
584
1107
|
}
|
|
1108
|
+
if (angularFullVersion >= 2e5) tsCompilerOptions["_enableSelectorless"] = true;
|
|
585
1109
|
if (!isTest && config.build?.lib) {
|
|
586
1110
|
tsCompilerOptions["declaration"] = true;
|
|
587
1111
|
tsCompilerOptions["declarationMap"] = watchMode;
|
|
@@ -590,13 +1114,80 @@ function angular(options) {
|
|
|
590
1114
|
if (isTest) tsCompilerOptions["supportTestBed"] = true;
|
|
591
1115
|
return tsCompilerOptions;
|
|
592
1116
|
});
|
|
1117
|
+
debugStyles("external stylesheets from compilation API", {
|
|
1118
|
+
count: compilationResult.externalStylesheets?.size ?? 0,
|
|
1119
|
+
hasPreprocessor: !!pluginOptions.stylePreprocessor,
|
|
1120
|
+
hasInlineMap: !!stylesheetRegistry
|
|
1121
|
+
});
|
|
1122
|
+
const preprocessStats = {
|
|
1123
|
+
total: 0,
|
|
1124
|
+
injected: 0,
|
|
1125
|
+
skipped: 0,
|
|
1126
|
+
errors: 0
|
|
1127
|
+
};
|
|
593
1128
|
compilationResult.externalStylesheets?.forEach((value, key) => {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
1129
|
+
preprocessStats.total++;
|
|
1130
|
+
const angularHash = `${value}.css`;
|
|
1131
|
+
stylesheetRegistry?.registerExternalRequest(angularHash, key);
|
|
1132
|
+
if (stylesheetRegistry && pluginOptions.stylePreprocessor && existsSync(key)) try {
|
|
1133
|
+
const rawCss = readFileSync(key, "utf-8");
|
|
1134
|
+
let preprocessed = preprocessStylesheet(rawCss, key, pluginOptions.stylePreprocessor);
|
|
1135
|
+
debugStylesV("external stylesheet raw snapshot", {
|
|
1136
|
+
angularHash,
|
|
1137
|
+
resolvedPath: key,
|
|
1138
|
+
mtimeMs: safeStatMtimeMs(key),
|
|
1139
|
+
...describeStylesheetContent(rawCss)
|
|
1140
|
+
});
|
|
1141
|
+
preprocessed = rewriteRelativeCssImports(preprocessed, key);
|
|
1142
|
+
stylesheetRegistry.registerServedStylesheet({
|
|
1143
|
+
publicId: angularHash,
|
|
1144
|
+
sourcePath: key,
|
|
1145
|
+
originalCode: rawCss,
|
|
1146
|
+
normalizedCode: preprocessed
|
|
1147
|
+
}, [
|
|
1148
|
+
key,
|
|
1149
|
+
normalizePath(key),
|
|
1150
|
+
basename(key),
|
|
1151
|
+
key.replace(/^\//, "")
|
|
1152
|
+
]);
|
|
1153
|
+
if (preprocessed && preprocessed !== rawCss) {
|
|
1154
|
+
preprocessStats.injected++;
|
|
1155
|
+
debugStylesV("preprocessed external stylesheet for Tailwind @reference", {
|
|
1156
|
+
angularHash,
|
|
1157
|
+
resolvedPath: key,
|
|
1158
|
+
mtimeMs: safeStatMtimeMs(key),
|
|
1159
|
+
raw: describeStylesheetContent(rawCss),
|
|
1160
|
+
served: describeStylesheetContent(preprocessed)
|
|
1161
|
+
});
|
|
1162
|
+
} else {
|
|
1163
|
+
preprocessStats.skipped++;
|
|
1164
|
+
debugStylesV("external stylesheet unchanged after preprocessing", {
|
|
1165
|
+
angularHash,
|
|
1166
|
+
resolvedPath: key,
|
|
1167
|
+
mtimeMs: safeStatMtimeMs(key),
|
|
1168
|
+
raw: describeStylesheetContent(rawCss),
|
|
1169
|
+
served: describeStylesheetContent(preprocessed),
|
|
1170
|
+
hint: "Registry mapping is still registered so Angular component stylesheet HMR can track and refresh this file even when preprocessing makes no textual changes."
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
} catch (e) {
|
|
1174
|
+
preprocessStats.errors++;
|
|
1175
|
+
console.warn(`[@analogjs/vite-plugin-angular] failed to preprocess external stylesheet: ${key}: ${e}`);
|
|
1176
|
+
}
|
|
1177
|
+
else {
|
|
1178
|
+
preprocessStats.skipped++;
|
|
1179
|
+
debugStylesV("external stylesheet preprocessing skipped", {
|
|
1180
|
+
filename: angularHash,
|
|
1181
|
+
resolvedPath: key,
|
|
1182
|
+
reason: !stylesheetRegistry ? "no stylesheetRegistry" : !pluginOptions.stylePreprocessor ? "no stylePreprocessor" : "file not found on disk"
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
debugStylesV("external stylesheet registered for resolveId mapping", {
|
|
1186
|
+
filename: angularHash,
|
|
597
1187
|
resolvedPath: key
|
|
598
1188
|
});
|
|
599
1189
|
});
|
|
1190
|
+
debugStyles("external stylesheet preprocessing complete", preprocessStats);
|
|
600
1191
|
const diagnostics = await angularCompilation.diagnoseFiles(pluginOptions.disableTypeChecking ? DiagnosticModes.All & ~DiagnosticModes.Semantic : DiagnosticModes.All);
|
|
601
1192
|
const errors = diagnostics.errors?.length ? diagnostics.errors : [];
|
|
602
1193
|
const warnings = diagnostics.warnings?.length ? diagnostics.warnings : [];
|
|
@@ -652,7 +1243,9 @@ function angular(options) {
|
|
|
652
1243
|
resolvedTsConfigPath,
|
|
653
1244
|
isProd ? "prod" : "dev",
|
|
654
1245
|
isTest ? "test" : "app",
|
|
655
|
-
config.build?.lib ? "lib" : "nolib"
|
|
1246
|
+
config.build?.lib ? "lib" : "nolib",
|
|
1247
|
+
pluginOptions.hmr ? "hmr" : "nohmr",
|
|
1248
|
+
pluginOptions.hasTailwindCss ? "tw" : "notw"
|
|
656
1249
|
].join("|");
|
|
657
1250
|
let cached = tsconfigOptionsCache.get(tsconfigKey);
|
|
658
1251
|
if (!cached) {
|
|
@@ -681,19 +1274,22 @@ function angular(options) {
|
|
|
681
1274
|
}
|
|
682
1275
|
const tsCompilerOptions = { ...cached.options };
|
|
683
1276
|
let rootNames = [...cached.rootNames];
|
|
684
|
-
if (
|
|
1277
|
+
if (shouldExternalizeStyles()) tsCompilerOptions["externalRuntimeStyles"] = true;
|
|
1278
|
+
if (shouldEnableHmr()) {
|
|
685
1279
|
tsCompilerOptions["_enableHmr"] = true;
|
|
686
|
-
tsCompilerOptions["externalRuntimeStyles"] = true;
|
|
687
1280
|
tsCompilerOptions["supportTestBed"] = true;
|
|
688
1281
|
}
|
|
689
1282
|
debugCompiler("tsCompilerOptions (NgtscProgram path)", {
|
|
1283
|
+
hmr: pluginOptions.hmr,
|
|
1284
|
+
shouldExternalize: shouldExternalizeStyles(),
|
|
690
1285
|
externalRuntimeStyles: !!tsCompilerOptions["externalRuntimeStyles"],
|
|
691
|
-
|
|
1286
|
+
hmrEnabled: !!tsCompilerOptions["_enableHmr"]
|
|
692
1287
|
});
|
|
693
1288
|
if (tsCompilerOptions["compilationMode"] === "partial") {
|
|
694
1289
|
tsCompilerOptions["supportTestBed"] = true;
|
|
695
1290
|
tsCompilerOptions["supportJitMode"] = true;
|
|
696
1291
|
}
|
|
1292
|
+
if (angularFullVersion >= 2e5) tsCompilerOptions["_enableSelectorless"] = true;
|
|
697
1293
|
if (!isTest && config.build?.lib) {
|
|
698
1294
|
tsCompilerOptions["declaration"] = true;
|
|
699
1295
|
tsCompilerOptions["declarationMap"] = watchMode;
|
|
@@ -701,11 +1297,7 @@ function angular(options) {
|
|
|
701
1297
|
}
|
|
702
1298
|
if (isTest) tsCompilerOptions["supportTestBed"] = true;
|
|
703
1299
|
const replacements = pluginOptions.fileReplacements.map((rp) => join(pluginOptions.workspaceRoot, rp.ssr || rp.with));
|
|
704
|
-
rootNames =
|
|
705
|
-
...rootNames,
|
|
706
|
-
...includeCache,
|
|
707
|
-
...replacements
|
|
708
|
-
])];
|
|
1300
|
+
rootNames = union(rootNames, includeCache, replacements);
|
|
709
1301
|
const hostKey = JSON.stringify(tsCompilerOptions);
|
|
710
1302
|
let host;
|
|
711
1303
|
if (cachedHost && cachedHostKey === hostKey) host = cachedHost;
|
|
@@ -725,14 +1317,12 @@ function angular(options) {
|
|
|
725
1317
|
}
|
|
726
1318
|
if (!jit) {
|
|
727
1319
|
const externalizeStyles = !!tsCompilerOptions["externalRuntimeStyles"];
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
debugStyles("style maps initialized (NgtscProgram path)", { externalizeStyles });
|
|
1320
|
+
stylesheetRegistry = externalizeStyles ? new AnalogStylesheetRegistry() : void 0;
|
|
1321
|
+
debugStyles("stylesheet registry initialized (NgtscProgram path)", { externalizeStyles });
|
|
731
1322
|
augmentHostWithResources(host, styleTransform, {
|
|
732
1323
|
inlineStylesExtension: pluginOptions.inlineStylesExtension,
|
|
733
1324
|
isProd,
|
|
734
|
-
|
|
735
|
-
externalComponentStyles,
|
|
1325
|
+
stylesheetRegistry,
|
|
736
1326
|
sourceFileCache: sourceFileCache$1,
|
|
737
1327
|
stylePreprocessor: pluginOptions.stylePreprocessor
|
|
738
1328
|
});
|
|
@@ -759,7 +1349,7 @@ function angular(options) {
|
|
|
759
1349
|
if (!watchMode) builder = ts.createAbstractBuilder(typeScriptProgram, host, oldBuilder);
|
|
760
1350
|
if (angularCompiler) await angularCompiler.analyzeAsync();
|
|
761
1351
|
const transformers = mergeTransformers({ before: jit ? [compilerCli.constructorParametersDownlevelTransform(builder.getProgram()), cjt(() => builder.getProgram().getTypeChecker())] : [] }, jit ? {} : angularCompiler.prepareEmit().transformers);
|
|
762
|
-
const fileMetadata = getFileMetadata(builder, angularCompiler, pluginOptions.
|
|
1352
|
+
const fileMetadata = getFileMetadata(builder, angularCompiler, pluginOptions.hmr, pluginOptions.disableTypeChecking);
|
|
763
1353
|
const writeFileCallback = (_filename, content, _a, _b, sourceFiles) => {
|
|
764
1354
|
if (!sourceFiles?.length) return;
|
|
765
1355
|
const filename = normalizePath(sourceFiles[0].fileName);
|
|
@@ -853,14 +1443,377 @@ function mapTemplateUpdatesToFiles(templateUpdates) {
|
|
|
853
1443
|
});
|
|
854
1444
|
return updatesByFile;
|
|
855
1445
|
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Returns every live Vite module that can legitimately represent a changed
|
|
1448
|
+
* Angular resource file.
|
|
1449
|
+
*
|
|
1450
|
+
* For normal files, `getModulesByFile()` is enough. For Angular component
|
|
1451
|
+
* stylesheets, it is not: the browser often holds virtual hashed requests
|
|
1452
|
+
* (`/abc123.css?direct&ngcomp=...` and `/abc123.css?ngcomp=...`) that are no
|
|
1453
|
+
* longer discoverable from the original source path alone. We therefore merge:
|
|
1454
|
+
* - watcher event modules
|
|
1455
|
+
* - module-graph modules by source file
|
|
1456
|
+
* - registry-tracked live request ids resolved back through the module graph
|
|
1457
|
+
*/
|
|
1458
|
+
async function getModulesForChangedFile(server, file, eventModules = [], stylesheetRegistry) {
|
|
1459
|
+
const normalizedFile = normalizePath(file.split("?")[0]);
|
|
1460
|
+
const modules = /* @__PURE__ */ new Map();
|
|
1461
|
+
for (const mod of eventModules) if (mod.id) modules.set(mod.id, mod);
|
|
1462
|
+
server.moduleGraph.getModulesByFile(normalizedFile)?.forEach((mod) => {
|
|
1463
|
+
if (mod.id) modules.set(mod.id, mod);
|
|
1464
|
+
});
|
|
1465
|
+
const stylesheetRequestIds = stylesheetRegistry?.getRequestIdsForSource(normalizedFile) ?? [];
|
|
1466
|
+
const requestIdHits = [];
|
|
1467
|
+
for (const requestId of stylesheetRequestIds) {
|
|
1468
|
+
const candidates = [requestId, requestId.startsWith("/") ? requestId : `/${requestId}`];
|
|
1469
|
+
for (const candidate of candidates) {
|
|
1470
|
+
const mod = await server.moduleGraph.getModuleByUrl(candidate) ?? server.moduleGraph.getModuleById(candidate);
|
|
1471
|
+
requestIdHits.push({
|
|
1472
|
+
requestId,
|
|
1473
|
+
candidate,
|
|
1474
|
+
via: mod?.url === candidate ? "url" : "id",
|
|
1475
|
+
moduleId: mod?.id
|
|
1476
|
+
});
|
|
1477
|
+
if (mod?.id) modules.set(mod.id, mod);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
debugHmrV("getModulesForChangedFile registry lookup", {
|
|
1481
|
+
file: normalizedFile,
|
|
1482
|
+
stylesheetRequestIds,
|
|
1483
|
+
requestIdHits,
|
|
1484
|
+
resolvedModuleIds: [...modules.keys()]
|
|
1485
|
+
});
|
|
1486
|
+
return [...modules.values()];
|
|
1487
|
+
}
|
|
1488
|
+
function isModuleForChangedResource(mod, changedFile, stylesheetRegistry) {
|
|
1489
|
+
const normalizedChangedFile = normalizePath(changedFile.split("?")[0]);
|
|
1490
|
+
if (normalizePath((mod.file ?? "").split("?")[0]) === normalizedChangedFile) return true;
|
|
1491
|
+
if (!mod.id) return false;
|
|
1492
|
+
const requestPath = getFilenameFromPath(mod.id);
|
|
1493
|
+
return normalizePath((stylesheetRegistry?.resolveExternalSource(requestPath) ?? stylesheetRegistry?.resolveExternalSource(requestPath.replace(/^\//, "")) ?? "").split("?")[0]) === normalizedChangedFile;
|
|
1494
|
+
}
|
|
1495
|
+
function describeStylesheetContent(code) {
|
|
1496
|
+
return {
|
|
1497
|
+
length: code.length,
|
|
1498
|
+
digest: createHash("sha256").update(code).digest("hex").slice(0, 12),
|
|
1499
|
+
preview: code.replace(/\s+/g, " ").trim().slice(0, 160)
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
function safeStatMtimeMs(file) {
|
|
1503
|
+
try {
|
|
1504
|
+
return statSync(file).mtimeMs;
|
|
1505
|
+
} catch {
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
/**
|
|
1510
|
+
* Refreshes any already-served stylesheet records that map back to a changed
|
|
1511
|
+
* source file.
|
|
1512
|
+
*
|
|
1513
|
+
* This is the critical bridge for externalized Angular component styles during
|
|
1514
|
+
* HMR. Angular's resource watcher can notice that `/src/...component.css`
|
|
1515
|
+
* changed before Angular recompilation has had a chance to repopulate the
|
|
1516
|
+
* stylesheet registry. If we emit a CSS update against the existing virtual
|
|
1517
|
+
* stylesheet id without first refreshing the registry content, the browser gets
|
|
1518
|
+
* a hot update containing stale CSS. By rewriting the existing served records
|
|
1519
|
+
* from disk up front, HMR always pushes the latest source content.
|
|
1520
|
+
*/
|
|
1521
|
+
function refreshStylesheetRegistryForFile(file, stylesheetRegistry, stylePreprocessor) {
|
|
1522
|
+
const normalizedFile = normalizePath(file.split("?")[0]);
|
|
1523
|
+
if (!stylesheetRegistry || !existsSync(normalizedFile)) return;
|
|
1524
|
+
const publicIds = stylesheetRegistry.getPublicIdsForSource(normalizedFile);
|
|
1525
|
+
if (publicIds.length === 0) return;
|
|
1526
|
+
const rawCss = readFileSync(normalizedFile, "utf-8");
|
|
1527
|
+
let servedCss = preprocessStylesheet(rawCss, normalizedFile, stylePreprocessor);
|
|
1528
|
+
servedCss = rewriteRelativeCssImports(servedCss, normalizedFile);
|
|
1529
|
+
for (const publicId of publicIds) stylesheetRegistry.registerServedStylesheet({
|
|
1530
|
+
publicId,
|
|
1531
|
+
sourcePath: normalizedFile,
|
|
1532
|
+
originalCode: rawCss,
|
|
1533
|
+
normalizedCode: servedCss
|
|
1534
|
+
}, [
|
|
1535
|
+
normalizedFile,
|
|
1536
|
+
normalizePath(normalizedFile),
|
|
1537
|
+
basename(normalizedFile),
|
|
1538
|
+
normalizedFile.replace(/^\//, "")
|
|
1539
|
+
]);
|
|
1540
|
+
debugStylesV("stylesheet registry refreshed from source file", {
|
|
1541
|
+
file: normalizedFile,
|
|
1542
|
+
publicIds,
|
|
1543
|
+
source: describeStylesheetContent(rawCss),
|
|
1544
|
+
served: describeStylesheetContent(servedCss)
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
function diagnoseComponentStylesheetPipeline(changedFile, directModule, stylesheetRegistry, wrapperModules, stylePreprocessor) {
|
|
1548
|
+
const normalizedFile = normalizePath(changedFile.split("?")[0]);
|
|
1549
|
+
const sourceExists = existsSync(normalizedFile);
|
|
1550
|
+
const sourceCode = sourceExists ? readFileSync(normalizedFile, "utf-8") : void 0;
|
|
1551
|
+
const directRequestPath = directModule.id ? getFilenameFromPath(directModule.id) : void 0;
|
|
1552
|
+
const sourcePath = directRequestPath ? stylesheetRegistry?.resolveExternalSource(directRequestPath) ?? stylesheetRegistry?.resolveExternalSource(directRequestPath.replace(/^\//, "")) : normalizedFile;
|
|
1553
|
+
const registryCode = directRequestPath ? stylesheetRegistry?.getServedContent(directRequestPath) : void 0;
|
|
1554
|
+
const trackedRequestIds = stylesheetRegistry?.getRequestIdsForSource(sourcePath ?? "") ?? [];
|
|
1555
|
+
const anomalies = [];
|
|
1556
|
+
const hints = [];
|
|
1557
|
+
if (!sourceExists) {
|
|
1558
|
+
anomalies.push("source_file_missing");
|
|
1559
|
+
hints.push("The stylesheet watcher fired for a file that no longer exists on disk.");
|
|
1560
|
+
}
|
|
1561
|
+
if (!registryCode) {
|
|
1562
|
+
anomalies.push("registry_content_missing");
|
|
1563
|
+
hints.push("The stylesheet registry has no served content for the direct module request path.");
|
|
1564
|
+
}
|
|
1565
|
+
if (sourceCode && registryCode) {
|
|
1566
|
+
let expectedRegistryCode = preprocessStylesheet(sourceCode, normalizedFile, stylePreprocessor);
|
|
1567
|
+
expectedRegistryCode = rewriteRelativeCssImports(expectedRegistryCode, normalizedFile);
|
|
1568
|
+
if (describeStylesheetContent(expectedRegistryCode).digest !== describeStylesheetContent(registryCode).digest) {
|
|
1569
|
+
anomalies.push("source_registry_mismatch");
|
|
1570
|
+
hints.push("The source file changed, but the served stylesheet content in the registry is still stale.");
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
if (trackedRequestIds.length === 0) {
|
|
1574
|
+
anomalies.push("no_tracked_requests");
|
|
1575
|
+
hints.push("No live stylesheet requests are tracked for this source file, so HMR has no browser-facing target.");
|
|
1576
|
+
}
|
|
1577
|
+
if (trackedRequestIds.some((id) => id.includes("?ngcomp=")) && wrapperModules.length === 0) {
|
|
1578
|
+
anomalies.push("tracked_wrapper_missing_from_module_graph");
|
|
1579
|
+
hints.push("A wrapper request id is known, but Vite did not expose a live wrapper module during this HMR pass.");
|
|
1580
|
+
}
|
|
1581
|
+
if (trackedRequestIds.every((id) => !id.includes("?ngcomp=")) && wrapperModules.length === 0) {
|
|
1582
|
+
anomalies.push("wrapper_not_yet_tracked");
|
|
1583
|
+
hints.push("Only direct stylesheet requests were tracked during this HMR pass; the wrapper request may be appearing too late.");
|
|
1584
|
+
}
|
|
1585
|
+
return {
|
|
1586
|
+
file: changedFile,
|
|
1587
|
+
sourcePath,
|
|
1588
|
+
source: sourceCode ? describeStylesheetContent(rewriteRelativeCssImports(preprocessStylesheet(sourceCode, normalizedFile, stylePreprocessor), normalizedFile)) : void 0,
|
|
1589
|
+
registry: registryCode ? describeStylesheetContent(registryCode) : void 0,
|
|
1590
|
+
directModuleId: directModule.id,
|
|
1591
|
+
directModuleUrl: directModule.url,
|
|
1592
|
+
trackedRequestIds,
|
|
1593
|
+
wrapperCount: wrapperModules.length,
|
|
1594
|
+
anomalies,
|
|
1595
|
+
hints
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
async function findComponentStylesheetWrapperModules(server, changedFile, directModule, fileModules, stylesheetRegistry) {
|
|
1599
|
+
const wrapperModules = /* @__PURE__ */ new Map();
|
|
1600
|
+
for (const mod of fileModules) if (mod.id && mod.type === "js" && isComponentStyleSheet(mod.id) && isModuleForChangedResource(mod, changedFile, stylesheetRegistry)) wrapperModules.set(mod.id, mod);
|
|
1601
|
+
const directRequestIds = /* @__PURE__ */ new Set();
|
|
1602
|
+
if (directModule.id) directRequestIds.add(directModule.id);
|
|
1603
|
+
if (directModule.url) directRequestIds.add(directModule.url);
|
|
1604
|
+
const requestPath = directModule.id ? getFilenameFromPath(directModule.id) : void 0;
|
|
1605
|
+
const sourcePath = requestPath ? stylesheetRegistry?.resolveExternalSource(requestPath) ?? stylesheetRegistry?.resolveExternalSource(requestPath.replace(/^\//, "")) : void 0;
|
|
1606
|
+
for (const requestId of stylesheetRegistry?.getRequestIdsForSource(sourcePath ?? "") ?? []) if (requestId.includes("?ngcomp=")) directRequestIds.add(requestId);
|
|
1607
|
+
const candidateWrapperIds = [...directRequestIds].filter((id) => id.includes("?direct&ngcomp=")).map((id) => id.replace("?direct&ngcomp=", "?ngcomp="));
|
|
1608
|
+
const lookupHits = [];
|
|
1609
|
+
for (const candidate of candidateWrapperIds) {
|
|
1610
|
+
const mod = await server.moduleGraph.getModuleByUrl(candidate) ?? server.moduleGraph.getModuleById(candidate);
|
|
1611
|
+
lookupHits.push({
|
|
1612
|
+
candidate,
|
|
1613
|
+
via: mod?.url === candidate ? "url" : mod ? "id" : void 0,
|
|
1614
|
+
moduleId: mod?.id,
|
|
1615
|
+
moduleType: mod?.type
|
|
1616
|
+
});
|
|
1617
|
+
if (mod?.id && mod.type === "js" && isComponentStyleSheet(mod.id) && isModuleForChangedResource(mod, changedFile, stylesheetRegistry)) wrapperModules.set(mod.id, mod);
|
|
1618
|
+
}
|
|
1619
|
+
debugHmrV("component stylesheet wrapper lookup", {
|
|
1620
|
+
file: changedFile,
|
|
1621
|
+
sourcePath,
|
|
1622
|
+
directModuleId: directModule.id,
|
|
1623
|
+
directModuleUrl: directModule.url,
|
|
1624
|
+
candidateWrapperIds,
|
|
1625
|
+
lookupHits
|
|
1626
|
+
});
|
|
1627
|
+
if (wrapperModules.size === 0) debugHmrV("component stylesheet wrapper lookup empty", {
|
|
1628
|
+
file: changedFile,
|
|
1629
|
+
sourcePath,
|
|
1630
|
+
directModuleId: directModule.id,
|
|
1631
|
+
directModuleUrl: directModule.url,
|
|
1632
|
+
candidateWrapperIds
|
|
1633
|
+
});
|
|
1634
|
+
return [...wrapperModules.values()];
|
|
1635
|
+
}
|
|
856
1636
|
function sendHMRComponentUpdate(server, id) {
|
|
1637
|
+
debugHmrV("ws send: angular component update", {
|
|
1638
|
+
id,
|
|
1639
|
+
timestamp: Date.now()
|
|
1640
|
+
});
|
|
857
1641
|
server.ws.send("angular:component-update", {
|
|
858
1642
|
id: encodeURIComponent(id),
|
|
859
1643
|
timestamp: Date.now()
|
|
860
1644
|
});
|
|
861
1645
|
classNames.delete(id);
|
|
862
1646
|
}
|
|
863
|
-
function
|
|
1647
|
+
function sendCssUpdate(server, update) {
|
|
1648
|
+
const timestamp = Date.now();
|
|
1649
|
+
debugHmrV("ws send: css-update", {
|
|
1650
|
+
...update,
|
|
1651
|
+
timestamp
|
|
1652
|
+
});
|
|
1653
|
+
server.ws.send({
|
|
1654
|
+
type: "update",
|
|
1655
|
+
updates: [{
|
|
1656
|
+
type: "css-update",
|
|
1657
|
+
timestamp,
|
|
1658
|
+
path: update.path,
|
|
1659
|
+
acceptedPath: update.acceptedPath
|
|
1660
|
+
}]
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
function sendFullReload(server, details) {
|
|
1664
|
+
debugHmrV("ws send: full-reload", details);
|
|
1665
|
+
server.ws.send("analog:debug-full-reload", details);
|
|
1666
|
+
server.ws.send({ type: "full-reload" });
|
|
1667
|
+
}
|
|
1668
|
+
function resolveComponentClassNamesForStyleOwner(ownerFile, sourcePath) {
|
|
1669
|
+
if (!existsSync(ownerFile)) return [];
|
|
1670
|
+
const components = getAngularComponentMetadata(readFileSync(ownerFile, "utf-8"));
|
|
1671
|
+
const normalizedSourcePath = normalizePath(sourcePath);
|
|
1672
|
+
return components.filter((component) => component.styleUrls.some((styleUrl) => normalizePath(resolve(dirname(ownerFile), styleUrl)) === normalizedSourcePath)).map((component) => component.className);
|
|
1673
|
+
}
|
|
1674
|
+
function findStaticClassAndBoundClassConflicts(template) {
|
|
1675
|
+
const issues = [];
|
|
1676
|
+
for (const { index, snippet } of findOpeningTagSnippets(template)) {
|
|
1677
|
+
if (!snippet.includes("[class]")) continue;
|
|
1678
|
+
const hasStaticClass = /\sclass\s*=\s*(['"])(?:(?!\1)[\s\S])*\1/.test(snippet);
|
|
1679
|
+
const hasBoundClass = /\s\[class\]\s*=\s*(['"])(?:(?!\1)[\s\S])*\1/.test(snippet);
|
|
1680
|
+
if (hasStaticClass && hasBoundClass) {
|
|
1681
|
+
const prefix = template.slice(0, index);
|
|
1682
|
+
const line = prefix.split("\n").length;
|
|
1683
|
+
const column = index - prefix.lastIndexOf("\n");
|
|
1684
|
+
issues.push({
|
|
1685
|
+
line,
|
|
1686
|
+
column,
|
|
1687
|
+
snippet: snippet.replace(/\s+/g, " ").trim()
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
return issues;
|
|
1692
|
+
}
|
|
1693
|
+
function throwTemplateClassBindingConflict(id, issue) {
|
|
1694
|
+
throw new Error([
|
|
1695
|
+
"[Analog Angular] Invalid template class binding.",
|
|
1696
|
+
`File: ${id}:${issue.line}:${issue.column}`,
|
|
1697
|
+
"The same element uses both a static `class=\"...\"` attribute and a whole-element `[class]=\"...\"` binding.",
|
|
1698
|
+
"That pattern can replace or conflict with static Tailwind classes, which makes styles appear to stop applying.",
|
|
1699
|
+
"Use `[ngClass]` or explicit `[class.foo]` bindings instead of `[class]` when the element also has static classes.",
|
|
1700
|
+
`Snippet: ${issue.snippet}`
|
|
1701
|
+
].join("\n"));
|
|
1702
|
+
}
|
|
1703
|
+
function findBoundClassAndNgClassConflicts(template) {
|
|
1704
|
+
const issues = [];
|
|
1705
|
+
if (!/\[class\]\s*=/.test(template) || !template.includes("[ngClass]")) return issues;
|
|
1706
|
+
for (const { index, snippet } of findOpeningTagSnippets(template)) {
|
|
1707
|
+
if (!/\[class\]\s*=/.test(snippet) || !snippet.includes("[ngClass]")) continue;
|
|
1708
|
+
const prefix = template.slice(0, index);
|
|
1709
|
+
const line = prefix.split("\n").length;
|
|
1710
|
+
const column = index - prefix.lastIndexOf("\n");
|
|
1711
|
+
issues.push({
|
|
1712
|
+
line,
|
|
1713
|
+
column,
|
|
1714
|
+
snippet: snippet.replace(/\s+/g, " ").trim()
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
return issues;
|
|
1718
|
+
}
|
|
1719
|
+
function findOpeningTagSnippets(template) {
|
|
1720
|
+
const matches = [];
|
|
1721
|
+
for (let index = 0; index < template.length; index++) {
|
|
1722
|
+
if (template[index] !== "<") continue;
|
|
1723
|
+
const tagStart = template[index + 1];
|
|
1724
|
+
if (!tagStart || !/[a-zA-Z]/.test(tagStart)) continue;
|
|
1725
|
+
let quote = null;
|
|
1726
|
+
for (let end = index + 1; end < template.length; end++) {
|
|
1727
|
+
const char = template[end];
|
|
1728
|
+
if (quote) {
|
|
1729
|
+
if (char === quote) quote = null;
|
|
1730
|
+
continue;
|
|
1731
|
+
}
|
|
1732
|
+
if (char === "\"" || char === "'") {
|
|
1733
|
+
quote = char;
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1736
|
+
if (char === ">") {
|
|
1737
|
+
matches.push({
|
|
1738
|
+
index,
|
|
1739
|
+
snippet: template.slice(index, end + 1)
|
|
1740
|
+
});
|
|
1741
|
+
index = end;
|
|
1742
|
+
break;
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
return matches;
|
|
1747
|
+
}
|
|
1748
|
+
function formatActiveGraphLocations(entries) {
|
|
1749
|
+
return [...entries].sort().map((entry) => `- ${entry}`).join("\n");
|
|
1750
|
+
}
|
|
1751
|
+
function logComponentStylesheetHmrOutcome(details) {
|
|
1752
|
+
const pitfalls = [];
|
|
1753
|
+
const rejectedPreferredPaths = [];
|
|
1754
|
+
const hints = [];
|
|
1755
|
+
if (details.encapsulation === "shadow") {
|
|
1756
|
+
pitfalls.push("shadow-encapsulation");
|
|
1757
|
+
rejectedPreferredPaths.push("css-update");
|
|
1758
|
+
rejectedPreferredPaths.push("owner-component-update");
|
|
1759
|
+
hints.push("Shadow DOM styles cannot rely on Vite CSS patching because Angular applies them inside a shadow root.");
|
|
1760
|
+
}
|
|
1761
|
+
if (details.diagnosis.anomalies.includes("wrapper_not_yet_tracked")) {
|
|
1762
|
+
pitfalls.push("wrapper-not-yet-tracked");
|
|
1763
|
+
rejectedPreferredPaths.push("css-update");
|
|
1764
|
+
hints.push("The direct stylesheet module exists, but the browser-visible Angular wrapper module was not available in the live graph during this HMR pass.");
|
|
1765
|
+
}
|
|
1766
|
+
if (details.diagnosis.anomalies.includes("tracked_wrapper_missing_from_module_graph")) {
|
|
1767
|
+
pitfalls.push("tracked-wrapper-missing-from-module-graph");
|
|
1768
|
+
rejectedPreferredPaths.push("css-update");
|
|
1769
|
+
hints.push("A wrapper request id is known, but Vite could not resolve a live wrapper module for targeted CSS HMR.");
|
|
1770
|
+
}
|
|
1771
|
+
if ((details.ownerIds?.filter(Boolean).length ?? 0) === 0) {
|
|
1772
|
+
pitfalls.push("no-owner-modules");
|
|
1773
|
+
if (details.outcome === "full-reload") {
|
|
1774
|
+
rejectedPreferredPaths.push("owner-component-update");
|
|
1775
|
+
hints.push("No owning TS component modules were available in the module graph for owner-based fallback.");
|
|
1776
|
+
}
|
|
1777
|
+
} else if ((details.updateIds?.length ?? 0) === 0) {
|
|
1778
|
+
pitfalls.push("owner-modules-without-class-identities");
|
|
1779
|
+
if (details.outcome === "full-reload") {
|
|
1780
|
+
rejectedPreferredPaths.push("owner-component-update");
|
|
1781
|
+
hints.push("Owner modules were found, but Angular did not expose component class identities after recompilation, so no targeted component update could be sent.");
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
debugHmrV("component stylesheet hmr outcome", {
|
|
1785
|
+
file: details.file,
|
|
1786
|
+
outcome: details.outcome,
|
|
1787
|
+
encapsulation: details.encapsulation,
|
|
1788
|
+
directModuleId: details.directModuleId,
|
|
1789
|
+
wrapperIds: details.wrapperIds ?? [],
|
|
1790
|
+
ownerIds: details.ownerIds ?? [],
|
|
1791
|
+
updateIds: details.updateIds ?? [],
|
|
1792
|
+
preferredPath: details.encapsulation === "shadow" ? "full-reload" : "css-update",
|
|
1793
|
+
rejectedPreferredPaths: [...new Set(rejectedPreferredPaths)],
|
|
1794
|
+
pitfalls: [...new Set(pitfalls)],
|
|
1795
|
+
anomalies: details.diagnosis.anomalies,
|
|
1796
|
+
hints: [...new Set([...details.diagnosis.hints, ...hints])]
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
function findTemplateOwnerModules(server, resourceFile) {
|
|
1800
|
+
const candidateTsFiles = [normalizePath(resourceFile.split("?")[0]).replace(/\.(html|htm)$/i, ".ts")];
|
|
1801
|
+
const modules = /* @__PURE__ */ new Map();
|
|
1802
|
+
for (const candidate of candidateTsFiles) server.moduleGraph.getModulesByFile(candidate)?.forEach((mod) => {
|
|
1803
|
+
if (mod.id) modules.set(mod.id, mod);
|
|
1804
|
+
});
|
|
1805
|
+
return [...modules.values()];
|
|
1806
|
+
}
|
|
1807
|
+
function findStyleOwnerModules(server, resourceFile, styleSourceOwners) {
|
|
1808
|
+
const normalizedResourceFile = normalizePath(resourceFile.split("?")[0]);
|
|
1809
|
+
const candidateOwnerFiles = [...styleSourceOwners.get(normalizedResourceFile) ?? []];
|
|
1810
|
+
const modules = /* @__PURE__ */ new Map();
|
|
1811
|
+
for (const ownerFile of candidateOwnerFiles) server.moduleGraph.getModulesByFile(ownerFile)?.forEach((mod) => {
|
|
1812
|
+
if (mod.id) modules.set(mod.id, mod);
|
|
1813
|
+
});
|
|
1814
|
+
return [...modules.values()];
|
|
1815
|
+
}
|
|
1816
|
+
function getFileMetadata(program, angularCompiler, hmrEnabled, disableTypeChecking) {
|
|
864
1817
|
const ts = require("typescript");
|
|
865
1818
|
return (file) => {
|
|
866
1819
|
const sourceFile = program.getSourceFile(file);
|
|
@@ -870,7 +1823,7 @@ function getFileMetadata(program, angularCompiler, liveReload, disableTypeChecki
|
|
|
870
1823
|
const warnings = diagnostics.filter((d) => d.category === ts.DiagnosticCategory?.Warning).map((d) => d.messageText);
|
|
871
1824
|
let hmrUpdateCode = void 0;
|
|
872
1825
|
let hmrEligible = false;
|
|
873
|
-
if (
|
|
1826
|
+
if (hmrEnabled) {
|
|
874
1827
|
for (const node of sourceFile.statements) if (ts.isClassDeclaration(node) && node.name != null) {
|
|
875
1828
|
hmrUpdateCode = angularCompiler?.emitHmrUpdateModule(node);
|
|
876
1829
|
if (hmrUpdateCode) {
|
|
@@ -930,7 +1883,12 @@ function getComponentStyleSheetMeta(id) {
|
|
|
930
1883
|
* @param id
|
|
931
1884
|
*/
|
|
932
1885
|
function getFilenameFromPath(id) {
|
|
933
|
-
|
|
1886
|
+
try {
|
|
1887
|
+
return new URL(id, "http://localhost").pathname.replace(/^\//, "");
|
|
1888
|
+
} catch {
|
|
1889
|
+
const queryIndex = id.indexOf("?");
|
|
1890
|
+
return (queryIndex >= 0 ? id.slice(0, queryIndex) : id).replace(/^\//, "");
|
|
1891
|
+
}
|
|
934
1892
|
}
|
|
935
1893
|
/**
|
|
936
1894
|
* Checks for vitest run from the command line
|