@analogjs/vite-plugin-angular 3.0.0-alpha.36 → 3.0.0-alpha.38

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.
Files changed (69) hide show
  1. package/README.md +22 -0
  2. package/package.json +3 -4
  3. package/src/lib/angular-jit-plugin.js +3 -0
  4. package/src/lib/angular-jit-plugin.js.map +1 -1
  5. package/src/lib/angular-vite-plugin.d.ts +12 -7
  6. package/src/lib/angular-vite-plugin.js +7 -5
  7. package/src/lib/angular-vite-plugin.js.map +1 -1
  8. package/src/lib/compiler/angular-version.d.ts +19 -0
  9. package/src/lib/compiler/angular-version.js +16 -0
  10. package/src/lib/compiler/angular-version.js.map +1 -0
  11. package/src/lib/compiler/class-field-lowering.d.ts +23 -0
  12. package/src/lib/compiler/class-field-lowering.js +131 -0
  13. package/src/lib/compiler/class-field-lowering.js.map +1 -0
  14. package/src/lib/compiler/compile.d.ts +44 -0
  15. package/src/lib/compiler/compile.js +731 -0
  16. package/src/lib/compiler/compile.js.map +1 -0
  17. package/src/lib/compiler/constants.d.ts +18 -0
  18. package/src/lib/compiler/constants.js +52 -0
  19. package/src/lib/compiler/constants.js.map +1 -0
  20. package/src/lib/compiler/debug.d.ts +22 -0
  21. package/src/lib/compiler/debug.js +20 -0
  22. package/src/lib/compiler/debug.js.map +1 -0
  23. package/src/lib/compiler/defer.d.ts +47 -0
  24. package/src/lib/compiler/defer.js +138 -0
  25. package/src/lib/compiler/defer.js.map +1 -0
  26. package/src/lib/compiler/dts-reader.d.ts +35 -0
  27. package/src/lib/compiler/dts-reader.js +365 -0
  28. package/src/lib/compiler/dts-reader.js.map +1 -0
  29. package/src/lib/compiler/hmr.d.ts +16 -0
  30. package/src/lib/compiler/hmr.js +69 -0
  31. package/src/lib/compiler/hmr.js.map +1 -0
  32. package/src/lib/compiler/index.d.ts +7 -0
  33. package/src/lib/compiler/index.js +7 -0
  34. package/src/lib/compiler/jit-metadata.d.ts +14 -0
  35. package/src/lib/compiler/jit-metadata.js +146 -0
  36. package/src/lib/compiler/jit-metadata.js.map +1 -0
  37. package/src/lib/compiler/jit-transform.d.ts +24 -0
  38. package/src/lib/compiler/jit-transform.js +200 -0
  39. package/src/lib/compiler/jit-transform.js.map +1 -0
  40. package/src/lib/compiler/js-emitter.d.ts +10 -0
  41. package/src/lib/compiler/js-emitter.js +420 -0
  42. package/src/lib/compiler/js-emitter.js.map +1 -0
  43. package/src/lib/compiler/metadata.d.ts +45 -0
  44. package/src/lib/compiler/metadata.js +633 -0
  45. package/src/lib/compiler/metadata.js.map +1 -0
  46. package/src/lib/compiler/registry.d.ts +49 -0
  47. package/src/lib/compiler/registry.js +164 -0
  48. package/src/lib/compiler/registry.js.map +1 -0
  49. package/src/lib/compiler/resource-inliner.d.ts +21 -0
  50. package/src/lib/compiler/resource-inliner.js +152 -0
  51. package/src/lib/compiler/resource-inliner.js.map +1 -0
  52. package/src/lib/compiler/style-ast.d.ts +8 -0
  53. package/src/lib/compiler/style-ast.js +54 -0
  54. package/src/lib/compiler/style-ast.js.map +1 -0
  55. package/src/lib/compiler/styles.d.ts +13 -0
  56. package/src/lib/compiler/test-helpers.d.ts +7 -0
  57. package/src/lib/compiler/type-elision.d.ts +26 -0
  58. package/src/lib/compiler/type-elision.js +211 -0
  59. package/src/lib/compiler/type-elision.js.map +1 -0
  60. package/src/lib/compiler/utils.d.ts +10 -0
  61. package/src/lib/compiler/utils.js +35 -0
  62. package/src/lib/compiler/utils.js.map +1 -0
  63. package/src/lib/{analog-compiler-plugin.d.ts → fast-compile-plugin.d.ts} +3 -3
  64. package/src/lib/{analog-compiler-plugin.js → fast-compile-plugin.js} +46 -33
  65. package/src/lib/fast-compile-plugin.js.map +1 -0
  66. package/src/lib/utils/virtual-resources.d.ts +16 -0
  67. package/src/lib/utils/virtual-resources.js +31 -2
  68. package/src/lib/utils/virtual-resources.js.map +1 -1
  69. package/src/lib/analog-compiler-plugin.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"style-ast.js","names":[],"sources":["../../../../src/lib/compiler/style-ast.ts"],"sourcesContent":["import { parseSync } from 'oxc-parser';\n\ntype ProgramNode = ReturnType<typeof parseSync>['program']['body'][number];\n\nfunction getClassDeclaration(node: ProgramNode): any {\n return node.type === 'ExportNamedDeclaration' ||\n node.type === 'ExportDefaultDeclaration'\n ? (node as any).declaration\n : node;\n}\n\nfunction getPropertyKey(prop: any): string | undefined {\n return prop.key?.name || prop.key?.value;\n}\n\nfunction getTemplateLiteralText(node: any): string | undefined {\n if (node?.type !== 'TemplateLiteral' || node.quasis?.length !== 1) {\n return undefined;\n }\n\n return node.quasis[0].value.cooked || node.quasis[0].value.raw;\n}\n\n/**\n * Extract styleUrl/styleUrls values from Angular @Component decorators.\n */\nexport function extractStyleUrls(code: string, fileName: string): string[] {\n const urls: string[] = [];\n const { program } = parseSync(fileName, code);\n\n for (const node of program.body) {\n const decl = getClassDeclaration(node);\n if (!decl || decl.type !== 'ClassDeclaration') continue;\n\n for (const dec of decl.decorators || []) {\n const expr = dec.expression;\n if (!expr || expr.type !== 'CallExpression') continue;\n if (expr.callee?.name !== 'Component') continue;\n\n const arg = expr.arguments?.[0];\n if (!arg || arg.type !== 'ObjectExpression') continue;\n\n for (const prop of arg.properties) {\n if (prop.type !== 'Property') continue;\n const key = getPropertyKey(prop);\n const val = prop.value;\n\n if (\n key === 'styleUrl' &&\n val?.type === 'Literal' &&\n typeof val.value === 'string'\n ) {\n urls.push(val.value);\n }\n\n if (key === 'styleUrls' && val?.type === 'ArrayExpression') {\n for (const el of val.elements) {\n if (el?.type === 'Literal' && typeof el.value === 'string') {\n urls.push(el.value);\n }\n }\n }\n }\n }\n }\n\n return urls;\n}\n\n/**\n * Extract inline style strings from Angular @Component decorators.\n */\nexport function extractInlineStyles(code: string, fileName: string): string[] {\n const styles: string[] = [];\n const { program } = parseSync(fileName, code);\n\n for (const node of program.body) {\n const decl = getClassDeclaration(node);\n if (!decl || decl.type !== 'ClassDeclaration') continue;\n\n for (const dec of decl.decorators || []) {\n const expr = dec.expression;\n if (!expr || expr.type !== 'CallExpression') continue;\n if (expr.callee?.name !== 'Component') continue;\n\n const arg = expr.arguments?.[0];\n if (!arg || arg.type !== 'ObjectExpression') continue;\n\n for (const prop of arg.properties) {\n if (prop.type !== 'Property') continue;\n const key = getPropertyKey(prop);\n const val = prop.value;\n\n if (key !== 'styles') continue;\n\n if (val?.type === 'ArrayExpression') {\n for (const el of val.elements) {\n if (el?.type === 'Literal' && typeof el.value === 'string') {\n styles.push(el.value);\n continue;\n }\n\n const templateText = getTemplateLiteralText(el);\n if (templateText !== undefined) {\n styles.push(templateText);\n }\n }\n } else if (val?.type === 'Literal' && typeof val.value === 'string') {\n styles.push(val.value);\n } else {\n const templateText = getTemplateLiteralText(val);\n if (templateText !== undefined) {\n styles.push(templateText);\n }\n }\n }\n }\n }\n\n return styles;\n}\n"],"mappings":";;AAIA,SAAS,oBAAoB,MAAwB;AACnD,QAAO,KAAK,SAAS,4BACnB,KAAK,SAAS,6BACX,KAAa,cACd;;AAGN,SAAS,eAAe,MAA+B;AACrD,QAAO,KAAK,KAAK,QAAQ,KAAK,KAAK;;AAGrC,SAAS,uBAAuB,MAA+B;AAC7D,KAAI,MAAM,SAAS,qBAAqB,KAAK,QAAQ,WAAW,EAC9D;AAGF,QAAO,KAAK,OAAO,GAAG,MAAM,UAAU,KAAK,OAAO,GAAG,MAAM;;;;;AAoD7D,SAAgB,oBAAoB,MAAc,UAA4B;CAC5E,MAAM,SAAmB,EAAE;CAC3B,MAAM,EAAE,YAAY,UAAU,UAAU,KAAK;AAE7C,MAAK,MAAM,QAAQ,QAAQ,MAAM;EAC/B,MAAM,OAAO,oBAAoB,KAAK;AACtC,MAAI,CAAC,QAAQ,KAAK,SAAS,mBAAoB;AAE/C,OAAK,MAAM,OAAO,KAAK,cAAc,EAAE,EAAE;GACvC,MAAM,OAAO,IAAI;AACjB,OAAI,CAAC,QAAQ,KAAK,SAAS,iBAAkB;AAC7C,OAAI,KAAK,QAAQ,SAAS,YAAa;GAEvC,MAAM,MAAM,KAAK,YAAY;AAC7B,OAAI,CAAC,OAAO,IAAI,SAAS,mBAAoB;AAE7C,QAAK,MAAM,QAAQ,IAAI,YAAY;AACjC,QAAI,KAAK,SAAS,WAAY;IAC9B,MAAM,MAAM,eAAe,KAAK;IAChC,MAAM,MAAM,KAAK;AAEjB,QAAI,QAAQ,SAAU;AAEtB,QAAI,KAAK,SAAS,kBAChB,MAAK,MAAM,MAAM,IAAI,UAAU;AAC7B,SAAI,IAAI,SAAS,aAAa,OAAO,GAAG,UAAU,UAAU;AAC1D,aAAO,KAAK,GAAG,MAAM;AACrB;;KAGF,MAAM,eAAe,uBAAuB,GAAG;AAC/C,SAAI,iBAAiB,KAAA,EACnB,QAAO,KAAK,aAAa;;aAGpB,KAAK,SAAS,aAAa,OAAO,IAAI,UAAU,SACzD,QAAO,KAAK,IAAI,MAAM;SACjB;KACL,MAAM,eAAe,uBAAuB,IAAI;AAChD,SAAI,iBAAiB,KAAA,EACnB,QAAO,KAAK,aAAa;;;;;AAOnC,QAAO"}
@@ -0,0 +1,13 @@
1
+ import { type ResolvedConfig } from "vite";
2
+ /**
3
+ * Extract styleUrl/styleUrls from source using OXC parser,
4
+ * read and preprocess them via Vite.
5
+ * Returns a Map of absolute path → compiled CSS for the compiler to use.
6
+ */
7
+ export declare function resolveStyleFiles(code: string, id: string, resolvedConfig: ResolvedConfig): Promise<Map<string, string> | undefined>;
8
+ /**
9
+ * Preprocess inline styles that contain SCSS/Sass syntax.
10
+ * Uses OXC parser to extract style strings from decorator arguments
11
+ * and runs them through Vite's preprocessCSS.
12
+ */
13
+ export declare function preprocessInlineStyles(code: string, id: string, inlineStyleLanguage: string, resolvedConfig: ResolvedConfig): Promise<Map<number, string> | undefined>;
@@ -0,0 +1,7 @@
1
+ import { ComponentRegistry } from "./registry";
2
+ export declare function buildRegistry(files: Record<string, string>): ComponentRegistry;
3
+ /** Convenience wrapper: compile and return just the code string. */
4
+ export declare function compileCode(sourceCode: string, fileName: string, registry?: ComponentRegistry): string;
5
+ /** Compile in partial mode and return just the code string. */
6
+ export declare function compilePartialCode(sourceCode: string, fileName: string, registry?: ComponentRegistry): string;
7
+ export declare function expectCompiles(result: string);
@@ -0,0 +1,26 @@
1
+ import MagicString from "magic-string";
2
+ /**
3
+ * Analyse compiled TypeScript source and return the set of imported names
4
+ * that are only referenced in type positions (type annotations, implements
5
+ * clauses, generics, etc.) and can safely be elided.
6
+ *
7
+ * Uses oxc-parser for fast, Rust-based AST analysis — no type-checker needed.
8
+ */
9
+ export declare function detectTypeOnlyImportNames(code: string): Set<string>;
10
+ /**
11
+ * Elide import specifiers (or entire import declarations) for names that are
12
+ * only used in type positions. Operates on the compiler's TypeScript output
13
+ * (before OXC strips remaining TS syntax).
14
+ *
15
+ * Returns the modified source code, or the original if nothing was elided.
16
+ */
17
+ export declare function elideTypeOnlyImports(code: string): string;
18
+ /**
19
+ * Elide type-only imports directly on a MagicString instance, so that
20
+ * subsequent `ms.generateMap()` calls produce an accurate sourcemap.
21
+ *
22
+ * Detects type-only names from `ms.toString()` (the fully-mutated code),
23
+ * but reads import positions from `ms.original` (the coordinate system
24
+ * MagicString expects).
25
+ */
26
+ export declare function elideTypeOnlyImportsMagicString(ms: MagicString): void;
@@ -0,0 +1,211 @@
1
+ import { parseSync } from "oxc-parser";
2
+ import "magic-string";
3
+ //#region packages/vite-plugin-angular/src/lib/compiler/type-elision.ts
4
+ /**
5
+ * AST property keys that represent type-only positions in TypeScript.
6
+ * Identifiers found only under these keys are never emitted as runtime values.
7
+ */
8
+ var TYPE_POSITION_KEYS = new Set([
9
+ "typeAnnotation",
10
+ "typeParameters",
11
+ "superTypeParameters",
12
+ "implements",
13
+ "returnType",
14
+ "typeArguments"
15
+ ]);
16
+ /**
17
+ * AST node types that represent type-level constructs.
18
+ * Any identifier nested inside one of these nodes is in a type position.
19
+ */
20
+ var TYPE_NODE_TYPES = new Set([
21
+ "TSTypeAnnotation",
22
+ "TSTypeReference",
23
+ "TSTypeParameterInstantiation",
24
+ "TSTypeParameterDeclaration",
25
+ "TSInterfaceDeclaration",
26
+ "TSTypeAliasDeclaration",
27
+ "TSAsExpression",
28
+ "TSSatisfiesExpression",
29
+ "TSUnionType",
30
+ "TSIntersectionType",
31
+ "TSArrayType",
32
+ "TSTupleType",
33
+ "TSFunctionType",
34
+ "TSConstructorType",
35
+ "TSMappedType",
36
+ "TSConditionalType",
37
+ "TSIndexedAccessType",
38
+ "TSTypeQuery",
39
+ "TSTypeLiteral",
40
+ "TSQualifiedName",
41
+ "TSInterfaceBody"
42
+ ]);
43
+ /**
44
+ * Internal helper: parse code and return both the AST and the set of
45
+ * type-only imported names. Avoids double-parsing when both are needed.
46
+ */
47
+ function analyzeTypeOnlyImports(code) {
48
+ const ast = parseSync("file.ts", code).program;
49
+ const importedNames = /* @__PURE__ */ new Set();
50
+ for (const node of ast.body) {
51
+ if (node.type !== "ImportDeclaration") continue;
52
+ if (node.importKind === "type") continue;
53
+ for (const spec of node.specifiers ?? []) {
54
+ if (spec.importKind === "type") continue;
55
+ importedNames.add(spec.local.name);
56
+ }
57
+ }
58
+ if (importedNames.size === 0) return {
59
+ ast,
60
+ typeOnlyNames: /* @__PURE__ */ new Set()
61
+ };
62
+ const valueReferenced = /* @__PURE__ */ new Set();
63
+ collectConstructorDiTokens(ast, importedNames, valueReferenced);
64
+ function walk(node, inTypePosition) {
65
+ if (!node || typeof node !== "object") return;
66
+ if (Array.isArray(node)) {
67
+ for (const item of node) walk(item, inTypePosition);
68
+ return;
69
+ }
70
+ if (valueReferenced.size === importedNames.size) return;
71
+ if (!inTypePosition && TYPE_NODE_TYPES.has(node.type)) {
72
+ if (node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression") {
73
+ walk(node.expression, false);
74
+ walk(node.typeAnnotation, true);
75
+ return;
76
+ }
77
+ inTypePosition = true;
78
+ }
79
+ if (!inTypePosition && node.type === "ExportNamedDeclaration" && !node.source) {
80
+ if (node.declaration) walk(node.declaration, false);
81
+ for (const spec of node.specifiers ?? []) {
82
+ if (node.exportKind === "type" || spec.exportKind === "type") continue;
83
+ walk(spec, false);
84
+ }
85
+ return;
86
+ }
87
+ if (!inTypePosition && node.type === "Identifier" && importedNames.has(node.name)) {
88
+ valueReferenced.add(node.name);
89
+ return;
90
+ }
91
+ for (const key of Object.keys(node)) {
92
+ if (key === "type" || key === "start" || key === "end" || key === "range" || key === "loc") continue;
93
+ const isTypePos = inTypePosition || TYPE_POSITION_KEYS.has(key);
94
+ walk(node[key], isTypePos);
95
+ }
96
+ }
97
+ for (const node of ast.body) if (node.type !== "ImportDeclaration") walk(node, false);
98
+ const typeOnly = /* @__PURE__ */ new Set();
99
+ for (const name of importedNames) if (!valueReferenced.has(name)) typeOnly.add(name);
100
+ return {
101
+ ast,
102
+ typeOnlyNames: typeOnly
103
+ };
104
+ }
105
+ /**
106
+ * Walk the program looking for decorated classes with constructors and add
107
+ * the type names from constructor parameter annotations to `valueReferenced`.
108
+ *
109
+ * Constructor parameter types are TypeScript type positions, but for decorated
110
+ * classes they double as runtime DI tokens — Angular's compiler reads them
111
+ * via `compileFactoryFunction` to wire `ɵɵdirectiveInject(...)` calls. The
112
+ * import of the type therefore must NOT be elided as type-only.
113
+ */
114
+ function collectConstructorDiTokens(ast, importedNames, valueReferenced) {
115
+ for (const stmt of ast.body || []) {
116
+ const classNode = stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportDefaultDeclaration" ? stmt.declaration : stmt;
117
+ if (!classNode || classNode.type !== "ClassDeclaration" && classNode.type !== "ClassExpression") continue;
118
+ if (!classNode.decorators?.length) continue;
119
+ const ctor = (classNode.body?.body || []).find((m) => m.type === "MethodDefinition" && m.kind === "constructor");
120
+ if (!ctor) continue;
121
+ const params = ctor.value?.params?.items || ctor.value?.params || [];
122
+ for (const param of params) {
123
+ const typeAnn = (param.type === "TSParameterProperty" ? param.parameter : param)?.typeAnnotation?.typeAnnotation ?? param?.typeAnnotation?.typeAnnotation;
124
+ if (!typeAnn) continue;
125
+ collectTypeReferenceNames(typeAnn, importedNames, valueReferenced);
126
+ }
127
+ }
128
+ }
129
+ /**
130
+ * Recursively extract identifier names from a TS type expression and mark
131
+ * any that match an imported name as value-referenced.
132
+ */
133
+ function collectTypeReferenceNames(typeNode, importedNames, valueReferenced) {
134
+ if (!typeNode || typeof typeNode !== "object") return;
135
+ if (typeNode.type === "TSTypeReference" && typeNode.typeName) {
136
+ const name = typeNode.typeName.name;
137
+ if (name && importedNames.has(name)) valueReferenced.add(name);
138
+ if (typeNode.typeArguments) collectTypeReferenceNames(typeNode.typeArguments, importedNames, valueReferenced);
139
+ } else if (typeNode.type === "TSUnionType" || typeNode.type === "TSIntersectionType") for (const t of typeNode.types || []) collectTypeReferenceNames(t, importedNames, valueReferenced);
140
+ else if (Array.isArray(typeNode)) for (const t of typeNode) collectTypeReferenceNames(t, importedNames, valueReferenced);
141
+ else if (typeNode.params) for (const t of typeNode.params) collectTypeReferenceNames(t, importedNames, valueReferenced);
142
+ }
143
+ /**
144
+ * Analyse compiled TypeScript source and return the set of imported names
145
+ * that are only referenced in type positions (type annotations, implements
146
+ * clauses, generics, etc.) and can safely be elided.
147
+ *
148
+ * Uses oxc-parser for fast, Rust-based AST analysis — no type-checker needed.
149
+ */
150
+ function detectTypeOnlyImportNames(code) {
151
+ return analyzeTypeOnlyImports(code).typeOnlyNames;
152
+ }
153
+ /**
154
+ * Apply import elision edits to a MagicString instance using positions from
155
+ * a parsed AST. Shared logic for both the string-based and MagicString APIs.
156
+ */
157
+ function applyElisionEdits(ms, ast, src, typeOnlyNames) {
158
+ let edited = false;
159
+ for (const node of ast.body) {
160
+ if (node.type !== "ImportDeclaration") continue;
161
+ if (node.importKind === "type") continue;
162
+ const allSpecs = node.specifiers ?? [];
163
+ const defaultSpec = allSpecs.find((s) => s.type === "ImportDefaultSpecifier");
164
+ const namedSpecs = allSpecs.filter((s) => s.type === "ImportSpecifier" && s.importKind !== "type");
165
+ const elideDefault = defaultSpec && typeOnlyNames.has(defaultSpec.local.name);
166
+ const keptNamed = namedSpecs.filter((s) => !typeOnlyNames.has(s.local.name));
167
+ const removingNamed = namedSpecs.length - keptNamed.length;
168
+ if (!elideDefault && removingNamed === 0) continue;
169
+ let declEnd = node.end;
170
+ while (declEnd < src.length && (src[declEnd] === "\n" || src[declEnd] === "\r")) declEnd++;
171
+ const keepDefault = defaultSpec && !elideDefault;
172
+ const hasKeptNamed = keptNamed.length > 0;
173
+ if (!keepDefault && !hasKeptNamed) {
174
+ ms.remove(node.start, declEnd);
175
+ edited = true;
176
+ } else {
177
+ const parts = [];
178
+ if (keepDefault) parts.push(defaultSpec.local.name);
179
+ if (hasKeptNamed) {
180
+ const namedList = keptNamed.map((s) => {
181
+ const imported = s.imported?.name ?? s.local.name;
182
+ return imported === s.local.name ? s.local.name : `${imported} as ${s.local.name}`;
183
+ });
184
+ parts.push(`{ ${namedList.join(", ")} }`);
185
+ }
186
+ const source = node.source.value;
187
+ const quote = src[node.source.start];
188
+ ms.overwrite(node.start, node.end, `import ${parts.join(", ")} from ${quote}${source}${quote};`);
189
+ edited = true;
190
+ }
191
+ }
192
+ return edited;
193
+ }
194
+ /**
195
+ * Elide type-only imports directly on a MagicString instance, so that
196
+ * subsequent `ms.generateMap()` calls produce an accurate sourcemap.
197
+ *
198
+ * Detects type-only names from `ms.toString()` (the fully-mutated code),
199
+ * but reads import positions from `ms.original` (the coordinate system
200
+ * MagicString expects).
201
+ */
202
+ function elideTypeOnlyImportsMagicString(ms) {
203
+ const typeOnlyNames = detectTypeOnlyImportNames(ms.toString());
204
+ if (typeOnlyNames.size === 0) return;
205
+ const ast = parseSync("file.ts", ms.original).program;
206
+ applyElisionEdits(ms, ast, ms.original, typeOnlyNames);
207
+ }
208
+ //#endregion
209
+ export { detectTypeOnlyImportNames, elideTypeOnlyImportsMagicString };
210
+
211
+ //# sourceMappingURL=type-elision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type-elision.js","names":[],"sources":["../../../../src/lib/compiler/type-elision.ts"],"sourcesContent":["import MagicString from 'magic-string';\nimport { parseSync } from 'oxc-parser';\n\n// ── Type-only import elision via OXC AST ────────────────────────────────\n\n/**\n * AST property keys that represent type-only positions in TypeScript.\n * Identifiers found only under these keys are never emitted as runtime values.\n */\nconst TYPE_POSITION_KEYS = new Set([\n 'typeAnnotation',\n 'typeParameters',\n 'superTypeParameters',\n 'implements',\n 'returnType',\n 'typeArguments',\n]);\n\n/**\n * AST node types that represent type-level constructs.\n * Any identifier nested inside one of these nodes is in a type position.\n */\nconst TYPE_NODE_TYPES = new Set([\n 'TSTypeAnnotation',\n 'TSTypeReference',\n 'TSTypeParameterInstantiation',\n 'TSTypeParameterDeclaration',\n 'TSInterfaceDeclaration',\n 'TSTypeAliasDeclaration',\n 'TSAsExpression',\n 'TSSatisfiesExpression',\n 'TSUnionType',\n 'TSIntersectionType',\n 'TSArrayType',\n 'TSTupleType',\n 'TSFunctionType',\n 'TSConstructorType',\n 'TSMappedType',\n 'TSConditionalType',\n 'TSIndexedAccessType',\n 'TSTypeQuery',\n 'TSTypeLiteral',\n 'TSQualifiedName',\n 'TSInterfaceBody',\n]);\n\n/**\n * Internal helper: parse code and return both the AST and the set of\n * type-only imported names. Avoids double-parsing when both are needed.\n */\nfunction analyzeTypeOnlyImports(code: string): {\n ast: any;\n typeOnlyNames: Set<string>;\n} {\n const ast = parseSync('file.ts', code).program;\n\n // Step 1 – collect all value-imported names (skip `import type` / `{ type X }`)\n // Namespace imports (`import * as ns`) are intentionally skipped — they are\n // always value imports in TypeScript and cannot be type-only at the\n // declaration level.\n const importedNames = new Set<string>();\n for (const node of (ast as any).body) {\n if (node.type !== 'ImportDeclaration') continue;\n if (node.importKind === 'type') continue; // already type-only\n for (const spec of node.specifiers ?? []) {\n if (spec.importKind === 'type') continue; // already type-only\n importedNames.add(spec.local.name);\n }\n }\n if (importedNames.size === 0) return { ast, typeOnlyNames: new Set() };\n\n // Step 2 – walk AST and collect names referenced in value positions\n const valueReferenced = new Set<string>();\n\n // Pre-pass: constructor parameter types of decorated classes are DI tokens\n // and must be preserved as runtime values, even though they appear in\n // type-annotation positions. Without this, `constructor(svc: MyService)`\n // would erase the `MyService` import and `extractConstructorDeps` would\n // emit `ɵɵinvalidFactory()`.\n collectConstructorDiTokens(ast, importedNames, valueReferenced);\n\n function walk(node: any, inTypePosition: boolean): void {\n if (!node || typeof node !== 'object') return;\n if (Array.isArray(node)) {\n for (const item of node) walk(item, inTypePosition);\n return;\n }\n\n // Short-circuit: if we found every imported name in a value position, stop\n if (valueReferenced.size === importedNames.size) return;\n\n // If this node's type itself marks a type construct, everything below is type-only\n // Exception: TSAsExpression/TSSatisfiesExpression have a value `expression`\n // child that is NOT in a type position — only the type annotation is.\n if (!inTypePosition && TYPE_NODE_TYPES.has(node.type)) {\n if (\n node.type === 'TSAsExpression' ||\n node.type === 'TSSatisfiesExpression'\n ) {\n // Walk the expression child in value context, type annotation in type context\n walk(node.expression, false);\n walk(node.typeAnnotation, true);\n return;\n }\n inTypePosition = true;\n }\n\n // Type-only exports: `export type { Foo }` or `export { type Foo }`\n // Identifiers inside type-only export specifiers are not value references.\n if (\n !inTypePosition &&\n node.type === 'ExportNamedDeclaration' &&\n !node.source\n ) {\n // Walk the declaration (if any) in value context\n if (node.declaration) walk(node.declaration, false);\n // Walk each specifier, skipping type-only ones\n for (const spec of node.specifiers ?? []) {\n if (node.exportKind === 'type' || spec.exportKind === 'type') continue;\n walk(spec, false);\n }\n return;\n }\n\n if (\n !inTypePosition &&\n node.type === 'Identifier' &&\n importedNames.has(node.name)\n ) {\n valueReferenced.add(node.name);\n return;\n }\n\n for (const key of Object.keys(node)) {\n if (\n key === 'type' ||\n key === 'start' ||\n key === 'end' ||\n key === 'range' ||\n key === 'loc'\n )\n continue;\n const isTypePos = inTypePosition || TYPE_POSITION_KEYS.has(key);\n walk(node[key], isTypePos);\n }\n }\n\n for (const node of (ast as any).body) {\n // Skip import declarations themselves – we already processed them\n if (node.type !== 'ImportDeclaration') {\n walk(node, false);\n }\n }\n\n // Step 3 – names that were imported but never value-referenced are type-only\n const typeOnly = new Set<string>();\n for (const name of importedNames) {\n if (!valueReferenced.has(name)) typeOnly.add(name);\n }\n return { ast, typeOnlyNames: typeOnly };\n}\n\n/**\n * Walk the program looking for decorated classes with constructors and add\n * the type names from constructor parameter annotations to `valueReferenced`.\n *\n * Constructor parameter types are TypeScript type positions, but for decorated\n * classes they double as runtime DI tokens — Angular's compiler reads them\n * via `compileFactoryFunction` to wire `ɵɵdirectiveInject(...)` calls. The\n * import of the type therefore must NOT be elided as type-only.\n */\nfunction collectConstructorDiTokens(\n ast: any,\n importedNames: Set<string>,\n valueReferenced: Set<string>,\n): void {\n for (const stmt of (ast as any).body || []) {\n const classNode =\n stmt.type === 'ExportNamedDeclaration' ||\n stmt.type === 'ExportDefaultDeclaration'\n ? (stmt as any).declaration\n : stmt;\n if (\n !classNode ||\n (classNode.type !== 'ClassDeclaration' &&\n classNode.type !== 'ClassExpression')\n ) {\n continue;\n }\n if (!classNode.decorators?.length) continue;\n\n const ctor = (classNode.body?.body || []).find(\n (m: any) => m.type === 'MethodDefinition' && m.kind === 'constructor',\n );\n if (!ctor) continue;\n\n const params: any[] = ctor.value?.params?.items || ctor.value?.params || [];\n for (const param of params) {\n const actualParam =\n param.type === 'TSParameterProperty' ? param.parameter : param;\n const typeAnn =\n actualParam?.typeAnnotation?.typeAnnotation ??\n param?.typeAnnotation?.typeAnnotation;\n if (!typeAnn) continue;\n collectTypeReferenceNames(typeAnn, importedNames, valueReferenced);\n }\n }\n}\n\n/**\n * Recursively extract identifier names from a TS type expression and mark\n * any that match an imported name as value-referenced.\n */\nfunction collectTypeReferenceNames(\n typeNode: any,\n importedNames: Set<string>,\n valueReferenced: Set<string>,\n): void {\n if (!typeNode || typeof typeNode !== 'object') return;\n if (typeNode.type === 'TSTypeReference' && typeNode.typeName) {\n const name = typeNode.typeName.name;\n if (name && importedNames.has(name)) {\n valueReferenced.add(name);\n }\n // Walk type arguments too (e.g. Foo<Bar>)\n if (typeNode.typeArguments) {\n collectTypeReferenceNames(\n typeNode.typeArguments,\n importedNames,\n valueReferenced,\n );\n }\n } else if (\n typeNode.type === 'TSUnionType' ||\n typeNode.type === 'TSIntersectionType'\n ) {\n for (const t of typeNode.types || []) {\n collectTypeReferenceNames(t, importedNames, valueReferenced);\n }\n } else if (Array.isArray(typeNode)) {\n for (const t of typeNode) {\n collectTypeReferenceNames(t, importedNames, valueReferenced);\n }\n } else if (typeNode.params) {\n // TSTypeParameterInstantiation\n for (const t of typeNode.params) {\n collectTypeReferenceNames(t, importedNames, valueReferenced);\n }\n }\n}\n\n/**\n * Analyse compiled TypeScript source and return the set of imported names\n * that are only referenced in type positions (type annotations, implements\n * clauses, generics, etc.) and can safely be elided.\n *\n * Uses oxc-parser for fast, Rust-based AST analysis — no type-checker needed.\n */\nexport function detectTypeOnlyImportNames(code: string): Set<string> {\n return analyzeTypeOnlyImports(code).typeOnlyNames;\n}\n\n/**\n * Apply import elision edits to a MagicString instance using positions from\n * a parsed AST. Shared logic for both the string-based and MagicString APIs.\n */\nfunction applyElisionEdits(\n ms: MagicString,\n ast: any,\n src: string,\n typeOnlyNames: Set<string>,\n): boolean {\n let edited = false;\n\n for (const node of (ast as any).body) {\n if (node.type !== 'ImportDeclaration') continue;\n if (node.importKind === 'type') continue;\n\n const allSpecs = node.specifiers ?? [];\n\n // Separate default and named specifiers\n const defaultSpec = allSpecs.find(\n (s: any) => s.type === 'ImportDefaultSpecifier',\n );\n const namedSpecs = allSpecs.filter(\n (s: any) => s.type === 'ImportSpecifier' && s.importKind !== 'type',\n );\n\n const elideDefault =\n defaultSpec && typeOnlyNames.has(defaultSpec.local.name);\n const keptNamed = namedSpecs.filter(\n (s: any) => !typeOnlyNames.has(s.local.name),\n );\n const removingNamed = namedSpecs.length - keptNamed.length;\n\n // Nothing to elide from this declaration\n if (!elideDefault && removingNamed === 0) continue;\n\n // Find the end of the declaration including trailing newline\n let declEnd = node.end;\n while (\n declEnd < src.length &&\n (src[declEnd] === '\\n' || src[declEnd] === '\\r')\n )\n declEnd++;\n\n const keepDefault = defaultSpec && !elideDefault;\n const hasKeptNamed = keptNamed.length > 0;\n\n if (!keepDefault && !hasKeptNamed) {\n // Remove entire import declaration\n ms.remove(node.start, declEnd);\n edited = true;\n } else {\n // Rebuild with only the kept specifiers\n const parts: string[] = [];\n if (keepDefault) parts.push(defaultSpec.local.name);\n if (hasKeptNamed) {\n const namedList = keptNamed.map((s: any) => {\n const imported = s.imported?.name ?? s.local.name;\n return imported === s.local.name\n ? s.local.name\n : `${imported} as ${s.local.name}`;\n });\n parts.push(`{ ${namedList.join(', ')} }`);\n }\n const source = node.source.value;\n const quote = src[node.source.start]; // preserve original quote style\n ms.overwrite(\n node.start,\n node.end,\n `import ${parts.join(', ')} from ${quote}${source}${quote};`,\n );\n edited = true;\n }\n }\n\n return edited;\n}\n\n/**\n * Elide import specifiers (or entire import declarations) for names that are\n * only used in type positions. Operates on the compiler's TypeScript output\n * (before OXC strips remaining TS syntax).\n *\n * Returns the modified source code, or the original if nothing was elided.\n */\nexport function elideTypeOnlyImports(code: string): string {\n const { ast, typeOnlyNames } = analyzeTypeOnlyImports(code);\n if (typeOnlyNames.size === 0) return code;\n\n const ms = new MagicString(code);\n const edited = applyElisionEdits(ms, ast, code, typeOnlyNames);\n return edited ? ms.toString() : code;\n}\n\n/**\n * Elide type-only imports directly on a MagicString instance, so that\n * subsequent `ms.generateMap()` calls produce an accurate sourcemap.\n *\n * Detects type-only names from `ms.toString()` (the fully-mutated code),\n * but reads import positions from `ms.original` (the coordinate system\n * MagicString expects).\n */\nexport function elideTypeOnlyImportsMagicString(ms: MagicString): void {\n const typeOnlyNames = detectTypeOnlyImportNames(ms.toString());\n if (typeOnlyNames.size === 0) return;\n\n // Parse the original source to get positions in MagicString's coordinate system\n const ast = parseSync('file.ts', ms.original).program;\n applyElisionEdits(ms, ast, ms.original, typeOnlyNames);\n}\n"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import * as ts from "typescript";
2
+ /** Collect type-only imported names: `import type { X }` and `import { type X }`. */
3
+ export declare function collectTypeOnlyImports(sf: ts.SourceFile): Set<string>;
4
+ /** Recursively find all class declarations in a source file, including nested scopes. */
5
+ export declare function findAllClasses(sf: ts.SourceFile): ts.ClassDeclaration[];
6
+ /** Unwrap forwardRef(() => X) to X. Returns the original node if not a forwardRef call. */
7
+ export declare function unwrapForwardRef(node: ts.Expression): ts.Expression;
8
+ /** Unwrap forwardRef(() => X) to X for OXC AST nodes. Returns the original node if not a forwardRef call. */
9
+ export declare function unwrapForwardRefOxc(node: any): any;
10
+ export { ANGULAR_DECORATORS } from "./constants.js";
@@ -0,0 +1,35 @@
1
+ import "./constants.js";
2
+ import * as ts from "typescript";
3
+ //#region packages/vite-plugin-angular/src/lib/compiler/utils.ts
4
+ /** Recursively find all class declarations in a source file, including nested scopes. */
5
+ function findAllClasses(sf) {
6
+ const result = [];
7
+ function walk(node) {
8
+ if (ts.isClassDeclaration(node)) result.push(node);
9
+ ts.forEachChild(node, walk);
10
+ }
11
+ walk(sf);
12
+ return result;
13
+ }
14
+ /** Unwrap forwardRef(() => X) to X for OXC AST nodes. Returns the original node if not a forwardRef call. */
15
+ function unwrapForwardRefOxc(node) {
16
+ if (node?.type === "CallExpression" && node.callee?.type === "Identifier" && node.callee.name === "forwardRef") {
17
+ const arg = node.arguments?.[0];
18
+ if (arg?.type === "ArrowFunctionExpression") if (arg.body?.type === "FunctionBody" || arg.body?.type === "BlockStatement") {
19
+ const stmt = (arg.body.statements || arg.body.body || [])[0];
20
+ if (stmt?.type === "ReturnStatement" && stmt.argument) return stmt.argument;
21
+ } else return arg.body;
22
+ if (arg?.type === "FunctionExpression") {
23
+ const stmts = arg.body?.statements || arg.body?.body || [];
24
+ if (stmts.length === 1) {
25
+ const stmt = stmts[0];
26
+ if (stmt?.type === "ReturnStatement" && stmt.argument) return stmt.argument;
27
+ }
28
+ }
29
+ }
30
+ return node;
31
+ }
32
+ //#endregion
33
+ export { findAllClasses, unwrapForwardRefOxc };
34
+
35
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../../../src/lib/compiler/utils.ts"],"sourcesContent":["import * as ts from 'typescript';\n\n/** Collect type-only imported names: `import type { X }` and `import { type X }`. */\nexport function collectTypeOnlyImports(sf: ts.SourceFile): Set<string> {\n const result = new Set<string>();\n for (const stmt of sf.statements) {\n if (!ts.isImportDeclaration(stmt) || !stmt.importClause) continue;\n const clause = stmt.importClause;\n if (clause.isTypeOnly) {\n if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {\n for (const el of clause.namedBindings.elements)\n result.add(el.name.text);\n }\n if (clause.name) result.add(clause.name.text);\n } else if (\n clause.namedBindings &&\n ts.isNamedImports(clause.namedBindings)\n ) {\n for (const el of clause.namedBindings.elements) {\n if (el.isTypeOnly) result.add(el.name.text);\n }\n }\n }\n return result;\n}\n\n/** Recursively find all class declarations in a source file, including nested scopes. */\nexport function findAllClasses(sf: ts.SourceFile): ts.ClassDeclaration[] {\n const result: ts.ClassDeclaration[] = [];\n function walk(node: ts.Node) {\n if (ts.isClassDeclaration(node)) result.push(node);\n ts.forEachChild(node, walk);\n }\n walk(sf);\n return result;\n}\n\n/** Unwrap forwardRef(() => X) to X. Returns the original node if not a forwardRef call. */\nexport function unwrapForwardRef(node: ts.Expression): ts.Expression {\n if (\n ts.isCallExpression(node) &&\n ts.isIdentifier(node.expression) &&\n node.expression.text === 'forwardRef'\n ) {\n const arg = node.arguments[0];\n if (arg && ts.isArrowFunction(arg)) {\n if (ts.isBlock(arg.body)) {\n const stmt = arg.body.statements[0];\n if (stmt && ts.isReturnStatement(stmt) && stmt.expression)\n return stmt.expression;\n } else {\n return arg.body;\n }\n }\n if (\n arg &&\n ts.isFunctionExpression(arg) &&\n arg.body.statements.length === 1\n ) {\n const stmt = arg.body.statements[0];\n if (ts.isReturnStatement(stmt) && stmt.expression) return stmt.expression;\n }\n }\n return node;\n}\n\n/** Unwrap forwardRef(() => X) to X for OXC AST nodes. Returns the original node if not a forwardRef call. */\nexport function unwrapForwardRefOxc(node: any): any {\n if (\n node?.type === 'CallExpression' &&\n node.callee?.type === 'Identifier' &&\n node.callee.name === 'forwardRef'\n ) {\n const arg = node.arguments?.[0];\n if (arg?.type === 'ArrowFunctionExpression') {\n if (\n arg.body?.type === 'FunctionBody' ||\n arg.body?.type === 'BlockStatement'\n ) {\n const stmts = arg.body.statements || arg.body.body || [];\n const stmt = stmts[0];\n if (stmt?.type === 'ReturnStatement' && stmt.argument)\n return stmt.argument;\n } else {\n // Expression body: forwardRef(() => X)\n return arg.body;\n }\n }\n if (arg?.type === 'FunctionExpression') {\n const stmts = arg.body?.statements || arg.body?.body || [];\n if (stmts.length === 1) {\n const stmt = stmts[0];\n if (stmt?.type === 'ReturnStatement' && stmt.argument)\n return stmt.argument;\n }\n }\n }\n return node;\n}\n\nexport { ANGULAR_DECORATORS } from './constants.js';\n"],"mappings":";;;;AA2BA,SAAgB,eAAe,IAA0C;CACvE,MAAM,SAAgC,EAAE;CACxC,SAAS,KAAK,MAAe;AAC3B,MAAI,GAAG,mBAAmB,KAAK,CAAE,QAAO,KAAK,KAAK;AAClD,KAAG,aAAa,MAAM,KAAK;;AAE7B,MAAK,GAAG;AACR,QAAO;;;AAiCT,SAAgB,oBAAoB,MAAgB;AAClD,KACE,MAAM,SAAS,oBACf,KAAK,QAAQ,SAAS,gBACtB,KAAK,OAAO,SAAS,cACrB;EACA,MAAM,MAAM,KAAK,YAAY;AAC7B,MAAI,KAAK,SAAS,0BAChB,KACE,IAAI,MAAM,SAAS,kBACnB,IAAI,MAAM,SAAS,kBACnB;GAEA,MAAM,QADQ,IAAI,KAAK,cAAc,IAAI,KAAK,QAAQ,EAAE,EACrC;AACnB,OAAI,MAAM,SAAS,qBAAqB,KAAK,SAC3C,QAAO,KAAK;QAGd,QAAO,IAAI;AAGf,MAAI,KAAK,SAAS,sBAAsB;GACtC,MAAM,QAAQ,IAAI,MAAM,cAAc,IAAI,MAAM,QAAQ,EAAE;AAC1D,OAAI,MAAM,WAAW,GAAG;IACtB,MAAM,OAAO,MAAM;AACnB,QAAI,MAAM,SAAS,qBAAqB,KAAK,SAC3C,QAAO,KAAK;;;;AAIpB,QAAO"}
@@ -1,5 +1,5 @@
1
1
  import { Plugin } from "vite";
2
- export interface AnalogCompilerPluginOptions {
2
+ export interface FastCompilePluginOptions {
3
3
  tsconfigGetter: () => string;
4
4
  workspaceRoot: string;
5
5
  inlineStylesExtension: string;
@@ -9,6 +9,6 @@ export interface AnalogCompilerPluginOptions {
9
9
  transformFilter?: (code: string, id: string) => boolean;
10
10
  isTest: boolean;
11
11
  isAstroIntegration: boolean;
12
- analogCompilationMode?: "full" | "partial";
12
+ fastCompileMode?: "full" | "partial";
13
13
  }
14
- export declare function analogCompilerPlugin(pluginOptions: AnalogCompilerPluginOptions): Plugin;
14
+ export declare function fastCompilePlugin(pluginOptions: FastCompilePluginOptions): Plugin;
@@ -1,21 +1,28 @@
1
- import { TS_EXT_REGEX, createDepOptimizerConfig, getTsConfigPath } from "./utils/plugin-config.js";
2
1
  import { toVirtualRawId, toVirtualStyleId } from "./utils/virtual-ids.js";
3
- import { loadVirtualRawModule, loadVirtualStyleModule, rewriteHtmlRawImport, rewriteInlineStyleImport } from "./utils/virtual-resources.js";
2
+ import { loadVirtualRawModule, loadVirtualStyleModule, rewriteHtmlRawImport, rewriteInlineStyleImport, shouldPreprocessTestCss } from "./utils/virtual-resources.js";
3
+ import { debugCompile, debugRegistry } from "./compiler/debug.js";
4
+ import { compile } from "./compiler/compile.js";
5
+ import { scanFile } from "./compiler/registry.js";
6
+ import { collectImportedPackages, collectRelativeReExports, scanPackageDts } from "./compiler/dts-reader.js";
7
+ import { jitTransform } from "./compiler/jit-transform.js";
8
+ import { generateHmrCode } from "./compiler/hmr.js";
9
+ import { extractInlineStyles, inlineResourceUrls } from "./compiler/resource-inliner.js";
10
+ import "./compiler/index.js";
11
+ import { TS_EXT_REGEX, createDepOptimizerConfig, getTsConfigPath } from "./utils/plugin-config.js";
4
12
  import { promises } from "node:fs";
5
13
  import { dirname, resolve } from "node:path";
6
14
  import * as compilerCli from "@angular/compiler-cli";
7
15
  import * as vite from "vite";
8
16
  import { defaultClientConditions, normalizePath, preprocessCSS } from "vite";
9
- import { collectImportedPackages, collectRelativeReExports, compile, debugCompile, debugRegistry, extractInlineStyles, generateHmrCode, inlineResourceUrls, jitTransform, scanFile, scanPackageDts } from "@analogjs/angular-compiler";
10
- //#region packages/vite-plugin-angular/src/lib/analog-compiler-plugin.ts
11
- function analogCompilerPlugin(pluginOptions) {
17
+ //#region packages/vite-plugin-angular/src/lib/fast-compile-plugin.ts
18
+ function fastCompilePlugin(pluginOptions) {
12
19
  let resolvedConfig;
13
20
  let tsConfigResolutionContext = null;
14
21
  let watchMode = false;
15
- const analogRegistry = /* @__PURE__ */ new Map();
16
- const analogResourceToSource = /* @__PURE__ */ new Map();
22
+ const registry = /* @__PURE__ */ new Map();
23
+ const resourceToSource = /* @__PURE__ */ new Map();
17
24
  const scannedDtsPackages = /* @__PURE__ */ new Set();
18
- let analogProjectRoot = "";
25
+ let projectRoot = "";
19
26
  let useDefineForClassFields = true;
20
27
  /**
21
28
  * Scan a file into the registry, then recursively walk its relative
@@ -40,7 +47,7 @@ function analogCompilerPlugin(pluginOptions) {
40
47
  return;
41
48
  }
42
49
  const entries = scanFile(code, file);
43
- for (const entry of entries) if (overwrite || !analogRegistry.has(entry.className)) analogRegistry.set(entry.className, entry);
50
+ for (const entry of entries) if (overwrite || !registry.has(entry.className)) registry.set(entry.className, entry);
44
51
  const dir = dirname(file);
45
52
  for (const rel of collectRelativeReExports(code, file)) {
46
53
  const normalizedRel = rel.replace(/\.(?:js|mjs)$/u, "");
@@ -55,17 +62,17 @@ function analogCompilerPlugin(pluginOptions) {
55
62
  if (!resolved && debugRegistry.enabled) debugRegistry("scanBarrelExports: %s re-export %s did not resolve to %o", file, rel, reExportCandidates);
56
63
  }
57
64
  }
58
- async function initAnalogCompiler() {
65
+ async function initFastCompile() {
59
66
  if (pluginOptions.jit) return;
60
- analogRegistry.clear();
67
+ registry.clear();
61
68
  scannedDtsPackages.clear();
62
69
  const resolvedTsConfigPath = resolveTsConfigPath();
63
- analogProjectRoot = dirname(resolvedTsConfigPath);
70
+ projectRoot = dirname(resolvedTsConfigPath);
64
71
  const config = compilerCli.readConfiguration(resolvedTsConfigPath);
65
72
  useDefineForClassFields = config.options?.useDefineForClassFields ?? true;
66
73
  const candidates = new Set(config.rootNames);
67
74
  const tsPaths = config.options?.paths;
68
- const baseUrl = config.options?.baseUrl ?? analogProjectRoot;
75
+ const baseUrl = config.options?.baseUrl ?? projectRoot;
69
76
  if (tsPaths) for (const targets of Object.values(tsPaths)) for (const target of targets) {
70
77
  if (target.includes("*")) continue;
71
78
  candidates.add(resolve(baseUrl, target));
@@ -74,11 +81,11 @@ function analogCompilerPlugin(pluginOptions) {
74
81
  try {
75
82
  return scanFile(await promises.readFile(file, "utf-8"), file);
76
83
  } catch (e) {
77
- if (debugRegistry.enabled) debugRegistry("initAnalogCompiler: skipping unreadable %s: %s", file, e?.message);
84
+ if (debugRegistry.enabled) debugRegistry("initFastCompile: skipping unreadable %s: %s", file, e?.message);
78
85
  return [];
79
86
  }
80
87
  }));
81
- for (const entries of results) for (const entry of entries) analogRegistry.set(entry.className, entry);
88
+ for (const entries of results) for (const entry of entries) registry.set(entry.className, entry);
82
89
  const buildStartVisited = /* @__PURE__ */ new Set();
83
90
  if (tsPaths) {
84
91
  const barrelCandidates = [];
@@ -88,19 +95,19 @@ function analogCompilerPlugin(pluginOptions) {
88
95
  }
89
96
  await Promise.all(barrelCandidates.map((c) => scanBarrelExports(c, buildStartVisited)));
90
97
  }
91
- debugRegistry("initAnalogCompiler done: %d entries from %d candidate files", analogRegistry.size, candidates.size);
98
+ debugRegistry("initFastCompile done: %d entries from %d candidate files", registry.size, candidates.size);
92
99
  }
93
100
  function ensureDtsRegistryForSource(code, id) {
94
101
  for (const pkg of collectImportedPackages(code, id)) {
95
102
  if (scannedDtsPackages.has(pkg)) continue;
96
103
  scannedDtsPackages.add(pkg);
97
104
  try {
98
- const dtsEntries = scanPackageDts(pkg, analogProjectRoot);
99
- for (const entry of dtsEntries) if (!analogRegistry.has(entry.className)) analogRegistry.set(entry.className, entry);
105
+ const dtsEntries = scanPackageDts(pkg, projectRoot);
106
+ for (const entry of dtsEntries) if (!registry.has(entry.className)) registry.set(entry.className, entry);
100
107
  } catch {}
101
108
  }
102
109
  }
103
- async function handleAnalogCompilerTransform(code, id) {
110
+ async function handleFastCompileTransform(code, id) {
104
111
  if (!/(Component|Directive|Pipe|Injectable|NgModule)\(/.test(code)) return;
105
112
  if (pluginOptions.jit) {
106
113
  const result = jitTransform(code, id);
@@ -118,6 +125,10 @@ function analogCompilerPlugin(pluginOptions) {
118
125
  resolvedInlineStyles = /* @__PURE__ */ new Map();
119
126
  for (let i = 0; i < styleStrings.length; i++) try {
120
127
  const fakePath = id.replace(/\.ts$/, `.inline-${i}.${pluginOptions.inlineStylesExtension}`);
128
+ if (!shouldPreprocessTestCss(resolvedConfig, fakePath)) {
129
+ resolvedInlineStyles.set(i, styleStrings[i]);
130
+ continue;
131
+ }
121
132
  const processed = await preprocessCSS(styleStrings[i], fakePath, resolvedConfig);
122
133
  resolvedInlineStyles.set(i, processed.code);
123
134
  } catch (e) {
@@ -128,13 +139,13 @@ function analogCompilerPlugin(pluginOptions) {
128
139
  }
129
140
  ensureDtsRegistryForSource(code, id);
130
141
  const result = compile(code, id, {
131
- registry: analogRegistry,
142
+ registry,
132
143
  resolvedStyles,
133
144
  resolvedInlineStyles,
134
145
  useDefineForClassFields,
135
- compilationMode: pluginOptions.analogCompilationMode
146
+ compilationMode: pluginOptions.fastCompileMode
136
147
  });
137
- for (const dep of result.resourceDependencies) analogResourceToSource.set(dep, id);
148
+ for (const dep of result.resourceDependencies) resourceToSource.set(dep, id);
138
149
  let outputCode = (vite.transformWithOxc ? await vite.transformWithOxc(result.code, id, {
139
150
  lang: "ts",
140
151
  sourcemap: false,
@@ -147,7 +158,7 @@ function analogCompilerPlugin(pluginOptions) {
147
158
  sourcemap: false
148
159
  })).code;
149
160
  if (watchMode && pluginOptions.liveReload) {
150
- const fileDeclarations = [...analogRegistry.values()].filter((e) => e.fileName === id);
161
+ const fileDeclarations = [...registry.values()].filter((e) => e.fileName === id);
151
162
  if (fileDeclarations.length > 0) {
152
163
  const localDepClassNames = fileDeclarations.map((e) => e.className);
153
164
  outputCode += generateHmrCode(fileDeclarations, localDepClassNames);
@@ -163,7 +174,7 @@ function analogCompilerPlugin(pluginOptions) {
163
174
  return getTsConfigPath(root, pluginOptions.tsconfigGetter(), isProd, pluginOptions.isTest, isLib);
164
175
  }
165
176
  return {
166
- name: "@analogjs/vite-plugin-angular-compiler",
177
+ name: "@analogjs/vite-plugin-angular-fast-compile",
167
178
  enforce: "pre",
168
179
  async config(config, { command }) {
169
180
  watchMode = command === "serve";
@@ -196,18 +207,18 @@ function analogCompilerPlugin(pluginOptions) {
196
207
  });
197
208
  },
198
209
  async buildStart() {
199
- await initAnalogCompiler();
210
+ await initFastCompile();
200
211
  },
201
212
  async handleHotUpdate(ctx) {
202
- if (analogResourceToSource.has(ctx.file)) {
203
- const parentSource = analogResourceToSource.get(ctx.file);
213
+ if (resourceToSource.has(ctx.file)) {
214
+ const parentSource = resourceToSource.get(ctx.file);
204
215
  const parentModule = ctx.server.moduleGraph.getModuleById(parentSource);
205
216
  if (parentModule) return [parentModule];
206
217
  }
207
218
  if (TS_EXT_REGEX.test(ctx.file)) {
208
219
  const [fileId] = ctx.file.split("?");
209
- const oldEntries = [...analogRegistry.entries()].filter(([_, v]) => v.fileName === fileId).map(([k]) => k);
210
- for (const key of oldEntries) analogRegistry.delete(key);
220
+ const oldEntries = [...registry.entries()].filter(([_, v]) => v.fileName === fileId).map(([k]) => k);
221
+ for (const key of oldEntries) registry.delete(key);
211
222
  await scanBarrelExports(fileId, /* @__PURE__ */ new Set(), true);
212
223
  }
213
224
  return ctx.modules;
@@ -230,7 +241,9 @@ function analogCompilerPlugin(pluginOptions) {
230
241
  if (rawModule !== void 0) return rawModule;
231
242
  if (/\.(css|scss|sass|less)\?inline$/.test(id)) {
232
243
  const filePath = id.split("?")[0];
233
- const result = await preprocessCSS(await promises.readFile(filePath, "utf-8"), filePath, resolvedConfig);
244
+ const code = await promises.readFile(filePath, "utf-8");
245
+ if (!shouldPreprocessTestCss(resolvedConfig, filePath)) return `export default ${JSON.stringify(code)}`;
246
+ const result = await preprocessCSS(code, filePath, resolvedConfig);
234
247
  return `export default ${JSON.stringify(result.code)}`;
235
248
  }
236
249
  },
@@ -246,12 +259,12 @@ function analogCompilerPlugin(pluginOptions) {
246
259
  async handler(code, id) {
247
260
  if (pluginOptions.transformFilter && !(pluginOptions.transformFilter(code, id) ?? true)) return;
248
261
  if (id.includes(".ts?")) id = id.replace(/\?(.*)/, "");
249
- return handleAnalogCompilerTransform(code, id);
262
+ return handleFastCompileTransform(code, id);
250
263
  }
251
264
  }
252
265
  };
253
266
  }
254
267
  //#endregion
255
- export { analogCompilerPlugin };
268
+ export { fastCompilePlugin };
256
269
 
257
- //# sourceMappingURL=analog-compiler-plugin.js.map
270
+ //# sourceMappingURL=fast-compile-plugin.js.map