@analogjs/vite-plugin-angular 3.0.0-alpha.42 → 3.0.0-alpha.44
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 +1 -1
- package/src/lib/angular-vite-plugin.d.ts +3 -77
- package/src/lib/angular-vite-plugin.js +77 -1012
- package/src/lib/angular-vite-plugin.js.map +1 -1
- package/src/lib/compilation-api/compilation-api-plugin.d.ts +29 -0
- package/src/lib/compilation-api/compilation-api-plugin.js +468 -0
- package/src/lib/compilation-api/compilation-api-plugin.js.map +1 -0
- package/src/lib/compilation-api/index.d.ts +1 -0
- package/src/lib/compilation-api/index.js +1 -0
- package/src/lib/compiler/compile.js +2 -2
- package/src/lib/compiler/defer.js +1 -1
- package/src/lib/compiler/js-emitter.js +1 -1
- package/src/lib/encapsulation-plugin.d.ts +13 -0
- package/src/lib/encapsulation-plugin.js +48 -0
- package/src/lib/encapsulation-plugin.js.map +1 -0
- package/src/lib/fast-compile-plugin.js +2 -28
- package/src/lib/fast-compile-plugin.js.map +1 -1
- package/src/lib/host.js +1 -1
- package/src/lib/stylesheet-registry.js +1 -1
- package/src/lib/tailwind-plugin.d.ts +34 -0
- package/src/lib/tailwind-plugin.js +116 -0
- package/src/lib/tailwind-plugin.js.map +1 -0
- package/src/lib/template-class-binding-guard-plugin.d.ts +30 -0
- package/src/lib/template-class-binding-guard-plugin.js +237 -0
- package/src/lib/template-class-binding-guard-plugin.js.map +1 -0
- package/src/lib/utils/compilation-shared.d.ts +58 -0
- package/src/lib/utils/compilation-shared.js +133 -0
- package/src/lib/utils/compilation-shared.js.map +1 -0
- package/src/lib/utils/tsconfig-resolver.d.ts +28 -0
- package/src/lib/utils/tsconfig-resolver.js +191 -0
- package/src/lib/utils/tsconfig-resolver.js.map +1 -0
- package/src/lib/utils/virtual-resources.js +4 -31
- package/src/lib/utils/virtual-resources.js.map +1 -1
- package/src/lib/virtual-modules-plugin.d.ts +5 -0
- package/src/lib/virtual-modules-plugin.js +50 -0
- package/src/lib/virtual-modules-plugin.js.map +1 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { debugHmrV } from "./utils/debug.js";
|
|
2
|
+
import { getAngularComponentMetadata } from "./component-resolvers.js";
|
|
3
|
+
import { TS_EXT_REGEX } from "./utils/plugin-config.js";
|
|
4
|
+
import { normalizePath } from "vite";
|
|
5
|
+
//#region packages/vite-plugin-angular/src/lib/template-class-binding-guard-plugin.ts
|
|
6
|
+
function templateClassBindingGuardPlugin(ctx) {
|
|
7
|
+
return {
|
|
8
|
+
name: "@analogjs/vite-plugin-angular:template-class-binding-guard",
|
|
9
|
+
enforce: "pre",
|
|
10
|
+
transform(code, id) {
|
|
11
|
+
if (id.includes("node_modules")) return;
|
|
12
|
+
const cleanId = id.split("?")[0];
|
|
13
|
+
if (/\.(html|htm)$/i.test(cleanId)) {
|
|
14
|
+
const staticClassIssue = findStaticClassAndBoundClassConflicts(code)[0];
|
|
15
|
+
if (staticClassIssue) throwTemplateClassBindingConflict(cleanId, staticClassIssue);
|
|
16
|
+
const mixedClassIssue = findBoundClassAndNgClassConflicts(code)[0];
|
|
17
|
+
if (mixedClassIssue) this.warn([
|
|
18
|
+
"[Analog Angular] Conflicting class composition.",
|
|
19
|
+
`File: ${cleanId}:${mixedClassIssue.line}:${mixedClassIssue.column}`,
|
|
20
|
+
"This element mixes `[class]` and `[ngClass]`.",
|
|
21
|
+
"Prefer a single class-binding strategy so class merging stays predictable.",
|
|
22
|
+
"Use one `[ngClass]` expression or explicit `[class.foo]` bindings.",
|
|
23
|
+
`Snippet: ${mixedClassIssue.snippet}`
|
|
24
|
+
].join("\n"));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (TS_EXT_REGEX.test(cleanId)) {
|
|
28
|
+
const rawStyleUrls = ctx.styleUrlsResolver.resolve(code, cleanId);
|
|
29
|
+
registerStyleOwnerMetadata(ctx, cleanId, rawStyleUrls);
|
|
30
|
+
debugHmrV("component stylesheet owner metadata registered", {
|
|
31
|
+
file: cleanId,
|
|
32
|
+
styleUrlCount: rawStyleUrls.length,
|
|
33
|
+
styleUrls: rawStyleUrls,
|
|
34
|
+
ownerSources: [...ctx.transformedStyleOwnerMetadata.get(cleanId)?.map((record) => record.sourcePath) ?? []]
|
|
35
|
+
});
|
|
36
|
+
const components = getAngularComponentMetadata(code);
|
|
37
|
+
const inlineTemplateIssue = components.flatMap((component) => component.inlineTemplates.flatMap((template) => findStaticClassAndBoundClassConflicts(template)))[0];
|
|
38
|
+
if (inlineTemplateIssue) throwTemplateClassBindingConflict(cleanId, inlineTemplateIssue);
|
|
39
|
+
const mixedInlineClassIssue = components.flatMap((component) => component.inlineTemplates.flatMap((template) => findBoundClassAndNgClassConflicts(template)))[0];
|
|
40
|
+
if (mixedInlineClassIssue) this.warn([
|
|
41
|
+
"[Analog Angular] Conflicting class composition.",
|
|
42
|
+
`File: ${cleanId}:${mixedInlineClassIssue.line}:${mixedInlineClassIssue.column}`,
|
|
43
|
+
"This element mixes `[class]` and `[ngClass]`.",
|
|
44
|
+
"Prefer a single class-binding strategy so class merging stays predictable.",
|
|
45
|
+
"Use one `[ngClass]` expression or explicit `[class.foo]` bindings.",
|
|
46
|
+
`Snippet: ${mixedInlineClassIssue.snippet}`
|
|
47
|
+
].join("\n"));
|
|
48
|
+
registerActiveGraphMetadata(ctx, cleanId, components.map((component) => ({
|
|
49
|
+
file: cleanId,
|
|
50
|
+
className: component.className,
|
|
51
|
+
selector: component.selector
|
|
52
|
+
})));
|
|
53
|
+
for (const component of components) {
|
|
54
|
+
if (!component.selector && !isLikelyPageOnlyComponent(cleanId)) throw new Error([
|
|
55
|
+
"[Analog Angular] Selectorless component detected.",
|
|
56
|
+
`File: ${cleanId}`,
|
|
57
|
+
`Component: ${component.className}`,
|
|
58
|
+
"This component has no `selector`, so Angular will render it as `ng-component`.",
|
|
59
|
+
"That increases the chance of component ID collisions and makes diagnostics harder to interpret.",
|
|
60
|
+
"Add an explicit selector for reusable components.",
|
|
61
|
+
"Selectorless components are only supported for page and route-only files."
|
|
62
|
+
].join("\n"));
|
|
63
|
+
if (component.selector) {
|
|
64
|
+
const selectorEntries = ctx.selectorOwners.get(component.selector);
|
|
65
|
+
if (selectorEntries && selectorEntries.size > 1) throw new Error([
|
|
66
|
+
"[Analog Angular] Duplicate component selector detected.",
|
|
67
|
+
`Selector: ${component.selector}`,
|
|
68
|
+
"Multiple components in the active application graph use the same selector.",
|
|
69
|
+
"Selectors must be unique within the active graph to avoid ambiguous rendering and confusing diagnostics.",
|
|
70
|
+
`Locations:\n${formatActiveGraphLocations(selectorEntries)}`
|
|
71
|
+
].join("\n"));
|
|
72
|
+
}
|
|
73
|
+
const classNameEntries = ctx.classNameOwners.get(component.className);
|
|
74
|
+
if (classNameEntries && classNameEntries.size > 1) this.warn([
|
|
75
|
+
"[Analog Angular] Duplicate component class name detected.",
|
|
76
|
+
`Class name: ${component.className}`,
|
|
77
|
+
"Two or more Angular components in the active graph share the same exported class name.",
|
|
78
|
+
"Rename one of them to keep HMR, stack traces, and compiler diagnostics unambiguous.",
|
|
79
|
+
`Locations:\n${formatActiveGraphLocations(classNameEntries)}`
|
|
80
|
+
].join("\n"));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function removeActiveGraphMetadata(ctx, file) {
|
|
87
|
+
const previous = ctx.activeGraphComponentMetadata.get(file);
|
|
88
|
+
if (!previous) return;
|
|
89
|
+
for (const record of previous) {
|
|
90
|
+
const location = `${record.file}#${record.className}`;
|
|
91
|
+
if (record.selector) {
|
|
92
|
+
const selectorSet = ctx.selectorOwners.get(record.selector);
|
|
93
|
+
selectorSet?.delete(location);
|
|
94
|
+
if (selectorSet?.size === 0) ctx.selectorOwners.delete(record.selector);
|
|
95
|
+
}
|
|
96
|
+
const classNameSet = ctx.classNameOwners.get(record.className);
|
|
97
|
+
classNameSet?.delete(location);
|
|
98
|
+
if (classNameSet?.size === 0) ctx.classNameOwners.delete(record.className);
|
|
99
|
+
}
|
|
100
|
+
ctx.activeGraphComponentMetadata.delete(file);
|
|
101
|
+
}
|
|
102
|
+
function registerActiveGraphMetadata(ctx, file, records) {
|
|
103
|
+
removeActiveGraphMetadata(ctx, file);
|
|
104
|
+
if (records.length === 0) return;
|
|
105
|
+
ctx.activeGraphComponentMetadata.set(file, records);
|
|
106
|
+
for (const record of records) {
|
|
107
|
+
const location = `${record.file}#${record.className}`;
|
|
108
|
+
if (record.selector) {
|
|
109
|
+
let selectorSet = ctx.selectorOwners.get(record.selector);
|
|
110
|
+
if (!selectorSet) {
|
|
111
|
+
selectorSet = /* @__PURE__ */ new Set();
|
|
112
|
+
ctx.selectorOwners.set(record.selector, selectorSet);
|
|
113
|
+
}
|
|
114
|
+
selectorSet.add(location);
|
|
115
|
+
}
|
|
116
|
+
let classNameSet = ctx.classNameOwners.get(record.className);
|
|
117
|
+
if (!classNameSet) {
|
|
118
|
+
classNameSet = /* @__PURE__ */ new Set();
|
|
119
|
+
ctx.classNameOwners.set(record.className, classNameSet);
|
|
120
|
+
}
|
|
121
|
+
classNameSet.add(location);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function removeStyleOwnerMetadata(ctx, file) {
|
|
125
|
+
const previous = ctx.transformedStyleOwnerMetadata.get(file);
|
|
126
|
+
if (!previous) return;
|
|
127
|
+
for (const record of previous) {
|
|
128
|
+
const owners = ctx.styleSourceOwners.get(record.sourcePath);
|
|
129
|
+
owners?.delete(record.ownerFile);
|
|
130
|
+
if (owners?.size === 0) ctx.styleSourceOwners.delete(record.sourcePath);
|
|
131
|
+
}
|
|
132
|
+
ctx.transformedStyleOwnerMetadata.delete(file);
|
|
133
|
+
}
|
|
134
|
+
function registerStyleOwnerMetadata(ctx, file, styleUrls) {
|
|
135
|
+
removeStyleOwnerMetadata(ctx, file);
|
|
136
|
+
const records = styleUrls.map((urlSet) => {
|
|
137
|
+
const [, absoluteFileUrl] = urlSet.split("|");
|
|
138
|
+
return absoluteFileUrl ? {
|
|
139
|
+
ownerFile: file,
|
|
140
|
+
sourcePath: normalizePath(absoluteFileUrl)
|
|
141
|
+
} : void 0;
|
|
142
|
+
}).filter((record) => !!record);
|
|
143
|
+
if (records.length === 0) return;
|
|
144
|
+
ctx.transformedStyleOwnerMetadata.set(file, records);
|
|
145
|
+
for (const record of records) {
|
|
146
|
+
let owners = ctx.styleSourceOwners.get(record.sourcePath);
|
|
147
|
+
if (!owners) {
|
|
148
|
+
owners = /* @__PURE__ */ new Set();
|
|
149
|
+
ctx.styleSourceOwners.set(record.sourcePath, owners);
|
|
150
|
+
}
|
|
151
|
+
owners.add(record.ownerFile);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function isLikelyPageOnlyComponent(id) {
|
|
155
|
+
return id.includes("/pages/") || /\.page\.[cm]?[jt]sx?$/i.test(id) || /\([^/]+\)\.page\.[cm]?[jt]sx?$/i.test(id);
|
|
156
|
+
}
|
|
157
|
+
function findStaticClassAndBoundClassConflicts(template) {
|
|
158
|
+
const issues = [];
|
|
159
|
+
for (const { index, snippet } of findOpeningTagSnippets(template)) {
|
|
160
|
+
if (!snippet.includes("[class]")) continue;
|
|
161
|
+
const hasStaticClass = /\sclass\s*=\s*(['"])(?:(?!\1)[\s\S])*\1/.test(snippet);
|
|
162
|
+
const hasBoundClass = /\s\[class\]\s*=\s*(['"])(?:(?!\1)[\s\S])*\1/.test(snippet);
|
|
163
|
+
if (hasStaticClass && hasBoundClass) {
|
|
164
|
+
const prefix = template.slice(0, index);
|
|
165
|
+
const line = prefix.split("\n").length;
|
|
166
|
+
const column = index - prefix.lastIndexOf("\n");
|
|
167
|
+
issues.push({
|
|
168
|
+
line,
|
|
169
|
+
column,
|
|
170
|
+
snippet: snippet.replace(/\s+/g, " ").trim()
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return issues;
|
|
175
|
+
}
|
|
176
|
+
function findBoundClassAndNgClassConflicts(template) {
|
|
177
|
+
const issues = [];
|
|
178
|
+
if (!/\[class\]\s*=/.test(template) || !template.includes("[ngClass]")) return issues;
|
|
179
|
+
for (const { index, snippet } of findOpeningTagSnippets(template)) {
|
|
180
|
+
if (!/\[class\]\s*=/.test(snippet) || !snippet.includes("[ngClass]")) continue;
|
|
181
|
+
const prefix = template.slice(0, index);
|
|
182
|
+
const line = prefix.split("\n").length;
|
|
183
|
+
const column = index - prefix.lastIndexOf("\n");
|
|
184
|
+
issues.push({
|
|
185
|
+
line,
|
|
186
|
+
column,
|
|
187
|
+
snippet: snippet.replace(/\s+/g, " ").trim()
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return issues;
|
|
191
|
+
}
|
|
192
|
+
function throwTemplateClassBindingConflict(id, issue) {
|
|
193
|
+
throw new Error([
|
|
194
|
+
"[Analog Angular] Invalid template class binding.",
|
|
195
|
+
`File: ${id}:${issue.line}:${issue.column}`,
|
|
196
|
+
"The same element uses both a static `class=\"...\"` attribute and a whole-element `[class]=\"...\"` binding.",
|
|
197
|
+
"That pattern can replace or conflict with static Tailwind classes, which makes styles appear to stop applying.",
|
|
198
|
+
"Use `[ngClass]` or explicit `[class.foo]` bindings instead of `[class]` when the element also has static classes.",
|
|
199
|
+
`Snippet: ${issue.snippet}`
|
|
200
|
+
].join("\n"));
|
|
201
|
+
}
|
|
202
|
+
function findOpeningTagSnippets(template) {
|
|
203
|
+
const matches = [];
|
|
204
|
+
for (let index = 0; index < template.length; index++) {
|
|
205
|
+
if (template[index] !== "<") continue;
|
|
206
|
+
const tagStart = template[index + 1];
|
|
207
|
+
if (!tagStart || !/[a-zA-Z]/.test(tagStart)) continue;
|
|
208
|
+
let quote = null;
|
|
209
|
+
for (let end = index + 1; end < template.length; end++) {
|
|
210
|
+
const char = template[end];
|
|
211
|
+
if (quote) {
|
|
212
|
+
if (char === quote) quote = null;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (char === "\"" || char === "'") {
|
|
216
|
+
quote = char;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (char === ">") {
|
|
220
|
+
matches.push({
|
|
221
|
+
index,
|
|
222
|
+
snippet: template.slice(index, end + 1)
|
|
223
|
+
});
|
|
224
|
+
index = end;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return matches;
|
|
230
|
+
}
|
|
231
|
+
function formatActiveGraphLocations(entries) {
|
|
232
|
+
return [...entries].sort().map((entry) => `- ${entry}`).join("\n");
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
235
|
+
export { findBoundClassAndNgClassConflicts, findStaticClassAndBoundClassConflicts, removeActiveGraphMetadata, removeStyleOwnerMetadata, templateClassBindingGuardPlugin };
|
|
236
|
+
|
|
237
|
+
//# sourceMappingURL=template-class-binding-guard-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-class-binding-guard-plugin.js","names":[],"sources":["../../../src/lib/template-class-binding-guard-plugin.ts"],"sourcesContent":["import { normalizePath, Plugin } from 'vite';\nimport {\n getAngularComponentMetadata,\n StyleUrlsResolver,\n} from './component-resolvers.js';\nimport { debugHmrV } from './utils/debug.js';\nimport { TS_EXT_REGEX } from './utils/plugin-config.js';\n\nexport interface ActiveGraphComponentRecord {\n file: string;\n className: string;\n selector?: string;\n}\n\nexport interface StyleOwnerRecord {\n sourcePath: string;\n ownerFile: string;\n}\n\ninterface TemplateClassBindingIssue {\n line: number;\n column: number;\n snippet: string;\n}\n\nexport interface TemplateClassBindingGuardContext {\n styleUrlsResolver: StyleUrlsResolver;\n activeGraphComponentMetadata: Map<string, ActiveGraphComponentRecord[]>;\n selectorOwners: Map<string, Set<string>>;\n classNameOwners: Map<string, Set<string>>;\n transformedStyleOwnerMetadata: Map<string, StyleOwnerRecord[]>;\n styleSourceOwners: Map<string, Set<string>>;\n}\n\nexport function templateClassBindingGuardPlugin(\n ctx: TemplateClassBindingGuardContext,\n): Plugin {\n return {\n name: '@analogjs/vite-plugin-angular:template-class-binding-guard',\n enforce: 'pre',\n transform(code: string, id: string) {\n if (id.includes('node_modules')) {\n return;\n }\n\n const cleanId = id.split('?')[0];\n\n if (/\\.(html|htm)$/i.test(cleanId)) {\n const staticClassIssue = findStaticClassAndBoundClassConflicts(code)[0];\n if (staticClassIssue) {\n throwTemplateClassBindingConflict(cleanId, staticClassIssue);\n }\n\n const mixedClassIssue = findBoundClassAndNgClassConflicts(code)[0];\n if (mixedClassIssue) {\n this.warn(\n [\n '[Analog Angular] Conflicting class composition.',\n `File: ${cleanId}:${mixedClassIssue.line}:${mixedClassIssue.column}`,\n 'This element mixes `[class]` and `[ngClass]`.',\n 'Prefer a single class-binding strategy so class merging stays predictable.',\n 'Use one `[ngClass]` expression or explicit `[class.foo]` bindings.',\n `Snippet: ${mixedClassIssue.snippet}`,\n ].join('\\n'),\n );\n }\n return;\n }\n\n if (TS_EXT_REGEX.test(cleanId)) {\n const rawStyleUrls = ctx.styleUrlsResolver.resolve(code, cleanId);\n registerStyleOwnerMetadata(ctx, cleanId, rawStyleUrls);\n debugHmrV('component stylesheet owner metadata registered', {\n file: cleanId,\n styleUrlCount: rawStyleUrls.length,\n styleUrls: rawStyleUrls,\n ownerSources: [\n ...(ctx.transformedStyleOwnerMetadata\n .get(cleanId)\n ?.map((record) => record.sourcePath) ?? []),\n ],\n });\n\n const components = getAngularComponentMetadata(code);\n\n const inlineTemplateIssue = components.flatMap((component) =>\n component.inlineTemplates.flatMap((template) =>\n findStaticClassAndBoundClassConflicts(template),\n ),\n )[0];\n\n if (inlineTemplateIssue) {\n throwTemplateClassBindingConflict(cleanId, inlineTemplateIssue);\n }\n\n const mixedInlineClassIssue = components.flatMap((component) =>\n component.inlineTemplates.flatMap((template) =>\n findBoundClassAndNgClassConflicts(template),\n ),\n )[0];\n\n if (mixedInlineClassIssue) {\n this.warn(\n [\n '[Analog Angular] Conflicting class composition.',\n `File: ${cleanId}:${mixedInlineClassIssue.line}:${mixedInlineClassIssue.column}`,\n 'This element mixes `[class]` and `[ngClass]`.',\n 'Prefer a single class-binding strategy so class merging stays predictable.',\n 'Use one `[ngClass]` expression or explicit `[class.foo]` bindings.',\n `Snippet: ${mixedInlineClassIssue.snippet}`,\n ].join('\\n'),\n );\n }\n\n const activeGraphRecords = components.map((component) => ({\n file: cleanId,\n className: component.className,\n selector: component.selector,\n }));\n\n registerActiveGraphMetadata(ctx, cleanId, activeGraphRecords);\n\n for (const component of components) {\n if (!component.selector && !isLikelyPageOnlyComponent(cleanId)) {\n throw new Error(\n [\n '[Analog Angular] Selectorless component detected.',\n `File: ${cleanId}`,\n `Component: ${component.className}`,\n 'This component has no `selector`, so Angular will render it as `ng-component`.',\n 'That increases the chance of component ID collisions and makes diagnostics harder to interpret.',\n 'Add an explicit selector for reusable components.',\n 'Selectorless components are only supported for page and route-only files.',\n ].join('\\n'),\n );\n }\n\n if (component.selector) {\n const selectorEntries = ctx.selectorOwners.get(component.selector);\n if (selectorEntries && selectorEntries.size > 1) {\n throw new Error(\n [\n '[Analog Angular] Duplicate component selector detected.',\n `Selector: ${component.selector}`,\n 'Multiple components in the active application graph use the same selector.',\n 'Selectors must be unique within the active graph to avoid ambiguous rendering and confusing diagnostics.',\n `Locations:\\n${formatActiveGraphLocations(selectorEntries)}`,\n ].join('\\n'),\n );\n }\n }\n\n const classNameEntries = ctx.classNameOwners.get(component.className);\n if (classNameEntries && classNameEntries.size > 1) {\n this.warn(\n [\n '[Analog Angular] Duplicate component class name detected.',\n `Class name: ${component.className}`,\n 'Two or more Angular components in the active graph share the same exported class name.',\n 'Rename one of them to keep HMR, stack traces, and compiler diagnostics unambiguous.',\n `Locations:\\n${formatActiveGraphLocations(classNameEntries)}`,\n ].join('\\n'),\n );\n }\n }\n }\n },\n };\n}\n\nexport function removeActiveGraphMetadata(\n ctx: TemplateClassBindingGuardContext,\n file: string,\n): void {\n const previous = ctx.activeGraphComponentMetadata.get(file);\n if (!previous) {\n return;\n }\n\n for (const record of previous) {\n const location = `${record.file}#${record.className}`;\n if (record.selector) {\n const selectorSet = ctx.selectorOwners.get(record.selector);\n selectorSet?.delete(location);\n if (selectorSet?.size === 0) {\n ctx.selectorOwners.delete(record.selector);\n }\n }\n\n const classNameSet = ctx.classNameOwners.get(record.className);\n classNameSet?.delete(location);\n if (classNameSet?.size === 0) {\n ctx.classNameOwners.delete(record.className);\n }\n }\n\n ctx.activeGraphComponentMetadata.delete(file);\n}\n\nfunction registerActiveGraphMetadata(\n ctx: TemplateClassBindingGuardContext,\n file: string,\n records: ActiveGraphComponentRecord[],\n) {\n removeActiveGraphMetadata(ctx, file);\n\n if (records.length === 0) {\n return;\n }\n\n ctx.activeGraphComponentMetadata.set(file, records);\n\n for (const record of records) {\n const location = `${record.file}#${record.className}`;\n\n if (record.selector) {\n let selectorSet = ctx.selectorOwners.get(record.selector);\n if (!selectorSet) {\n selectorSet = new Set<string>();\n ctx.selectorOwners.set(record.selector, selectorSet);\n }\n selectorSet.add(location);\n }\n\n let classNameSet = ctx.classNameOwners.get(record.className);\n if (!classNameSet) {\n classNameSet = new Set<string>();\n ctx.classNameOwners.set(record.className, classNameSet);\n }\n classNameSet.add(location);\n }\n}\n\nexport function removeStyleOwnerMetadata(\n ctx: TemplateClassBindingGuardContext,\n file: string,\n): void {\n const previous = ctx.transformedStyleOwnerMetadata.get(file);\n if (!previous) {\n return;\n }\n\n for (const record of previous) {\n const owners = ctx.styleSourceOwners.get(record.sourcePath);\n owners?.delete(record.ownerFile);\n if (owners?.size === 0) {\n ctx.styleSourceOwners.delete(record.sourcePath);\n }\n }\n\n ctx.transformedStyleOwnerMetadata.delete(file);\n}\n\nfunction registerStyleOwnerMetadata(\n ctx: TemplateClassBindingGuardContext,\n file: string,\n styleUrls: string[],\n) {\n removeStyleOwnerMetadata(ctx, file);\n\n const records = styleUrls\n .map((urlSet) => {\n const [, absoluteFileUrl] = urlSet.split('|');\n return absoluteFileUrl\n ? {\n ownerFile: file,\n sourcePath: normalizePath(absoluteFileUrl),\n }\n : undefined;\n })\n .filter((record): record is StyleOwnerRecord => !!record);\n\n if (records.length === 0) {\n return;\n }\n\n ctx.transformedStyleOwnerMetadata.set(file, records);\n\n for (const record of records) {\n let owners = ctx.styleSourceOwners.get(record.sourcePath);\n if (!owners) {\n owners = new Set<string>();\n ctx.styleSourceOwners.set(record.sourcePath, owners);\n }\n owners.add(record.ownerFile);\n }\n}\n\nfunction isLikelyPageOnlyComponent(id: string): boolean {\n return (\n id.includes('/pages/') ||\n /\\.page\\.[cm]?[jt]sx?$/i.test(id) ||\n /\\([^/]+\\)\\.page\\.[cm]?[jt]sx?$/i.test(id)\n );\n}\n\nexport function findStaticClassAndBoundClassConflicts(\n template: string,\n): TemplateClassBindingIssue[] {\n const issues: TemplateClassBindingIssue[] = [];\n\n for (const { index, snippet } of findOpeningTagSnippets(template)) {\n if (!snippet.includes('[class]')) {\n continue;\n }\n\n const hasStaticClass = /\\sclass\\s*=\\s*(['\"])(?:(?!\\1)[\\s\\S])*\\1/.test(\n snippet,\n );\n const hasBoundClass = /\\s\\[class\\]\\s*=\\s*(['\"])(?:(?!\\1)[\\s\\S])*\\1/.test(\n snippet,\n );\n\n if (hasStaticClass && hasBoundClass) {\n const prefix = template.slice(0, index);\n const line = prefix.split('\\n').length;\n const lastNewline = prefix.lastIndexOf('\\n');\n const column = index - lastNewline;\n issues.push({\n line,\n column,\n snippet: snippet.replace(/\\s+/g, ' ').trim(),\n });\n }\n }\n\n return issues;\n}\n\nexport function findBoundClassAndNgClassConflicts(\n template: string,\n): TemplateClassBindingIssue[] {\n const issues: TemplateClassBindingIssue[] = [];\n const hasWholeElementClassBinding = /\\[class\\]\\s*=/.test(template);\n\n if (!hasWholeElementClassBinding || !template.includes('[ngClass]')) {\n return issues;\n }\n\n for (const { index, snippet } of findOpeningTagSnippets(template)) {\n if (!/\\[class\\]\\s*=/.test(snippet) || !snippet.includes('[ngClass]')) {\n continue;\n }\n\n const prefix = template.slice(0, index);\n const line = prefix.split('\\n').length;\n const lastNewline = prefix.lastIndexOf('\\n');\n const column = index - lastNewline;\n issues.push({\n line,\n column,\n snippet: snippet.replace(/\\s+/g, ' ').trim(),\n });\n }\n\n return issues;\n}\n\nfunction throwTemplateClassBindingConflict(\n id: string,\n issue: TemplateClassBindingIssue,\n): never {\n throw new Error(\n [\n '[Analog Angular] Invalid template class binding.',\n `File: ${id}:${issue.line}:${issue.column}`,\n 'The same element uses both a static `class=\"...\"` attribute and a whole-element `[class]=\"...\"` binding.',\n 'That pattern can replace or conflict with static Tailwind classes, which makes styles appear to stop applying.',\n 'Use `[ngClass]` or explicit `[class.foo]` bindings instead of `[class]` when the element also has static classes.',\n `Snippet: ${issue.snippet}`,\n ].join('\\n'),\n );\n}\n\nfunction findOpeningTagSnippets(\n template: string,\n): Array<{ index: number; snippet: string }> {\n const matches: Array<{ index: number; snippet: string }> = [];\n\n for (let index = 0; index < template.length; index++) {\n if (template[index] !== '<') {\n continue;\n }\n\n const tagStart = template[index + 1];\n if (!tagStart || !/[a-zA-Z]/.test(tagStart)) {\n continue;\n }\n\n let quote: '\"' | \"'\" | null = null;\n\n for (let end = index + 1; end < template.length; end++) {\n const char = template[end];\n\n if (quote) {\n if (char === quote) {\n quote = null;\n }\n continue;\n }\n\n if (char === '\"' || char === \"'\") {\n quote = char;\n continue;\n }\n\n if (char === '>') {\n matches.push({\n index,\n snippet: template.slice(index, end + 1),\n });\n index = end;\n break;\n }\n }\n }\n\n return matches;\n}\n\nfunction formatActiveGraphLocations(entries: Iterable<string>): string {\n return [...entries]\n .sort()\n .map((entry) => `- ${entry}`)\n .join('\\n');\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { AnalogStylesheetRegistry } from "../stylesheet-registry.js";
|
|
2
|
+
import type { StylePreprocessor } from "../style-preprocessor.js";
|
|
3
|
+
import type { FileReplacement } from "../plugins/file-replacements.plugin.js";
|
|
4
|
+
export declare enum DiagnosticModes {
|
|
5
|
+
None = 0,
|
|
6
|
+
Option = 1,
|
|
7
|
+
Syntactic = 2,
|
|
8
|
+
Semantic = 4,
|
|
9
|
+
All = 7
|
|
10
|
+
}
|
|
11
|
+
export declare function injectViteIgnoreForHmrMetadata(code: string): string;
|
|
12
|
+
export declare function isIgnoredHmrFile(file: string): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Convert Analog/Angular CLI-style file replacements into the flat record
|
|
15
|
+
* expected by `AngularHostOptions.fileReplacements`.
|
|
16
|
+
*
|
|
17
|
+
* Only browser replacements (`{ replace, with }`) are converted. SSR-only
|
|
18
|
+
* replacements (`{ replace, ssr }`) are left for the Vite runtime plugin to
|
|
19
|
+
* handle — they should not be baked into the Angular compilation host because
|
|
20
|
+
* that would apply them to both browser and server builds.
|
|
21
|
+
*
|
|
22
|
+
* Relative paths are resolved against `workspaceRoot` so that the host
|
|
23
|
+
* receives the same absolute paths it would get from the Angular CLI.
|
|
24
|
+
*/
|
|
25
|
+
export declare function toAngularCompilationFileReplacements(replacements: FileReplacement[], workspaceRoot: string): Record<string, string> | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Map Angular's `templateUpdates` (keyed by `encodedFilePath@ClassName`)
|
|
28
|
+
* back to absolute file paths with their associated HMR code and component
|
|
29
|
+
* class name.
|
|
30
|
+
*
|
|
31
|
+
* Angular's private Compilation API emits template update keys in the form
|
|
32
|
+
* `encodeURIComponent(relativePath + '@' + className)`. We decode and resolve
|
|
33
|
+
* them so the caller can look up updates by the same normalized absolute path
|
|
34
|
+
* used elsewhere in the plugin (`outputFiles`, `classNames`, etc.).
|
|
35
|
+
*/
|
|
36
|
+
export declare function mapTemplateUpdatesToFiles(templateUpdates: ReadonlyMap<string, string> | undefined): Map<string, {
|
|
37
|
+
className: string;
|
|
38
|
+
code: string;
|
|
39
|
+
}>;
|
|
40
|
+
export declare function describeStylesheetContent(code: string): {
|
|
41
|
+
length: number;
|
|
42
|
+
digest: string;
|
|
43
|
+
preview: string;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Refreshes any already-served stylesheet records that map back to a changed
|
|
47
|
+
* source file.
|
|
48
|
+
*
|
|
49
|
+
* This is the critical bridge for externalized Angular component styles during
|
|
50
|
+
* HMR. Angular's resource watcher can notice that `/src/...component.css`
|
|
51
|
+
* changed before Angular recompilation has had a chance to repopulate the
|
|
52
|
+
* stylesheet registry. If we emit a CSS update against the existing virtual
|
|
53
|
+
* stylesheet id without first refreshing the registry content, the browser gets
|
|
54
|
+
* a hot update containing stale CSS. By rewriting the existing served records
|
|
55
|
+
* from disk up front, HMR always pushes the latest source content.
|
|
56
|
+
*/
|
|
57
|
+
export declare function refreshStylesheetRegistryForFile(file: string, stylesheetRegistry?: AnalogStylesheetRegistry, stylePreprocessor?: StylePreprocessor): void;
|
|
58
|
+
export declare function isTestWatchMode(args?: string[]): boolean;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { debugStylesV } from "./debug.js";
|
|
2
|
+
import { normalizeStylesheetDependencies } from "../style-preprocessor.js";
|
|
3
|
+
import { preprocessStylesheetResult, rewriteRelativeCssImports } from "../stylesheet-registry.js";
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
import { basename, isAbsolute, resolve } from "node:path";
|
|
6
|
+
import { normalizePath } from "vite";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
//#region packages/vite-plugin-angular/src/lib/utils/compilation-shared.ts
|
|
9
|
+
/**
|
|
10
|
+
* Shared utilities used by both angular-vite-plugin.ts and
|
|
11
|
+
* compilation-api/compilation-api-plugin.ts.
|
|
12
|
+
*/
|
|
13
|
+
var DiagnosticModes = /* @__PURE__ */ function(DiagnosticModes) {
|
|
14
|
+
DiagnosticModes[DiagnosticModes["None"] = 0] = "None";
|
|
15
|
+
DiagnosticModes[DiagnosticModes["Option"] = 1] = "Option";
|
|
16
|
+
DiagnosticModes[DiagnosticModes["Syntactic"] = 2] = "Syntactic";
|
|
17
|
+
DiagnosticModes[DiagnosticModes["Semantic"] = 4] = "Semantic";
|
|
18
|
+
DiagnosticModes[DiagnosticModes["All"] = 7] = "All";
|
|
19
|
+
return DiagnosticModes;
|
|
20
|
+
}({});
|
|
21
|
+
function injectViteIgnoreForHmrMetadata(code) {
|
|
22
|
+
let patched = code.replace(/\bimport\(([a-zA-Z_$][\w$]*\.\u0275\u0275getReplaceMetadataURL)/g, "import(/* @vite-ignore */ $1");
|
|
23
|
+
if (patched === code) patched = patched.replace(/import\((\S+getReplaceMetadataURL)/g, "import(/* @vite-ignore */ $1");
|
|
24
|
+
return patched;
|
|
25
|
+
}
|
|
26
|
+
function isIgnoredHmrFile(file) {
|
|
27
|
+
return file.endsWith(".tsbuildinfo");
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Convert Analog/Angular CLI-style file replacements into the flat record
|
|
31
|
+
* expected by `AngularHostOptions.fileReplacements`.
|
|
32
|
+
*
|
|
33
|
+
* Only browser replacements (`{ replace, with }`) are converted. SSR-only
|
|
34
|
+
* replacements (`{ replace, ssr }`) are left for the Vite runtime plugin to
|
|
35
|
+
* handle — they should not be baked into the Angular compilation host because
|
|
36
|
+
* that would apply them to both browser and server builds.
|
|
37
|
+
*
|
|
38
|
+
* Relative paths are resolved against `workspaceRoot` so that the host
|
|
39
|
+
* receives the same absolute paths it would get from the Angular CLI.
|
|
40
|
+
*/
|
|
41
|
+
function toAngularCompilationFileReplacements(replacements, workspaceRoot) {
|
|
42
|
+
const mappedReplacements = replacements.flatMap((replacement) => {
|
|
43
|
+
if (!("with" in replacement)) return [];
|
|
44
|
+
return [[isAbsolute(replacement.replace) ? replacement.replace : resolve(workspaceRoot, replacement.replace), isAbsolute(replacement.with) ? replacement.with : resolve(workspaceRoot, replacement.with)]];
|
|
45
|
+
});
|
|
46
|
+
return mappedReplacements.length ? Object.fromEntries(mappedReplacements) : void 0;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Map Angular's `templateUpdates` (keyed by `encodedFilePath@ClassName`)
|
|
50
|
+
* back to absolute file paths with their associated HMR code and component
|
|
51
|
+
* class name.
|
|
52
|
+
*
|
|
53
|
+
* Angular's private Compilation API emits template update keys in the form
|
|
54
|
+
* `encodeURIComponent(relativePath + '@' + className)`. We decode and resolve
|
|
55
|
+
* them so the caller can look up updates by the same normalized absolute path
|
|
56
|
+
* used elsewhere in the plugin (`outputFiles`, `classNames`, etc.).
|
|
57
|
+
*/
|
|
58
|
+
function mapTemplateUpdatesToFiles(templateUpdates) {
|
|
59
|
+
const updatesByFile = /* @__PURE__ */ new Map();
|
|
60
|
+
templateUpdates?.forEach((code, encodedUpdateId) => {
|
|
61
|
+
const [file, className = ""] = decodeURIComponent(encodedUpdateId).split("@");
|
|
62
|
+
const resolvedFile = normalizePath(resolve(process.cwd(), file));
|
|
63
|
+
updatesByFile.set(resolvedFile, {
|
|
64
|
+
className,
|
|
65
|
+
code
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
return updatesByFile;
|
|
69
|
+
}
|
|
70
|
+
function describeStylesheetContent(code) {
|
|
71
|
+
return {
|
|
72
|
+
length: code.length,
|
|
73
|
+
digest: createHash("sha256").update(code).digest("hex").slice(0, 12),
|
|
74
|
+
preview: code.replace(/\s+/g, " ").trim().slice(0, 160)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Refreshes any already-served stylesheet records that map back to a changed
|
|
79
|
+
* source file.
|
|
80
|
+
*
|
|
81
|
+
* This is the critical bridge for externalized Angular component styles during
|
|
82
|
+
* HMR. Angular's resource watcher can notice that `/src/...component.css`
|
|
83
|
+
* changed before Angular recompilation has had a chance to repopulate the
|
|
84
|
+
* stylesheet registry. If we emit a CSS update against the existing virtual
|
|
85
|
+
* stylesheet id without first refreshing the registry content, the browser gets
|
|
86
|
+
* a hot update containing stale CSS. By rewriting the existing served records
|
|
87
|
+
* from disk up front, HMR always pushes the latest source content.
|
|
88
|
+
*/
|
|
89
|
+
function refreshStylesheetRegistryForFile(file, stylesheetRegistry, stylePreprocessor) {
|
|
90
|
+
const normalizedFile = normalizePath(file.split("?")[0]);
|
|
91
|
+
if (!stylesheetRegistry || !existsSync(normalizedFile)) return;
|
|
92
|
+
const publicIds = stylesheetRegistry.getPublicIdsForSource(normalizedFile);
|
|
93
|
+
if (publicIds.length === 0) return;
|
|
94
|
+
const rawCss = readFileSync(normalizedFile, "utf-8");
|
|
95
|
+
const preprocessed = preprocessStylesheetResult(rawCss, normalizedFile, stylePreprocessor);
|
|
96
|
+
const servedCss = rewriteRelativeCssImports(preprocessed.code, normalizedFile);
|
|
97
|
+
for (const publicId of publicIds) stylesheetRegistry.registerServedStylesheet({
|
|
98
|
+
publicId,
|
|
99
|
+
sourcePath: normalizedFile,
|
|
100
|
+
originalCode: rawCss,
|
|
101
|
+
normalizedCode: servedCss,
|
|
102
|
+
dependencies: normalizeStylesheetDependencies(preprocessed.dependencies),
|
|
103
|
+
diagnostics: preprocessed.diagnostics,
|
|
104
|
+
tags: preprocessed.tags
|
|
105
|
+
}, [
|
|
106
|
+
normalizedFile,
|
|
107
|
+
normalizePath(normalizedFile),
|
|
108
|
+
basename(normalizedFile),
|
|
109
|
+
normalizedFile.replace(/^\//, "")
|
|
110
|
+
]);
|
|
111
|
+
debugStylesV("stylesheet registry refreshed from source file", {
|
|
112
|
+
file: normalizedFile,
|
|
113
|
+
publicIds,
|
|
114
|
+
dependencies: preprocessed.dependencies,
|
|
115
|
+
diagnostics: preprocessed.diagnostics,
|
|
116
|
+
tags: preprocessed.tags,
|
|
117
|
+
source: describeStylesheetContent(rawCss),
|
|
118
|
+
served: describeStylesheetContent(servedCss)
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function isTestWatchMode(args = process.argv) {
|
|
122
|
+
if (args.find((arg) => arg.includes("--run"))) return false;
|
|
123
|
+
if (args.find((arg) => arg.includes("--no-run"))) return true;
|
|
124
|
+
const hasWatch = args.find((arg) => arg.includes("watch"));
|
|
125
|
+
if (hasWatch && ["false", "no"].some((neg) => hasWatch.includes(neg))) return false;
|
|
126
|
+
const watchArg = args[args.findIndex((arg) => arg.includes("watch")) + 1];
|
|
127
|
+
if (watchArg && watchArg === "false") return false;
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
export { DiagnosticModes, describeStylesheetContent, injectViteIgnoreForHmrMetadata, isIgnoredHmrFile, isTestWatchMode, mapTemplateUpdatesToFiles, refreshStylesheetRegistryForFile, toAngularCompilationFileReplacements };
|
|
132
|
+
|
|
133
|
+
//# sourceMappingURL=compilation-shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compilation-shared.js","names":[],"sources":["../../../../src/lib/utils/compilation-shared.ts"],"sourcesContent":["/**\n * Shared utilities used by both angular-vite-plugin.ts and\n * compilation-api/compilation-api-plugin.ts.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { basename, isAbsolute, resolve } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport { normalizePath } from 'vite';\n\nimport {\n AnalogStylesheetRegistry,\n preprocessStylesheetResult,\n rewriteRelativeCssImports,\n} from '../stylesheet-registry.js';\nimport { normalizeStylesheetDependencies } from '../style-preprocessor.js';\nimport type { StylePreprocessor } from '../style-preprocessor.js';\nimport type { FileReplacement } from '../plugins/file-replacements.plugin.js';\nimport { debugStylesV } from './debug.js';\n\nexport enum DiagnosticModes {\n None = 0,\n Option = 1 << 0,\n Syntactic = 1 << 1,\n Semantic = 1 << 2,\n All = Option | Syntactic | Semantic,\n}\n\nexport function injectViteIgnoreForHmrMetadata(code: string): string {\n let patched = code.replace(\n /\\bimport\\(([a-zA-Z_$][\\w$]*\\.\\u0275\\u0275getReplaceMetadataURL)/g,\n 'import(/* @vite-ignore */ $1',\n );\n\n if (patched === code) {\n patched = patched.replace(\n /import\\((\\S+getReplaceMetadataURL)/g,\n 'import(/* @vite-ignore */ $1',\n );\n }\n\n return patched;\n}\n\nexport function isIgnoredHmrFile(file: string): boolean {\n return file.endsWith('.tsbuildinfo');\n}\n\n/**\n * Convert Analog/Angular CLI-style file replacements into the flat record\n * expected by `AngularHostOptions.fileReplacements`.\n *\n * Only browser replacements (`{ replace, with }`) are converted. SSR-only\n * replacements (`{ replace, ssr }`) are left for the Vite runtime plugin to\n * handle — they should not be baked into the Angular compilation host because\n * that would apply them to both browser and server builds.\n *\n * Relative paths are resolved against `workspaceRoot` so that the host\n * receives the same absolute paths it would get from the Angular CLI.\n */\nexport function toAngularCompilationFileReplacements(\n replacements: FileReplacement[],\n workspaceRoot: string,\n): Record<string, string> | undefined {\n const mappedReplacements = replacements.flatMap((replacement) => {\n if (!('with' in replacement)) {\n return [];\n }\n\n return [\n [\n isAbsolute(replacement.replace)\n ? replacement.replace\n : resolve(workspaceRoot, replacement.replace),\n isAbsolute(replacement.with)\n ? replacement.with\n : resolve(workspaceRoot, replacement.with),\n ] as const,\n ];\n });\n\n return mappedReplacements.length\n ? Object.fromEntries(mappedReplacements)\n : undefined;\n}\n\n/**\n * Map Angular's `templateUpdates` (keyed by `encodedFilePath@ClassName`)\n * back to absolute file paths with their associated HMR code and component\n * class name.\n *\n * Angular's private Compilation API emits template update keys in the form\n * `encodeURIComponent(relativePath + '@' + className)`. We decode and resolve\n * them so the caller can look up updates by the same normalized absolute path\n * used elsewhere in the plugin (`outputFiles`, `classNames`, etc.).\n */\nexport function mapTemplateUpdatesToFiles(\n templateUpdates: ReadonlyMap<string, string> | undefined,\n): Map<\n string,\n {\n className: string;\n code: string;\n }\n> {\n const updatesByFile = new Map<string, { className: string; code: string }>();\n\n templateUpdates?.forEach((code, encodedUpdateId) => {\n const [file, className = ''] =\n decodeURIComponent(encodedUpdateId).split('@');\n const resolvedFile = normalizePath(resolve(process.cwd(), file));\n\n updatesByFile.set(resolvedFile, {\n className,\n code,\n });\n });\n\n return updatesByFile;\n}\n\nexport function describeStylesheetContent(code: string): {\n length: number;\n digest: string;\n preview: string;\n} {\n return {\n length: code.length,\n digest: createHash('sha256').update(code).digest('hex').slice(0, 12),\n preview: code.replace(/\\s+/g, ' ').trim().slice(0, 160),\n };\n}\n\n/**\n * Refreshes any already-served stylesheet records that map back to a changed\n * source file.\n *\n * This is the critical bridge for externalized Angular component styles during\n * HMR. Angular's resource watcher can notice that `/src/...component.css`\n * changed before Angular recompilation has had a chance to repopulate the\n * stylesheet registry. If we emit a CSS update against the existing virtual\n * stylesheet id without first refreshing the registry content, the browser gets\n * a hot update containing stale CSS. By rewriting the existing served records\n * from disk up front, HMR always pushes the latest source content.\n */\nexport function refreshStylesheetRegistryForFile(\n file: string,\n stylesheetRegistry?: AnalogStylesheetRegistry,\n stylePreprocessor?: StylePreprocessor,\n): void {\n const normalizedFile = normalizePath(file.split('?')[0]);\n if (!stylesheetRegistry || !existsSync(normalizedFile)) {\n return;\n }\n\n const publicIds = stylesheetRegistry.getPublicIdsForSource(normalizedFile);\n if (publicIds.length === 0) {\n return;\n }\n\n const rawCss = readFileSync(normalizedFile, 'utf-8');\n const preprocessed = preprocessStylesheetResult(\n rawCss,\n normalizedFile,\n stylePreprocessor,\n );\n const servedCss = rewriteRelativeCssImports(\n preprocessed.code,\n normalizedFile,\n );\n\n for (const publicId of publicIds) {\n stylesheetRegistry.registerServedStylesheet(\n {\n publicId,\n sourcePath: normalizedFile,\n originalCode: rawCss,\n normalizedCode: servedCss,\n dependencies: normalizeStylesheetDependencies(\n preprocessed.dependencies,\n ),\n diagnostics: preprocessed.diagnostics,\n tags: preprocessed.tags,\n },\n [\n normalizedFile,\n normalizePath(normalizedFile),\n basename(normalizedFile),\n normalizedFile.replace(/^\\//, ''),\n ],\n );\n }\n\n debugStylesV('stylesheet registry refreshed from source file', {\n file: normalizedFile,\n publicIds,\n dependencies: preprocessed.dependencies,\n diagnostics: preprocessed.diagnostics,\n tags: preprocessed.tags,\n source: describeStylesheetContent(rawCss),\n served: describeStylesheetContent(servedCss),\n });\n}\n\nexport function isTestWatchMode(args: string[] = process.argv): boolean {\n // vitest --run\n const hasRun = args.find((arg) => arg.includes('--run'));\n if (hasRun) {\n return false;\n }\n\n // vitest --no-run\n const hasNoRun = args.find((arg) => arg.includes('--no-run'));\n if (hasNoRun) {\n return true;\n }\n\n // check for --watch=false or --no-watch\n const hasWatch = args.find((arg) => arg.includes('watch'));\n if (hasWatch && ['false', 'no'].some((neg) => hasWatch.includes(neg))) {\n return false;\n }\n\n // check for --watch false\n const watchIndex = args.findIndex((arg) => arg.includes('watch'));\n const watchArg = args[watchIndex + 1];\n if (watchArg && watchArg === 'false') {\n return false;\n }\n\n return true;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ResolvedConfig } from "vite";
|
|
2
|
+
export interface TsconfigResolverOptions {
|
|
3
|
+
workspaceRoot: string;
|
|
4
|
+
include: string[];
|
|
5
|
+
liveReload: boolean;
|
|
6
|
+
hasTailwindCss: boolean;
|
|
7
|
+
isTest: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare class TsconfigResolver {
|
|
10
|
+
private options;
|
|
11
|
+
private includeCache;
|
|
12
|
+
private tsconfigOptionsCache;
|
|
13
|
+
private tsconfigGraphRootCache;
|
|
14
|
+
constructor(options: TsconfigResolverOptions);
|
|
15
|
+
invalidateIncludeCache(): void;
|
|
16
|
+
invalidateTsconfigCaches(): void;
|
|
17
|
+
invalidateAll(): void;
|
|
18
|
+
ensureIncludeCache(): string[];
|
|
19
|
+
readAngularTsconfigConfiguration(resolvedTsConfigPath: string, config: ResolvedConfig);
|
|
20
|
+
getCachedTsconfigOptions(resolvedTsConfigPath: string, config: ResolvedConfig): {
|
|
21
|
+
options: any;
|
|
22
|
+
rootNames: string[];
|
|
23
|
+
};
|
|
24
|
+
collectExpandedTsconfigRoots(resolvedTsConfigPath: string, config: ResolvedConfig, visited?): string[];
|
|
25
|
+
private getTsconfigCacheKey;
|
|
26
|
+
private findIncludes;
|
|
27
|
+
}
|
|
28
|
+
export declare function normalizeIncludeGlob(workspaceRoot: string, glob: string): string;
|