@analogjs/vite-plugin-angular 3.0.0-alpha.24 → 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 +1 -1
- 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 +1043 -82
- 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.d.ts +6 -2
- package/src/lib/utils/debug.js +11 -3
- 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";
|
|
@@ -13,14 +14,14 @@ 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
16
|
import { union } from "es-toolkit";
|
|
16
|
-
import {
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
18
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
17
19
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
18
20
|
import * as compilerCli from "@angular/compiler-cli";
|
|
19
21
|
import { createRequire } from "node:module";
|
|
20
22
|
import * as ngCompiler from "@angular/compiler";
|
|
21
23
|
import { globSync } from "tinyglobby";
|
|
22
24
|
import { defaultClientConditions, normalizePath, preprocessCSS } from "vite";
|
|
23
|
-
import { createHash } from "node:crypto";
|
|
24
25
|
//#region packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts
|
|
25
26
|
var require = createRequire(import.meta.url);
|
|
26
27
|
var DiagnosticModes = /* @__PURE__ */ function(DiagnosticModes) {
|
|
@@ -31,6 +32,13 @@ var DiagnosticModes = /* @__PURE__ */ function(DiagnosticModes) {
|
|
|
31
32
|
DiagnosticModes[DiagnosticModes["All"] = 7] = "All";
|
|
32
33
|
return DiagnosticModes;
|
|
33
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
|
+
}
|
|
34
42
|
/**
|
|
35
43
|
* TypeScript file extension regex
|
|
36
44
|
* Match .(c or m)ts, .ts extensions with an optional ? for query params
|
|
@@ -38,10 +46,33 @@ var DiagnosticModes = /* @__PURE__ */ function(DiagnosticModes) {
|
|
|
38
46
|
*/
|
|
39
47
|
var TS_EXT_REGEX = /\.[cm]?(ts)[^x]?\??/;
|
|
40
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
|
+
}
|
|
41
64
|
/**
|
|
42
65
|
* Builds a resolved stylePreprocessor function from plugin options.
|
|
43
|
-
*
|
|
44
|
-
*
|
|
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.
|
|
45
76
|
*/
|
|
46
77
|
function buildStylePreprocessor(options) {
|
|
47
78
|
const userPreprocessor = options?.stylePreprocessor;
|
|
@@ -55,17 +86,18 @@ function buildStylePreprocessor(options) {
|
|
|
55
86
|
rootStylesheet,
|
|
56
87
|
prefixes
|
|
57
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.`);
|
|
58
90
|
tailwindPreprocessor = (code, filename) => {
|
|
59
91
|
if (code.includes("@reference") || code.includes("@import \"tailwindcss\"") || code.includes("@import 'tailwindcss'")) {
|
|
60
|
-
|
|
92
|
+
debugTailwindV("skip (already has @reference or is root)", { filename });
|
|
61
93
|
return code;
|
|
62
94
|
}
|
|
63
95
|
if (!(prefixes ? prefixes.some((prefix) => code.includes(prefix)) : code.includes("@apply"))) {
|
|
64
|
-
|
|
96
|
+
debugTailwindV("skip (no Tailwind usage detected)", { filename });
|
|
65
97
|
return code;
|
|
66
98
|
}
|
|
67
|
-
debugTailwind("injected @reference", { filename });
|
|
68
|
-
return `@reference "${
|
|
99
|
+
debugTailwind("injected @reference via preprocessor", { filename });
|
|
100
|
+
return `@reference "${rootStylesheet}";\n${code}`;
|
|
69
101
|
};
|
|
70
102
|
}
|
|
71
103
|
if (tailwindPreprocessor && userPreprocessor) {
|
|
@@ -95,10 +127,12 @@ function angular(options) {
|
|
|
95
127
|
jit: options?.jit,
|
|
96
128
|
include: options?.include ?? [],
|
|
97
129
|
additionalContentDirs: options?.additionalContentDirs ?? [],
|
|
98
|
-
|
|
130
|
+
hmr: options?.hmr ?? options?.liveReload ?? true,
|
|
99
131
|
disableTypeChecking: options?.disableTypeChecking ?? true,
|
|
100
132
|
fileReplacements: options?.fileReplacements ?? [],
|
|
101
133
|
useAngularCompilationAPI: options?.experimental?.useAngularCompilationAPI ?? false,
|
|
134
|
+
hasTailwindCss: !!options?.tailwindCss,
|
|
135
|
+
tailwindCss: options?.tailwindCss,
|
|
102
136
|
stylePreprocessor: buildStylePreprocessor(options)
|
|
103
137
|
};
|
|
104
138
|
let resolvedConfig;
|
|
@@ -120,8 +154,134 @@ function angular(options) {
|
|
|
120
154
|
}
|
|
121
155
|
let watchMode = false;
|
|
122
156
|
let testWatchMode = isTestWatchMode();
|
|
123
|
-
|
|
124
|
-
|
|
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;
|
|
125
285
|
const sourceFileCache$1 = new sourceFileCache();
|
|
126
286
|
const isTest = process.env.NODE_ENV === "test" || !!process.env["VITEST"];
|
|
127
287
|
const isVitestVscode = !!process.env["VITEST_VSCODE"];
|
|
@@ -146,9 +306,17 @@ function angular(options) {
|
|
|
146
306
|
let angularCompilation;
|
|
147
307
|
function angularPlugin() {
|
|
148
308
|
let isProd = false;
|
|
149
|
-
if (angularFullVersion < 19e4
|
|
150
|
-
|
|
151
|
-
|
|
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", {
|
|
152
320
|
angularVersion: angularFullVersion,
|
|
153
321
|
isTest
|
|
154
322
|
});
|
|
@@ -201,7 +369,11 @@ function angular(options) {
|
|
|
201
369
|
return {
|
|
202
370
|
[jsTransformConfigKey]: jsTransformConfigValue,
|
|
203
371
|
optimizeDeps: {
|
|
204
|
-
include: [
|
|
372
|
+
include: [
|
|
373
|
+
"rxjs/operators",
|
|
374
|
+
"rxjs",
|
|
375
|
+
"tslib"
|
|
376
|
+
],
|
|
205
377
|
exclude: ["@angular/platform-server"],
|
|
206
378
|
...useRolldown ? { rolldownOptions } : { esbuildOptions }
|
|
207
379
|
},
|
|
@@ -210,10 +382,10 @@ function angular(options) {
|
|
|
210
382
|
},
|
|
211
383
|
configResolved(config) {
|
|
212
384
|
resolvedConfig = config;
|
|
385
|
+
if (pluginOptions.hasTailwindCss) validateTailwindConfig(config, watchMode);
|
|
213
386
|
if (pluginOptions.useAngularCompilationAPI) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
debugStyles("style maps initialized (Angular Compilation API)");
|
|
387
|
+
stylesheetRegistry = new AnalogStylesheetRegistry();
|
|
388
|
+
debugStyles("stylesheet registry initialized (Angular Compilation API)");
|
|
217
389
|
}
|
|
218
390
|
if (!jit) styleTransform = (code, filename) => preprocessCSS(code, filename, config);
|
|
219
391
|
if (isTest) testWatchMode = !(config.server.watch === null) || config.test?.watch === true || testWatchMode;
|
|
@@ -222,7 +394,15 @@ function angular(options) {
|
|
|
222
394
|
viteServer = server;
|
|
223
395
|
const invalidateCompilationOnFsChange = createFsWatcherCacheInvalidator(invalidateFsCaches, invalidateTsconfigCaches, () => performCompilation(resolvedConfig));
|
|
224
396
|
server.watcher.on("add", invalidateCompilationOnFsChange);
|
|
225
|
-
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
|
+
});
|
|
226
406
|
server.watcher.on("change", (file) => {
|
|
227
407
|
if (file.includes("tsconfig")) invalidateTsconfigCaches();
|
|
228
408
|
});
|
|
@@ -235,6 +415,10 @@ function angular(options) {
|
|
|
235
415
|
}
|
|
236
416
|
},
|
|
237
417
|
async handleHotUpdate(ctx) {
|
|
418
|
+
if (isIgnoredHmrFile(ctx.file)) {
|
|
419
|
+
debugHmr("ignored file change", { file: ctx.file });
|
|
420
|
+
return [];
|
|
421
|
+
}
|
|
238
422
|
if (TS_EXT_REGEX.test(ctx.file)) {
|
|
239
423
|
const [fileId] = ctx.file.split("?");
|
|
240
424
|
debugHmr("TS file changed", {
|
|
@@ -243,7 +427,7 @@ function angular(options) {
|
|
|
243
427
|
});
|
|
244
428
|
pendingCompilation = performCompilation(resolvedConfig, [fileId]);
|
|
245
429
|
let result;
|
|
246
|
-
if (
|
|
430
|
+
if (shouldEnableHmr()) {
|
|
247
431
|
await pendingCompilation;
|
|
248
432
|
pendingCompilation = null;
|
|
249
433
|
result = fileEmitter(fileId);
|
|
@@ -252,10 +436,28 @@ function angular(options) {
|
|
|
252
436
|
hmrEligible: !!result?.hmrEligible,
|
|
253
437
|
hasClassName: !!classNames.get(fileId)
|
|
254
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
|
+
});
|
|
255
451
|
}
|
|
256
|
-
if (
|
|
452
|
+
if (shouldEnableHmr() && result?.hmrEligible && classNames.get(fileId)) {
|
|
257
453
|
const relativeFileId = `${normalizePath(relative(process.cwd(), fileId))}@${classNames.get(fileId)}`;
|
|
258
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
|
+
});
|
|
259
461
|
sendHMRComponentUpdate(ctx.server, relativeFileId);
|
|
260
462
|
return ctx.modules.map((mod) => {
|
|
261
463
|
if (mod.id === ctx.file) return markModuleSelfAccepting(mod);
|
|
@@ -266,51 +468,224 @@ function angular(options) {
|
|
|
266
468
|
if (/\.(html|htm|css|less|sass|scss)$/.test(ctx.file)) {
|
|
267
469
|
debugHmr("resource file changed", { file: ctx.file });
|
|
268
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
|
+
});
|
|
269
497
|
/**
|
|
270
498
|
* Check to see if this was a direct request
|
|
271
499
|
* for an external resource (styles, html).
|
|
272
500
|
*/
|
|
273
|
-
const isDirect =
|
|
274
|
-
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
|
+
});
|
|
275
510
|
if (isDirect || isInline) {
|
|
276
|
-
if (
|
|
277
|
-
|
|
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) {
|
|
278
521
|
const { encapsulation } = getComponentStyleSheetMeta(isDirect.id);
|
|
279
|
-
|
|
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", {
|
|
280
525
|
file: isDirect.file,
|
|
281
526
|
encapsulation
|
|
282
527
|
});
|
|
283
|
-
|
|
284
|
-
ctx.
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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)
|
|
292
564
|
});
|
|
293
|
-
return
|
|
565
|
+
return union(fileModules.filter((mod) => {
|
|
294
566
|
return mod.file !== ctx.file || mod.id !== isDirect.id;
|
|
295
567
|
}).map((mod) => {
|
|
296
568
|
if (mod.file === ctx.file) return markModuleSelfAccepting(mod);
|
|
297
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."
|
|
298
610
|
});
|
|
299
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));
|
|
300
669
|
}
|
|
301
670
|
}
|
|
302
|
-
return ctx.modules;
|
|
303
671
|
}
|
|
304
672
|
const mods = [];
|
|
305
673
|
const updates = [];
|
|
306
|
-
|
|
674
|
+
fileModules.forEach((mod) => {
|
|
307
675
|
mod.importers.forEach((imp) => {
|
|
308
676
|
ctx.server.moduleGraph.invalidateModule(imp);
|
|
309
|
-
if (
|
|
677
|
+
if (shouldExternalizeStyles() && classNames.get(imp.id)) updates.push(imp.id);
|
|
310
678
|
else mods.push(imp);
|
|
311
679
|
});
|
|
312
680
|
});
|
|
313
|
-
|
|
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]);
|
|
314
689
|
if (updates.length > 0) {
|
|
315
690
|
await pendingCompilation;
|
|
316
691
|
pendingCompilation = null;
|
|
@@ -322,7 +697,7 @@ function angular(options) {
|
|
|
322
697
|
const impRelativeFileId = `${normalizePath(relative(process.cwd(), updateId))}@${classNames.get(updateId)}`;
|
|
323
698
|
sendHMRComponentUpdate(ctx.server, impRelativeFileId);
|
|
324
699
|
});
|
|
325
|
-
return
|
|
700
|
+
return fileModules.map((mod) => {
|
|
326
701
|
if (mod.id === ctx.file) return markModuleSelfAccepting(mod);
|
|
327
702
|
return mod;
|
|
328
703
|
});
|
|
@@ -340,24 +715,42 @@ function angular(options) {
|
|
|
340
715
|
}
|
|
341
716
|
if (isComponentStyleSheet(id)) {
|
|
342
717
|
const filename = getFilenameFromPath(id);
|
|
343
|
-
|
|
718
|
+
if (stylesheetRegistry?.hasServed(filename)) {
|
|
719
|
+
debugStylesV("resolveId: kept preprocessed ID", { filename });
|
|
720
|
+
return id;
|
|
721
|
+
}
|
|
722
|
+
const componentStyles = stylesheetRegistry?.resolveExternalSource(filename);
|
|
344
723
|
if (componentStyles) {
|
|
345
|
-
|
|
724
|
+
debugStylesV("resolveId: mapped external stylesheet", {
|
|
346
725
|
filename,
|
|
347
726
|
resolvedPath: componentStyles
|
|
348
727
|
});
|
|
349
728
|
return componentStyles + new URL(id, "http://localhost").search;
|
|
350
729
|
}
|
|
730
|
+
debugStyles("resolveId: component stylesheet NOT FOUND in either map", {
|
|
731
|
+
filename,
|
|
732
|
+
inlineMapSize: stylesheetRegistry?.servedCount ?? 0,
|
|
733
|
+
externalMapSize: stylesheetRegistry?.externalCount ?? 0
|
|
734
|
+
});
|
|
351
735
|
}
|
|
352
736
|
},
|
|
353
737
|
async load(id) {
|
|
354
738
|
if (isComponentStyleSheet(id)) {
|
|
355
739
|
const filename = getFilenameFromPath(id);
|
|
356
|
-
const componentStyles =
|
|
740
|
+
const componentStyles = stylesheetRegistry?.getServedContent(filename);
|
|
357
741
|
if (componentStyles) {
|
|
358
|
-
|
|
742
|
+
stylesheetRegistry?.registerActiveRequest(id);
|
|
743
|
+
debugHmrV("stylesheet active request registered", {
|
|
744
|
+
requestId: id,
|
|
359
745
|
filename,
|
|
360
|
-
|
|
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)
|
|
361
754
|
});
|
|
362
755
|
return componentStyles;
|
|
363
756
|
}
|
|
@@ -388,12 +781,15 @@ function angular(options) {
|
|
|
388
781
|
*/
|
|
389
782
|
if (id.includes("?") && id.includes("analog-content-")) return;
|
|
390
783
|
/**
|
|
391
|
-
* 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.
|
|
392
788
|
*/
|
|
393
|
-
if (
|
|
789
|
+
if (shouldExternalizeStyles() && isComponentStyleSheet(id)) {
|
|
394
790
|
const { encapsulation, componentId } = getComponentStyleSheetMeta(id);
|
|
395
791
|
if (encapsulation === "emulated" && componentId) {
|
|
396
|
-
|
|
792
|
+
debugStylesV("applying emulated view encapsulation", {
|
|
397
793
|
stylesheet: id.split("?")[0],
|
|
398
794
|
componentId
|
|
399
795
|
});
|
|
@@ -421,7 +817,7 @@ function angular(options) {
|
|
|
421
817
|
}
|
|
422
818
|
}
|
|
423
819
|
const hasComponent = code.includes("@Component");
|
|
424
|
-
|
|
820
|
+
debugCompilerV("transform", {
|
|
425
821
|
id,
|
|
426
822
|
codeLength: code.length,
|
|
427
823
|
hasComponent
|
|
@@ -451,6 +847,19 @@ function angular(options) {
|
|
|
451
847
|
data = data.replace(`angular:jit:style:file;${styleFile}`, `${resolvedStyleUrl}?inline`);
|
|
452
848
|
});
|
|
453
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
|
+
}
|
|
454
863
|
return {
|
|
455
864
|
code: data,
|
|
456
865
|
map: null
|
|
@@ -469,8 +878,103 @@ function angular(options) {
|
|
|
469
878
|
}
|
|
470
879
|
return [
|
|
471
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
|
+
},
|
|
472
976
|
angularPlugin(),
|
|
473
|
-
pluginOptions.
|
|
977
|
+
pluginOptions.hmr && liveReloadPlugin({
|
|
474
978
|
classNames,
|
|
475
979
|
fileEmitter
|
|
476
980
|
}),
|
|
@@ -485,8 +989,7 @@ function angular(options) {
|
|
|
485
989
|
nxFolderPlugin()
|
|
486
990
|
].filter(Boolean);
|
|
487
991
|
function findIncludes() {
|
|
488
|
-
|
|
489
|
-
return globSync([...pluginOptions.include.map((glob) => `${workspaceRoot}${glob}`)], {
|
|
992
|
+
return globSync(pluginOptions.include.map((glob) => normalizeIncludeGlob(pluginOptions.workspaceRoot, glob)), {
|
|
490
993
|
dot: true,
|
|
491
994
|
absolute: true
|
|
492
995
|
});
|
|
@@ -541,17 +1044,30 @@ function angular(options) {
|
|
|
541
1044
|
modifiedFiles,
|
|
542
1045
|
async transformStylesheet(data, containingFile, resourceFile, order, className) {
|
|
543
1046
|
const filename = resourceFile ?? containingFile.replace(".ts", `.${pluginOptions.inlineStylesExtension}`);
|
|
544
|
-
const preprocessedData =
|
|
545
|
-
if (
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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", {
|
|
549
1059
|
stylesheetId,
|
|
550
1060
|
resourceFile: resourceFile ?? "(inline)"
|
|
551
1061
|
});
|
|
1062
|
+
debugStylesV("stylesheet deferred content snapshot", {
|
|
1063
|
+
stylesheetId,
|
|
1064
|
+
filename,
|
|
1065
|
+
resourceFile: resourceFile ?? "(inline)",
|
|
1066
|
+
...describeStylesheetContent(preprocessedData)
|
|
1067
|
+
});
|
|
552
1068
|
return stylesheetId;
|
|
553
1069
|
}
|
|
554
|
-
debugStyles("stylesheet processed inline via preprocessCSS
|
|
1070
|
+
debugStyles("stylesheet processed inline via preprocessCSS", {
|
|
555
1071
|
filename,
|
|
556
1072
|
resourceFile: resourceFile ?? "(inline)",
|
|
557
1073
|
dataLength: preprocessedData.length
|
|
@@ -560,7 +1076,11 @@ function angular(options) {
|
|
|
560
1076
|
try {
|
|
561
1077
|
stylesheetResult = await preprocessCSS(preprocessedData, `${filename}?direct`, resolvedConfig);
|
|
562
1078
|
} catch (e) {
|
|
563
|
-
|
|
1079
|
+
debugStyles("preprocessCSS error", {
|
|
1080
|
+
filename,
|
|
1081
|
+
resourceFile: resourceFile ?? "(inline)",
|
|
1082
|
+
error: String(e)
|
|
1083
|
+
});
|
|
564
1084
|
}
|
|
565
1085
|
return stylesheetResult?.code || "";
|
|
566
1086
|
},
|
|
@@ -568,21 +1088,24 @@ function angular(options) {
|
|
|
568
1088
|
return "";
|
|
569
1089
|
}
|
|
570
1090
|
}, (tsCompilerOptions) => {
|
|
571
|
-
if (
|
|
1091
|
+
if (shouldExternalizeStyles()) tsCompilerOptions["externalRuntimeStyles"] = true;
|
|
1092
|
+
if (shouldEnableHmr()) {
|
|
572
1093
|
tsCompilerOptions["_enableHmr"] = true;
|
|
573
|
-
tsCompilerOptions["externalRuntimeStyles"] = true;
|
|
574
1094
|
tsCompilerOptions["supportTestBed"] = true;
|
|
575
1095
|
}
|
|
576
1096
|
debugCompiler("tsCompilerOptions (compilation API)", {
|
|
577
|
-
|
|
1097
|
+
hmr: pluginOptions.hmr,
|
|
1098
|
+
hasTailwindCss: pluginOptions.hasTailwindCss,
|
|
578
1099
|
watchMode,
|
|
1100
|
+
shouldExternalize: shouldExternalizeStyles(),
|
|
579
1101
|
externalRuntimeStyles: !!tsCompilerOptions["externalRuntimeStyles"],
|
|
580
|
-
|
|
1102
|
+
hmrEnabled: !!tsCompilerOptions["_enableHmr"]
|
|
581
1103
|
});
|
|
582
1104
|
if (tsCompilerOptions.compilationMode === "partial") {
|
|
583
1105
|
tsCompilerOptions["supportTestBed"] = true;
|
|
584
1106
|
tsCompilerOptions["supportJitMode"] = true;
|
|
585
1107
|
}
|
|
1108
|
+
if (angularFullVersion >= 2e5) tsCompilerOptions["_enableSelectorless"] = true;
|
|
586
1109
|
if (!isTest && config.build?.lib) {
|
|
587
1110
|
tsCompilerOptions["declaration"] = true;
|
|
588
1111
|
tsCompilerOptions["declarationMap"] = watchMode;
|
|
@@ -591,13 +1114,80 @@ function angular(options) {
|
|
|
591
1114
|
if (isTest) tsCompilerOptions["supportTestBed"] = true;
|
|
592
1115
|
return tsCompilerOptions;
|
|
593
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
|
+
};
|
|
594
1128
|
compilationResult.externalStylesheets?.forEach((value, key) => {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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,
|
|
598
1187
|
resolvedPath: key
|
|
599
1188
|
});
|
|
600
1189
|
});
|
|
1190
|
+
debugStyles("external stylesheet preprocessing complete", preprocessStats);
|
|
601
1191
|
const diagnostics = await angularCompilation.diagnoseFiles(pluginOptions.disableTypeChecking ? DiagnosticModes.All & ~DiagnosticModes.Semantic : DiagnosticModes.All);
|
|
602
1192
|
const errors = diagnostics.errors?.length ? diagnostics.errors : [];
|
|
603
1193
|
const warnings = diagnostics.warnings?.length ? diagnostics.warnings : [];
|
|
@@ -653,7 +1243,9 @@ function angular(options) {
|
|
|
653
1243
|
resolvedTsConfigPath,
|
|
654
1244
|
isProd ? "prod" : "dev",
|
|
655
1245
|
isTest ? "test" : "app",
|
|
656
|
-
config.build?.lib ? "lib" : "nolib"
|
|
1246
|
+
config.build?.lib ? "lib" : "nolib",
|
|
1247
|
+
pluginOptions.hmr ? "hmr" : "nohmr",
|
|
1248
|
+
pluginOptions.hasTailwindCss ? "tw" : "notw"
|
|
657
1249
|
].join("|");
|
|
658
1250
|
let cached = tsconfigOptionsCache.get(tsconfigKey);
|
|
659
1251
|
if (!cached) {
|
|
@@ -682,19 +1274,22 @@ function angular(options) {
|
|
|
682
1274
|
}
|
|
683
1275
|
const tsCompilerOptions = { ...cached.options };
|
|
684
1276
|
let rootNames = [...cached.rootNames];
|
|
685
|
-
if (
|
|
1277
|
+
if (shouldExternalizeStyles()) tsCompilerOptions["externalRuntimeStyles"] = true;
|
|
1278
|
+
if (shouldEnableHmr()) {
|
|
686
1279
|
tsCompilerOptions["_enableHmr"] = true;
|
|
687
|
-
tsCompilerOptions["externalRuntimeStyles"] = true;
|
|
688
1280
|
tsCompilerOptions["supportTestBed"] = true;
|
|
689
1281
|
}
|
|
690
1282
|
debugCompiler("tsCompilerOptions (NgtscProgram path)", {
|
|
1283
|
+
hmr: pluginOptions.hmr,
|
|
1284
|
+
shouldExternalize: shouldExternalizeStyles(),
|
|
691
1285
|
externalRuntimeStyles: !!tsCompilerOptions["externalRuntimeStyles"],
|
|
692
|
-
|
|
1286
|
+
hmrEnabled: !!tsCompilerOptions["_enableHmr"]
|
|
693
1287
|
});
|
|
694
1288
|
if (tsCompilerOptions["compilationMode"] === "partial") {
|
|
695
1289
|
tsCompilerOptions["supportTestBed"] = true;
|
|
696
1290
|
tsCompilerOptions["supportJitMode"] = true;
|
|
697
1291
|
}
|
|
1292
|
+
if (angularFullVersion >= 2e5) tsCompilerOptions["_enableSelectorless"] = true;
|
|
698
1293
|
if (!isTest && config.build?.lib) {
|
|
699
1294
|
tsCompilerOptions["declaration"] = true;
|
|
700
1295
|
tsCompilerOptions["declarationMap"] = watchMode;
|
|
@@ -722,14 +1317,12 @@ function angular(options) {
|
|
|
722
1317
|
}
|
|
723
1318
|
if (!jit) {
|
|
724
1319
|
const externalizeStyles = !!tsCompilerOptions["externalRuntimeStyles"];
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
debugStyles("style maps initialized (NgtscProgram path)", { externalizeStyles });
|
|
1320
|
+
stylesheetRegistry = externalizeStyles ? new AnalogStylesheetRegistry() : void 0;
|
|
1321
|
+
debugStyles("stylesheet registry initialized (NgtscProgram path)", { externalizeStyles });
|
|
728
1322
|
augmentHostWithResources(host, styleTransform, {
|
|
729
1323
|
inlineStylesExtension: pluginOptions.inlineStylesExtension,
|
|
730
1324
|
isProd,
|
|
731
|
-
|
|
732
|
-
externalComponentStyles,
|
|
1325
|
+
stylesheetRegistry,
|
|
733
1326
|
sourceFileCache: sourceFileCache$1,
|
|
734
1327
|
stylePreprocessor: pluginOptions.stylePreprocessor
|
|
735
1328
|
});
|
|
@@ -756,7 +1349,7 @@ function angular(options) {
|
|
|
756
1349
|
if (!watchMode) builder = ts.createAbstractBuilder(typeScriptProgram, host, oldBuilder);
|
|
757
1350
|
if (angularCompiler) await angularCompiler.analyzeAsync();
|
|
758
1351
|
const transformers = mergeTransformers({ before: jit ? [compilerCli.constructorParametersDownlevelTransform(builder.getProgram()), cjt(() => builder.getProgram().getTypeChecker())] : [] }, jit ? {} : angularCompiler.prepareEmit().transformers);
|
|
759
|
-
const fileMetadata = getFileMetadata(builder, angularCompiler, pluginOptions.
|
|
1352
|
+
const fileMetadata = getFileMetadata(builder, angularCompiler, pluginOptions.hmr, pluginOptions.disableTypeChecking);
|
|
760
1353
|
const writeFileCallback = (_filename, content, _a, _b, sourceFiles) => {
|
|
761
1354
|
if (!sourceFiles?.length) return;
|
|
762
1355
|
const filename = normalizePath(sourceFiles[0].fileName);
|
|
@@ -850,14 +1443,377 @@ function mapTemplateUpdatesToFiles(templateUpdates) {
|
|
|
850
1443
|
});
|
|
851
1444
|
return updatesByFile;
|
|
852
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
|
+
}
|
|
853
1636
|
function sendHMRComponentUpdate(server, id) {
|
|
1637
|
+
debugHmrV("ws send: angular component update", {
|
|
1638
|
+
id,
|
|
1639
|
+
timestamp: Date.now()
|
|
1640
|
+
});
|
|
854
1641
|
server.ws.send("angular:component-update", {
|
|
855
1642
|
id: encodeURIComponent(id),
|
|
856
1643
|
timestamp: Date.now()
|
|
857
1644
|
});
|
|
858
1645
|
classNames.delete(id);
|
|
859
1646
|
}
|
|
860
|
-
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) {
|
|
861
1817
|
const ts = require("typescript");
|
|
862
1818
|
return (file) => {
|
|
863
1819
|
const sourceFile = program.getSourceFile(file);
|
|
@@ -867,7 +1823,7 @@ function getFileMetadata(program, angularCompiler, liveReload, disableTypeChecki
|
|
|
867
1823
|
const warnings = diagnostics.filter((d) => d.category === ts.DiagnosticCategory?.Warning).map((d) => d.messageText);
|
|
868
1824
|
let hmrUpdateCode = void 0;
|
|
869
1825
|
let hmrEligible = false;
|
|
870
|
-
if (
|
|
1826
|
+
if (hmrEnabled) {
|
|
871
1827
|
for (const node of sourceFile.statements) if (ts.isClassDeclaration(node) && node.name != null) {
|
|
872
1828
|
hmrUpdateCode = angularCompiler?.emitHmrUpdateModule(node);
|
|
873
1829
|
if (hmrUpdateCode) {
|
|
@@ -927,7 +1883,12 @@ function getComponentStyleSheetMeta(id) {
|
|
|
927
1883
|
* @param id
|
|
928
1884
|
*/
|
|
929
1885
|
function getFilenameFromPath(id) {
|
|
930
|
-
|
|
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
|
+
}
|
|
931
1892
|
}
|
|
932
1893
|
/**
|
|
933
1894
|
* Checks for vitest run from the command line
|