@analogjs/vite-plugin-angular 3.0.0-alpha.23 → 3.0.0-alpha.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -3
- package/src/lib/angular-jit-plugin.js +8 -2
- package/src/lib/angular-jit-plugin.js.map +1 -1
- package/src/lib/angular-vite-plugin.d.ts +60 -3
- package/src/lib/angular-vite-plugin.js +1046 -88
- package/src/lib/angular-vite-plugin.js.map +1 -1
- package/src/lib/component-resolvers.d.ts +16 -0
- package/src/lib/component-resolvers.js +77 -16
- package/src/lib/component-resolvers.js.map +1 -1
- package/src/lib/host.d.ts +2 -2
- package/src/lib/host.js +26 -16
- package/src/lib/host.js.map +1 -1
- package/src/lib/plugins/file-replacements.plugin.js +6 -1
- package/src/lib/plugins/file-replacements.plugin.js.map +1 -1
- package/src/lib/stylesheet-registry.d.ts +59 -0
- package/src/lib/stylesheet-registry.js +127 -0
- package/src/lib/stylesheet-registry.js.map +1 -0
- package/src/lib/utils/debug-harness.d.ts +23 -0
- package/src/lib/utils/debug-harness.js +88 -0
- package/src/lib/utils/debug-harness.js.map +1 -0
- package/src/lib/utils/debug-log-file.d.ts +5 -0
- package/src/lib/utils/debug-log-file.js +56 -0
- package/src/lib/utils/debug-log-file.js.map +1 -0
- package/src/lib/utils/debug.d.ts +15 -29
- package/src/lib/utils/debug.js +25 -66
- package/src/lib/utils/debug.js.map +1 -1
- package/src/lib/utils/devkit.d.ts +4 -4
- package/src/lib/utils/devkit.js.map +1 -1
|
@@ -1,7 +1,23 @@
|
|
|
1
|
+
export interface AngularComponentMetadata {
|
|
2
|
+
className: string;
|
|
3
|
+
selector?: string;
|
|
4
|
+
styleUrls: string[];
|
|
5
|
+
templateUrls: string[];
|
|
6
|
+
inlineTemplates: string[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Extract Angular component identities from raw source code before Angular's
|
|
10
|
+
* compilation pipeline strips decorators. This is used for dev-time
|
|
11
|
+
* diagnostics such as duplicate selectors, duplicate component class names,
|
|
12
|
+
* selectorless shared components, and inline-template validation.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getAngularComponentMetadata(code: string): AngularComponentMetadata[];
|
|
1
15
|
/** Extract all `styleUrl` / `styleUrls` values from Angular component source. */
|
|
2
16
|
export declare function getStyleUrls(code: string): string[];
|
|
3
17
|
/** Extract all `templateUrl` values from Angular component source. */
|
|
4
18
|
export declare function getTemplateUrls(code: string): string[];
|
|
19
|
+
/** Extract inline `template` strings from Angular component source. */
|
|
20
|
+
export declare function getInlineTemplates(code: string): string[];
|
|
5
21
|
export declare class StyleUrlsResolver {
|
|
6
22
|
private readonly styleUrlsCache;
|
|
7
23
|
resolve(code: string, id: string): string[];
|
|
@@ -34,27 +34,88 @@ function collectComponentUrls(code) {
|
|
|
34
34
|
const { program } = parseSync("cmp.ts", code);
|
|
35
35
|
const styleUrls = [];
|
|
36
36
|
const templateUrls = [];
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
37
|
+
const inlineTemplates = [];
|
|
38
|
+
new Visitor({ ClassDeclaration(node) {
|
|
39
|
+
const decorators = node.decorators ?? [];
|
|
40
|
+
for (const decorator of decorators) {
|
|
41
|
+
const expression = decorator.expression;
|
|
42
|
+
if (expression?.type !== "CallExpression" || expression.callee?.type !== "Identifier" || expression.callee.name !== "Component") continue;
|
|
43
|
+
const componentArg = expression.arguments?.[0];
|
|
44
|
+
if (componentArg?.type !== "ObjectExpression") continue;
|
|
45
|
+
for (const property of componentArg.properties ?? []) {
|
|
46
|
+
if (property?.type !== "Property" || property.key?.type !== "Identifier") continue;
|
|
47
|
+
const name = property.key.name;
|
|
48
|
+
if (name === "styleUrls" && property.value?.type === "ArrayExpression") for (const el of property.value.elements) {
|
|
49
|
+
const val = getStringValue(el);
|
|
50
|
+
if (val !== void 0) styleUrls.push(val);
|
|
51
|
+
}
|
|
52
|
+
if (name === "styleUrl") {
|
|
53
|
+
const val = getStringValue(property.value);
|
|
54
|
+
if (val !== void 0) styleUrls.push(val);
|
|
55
|
+
}
|
|
56
|
+
if (name === "templateUrl") {
|
|
57
|
+
const val = getStringValue(property.value);
|
|
58
|
+
if (val !== void 0) templateUrls.push(val);
|
|
59
|
+
}
|
|
60
|
+
if (name === "template") {
|
|
61
|
+
const val = getStringValue(property.value);
|
|
62
|
+
if (val !== void 0) inlineTemplates.push(val);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
51
65
|
}
|
|
52
66
|
} }).visit(program);
|
|
53
67
|
return {
|
|
54
68
|
styleUrls,
|
|
55
|
-
templateUrls
|
|
69
|
+
templateUrls,
|
|
70
|
+
inlineTemplates
|
|
56
71
|
};
|
|
57
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Extract Angular component identities from raw source code before Angular's
|
|
75
|
+
* compilation pipeline strips decorators. This is used for dev-time
|
|
76
|
+
* diagnostics such as duplicate selectors, duplicate component class names,
|
|
77
|
+
* selectorless shared components, and inline-template validation.
|
|
78
|
+
*/
|
|
79
|
+
function getAngularComponentMetadata(code) {
|
|
80
|
+
const { program } = parseSync("cmp.ts", code);
|
|
81
|
+
const components = [];
|
|
82
|
+
new Visitor({ ClassDeclaration(node) {
|
|
83
|
+
const decorators = node.decorators ?? [];
|
|
84
|
+
for (const decorator of decorators) {
|
|
85
|
+
const expression = decorator.expression;
|
|
86
|
+
if (expression?.type !== "CallExpression" || expression.callee?.type !== "Identifier" || expression.callee.name !== "Component") continue;
|
|
87
|
+
const componentArg = expression.arguments?.[0];
|
|
88
|
+
if (componentArg?.type !== "ObjectExpression") continue;
|
|
89
|
+
const metadata = {
|
|
90
|
+
className: node.id?.name ?? "(anonymous)",
|
|
91
|
+
styleUrls: [],
|
|
92
|
+
templateUrls: [],
|
|
93
|
+
inlineTemplates: []
|
|
94
|
+
};
|
|
95
|
+
for (const property of componentArg.properties ?? []) {
|
|
96
|
+
if (property?.type !== "Property" || property.key?.type !== "Identifier") continue;
|
|
97
|
+
const name = property.key.name;
|
|
98
|
+
if (name === "selector") metadata.selector = getStringValue(property.value);
|
|
99
|
+
else if (name === "styleUrl") {
|
|
100
|
+
const val = getStringValue(property.value);
|
|
101
|
+
if (val !== void 0) metadata.styleUrls.push(val);
|
|
102
|
+
} else if (name === "styleUrls" && property.value?.type === "ArrayExpression") for (const el of property.value.elements ?? []) {
|
|
103
|
+
const val = getStringValue(el);
|
|
104
|
+
if (val !== void 0) metadata.styleUrls.push(val);
|
|
105
|
+
}
|
|
106
|
+
else if (name === "templateUrl") {
|
|
107
|
+
const val = getStringValue(property.value);
|
|
108
|
+
if (val !== void 0) metadata.templateUrls.push(val);
|
|
109
|
+
} else if (name === "template") {
|
|
110
|
+
const val = getStringValue(property.value);
|
|
111
|
+
if (val !== void 0) metadata.inlineTemplates.push(val);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
components.push(metadata);
|
|
115
|
+
}
|
|
116
|
+
} }).visit(program);
|
|
117
|
+
return components;
|
|
118
|
+
}
|
|
58
119
|
/** Extract all `styleUrl` / `styleUrls` values from Angular component source. */
|
|
59
120
|
function getStyleUrls(code) {
|
|
60
121
|
return collectComponentUrls(code).styleUrls;
|
|
@@ -93,6 +154,6 @@ var TemplateUrlsResolver = class {
|
|
|
93
154
|
}
|
|
94
155
|
};
|
|
95
156
|
//#endregion
|
|
96
|
-
export { StyleUrlsResolver, TemplateUrlsResolver };
|
|
157
|
+
export { StyleUrlsResolver, TemplateUrlsResolver, getAngularComponentMetadata };
|
|
97
158
|
|
|
98
159
|
//# sourceMappingURL=component-resolvers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component-resolvers.js","names":[],"sources":["../../../src/lib/component-resolvers.ts"],"sourcesContent":["import { dirname, resolve } from 'node:path';\n// OXC parser (native Rust, NAPI-RS) replaces ts-morph for AST extraction.\n// It is ~10-50x faster for the narrow task of pulling property values from\n// Angular component decorators. The Visitor helper from Rolldown walks the\n// ESTree-compatible AST that OXC produces.\nimport { parseSync } from 'oxc-parser';\nimport { Visitor } from 'rolldown/utils';\nimport { normalizePath } from 'vite';\n\n// ---------------------------------------------------------------------------\n// AST helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Extracts a string value from an ESTree AST node.\n *\n * Handles three forms that Angular decorators may use:\n * - `Literal` with a string value → `'./foo.css'` / `\"./foo.css\"`\n * - `StringLiteral` (OXC-specific) → same representation\n * - `TemplateLiteral` with zero expressions → `` `./foo.css` ``\n *\n * Uses `any` because OXC's AST mixes standard ESTree nodes with\n * OXC-specific variants (e.g. `StringLiteral`), and the project's\n * tsconfig enforces `noPropertyAccessFromIndexSignature`.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction getStringValue(node: any): string | undefined {\n if (!node) return undefined;\n // Standard ESTree Literal (string value)\n if (node.type === 'Literal' && typeof node.value === 'string') {\n return node.value;\n }\n // OXC-specific StringLiteral node\n if (node.type === 'StringLiteral') {\n return node.value;\n }\n // Template literal with no interpolation (e.g., `./foo.css`)\n if (\n node.type === 'TemplateLiteral' &&\n node.expressions.length === 0 &&\n node.quasis.length === 1\n ) {\n return node.quasis[0].value.cooked ?? node.quasis[0].value.raw;\n }\n return undefined;\n}\n\n/**\n * Parses TypeScript/JS source with OXC and collects `styleUrl`, `styleUrls`,\n * and `templateUrl` property values from Angular `@Component()` decorators\n * in a single AST pass.\n *\n * This replaces the previous ts-morph implementation — OXC parses natively\n * via Rust NAPI bindings, avoiding the overhead of spinning up a full\n * TypeScript `Project` for each file.\n */\nfunction collectComponentUrls(code: string): {\n styleUrls: string[];\n templateUrls: string[];\n} {\n const { program } = parseSync('cmp.ts', code);\n const styleUrls: string[] = [];\n const templateUrls: string[] = [];\n\n const visitor = new Visitor({\n // The Visitor callback receives raw ESTree nodes. We use `any`\n // because OXC's AST includes non-standard node variants and the\n // project tsconfig enforces `noPropertyAccessFromIndexSignature`.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Property(node: any) {\n if (node.key?.type !== 'Identifier') return;\n const name: string = node.key.name;\n\n if (name === 'styleUrls' && node.value?.type === 'ArrayExpression') {\n for (const el of node.value.elements) {\n const val = getStringValue(el);\n if (val !== undefined) styleUrls.push(val);\n }\n }\n\n if (name === 'styleUrl') {\n const val = getStringValue(node.value);\n if (val !== undefined) styleUrls.push(val);\n }\n\n if (name === 'templateUrl') {\n const val = getStringValue(node.value);\n if (val !== undefined) templateUrls.push(val);\n }\n },\n });\n visitor.visit(program);\n\n return { styleUrls, templateUrls };\n}\n\n/** Extract all `styleUrl` / `styleUrls` values from Angular component source. */\nexport function getStyleUrls(code: string): string[] {\n return collectComponentUrls(code).styleUrls;\n}\n\n/** Extract all `templateUrl` values from Angular component source. */\nexport function getTemplateUrls(code: string): string[] {\n return collectComponentUrls(code).templateUrls;\n}\n\n// ---------------------------------------------------------------------------\n// Resolver caches\n// ---------------------------------------------------------------------------\n\ninterface StyleUrlsCacheEntry {\n matchedStyleUrls: string[];\n styleUrls: string[];\n}\n\nexport class StyleUrlsResolver {\n // These resolvers may be called multiple times during the same\n // compilation for the same files. Caching is required because these\n // resolvers use synchronous system calls to the filesystem, which can\n // degrade performance when running compilations for multiple files.\n private readonly styleUrlsCache = new Map<string, StyleUrlsCacheEntry>();\n\n resolve(code: string, id: string): string[] {\n const matchedStyleUrls = getStyleUrls(code);\n const entry = this.styleUrlsCache.get(id);\n // We're using `matchedStyleUrls` as a key because the code may be changing continuously,\n // resulting in the resolver being called multiple times. While the code changes, the\n // `styleUrls` may remain constant, which means we should always return the previously\n // resolved style URLs.\n if (entry && entry.matchedStyleUrls === matchedStyleUrls) {\n return entry.styleUrls;\n }\n\n const styleUrls = matchedStyleUrls.map((styleUrlPath) => {\n return `${styleUrlPath}|${normalizePath(\n resolve(dirname(id), styleUrlPath),\n )}`;\n });\n\n this.styleUrlsCache.set(id, { styleUrls, matchedStyleUrls });\n return styleUrls;\n }\n}\n\ninterface TemplateUrlsCacheEntry {\n code: string;\n templateUrlPaths: string[];\n}\n\nexport class TemplateUrlsResolver {\n private readonly templateUrlsCache = new Map<\n string,\n TemplateUrlsCacheEntry\n >();\n\n resolve(code: string, id: string): string[] {\n const entry = this.templateUrlsCache.get(id);\n if (entry?.code === code) {\n return entry.templateUrlPaths;\n }\n\n const templateUrlPaths = getTemplateUrls(code).map(\n (url) => `${url}|${normalizePath(resolve(dirname(id), url))}`,\n );\n\n this.templateUrlsCache.set(id, { code, templateUrlPaths });\n return templateUrlPaths;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA0BA,SAAS,eAAe,MAA+B;AACrD,KAAI,CAAC,KAAM,QAAO,KAAA;AAElB,KAAI,KAAK,SAAS,aAAa,OAAO,KAAK,UAAU,SACnD,QAAO,KAAK;AAGd,KAAI,KAAK,SAAS,gBAChB,QAAO,KAAK;AAGd,KACE,KAAK,SAAS,qBACd,KAAK,YAAY,WAAW,KAC5B,KAAK,OAAO,WAAW,EAEvB,QAAO,KAAK,OAAO,GAAG,MAAM,UAAU,KAAK,OAAO,GAAG,MAAM;;;;;;;;;;;AAc/D,SAAS,qBAAqB,MAG5B;CACA,MAAM,EAAE,YAAY,UAAU,UAAU,KAAK;CAC7C,MAAM,YAAsB,EAAE;CAC9B,MAAM,eAAyB,EAAE;AAEjB,KAAI,QAAQ,EAK1B,SAAS,MAAW;AAClB,MAAI,KAAK,KAAK,SAAS,aAAc;EACrC,MAAM,OAAe,KAAK,IAAI;AAE9B,MAAI,SAAS,eAAe,KAAK,OAAO,SAAS,kBAC/C,MAAK,MAAM,MAAM,KAAK,MAAM,UAAU;GACpC,MAAM,MAAM,eAAe,GAAG;AAC9B,OAAI,QAAQ,KAAA,EAAW,WAAU,KAAK,IAAI;;AAI9C,MAAI,SAAS,YAAY;GACvB,MAAM,MAAM,eAAe,KAAK,MAAM;AACtC,OAAI,QAAQ,KAAA,EAAW,WAAU,KAAK,IAAI;;AAG5C,MAAI,SAAS,eAAe;GAC1B,MAAM,MAAM,eAAe,KAAK,MAAM;AACtC,OAAI,QAAQ,KAAA,EAAW,cAAa,KAAK,IAAI;;IAGlD,CAAC,CACM,MAAM,QAAQ;AAEtB,QAAO;EAAE;EAAW;EAAc;;;AAIpC,SAAgB,aAAa,MAAwB;AACnD,QAAO,qBAAqB,KAAK,CAAC;;;AAIpC,SAAgB,gBAAgB,MAAwB;AACtD,QAAO,qBAAqB,KAAK,CAAC;;AAYpC,IAAa,oBAAb,MAA+B;CAK7B,iCAAkC,IAAI,KAAkC;CAExE,QAAQ,MAAc,IAAsB;EAC1C,MAAM,mBAAmB,aAAa,KAAK;EAC3C,MAAM,QAAQ,KAAK,eAAe,IAAI,GAAG;AAKzC,MAAI,SAAS,MAAM,qBAAqB,iBACtC,QAAO,MAAM;EAGf,MAAM,YAAY,iBAAiB,KAAK,iBAAiB;AACvD,UAAO,GAAG,aAAa,GAAG,cACxB,QAAQ,QAAQ,GAAG,EAAE,aAAa,CACnC;IACD;AAEF,OAAK,eAAe,IAAI,IAAI;GAAE;GAAW;GAAkB,CAAC;AAC5D,SAAO;;;AASX,IAAa,uBAAb,MAAkC;CAChC,oCAAqC,IAAI,KAGtC;CAEH,QAAQ,MAAc,IAAsB;EAC1C,MAAM,QAAQ,KAAK,kBAAkB,IAAI,GAAG;AAC5C,MAAI,OAAO,SAAS,KAClB,QAAO,MAAM;EAGf,MAAM,mBAAmB,gBAAgB,KAAK,CAAC,KAC5C,QAAQ,GAAG,IAAI,GAAG,cAAc,QAAQ,QAAQ,GAAG,EAAE,IAAI,CAAC,GAC5D;AAED,OAAK,kBAAkB,IAAI,IAAI;GAAE;GAAM;GAAkB,CAAC;AAC1D,SAAO"}
|
|
1
|
+
{"version":3,"file":"component-resolvers.js","names":[],"sources":["../../../src/lib/component-resolvers.ts"],"sourcesContent":["import { dirname, resolve } from 'node:path';\n// OXC parser (native Rust, NAPI-RS) replaces ts-morph for AST extraction.\n// It is ~10-50x faster for the narrow task of pulling property values from\n// Angular component decorators. The Visitor helper from Rolldown walks the\n// ESTree-compatible AST that OXC produces.\nimport { parseSync } from 'oxc-parser';\nimport { Visitor } from 'rolldown/utils';\nimport { normalizePath } from 'vite';\n\n// ---------------------------------------------------------------------------\n// AST helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Extracts a string value from an ESTree AST node.\n *\n * Handles three forms that Angular decorators may use:\n * - `Literal` with a string value → `'./foo.css'` / `\"./foo.css\"`\n * - `StringLiteral` (OXC-specific) → same representation\n * - `TemplateLiteral` with zero expressions → `` `./foo.css` ``\n *\n * Uses `any` because OXC's AST mixes standard ESTree nodes with\n * OXC-specific variants (e.g. `StringLiteral`), and the project's\n * tsconfig enforces `noPropertyAccessFromIndexSignature`.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction getStringValue(node: any): string | undefined {\n if (!node) return undefined;\n // Standard ESTree Literal (string value)\n if (node.type === 'Literal' && typeof node.value === 'string') {\n return node.value;\n }\n // OXC-specific StringLiteral node\n if (node.type === 'StringLiteral') {\n return node.value;\n }\n // Template literal with no interpolation (e.g., `./foo.css`)\n if (\n node.type === 'TemplateLiteral' &&\n node.expressions.length === 0 &&\n node.quasis.length === 1\n ) {\n return node.quasis[0].value.cooked ?? node.quasis[0].value.raw;\n }\n return undefined;\n}\n\n/**\n * Parses TypeScript/JS source with OXC and collects `styleUrl`, `styleUrls`,\n * and `templateUrl` property values from Angular `@Component()` decorators\n * in a single AST pass.\n *\n * This replaces the previous ts-morph implementation — OXC parses natively\n * via Rust NAPI bindings, avoiding the overhead of spinning up a full\n * TypeScript `Project` for each file.\n */\nfunction collectComponentUrls(code: string): {\n styleUrls: string[];\n templateUrls: string[];\n inlineTemplates: string[];\n} {\n const { program } = parseSync('cmp.ts', code);\n const styleUrls: string[] = [];\n const templateUrls: string[] = [];\n const inlineTemplates: string[] = [];\n\n const visitor = new Visitor({\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ClassDeclaration(node: any) {\n const decorators = node.decorators ?? [];\n for (const decorator of decorators) {\n const expression = decorator.expression;\n if (\n expression?.type !== 'CallExpression' ||\n expression.callee?.type !== 'Identifier' ||\n expression.callee.name !== 'Component'\n ) {\n continue;\n }\n\n const componentArg = expression.arguments?.[0];\n if (componentArg?.type !== 'ObjectExpression') {\n continue;\n }\n\n for (const property of componentArg.properties ?? []) {\n if (\n property?.type !== 'Property' ||\n property.key?.type !== 'Identifier'\n ) {\n continue;\n }\n\n const name = property.key.name;\n\n if (\n name === 'styleUrls' &&\n property.value?.type === 'ArrayExpression'\n ) {\n for (const el of property.value.elements) {\n const val = getStringValue(el);\n if (val !== undefined) styleUrls.push(val);\n }\n }\n\n if (name === 'styleUrl') {\n const val = getStringValue(property.value);\n if (val !== undefined) styleUrls.push(val);\n }\n\n if (name === 'templateUrl') {\n const val = getStringValue(property.value);\n if (val !== undefined) templateUrls.push(val);\n }\n\n if (name === 'template') {\n const val = getStringValue(property.value);\n if (val !== undefined) inlineTemplates.push(val);\n }\n }\n }\n },\n });\n visitor.visit(program);\n\n return { styleUrls, templateUrls, inlineTemplates };\n}\n\nexport interface AngularComponentMetadata {\n className: string;\n selector?: string;\n styleUrls: string[];\n templateUrls: string[];\n inlineTemplates: string[];\n}\n\n/**\n * Extract Angular component identities from raw source code before Angular's\n * compilation pipeline strips decorators. This is used for dev-time\n * diagnostics such as duplicate selectors, duplicate component class names,\n * selectorless shared components, and inline-template validation.\n */\nexport function getAngularComponentMetadata(\n code: string,\n): AngularComponentMetadata[] {\n const { program } = parseSync('cmp.ts', code);\n const components: AngularComponentMetadata[] = [];\n\n const visitor = new Visitor({\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ClassDeclaration(node: any) {\n const decorators = node.decorators ?? [];\n for (const decorator of decorators) {\n const expression = decorator.expression;\n if (\n expression?.type !== 'CallExpression' ||\n expression.callee?.type !== 'Identifier' ||\n expression.callee.name !== 'Component'\n ) {\n continue;\n }\n\n const componentArg = expression.arguments?.[0];\n if (componentArg?.type !== 'ObjectExpression') {\n continue;\n }\n\n const metadata: AngularComponentMetadata = {\n className: node.id?.name ?? '(anonymous)',\n styleUrls: [],\n templateUrls: [],\n inlineTemplates: [],\n };\n\n for (const property of componentArg.properties ?? []) {\n if (\n property?.type !== 'Property' ||\n property.key?.type !== 'Identifier'\n ) {\n continue;\n }\n\n const name = property.key.name;\n if (name === 'selector') {\n metadata.selector = getStringValue(property.value);\n } else if (name === 'styleUrl') {\n const val = getStringValue(property.value);\n if (val !== undefined) {\n metadata.styleUrls.push(val);\n }\n } else if (\n name === 'styleUrls' &&\n property.value?.type === 'ArrayExpression'\n ) {\n for (const el of property.value.elements ?? []) {\n const val = getStringValue(el);\n if (val !== undefined) {\n metadata.styleUrls.push(val);\n }\n }\n } else if (name === 'templateUrl') {\n const val = getStringValue(property.value);\n if (val !== undefined) {\n metadata.templateUrls.push(val);\n }\n } else if (name === 'template') {\n const val = getStringValue(property.value);\n if (val !== undefined) {\n metadata.inlineTemplates.push(val);\n }\n }\n }\n\n components.push(metadata);\n }\n },\n });\n visitor.visit(program);\n\n return components;\n}\n\n/** Extract all `styleUrl` / `styleUrls` values from Angular component source. */\nexport function getStyleUrls(code: string): string[] {\n return collectComponentUrls(code).styleUrls;\n}\n\n/** Extract all `templateUrl` values from Angular component source. */\nexport function getTemplateUrls(code: string): string[] {\n return collectComponentUrls(code).templateUrls;\n}\n\n/** Extract inline `template` strings from Angular component source. */\nexport function getInlineTemplates(code: string): string[] {\n return collectComponentUrls(code).inlineTemplates;\n}\n\n// ---------------------------------------------------------------------------\n// Resolver caches\n// ---------------------------------------------------------------------------\n\ninterface StyleUrlsCacheEntry {\n matchedStyleUrls: string[];\n styleUrls: string[];\n}\n\nexport class StyleUrlsResolver {\n // These resolvers may be called multiple times during the same\n // compilation for the same files. Caching is required because these\n // resolvers use synchronous system calls to the filesystem, which can\n // degrade performance when running compilations for multiple files.\n private readonly styleUrlsCache = new Map<string, StyleUrlsCacheEntry>();\n\n resolve(code: string, id: string): string[] {\n const matchedStyleUrls = getStyleUrls(code);\n const entry = this.styleUrlsCache.get(id);\n // We're using `matchedStyleUrls` as a key because the code may be changing continuously,\n // resulting in the resolver being called multiple times. While the code changes, the\n // `styleUrls` may remain constant, which means we should always return the previously\n // resolved style URLs.\n if (entry && entry.matchedStyleUrls === matchedStyleUrls) {\n return entry.styleUrls;\n }\n\n const styleUrls = matchedStyleUrls.map((styleUrlPath) => {\n return `${styleUrlPath}|${normalizePath(\n resolve(dirname(id), styleUrlPath),\n )}`;\n });\n\n this.styleUrlsCache.set(id, { styleUrls, matchedStyleUrls });\n return styleUrls;\n }\n}\n\ninterface TemplateUrlsCacheEntry {\n code: string;\n templateUrlPaths: string[];\n}\n\nexport class TemplateUrlsResolver {\n private readonly templateUrlsCache = new Map<\n string,\n TemplateUrlsCacheEntry\n >();\n\n resolve(code: string, id: string): string[] {\n const entry = this.templateUrlsCache.get(id);\n if (entry?.code === code) {\n return entry.templateUrlPaths;\n }\n\n const templateUrlPaths = getTemplateUrls(code).map(\n (url) => `${url}|${normalizePath(resolve(dirname(id), url))}`,\n );\n\n this.templateUrlsCache.set(id, { code, templateUrlPaths });\n return templateUrlPaths;\n }\n}\n"],"mappings":"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"}
|
package/src/lib/host.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import * as ts from "typescript";
|
|
2
2
|
import type { StylePreprocessor } from "./style-preprocessor.js";
|
|
3
|
+
import { AnalogStylesheetRegistry } from "./stylesheet-registry.js";
|
|
3
4
|
import type { SourceFileCache } from "./utils/source-file-cache.js";
|
|
4
5
|
export declare function augmentHostWithResources(host: ts.CompilerHost, transform: (code: string, id: string, options?: {
|
|
5
6
|
ssr?: boolean;
|
|
6
7
|
}) => ReturnType<any> | null, options: {
|
|
7
8
|
inlineStylesExtension: string;
|
|
8
9
|
isProd?: boolean;
|
|
9
|
-
|
|
10
|
-
externalComponentStyles?: Map<string, string>;
|
|
10
|
+
stylesheetRegistry?: AnalogStylesheetRegistry;
|
|
11
11
|
sourceFileCache?: SourceFileCache;
|
|
12
12
|
stylePreprocessor?: StylePreprocessor;
|
|
13
13
|
}): void;
|
package/src/lib/host.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { debugStyles } from "./utils/debug.js";
|
|
2
|
+
import { preprocessStylesheet, registerStylesheetContent } from "./stylesheet-registry.js";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
2
4
|
import path from "node:path";
|
|
3
5
|
import { normalizePath } from "vite";
|
|
4
|
-
import { createHash } from "node:crypto";
|
|
5
6
|
//#region packages/vite-plugin-angular/src/lib/host.ts
|
|
6
7
|
function augmentHostWithResources(host, transform, options) {
|
|
7
8
|
const resourceHost = host;
|
|
@@ -17,17 +18,23 @@ function augmentHostWithResources(host, transform, options) {
|
|
|
17
18
|
resourceHost.transformResource = async function(data, context) {
|
|
18
19
|
if (context.type !== "style") return null;
|
|
19
20
|
const filename = context.resourceFile ?? context.containingFile.replace(".ts", `.${options?.inlineStylesExtension}`);
|
|
20
|
-
const preprocessedData =
|
|
21
|
-
if (options.
|
|
22
|
-
const stylesheetId =
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
const preprocessedData = preprocessStylesheet(data, filename, options.stylePreprocessor);
|
|
22
|
+
if (options.stylesheetRegistry) {
|
|
23
|
+
const stylesheetId = registerStylesheetContent(options.stylesheetRegistry, {
|
|
24
|
+
code: preprocessedData,
|
|
25
|
+
containingFile: context.containingFile,
|
|
26
|
+
className: context.className,
|
|
27
|
+
order: context.order,
|
|
28
|
+
inlineStylesExtension: options.inlineStylesExtension,
|
|
29
|
+
resourceFile: context.resourceFile ?? void 0
|
|
30
|
+
});
|
|
31
|
+
debugStyles("NgtscProgram: stylesheet deferred to Vite pipeline", {
|
|
25
32
|
stylesheetId,
|
|
26
33
|
resourceFile: context.resourceFile ?? "(inline)"
|
|
27
34
|
});
|
|
28
35
|
return { content: stylesheetId };
|
|
29
36
|
}
|
|
30
|
-
debugStyles("NgtscProgram: stylesheet processed inline via transform
|
|
37
|
+
debugStyles("NgtscProgram: stylesheet processed inline via transform", {
|
|
31
38
|
filename,
|
|
32
39
|
resourceFile: context.resourceFile ?? "(inline)",
|
|
33
40
|
dataLength: preprocessedData.length
|
|
@@ -36,17 +43,20 @@ function augmentHostWithResources(host, transform, options) {
|
|
|
36
43
|
try {
|
|
37
44
|
stylesheetResult = await transform(preprocessedData, `${filename}?direct`);
|
|
38
45
|
} catch (e) {
|
|
39
|
-
|
|
46
|
+
debugStyles("NgtscProgram: stylesheet transform error", {
|
|
47
|
+
filename,
|
|
48
|
+
resourceFile: context.resourceFile ?? "(inline)",
|
|
49
|
+
error: String(e)
|
|
50
|
+
});
|
|
40
51
|
}
|
|
41
|
-
|
|
52
|
+
if (!stylesheetResult?.code) return null;
|
|
53
|
+
return { content: stylesheetResult.code };
|
|
42
54
|
};
|
|
43
|
-
resourceHost.resourceNameToFileName = function(resourceName, containingFile) {
|
|
44
|
-
const resolvedPath = path.join(path.dirname(containingFile), resourceName);
|
|
45
|
-
if (!options.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const filename = externalId + path.extname(resolvedPath);
|
|
49
|
-
options.externalComponentStyles.set(filename, resolvedPath);
|
|
55
|
+
resourceHost.resourceNameToFileName = function(resourceName, containingFile, fallbackResolve) {
|
|
56
|
+
const resolvedPath = normalizePath(fallbackResolve ? fallbackResolve(path.dirname(containingFile), resourceName) : path.join(path.dirname(containingFile), resourceName));
|
|
57
|
+
if (!options.stylesheetRegistry || !hasStyleExtension(resolvedPath)) return resolvedPath;
|
|
58
|
+
const filename = createHash("sha256").update(resolvedPath).digest("hex") + path.extname(resolvedPath);
|
|
59
|
+
options.stylesheetRegistry.registerExternalRequest(filename, resolvedPath);
|
|
50
60
|
debugStyles("NgtscProgram: external stylesheet ID mapped for resolveId", {
|
|
51
61
|
resourceName,
|
|
52
62
|
resolvedPath,
|
package/src/lib/host.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.js","names":[],"sources":["../../../src/lib/host.ts"],"sourcesContent":["import type { CompilerHost } from '@angular/compiler-cli';\nimport { normalizePath } from 'vite';\n\nimport * as ts from 'typescript';\n\nimport { createHash } from 'node:crypto';\nimport path from 'node:path';\nimport type { StylePreprocessor } from './style-preprocessor.js';\nimport { debugStyles } from './utils/debug.js';\nimport type { SourceFileCache } from './utils/source-file-cache.js';\n\nexport function augmentHostWithResources(\n host: ts.CompilerHost,\n transform: (\n code: string,\n id: string,\n options?: { ssr?: boolean },\n ) => ReturnType<any> | null,\n options: {\n inlineStylesExtension: string;\n isProd?: boolean;\n
|
|
1
|
+
{"version":3,"file":"host.js","names":[],"sources":["../../../src/lib/host.ts"],"sourcesContent":["import type { CompilerHost } from '@angular/compiler-cli';\nimport { normalizePath } from 'vite';\n\nimport * as ts from 'typescript';\n\nimport { createHash } from 'node:crypto';\nimport path from 'node:path';\nimport type { StylePreprocessor } from './style-preprocessor.js';\nimport {\n AnalogStylesheetRegistry,\n preprocessStylesheet,\n registerStylesheetContent,\n} from './stylesheet-registry.js';\nimport { debugStyles } from './utils/debug.js';\nimport type { SourceFileCache } from './utils/source-file-cache.js';\n\nexport function augmentHostWithResources(\n host: ts.CompilerHost,\n transform: (\n code: string,\n id: string,\n options?: { ssr?: boolean },\n ) => ReturnType<any> | null,\n options: {\n inlineStylesExtension: string;\n isProd?: boolean;\n stylesheetRegistry?: AnalogStylesheetRegistry;\n sourceFileCache?: SourceFileCache;\n stylePreprocessor?: StylePreprocessor;\n },\n): void {\n const resourceHost = host as CompilerHost;\n\n resourceHost.readResource = async function (fileName: string) {\n const filePath = normalizePath(fileName);\n\n const content = (this as any).readFile(filePath);\n\n if (content === undefined) {\n throw new Error('Unable to locate component resource: ' + fileName);\n }\n\n return content;\n };\n\n resourceHost.getModifiedResourceFiles = function () {\n return options?.sourceFileCache?.modifiedFiles;\n };\n\n resourceHost.transformResource = async function (data, context) {\n // Only style resources are supported currently\n if (context.type !== 'style') {\n return null;\n }\n\n const filename =\n context.resourceFile ??\n context.containingFile.replace(\n '.ts',\n `.${options?.inlineStylesExtension}`,\n );\n const preprocessedData = preprocessStylesheet(\n data,\n filename,\n options.stylePreprocessor,\n );\n\n // Externalized path: store preprocessed CSS for Vite's serve-time pipeline.\n // CSS must NOT be transformed here — the load hook returns it into\n // Vite's transform pipeline where PostCSS / Tailwind process it once.\n if (options.stylesheetRegistry) {\n const stylesheetId = registerStylesheetContent(\n options.stylesheetRegistry,\n {\n code: preprocessedData,\n containingFile: context.containingFile,\n className: context.className,\n order: context.order,\n inlineStylesExtension: options.inlineStylesExtension,\n resourceFile: context.resourceFile ?? undefined,\n },\n );\n debugStyles('NgtscProgram: stylesheet deferred to Vite pipeline', {\n stylesheetId,\n resourceFile: context.resourceFile ?? '(inline)',\n });\n return { content: stylesheetId };\n }\n\n // Non-externalized: CSS is returned directly to the Angular compiler\n // and never re-enters Vite's pipeline, so transform eagerly.\n debugStyles('NgtscProgram: stylesheet processed inline via transform', {\n filename,\n resourceFile: context.resourceFile ?? '(inline)',\n dataLength: preprocessedData.length,\n });\n let stylesheetResult;\n\n try {\n stylesheetResult = await transform(\n preprocessedData,\n `${filename}?direct`,\n );\n } catch (e) {\n debugStyles('NgtscProgram: stylesheet transform error', {\n filename,\n resourceFile: context.resourceFile ?? '(inline)',\n error: String(e),\n });\n }\n\n if (!stylesheetResult?.code) {\n return null;\n }\n\n return { content: stylesheetResult.code };\n };\n\n resourceHost.resourceNameToFileName = function (\n resourceName,\n containingFile,\n fallbackResolve,\n ) {\n const resolvedPath = normalizePath(\n fallbackResolve\n ? fallbackResolve(path.dirname(containingFile), resourceName)\n : path.join(path.dirname(containingFile), resourceName),\n );\n\n // All resource names that have template file extensions are assumed to be templates\n if (!options.stylesheetRegistry || !hasStyleExtension(resolvedPath)) {\n return resolvedPath;\n }\n\n // For external stylesheets, create a unique identifier and store the mapping\n const externalId = createHash('sha256').update(resolvedPath).digest('hex');\n const filename = externalId + path.extname(resolvedPath);\n\n options.stylesheetRegistry.registerExternalRequest(filename, resolvedPath);\n debugStyles('NgtscProgram: external stylesheet ID mapped for resolveId', {\n resourceName,\n resolvedPath,\n filename,\n });\n\n return filename;\n };\n}\n\nexport function augmentProgramWithVersioning(program: ts.Program): void {\n const baseGetSourceFiles = program.getSourceFiles;\n program.getSourceFiles = function (...parameters) {\n const files: readonly (ts.SourceFile & { version?: string })[] =\n baseGetSourceFiles(...parameters);\n\n for (const file of files) {\n file.version ??= createHash('sha256').update(file.text).digest('hex');\n }\n\n return files;\n };\n}\n\nexport function augmentHostWithCaching(\n host: ts.CompilerHost,\n cache: Map<string, ts.SourceFile>,\n): void {\n const baseGetSourceFile = host.getSourceFile;\n host.getSourceFile = function (\n fileName,\n languageVersion,\n onError,\n shouldCreateNewSourceFile,\n ...parameters\n ) {\n if (!shouldCreateNewSourceFile && cache.has(fileName)) {\n return cache.get(fileName);\n }\n\n const file = baseGetSourceFile.call(\n host,\n fileName,\n languageVersion,\n onError,\n true,\n ...parameters,\n );\n\n if (file) {\n cache.set(fileName, file);\n }\n\n return file;\n };\n}\n\nexport function mergeTransformers(\n first: ts.CustomTransformers,\n second: ts.CustomTransformers,\n): ts.CustomTransformers {\n const result: ts.CustomTransformers = {};\n\n if (first.before || second.before) {\n result.before = [...(first.before || []), ...(second.before || [])];\n }\n\n if (first.after || second.after) {\n result.after = [...(first.after || []), ...(second.after || [])];\n }\n\n if (first.afterDeclarations || second.afterDeclarations) {\n result.afterDeclarations = [\n ...(first.afterDeclarations || []),\n ...(second.afterDeclarations || []),\n ];\n }\n\n return result;\n}\n\nfunction hasStyleExtension(file: string): boolean {\n const extension = path.extname(file).toLowerCase();\n\n switch (extension) {\n case '.css':\n case '.scss':\n return true;\n default:\n return false;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { debugCompiler } from "../utils/debug.js";
|
|
1
2
|
import { isAbsolute, resolve } from "node:path";
|
|
2
3
|
//#region packages/vite-plugin-angular/src/lib/plugins/file-replacements.plugin.ts
|
|
3
4
|
function replaceFiles(replacements, workspaceRoot) {
|
|
@@ -24,7 +25,11 @@ function replaceFiles(replacements, workspaceRoot) {
|
|
|
24
25
|
else if (foundReplace.ssr) return null;
|
|
25
26
|
return { id: foundReplace.with };
|
|
26
27
|
} catch (err) {
|
|
27
|
-
|
|
28
|
+
debugCompiler("file replacement error", {
|
|
29
|
+
error: String(err),
|
|
30
|
+
source,
|
|
31
|
+
importer
|
|
32
|
+
});
|
|
28
33
|
return null;
|
|
29
34
|
}
|
|
30
35
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-replacements.plugin.js","names":[],"sources":["../../../../src/lib/plugins/file-replacements.plugin.ts"],"sourcesContent":["// source: https://github.com/Myrmod/vitejs-theming/blob/master/build-plugins/rollup/replace-files.js\nimport { isAbsolute, resolve } from 'node:path';\nimport { Plugin } from 'vite';\n\nexport function replaceFiles(\n replacements: FileReplacement[],\n workspaceRoot: string,\n): Plugin | false {\n if (!replacements?.length) {\n return false;\n }\n\n return {\n name: 'rollup-plugin-replace-files',\n enforce: 'pre',\n async resolveId(source, importer, options) {\n const resolved = await this.resolve(source, importer, {\n ...options,\n skipSelf: true,\n });\n /**\n * The reason we're using endsWith here is because the resolved id\n * will be the absolute path to the file. We want to check if the\n * file ends with the file we're trying to replace, which will be essentially\n * the path from the root of our workspace.\n */\n const mappedReplacements = replacements.map((fr: FileReplacement) => {\n const frSSR = fr as FileReplacementSSR;\n const frWith = fr as FileReplacementWith;\n\n return {\n ...fr,\n ssr: frSSR.ssr\n ? isAbsolute(frSSR.ssr)\n ? frSSR.ssr\n : resolve(workspaceRoot, frSSR.ssr)\n : '',\n with: frWith.with\n ? isAbsolute(frWith.with)\n ? frWith.with\n : resolve(workspaceRoot, frWith.with)\n : '',\n };\n });\n const foundReplace = mappedReplacements.find((replacement) =>\n resolved?.id?.endsWith(replacement.replace),\n );\n if (foundReplace) {\n try {\n if (this.environment.name === 'ssr' && foundReplace.ssr) {\n // return new file id for ssr\n return {\n id: foundReplace.ssr,\n };\n } else if (foundReplace.ssr) {\n return null;\n }\n\n // return new file id\n return {\n id: foundReplace.with,\n };\n } catch (err) {\n
|
|
1
|
+
{"version":3,"file":"file-replacements.plugin.js","names":[],"sources":["../../../../src/lib/plugins/file-replacements.plugin.ts"],"sourcesContent":["// source: https://github.com/Myrmod/vitejs-theming/blob/master/build-plugins/rollup/replace-files.js\nimport { isAbsolute, resolve } from 'node:path';\nimport { Plugin } from 'vite';\nimport { debugCompiler } from '../utils/debug.js';\n\nexport function replaceFiles(\n replacements: FileReplacement[],\n workspaceRoot: string,\n): Plugin | false {\n if (!replacements?.length) {\n return false;\n }\n\n return {\n name: 'rollup-plugin-replace-files',\n enforce: 'pre',\n async resolveId(source, importer, options) {\n const resolved = await this.resolve(source, importer, {\n ...options,\n skipSelf: true,\n });\n /**\n * The reason we're using endsWith here is because the resolved id\n * will be the absolute path to the file. We want to check if the\n * file ends with the file we're trying to replace, which will be essentially\n * the path from the root of our workspace.\n */\n const mappedReplacements = replacements.map((fr: FileReplacement) => {\n const frSSR = fr as FileReplacementSSR;\n const frWith = fr as FileReplacementWith;\n\n return {\n ...fr,\n ssr: frSSR.ssr\n ? isAbsolute(frSSR.ssr)\n ? frSSR.ssr\n : resolve(workspaceRoot, frSSR.ssr)\n : '',\n with: frWith.with\n ? isAbsolute(frWith.with)\n ? frWith.with\n : resolve(workspaceRoot, frWith.with)\n : '',\n };\n });\n const foundReplace = mappedReplacements.find((replacement) =>\n resolved?.id?.endsWith(replacement.replace),\n );\n if (foundReplace) {\n try {\n if (this.environment.name === 'ssr' && foundReplace.ssr) {\n // return new file id for ssr\n return {\n id: foundReplace.ssr,\n };\n } else if (foundReplace.ssr) {\n return null;\n }\n\n // return new file id\n return {\n id: foundReplace.with,\n };\n } catch (err) {\n debugCompiler('file replacement error', {\n error: String(err),\n source,\n importer,\n });\n return null;\n }\n }\n return null;\n },\n };\n}\n\nexport type FileReplacement = FileReplacementWith | FileReplacementSSR;\n\nexport interface FileReplacementBase {\n replace: string;\n}\nexport interface FileReplacementWith extends FileReplacementBase {\n with: string;\n}\n\nexport interface FileReplacementSSR extends FileReplacementBase {\n ssr: string;\n}\n"],"mappings":";;;AAKA,SAAgB,aACd,cACA,eACgB;AAChB,KAAI,CAAC,cAAc,OACjB,QAAO;AAGT,QAAO;EACL,MAAM;EACN,SAAS;EACT,MAAM,UAAU,QAAQ,UAAU,SAAS;GACzC,MAAM,WAAW,MAAM,KAAK,QAAQ,QAAQ,UAAU;IACpD,GAAG;IACH,UAAU;IACX,CAAC;GAyBF,MAAM,eAlBqB,aAAa,KAAK,OAAwB;IACnE,MAAM,QAAQ;IACd,MAAM,SAAS;AAEf,WAAO;KACL,GAAG;KACH,KAAK,MAAM,MACP,WAAW,MAAM,IAAI,GACnB,MAAM,MACN,QAAQ,eAAe,MAAM,IAAI,GACnC;KACJ,MAAM,OAAO,OACT,WAAW,OAAO,KAAK,GACrB,OAAO,OACP,QAAQ,eAAe,OAAO,KAAK,GACrC;KACL;KACD,CACsC,MAAM,gBAC5C,UAAU,IAAI,SAAS,YAAY,QAAQ,CAC5C;AACD,OAAI,aACF,KAAI;AACF,QAAI,KAAK,YAAY,SAAS,SAAS,aAAa,IAElD,QAAO,EACL,IAAI,aAAa,KAClB;aACQ,aAAa,IACtB,QAAO;AAIT,WAAO,EACL,IAAI,aAAa,MAClB;YACM,KAAK;AACZ,kBAAc,0BAA0B;KACtC,OAAO,OAAO,IAAI;KAClB;KACA;KACD,CAAC;AACF,WAAO;;AAGX,UAAO;;EAEV"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { StylePreprocessor } from "./style-preprocessor.js";
|
|
2
|
+
export interface AnalogStylesheetRecord {
|
|
3
|
+
publicId: string;
|
|
4
|
+
sourcePath?: string;
|
|
5
|
+
originalCode?: string;
|
|
6
|
+
normalizedCode: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class AnalogStylesheetRegistry {
|
|
9
|
+
private servedById;
|
|
10
|
+
private servedAliasToId;
|
|
11
|
+
private externalRequestToSource;
|
|
12
|
+
/**
|
|
13
|
+
* Maps a real source stylesheet path back to the generated public stylesheet
|
|
14
|
+
* ids Analog serves for Angular. This is stable across requests and lets HMR
|
|
15
|
+
* reason about "which virtual stylesheet came from this source file?"
|
|
16
|
+
*/
|
|
17
|
+
private sourceToPublicIds;
|
|
18
|
+
/**
|
|
19
|
+
* Tracks the live request ids Vite/Angular have actually served for a source
|
|
20
|
+
* stylesheet, including both `?direct&ngcomp=...` CSS modules and
|
|
21
|
+
* `?ngcomp=...` JS wrapper modules. HMR must use these live request ids
|
|
22
|
+
* because Angular component styles are no longer addressed by their original
|
|
23
|
+
* file paths once externalized.
|
|
24
|
+
*/
|
|
25
|
+
private sourceToRequestIds;
|
|
26
|
+
/**
|
|
27
|
+
* Canonicalizes browser-facing stylesheet request ids so Vite timestamp
|
|
28
|
+
* variants (`?t=...`) and path-shape variants (`abc.css?...` vs
|
|
29
|
+
* `/abc.css?...`) all collapse onto one logical module identity.
|
|
30
|
+
*
|
|
31
|
+
* This is critical for Angular component stylesheet HMR because the browser
|
|
32
|
+
* can keep both timestamped and non-timestamped requests alive for the same
|
|
33
|
+
* externalized stylesheet. If Analog tracks them as distinct resources, HMR
|
|
34
|
+
* can update one module while the browser continues rendering another stale
|
|
35
|
+
* module for the same public stylesheet id.
|
|
36
|
+
*/
|
|
37
|
+
private normalizeRequestId;
|
|
38
|
+
get servedCount(): number;
|
|
39
|
+
get externalCount(): number;
|
|
40
|
+
hasServed(requestId: string): boolean;
|
|
41
|
+
getServedContent(requestId: string): string | undefined;
|
|
42
|
+
resolveExternalSource(requestId: string): string | undefined;
|
|
43
|
+
getPublicIdsForSource(sourcePath: string): string[];
|
|
44
|
+
getRequestIdsForSource(sourcePath: string): string[];
|
|
45
|
+
registerExternalRequest(requestId: string, sourcePath: string): void;
|
|
46
|
+
registerActiveRequest(requestId: string): void;
|
|
47
|
+
registerServedStylesheet(record: AnalogStylesheetRecord, aliases?: string[]): void;
|
|
48
|
+
private resolveServedRecord;
|
|
49
|
+
}
|
|
50
|
+
export declare function preprocessStylesheet(code: string, filename: string, stylePreprocessor?: StylePreprocessor): string;
|
|
51
|
+
export declare function rewriteRelativeCssImports(code: string, filename: string): string;
|
|
52
|
+
export declare function registerStylesheetContent(registry: AnalogStylesheetRegistry, { code, containingFile, className, order, inlineStylesExtension, resourceFile }: {
|
|
53
|
+
code: string;
|
|
54
|
+
containingFile: string;
|
|
55
|
+
className?: string;
|
|
56
|
+
order?: number;
|
|
57
|
+
inlineStylesExtension: string;
|
|
58
|
+
resourceFile?: string;
|
|
59
|
+
}): string;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { dirname, normalize, resolve } from "node:path";
|
|
3
|
+
import { normalizePath } from "vite";
|
|
4
|
+
//#region packages/vite-plugin-angular/src/lib/stylesheet-registry.ts
|
|
5
|
+
var AnalogStylesheetRegistry = class {
|
|
6
|
+
servedById = /* @__PURE__ */ new Map();
|
|
7
|
+
servedAliasToId = /* @__PURE__ */ new Map();
|
|
8
|
+
externalRequestToSource = /* @__PURE__ */ new Map();
|
|
9
|
+
/**
|
|
10
|
+
* Maps a real source stylesheet path back to the generated public stylesheet
|
|
11
|
+
* ids Analog serves for Angular. This is stable across requests and lets HMR
|
|
12
|
+
* reason about "which virtual stylesheet came from this source file?"
|
|
13
|
+
*/
|
|
14
|
+
sourceToPublicIds = /* @__PURE__ */ new Map();
|
|
15
|
+
/**
|
|
16
|
+
* Tracks the live request ids Vite/Angular have actually served for a source
|
|
17
|
+
* stylesheet, including both `?direct&ngcomp=...` CSS modules and
|
|
18
|
+
* `?ngcomp=...` JS wrapper modules. HMR must use these live request ids
|
|
19
|
+
* because Angular component styles are no longer addressed by their original
|
|
20
|
+
* file paths once externalized.
|
|
21
|
+
*/
|
|
22
|
+
sourceToRequestIds = /* @__PURE__ */ new Map();
|
|
23
|
+
/**
|
|
24
|
+
* Canonicalizes browser-facing stylesheet request ids so Vite timestamp
|
|
25
|
+
* variants (`?t=...`) and path-shape variants (`abc.css?...` vs
|
|
26
|
+
* `/abc.css?...`) all collapse onto one logical module identity.
|
|
27
|
+
*
|
|
28
|
+
* This is critical for Angular component stylesheet HMR because the browser
|
|
29
|
+
* can keep both timestamped and non-timestamped requests alive for the same
|
|
30
|
+
* externalized stylesheet. If Analog tracks them as distinct resources, HMR
|
|
31
|
+
* can update one module while the browser continues rendering another stale
|
|
32
|
+
* module for the same public stylesheet id.
|
|
33
|
+
*/
|
|
34
|
+
normalizeRequestId(requestId) {
|
|
35
|
+
const [rawPathname, rawSearch = ""] = requestId.split("?");
|
|
36
|
+
const normalizedPathname = rawPathname.replace(/^\//, "");
|
|
37
|
+
if (!rawSearch) return normalizedPathname;
|
|
38
|
+
const normalizedSearch = rawSearch.split("&").filter((segment) => segment.length > 0).filter((segment) => {
|
|
39
|
+
const [key] = segment.split("=");
|
|
40
|
+
return key !== "t";
|
|
41
|
+
}).join("&");
|
|
42
|
+
return normalizedSearch ? `${normalizedPathname}?${normalizedSearch}` : normalizedPathname;
|
|
43
|
+
}
|
|
44
|
+
get servedCount() {
|
|
45
|
+
return this.servedById.size;
|
|
46
|
+
}
|
|
47
|
+
get externalCount() {
|
|
48
|
+
return this.externalRequestToSource.size;
|
|
49
|
+
}
|
|
50
|
+
hasServed(requestId) {
|
|
51
|
+
return this.resolveServedRecord(requestId) !== void 0;
|
|
52
|
+
}
|
|
53
|
+
getServedContent(requestId) {
|
|
54
|
+
return this.resolveServedRecord(requestId)?.normalizedCode;
|
|
55
|
+
}
|
|
56
|
+
resolveExternalSource(requestId) {
|
|
57
|
+
const normalizedRequestId = this.normalizeRequestId(requestId);
|
|
58
|
+
return this.externalRequestToSource.get(normalizedRequestId);
|
|
59
|
+
}
|
|
60
|
+
getPublicIdsForSource(sourcePath) {
|
|
61
|
+
return [...this.sourceToPublicIds.get(sourcePath) ?? []];
|
|
62
|
+
}
|
|
63
|
+
getRequestIdsForSource(sourcePath) {
|
|
64
|
+
return [...this.sourceToRequestIds.get(sourcePath) ?? []];
|
|
65
|
+
}
|
|
66
|
+
registerExternalRequest(requestId, sourcePath) {
|
|
67
|
+
this.externalRequestToSource.set(this.normalizeRequestId(requestId), sourcePath);
|
|
68
|
+
}
|
|
69
|
+
registerActiveRequest(requestId) {
|
|
70
|
+
const normalizedRequestId = this.normalizeRequestId(requestId);
|
|
71
|
+
const requestPath = normalizedRequestId.split("?")[0];
|
|
72
|
+
const sourcePath = this.resolveExternalSource(requestPath) ?? this.resolveExternalSource(requestPath.replace(/^\//, ""));
|
|
73
|
+
if (!sourcePath) return;
|
|
74
|
+
const requestIds = this.sourceToRequestIds.get(sourcePath) ?? /* @__PURE__ */ new Set();
|
|
75
|
+
requestIds.add(normalizedRequestId);
|
|
76
|
+
if (normalizedRequestId.includes("?direct&ngcomp=")) requestIds.add(normalizedRequestId.replace("?direct&ngcomp=", "?ngcomp="));
|
|
77
|
+
this.sourceToRequestIds.set(sourcePath, requestIds);
|
|
78
|
+
}
|
|
79
|
+
registerServedStylesheet(record, aliases = []) {
|
|
80
|
+
const publicId = this.normalizeRequestId(record.publicId);
|
|
81
|
+
this.servedById.set(publicId, {
|
|
82
|
+
...record,
|
|
83
|
+
publicId
|
|
84
|
+
});
|
|
85
|
+
this.servedAliasToId.set(publicId, publicId);
|
|
86
|
+
for (const alias of aliases) this.servedAliasToId.set(this.normalizeRequestId(alias), publicId);
|
|
87
|
+
if (record.sourcePath) {
|
|
88
|
+
const publicIds = this.sourceToPublicIds.get(record.sourcePath) ?? /* @__PURE__ */ new Set();
|
|
89
|
+
publicIds.add(publicId);
|
|
90
|
+
this.sourceToPublicIds.set(record.sourcePath, publicIds);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
resolveServedRecord(requestId) {
|
|
94
|
+
const normalizedRequestId = this.normalizeRequestId(requestId);
|
|
95
|
+
const publicId = this.servedAliasToId.get(normalizedRequestId) ?? this.servedAliasToId.get(normalizedRequestId.split("?")[0]) ?? normalizedRequestId.split("?")[0];
|
|
96
|
+
return this.servedById.get(publicId);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
function preprocessStylesheet(code, filename, stylePreprocessor) {
|
|
100
|
+
return stylePreprocessor ? stylePreprocessor(code, filename) ?? code : code;
|
|
101
|
+
}
|
|
102
|
+
function rewriteRelativeCssImports(code, filename) {
|
|
103
|
+
const cssDir = dirname(filename);
|
|
104
|
+
return code.replace(/@import\s+(?:url\(\s*(["']?)(\.[^'")\s;]+)\1\s*\)|(["'])(\.[^'"]+)\3)/g, (_match, urlQuote, urlPath, stringQuote, stringPath) => {
|
|
105
|
+
const absPath = resolve(cssDir, urlPath ?? stringPath);
|
|
106
|
+
if (typeof urlPath === "string") return `@import url(${urlQuote}${absPath}${urlQuote})`;
|
|
107
|
+
return `@import ${stringQuote}${absPath}${stringQuote}`;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function registerStylesheetContent(registry, { code, containingFile, className, order, inlineStylesExtension, resourceFile }) {
|
|
111
|
+
const stylesheetId = `${createHash("sha256").update(containingFile).update(className ?? "").update(String(order ?? 0)).update(code).digest("hex")}.${inlineStylesExtension}`;
|
|
112
|
+
const aliases = [];
|
|
113
|
+
if (resourceFile) {
|
|
114
|
+
const normalizedResourceFile = normalizePath(normalize(resourceFile));
|
|
115
|
+
aliases.push(resourceFile, normalizedResourceFile, resourceFile.replace(/^\//, ""), normalizedResourceFile.replace(/^\//, ""));
|
|
116
|
+
}
|
|
117
|
+
registry.registerServedStylesheet({
|
|
118
|
+
publicId: stylesheetId,
|
|
119
|
+
sourcePath: resourceFile,
|
|
120
|
+
normalizedCode: code
|
|
121
|
+
}, aliases);
|
|
122
|
+
return stylesheetId;
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
export { AnalogStylesheetRegistry, preprocessStylesheet, registerStylesheetContent, rewriteRelativeCssImports };
|
|
126
|
+
|
|
127
|
+
//# sourceMappingURL=stylesheet-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stylesheet-registry.js","names":[],"sources":["../../../src/lib/stylesheet-registry.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { dirname, normalize, resolve } from 'node:path';\nimport { normalizePath } from 'vite';\nimport type { StylePreprocessor } from './style-preprocessor.js';\n\nexport interface AnalogStylesheetRecord {\n publicId: string;\n sourcePath?: string;\n originalCode?: string;\n normalizedCode: string;\n}\n\nexport class AnalogStylesheetRegistry {\n private servedById = new Map<string, AnalogStylesheetRecord>();\n private servedAliasToId = new Map<string, string>();\n private externalRequestToSource = new Map<string, string>();\n /**\n * Maps a real source stylesheet path back to the generated public stylesheet\n * ids Analog serves for Angular. This is stable across requests and lets HMR\n * reason about \"which virtual stylesheet came from this source file?\"\n */\n private sourceToPublicIds = new Map<string, Set<string>>();\n /**\n * Tracks the live request ids Vite/Angular have actually served for a source\n * stylesheet, including both `?direct&ngcomp=...` CSS modules and\n * `?ngcomp=...` JS wrapper modules. HMR must use these live request ids\n * because Angular component styles are no longer addressed by their original\n * file paths once externalized.\n */\n private sourceToRequestIds = new Map<string, Set<string>>();\n\n /**\n * Canonicalizes browser-facing stylesheet request ids so Vite timestamp\n * variants (`?t=...`) and path-shape variants (`abc.css?...` vs\n * `/abc.css?...`) all collapse onto one logical module identity.\n *\n * This is critical for Angular component stylesheet HMR because the browser\n * can keep both timestamped and non-timestamped requests alive for the same\n * externalized stylesheet. If Analog tracks them as distinct resources, HMR\n * can update one module while the browser continues rendering another stale\n * module for the same public stylesheet id.\n */\n private normalizeRequestId(requestId: string): string {\n const [rawPathname, rawSearch = ''] = requestId.split('?');\n const normalizedPathname = rawPathname.replace(/^\\//, '');\n\n if (!rawSearch) {\n return normalizedPathname;\n }\n\n // Preserve bare query flags like `?direct&ngcomp=...` exactly. Using\n // URLSearchParams reserializes `direct` as `direct=`, which changes the\n // module identity and breaks Vite module-graph lookups for Angular's\n // externalized component stylesheet requests.\n const normalizedSearch = rawSearch\n .split('&')\n .filter((segment) => segment.length > 0)\n .filter((segment) => {\n const [key] = segment.split('=');\n return key !== 't';\n })\n .join('&');\n\n return normalizedSearch\n ? `${normalizedPathname}?${normalizedSearch}`\n : normalizedPathname;\n }\n\n get servedCount(): number {\n return this.servedById.size;\n }\n\n get externalCount(): number {\n return this.externalRequestToSource.size;\n }\n\n hasServed(requestId: string): boolean {\n return this.resolveServedRecord(requestId) !== undefined;\n }\n\n getServedContent(requestId: string): string | undefined {\n return this.resolveServedRecord(requestId)?.normalizedCode;\n }\n\n resolveExternalSource(requestId: string): string | undefined {\n const normalizedRequestId = this.normalizeRequestId(requestId);\n return this.externalRequestToSource.get(normalizedRequestId);\n }\n\n getPublicIdsForSource(sourcePath: string): string[] {\n return [...(this.sourceToPublicIds.get(sourcePath) ?? [])];\n }\n\n getRequestIdsForSource(sourcePath: string): string[] {\n return [...(this.sourceToRequestIds.get(sourcePath) ?? [])];\n }\n\n registerExternalRequest(requestId: string, sourcePath: string): void {\n this.externalRequestToSource.set(\n this.normalizeRequestId(requestId),\n sourcePath,\n );\n }\n\n registerActiveRequest(requestId: string): void {\n // Requests arrive in multiple shapes depending on who asked for the\n // stylesheet (`abc123.css?...` vs `/abc123.css?...`). Normalize both back to\n // the source file so later HMR events for `/src/...component.css` can find\n // the currently active virtual requests.\n const normalizedRequestId = this.normalizeRequestId(requestId);\n const requestPath = normalizedRequestId.split('?')[0];\n const sourcePath =\n this.resolveExternalSource(requestPath) ??\n this.resolveExternalSource(requestPath.replace(/^\\//, ''));\n if (!sourcePath) {\n return;\n }\n\n const requestIds = this.sourceToRequestIds.get(sourcePath) ?? new Set();\n requestIds.add(normalizedRequestId);\n // Angular component styles are served through both a direct CSS request\n // (`?direct&ngcomp=...`) and a JS wrapper request (`?ngcomp=...`). The\n // browser can already have the wrapper loaded even when Vite's live module\n // graph only surfaces the direct request during a CSS-only edit. Track the\n // derived wrapper id eagerly so HMR can reason about the browser-visible\n // stylesheet identity without waiting for that wrapper request to be\n // observed later in the session.\n if (normalizedRequestId.includes('?direct&ngcomp=')) {\n requestIds.add(\n normalizedRequestId.replace('?direct&ngcomp=', '?ngcomp='),\n );\n }\n this.sourceToRequestIds.set(sourcePath, requestIds);\n }\n\n registerServedStylesheet(\n record: AnalogStylesheetRecord,\n aliases: string[] = [],\n ): void {\n const publicId = this.normalizeRequestId(record.publicId);\n this.servedById.set(publicId, { ...record, publicId });\n this.servedAliasToId.set(publicId, publicId);\n\n for (const alias of aliases) {\n this.servedAliasToId.set(this.normalizeRequestId(alias), publicId);\n }\n\n if (record.sourcePath) {\n const publicIds =\n this.sourceToPublicIds.get(record.sourcePath) ?? new Set();\n publicIds.add(publicId);\n this.sourceToPublicIds.set(record.sourcePath, publicIds);\n }\n }\n\n private resolveServedRecord(\n requestId: string,\n ): AnalogStylesheetRecord | undefined {\n const normalizedRequestId = this.normalizeRequestId(requestId);\n const publicId =\n this.servedAliasToId.get(normalizedRequestId) ??\n this.servedAliasToId.get(normalizedRequestId.split('?')[0]) ??\n normalizedRequestId.split('?')[0];\n return this.servedById.get(publicId);\n }\n}\n\nexport function preprocessStylesheet(\n code: string,\n filename: string,\n stylePreprocessor?: StylePreprocessor,\n): string {\n return stylePreprocessor ? (stylePreprocessor(code, filename) ?? code) : code;\n}\n\nexport function rewriteRelativeCssImports(\n code: string,\n filename: string,\n): string {\n const cssDir = dirname(filename);\n return code.replace(\n /@import\\s+(?:url\\(\\s*([\"']?)(\\.[^'\")\\s;]+)\\1\\s*\\)|([\"'])(\\.[^'\"]+)\\3)/g,\n (_match, urlQuote, urlPath, stringQuote, stringPath) => {\n const relPath = urlPath ?? stringPath;\n const absPath = resolve(cssDir, relPath);\n\n if (typeof urlPath === 'string') {\n return `@import url(${urlQuote}${absPath}${urlQuote})`;\n }\n\n return `@import ${stringQuote}${absPath}${stringQuote}`;\n },\n );\n}\n\nexport function registerStylesheetContent(\n registry: AnalogStylesheetRegistry,\n {\n code,\n containingFile,\n className,\n order,\n inlineStylesExtension,\n resourceFile,\n }: {\n code: string;\n containingFile: string;\n className?: string;\n order?: number;\n inlineStylesExtension: string;\n resourceFile?: string;\n },\n): string {\n const id = createHash('sha256')\n .update(containingFile)\n .update(className ?? '')\n .update(String(order ?? 0))\n .update(code)\n .digest('hex');\n const stylesheetId = `${id}.${inlineStylesExtension}`;\n\n const aliases: string[] = [];\n\n if (resourceFile) {\n const normalizedResourceFile = normalizePath(normalize(resourceFile));\n // Avoid basename-only aliases here: shared filenames like `index.css`\n // can collide across components and break HMR lookups.\n aliases.push(\n resourceFile,\n normalizedResourceFile,\n resourceFile.replace(/^\\//, ''),\n normalizedResourceFile.replace(/^\\//, ''),\n );\n }\n\n registry.registerServedStylesheet(\n {\n publicId: stylesheetId,\n sourcePath: resourceFile,\n normalizedCode: code,\n },\n aliases,\n );\n\n return stylesheetId;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Debugger } from "obug";
|
|
2
|
+
export type DebugMode = "build" | "dev";
|
|
3
|
+
export interface DebugModeOptions<S extends string = string> {
|
|
4
|
+
scopes?: boolean | S[];
|
|
5
|
+
mode?: DebugMode;
|
|
6
|
+
/**
|
|
7
|
+
* Write debug output to log files under `tmp/debug/` in the workspace root.
|
|
8
|
+
* - `true` or `'single'` — all output to `tmp/debug/analog.log`
|
|
9
|
+
* - `'scoped'` — one file per scope, e.g. `tmp/debug/analog.angular.hmr.log`
|
|
10
|
+
*/
|
|
11
|
+
logFile?: boolean | "single" | "scoped";
|
|
12
|
+
}
|
|
13
|
+
export type DebugOption<S extends string = string> = boolean | S[] | DebugModeOptions<S> | DebugModeOptions<S>[];
|
|
14
|
+
export interface DebugHarness<S extends string = string> {
|
|
15
|
+
applyDebugOption(debug: DebugOption<S> | undefined, workspaceRoot?: string): void;
|
|
16
|
+
activateDeferredDebug(command: "build" | "serve"): void;
|
|
17
|
+
/** @internal test-only reset */
|
|
18
|
+
_resetDeferredDebug(): void;
|
|
19
|
+
}
|
|
20
|
+
export declare function createDebugHarness<S extends string = string>(config: {
|
|
21
|
+
fallbackNamespace: string;
|
|
22
|
+
instanceGroups: Debugger[][];
|
|
23
|
+
}): DebugHarness<S>;
|