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