@analogjs/vite-plugin-angular 3.0.0-alpha.59 → 3.0.0-alpha.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/lib/angular-vite-plugin.js +9 -3
- package/src/lib/angular-vite-plugin.js.map +1 -1
- package/src/lib/compiler/compile.d.ts +6 -0
- package/src/lib/compiler/compile.js +68 -9
- package/src/lib/compiler/compile.js.map +1 -1
- package/src/lib/compiler/constants.d.ts +6 -0
- package/src/lib/compiler/constants.js +7 -1
- package/src/lib/compiler/constants.js.map +1 -1
- package/src/lib/compiler/defer.js +1 -1
- package/src/lib/compiler/dts-reader.d.ts +13 -3
- package/src/lib/compiler/dts-reader.js +38 -8
- package/src/lib/compiler/dts-reader.js.map +1 -1
- package/src/lib/compiler/index.d.ts +2 -1
- package/src/lib/compiler/index.js +1 -0
- package/src/lib/compiler/jit-transform.js +1 -1
- package/src/lib/compiler/jit-transform.js.map +1 -1
- package/src/lib/compiler/js-emitter.js +2 -2
- package/src/lib/compiler/js-emitter.js.map +1 -1
- package/src/lib/compiler/metadata.d.ts +0 -9
- package/src/lib/compiler/metadata.js +67 -6
- package/src/lib/compiler/metadata.js.map +1 -1
- package/src/lib/compiler/registry.d.ts +8 -1
- package/src/lib/compiler/registry.js +15 -3
- package/src/lib/compiler/registry.js.map +1 -1
- package/src/lib/compiler/resource-inliner.d.ts +27 -5
- package/src/lib/compiler/resource-inliner.js +138 -60
- package/src/lib/compiler/resource-inliner.js.map +1 -1
- package/src/lib/compiler/style-ast.d.ts +2 -1
- package/src/lib/compiler/style-ast.js +2 -2
- package/src/lib/compiler/style-ast.js.map +1 -1
- package/src/lib/compiler/type-elision.d.ts +5 -3
- package/src/lib/compiler/type-elision.js +8 -8
- package/src/lib/compiler/type-elision.js.map +1 -1
- package/src/lib/compiler-plugin.d.ts +3 -2
- package/src/lib/compiler-plugin.js +4 -4
- package/src/lib/compiler-plugin.js.map +1 -1
- package/src/lib/component-resolvers.js +4 -5
- package/src/lib/component-resolvers.js.map +1 -1
- package/src/lib/fast-compile-plugin.js +136 -23
- package/src/lib/fast-compile-plugin.js.map +1 -1
- package/src/lib/router-plugin.js +13 -1
- package/src/lib/router-plugin.js.map +1 -1
- package/src/lib/utils/plugin-config.js +6 -3
- package/src/lib/utils/plugin-config.js.map +1 -1
- package/src/lib/utils/transform-cache.d.ts +43 -0
- package/src/lib/utils/transform-cache.js +97 -0
- package/src/lib/utils/transform-cache.js.map +1 -0
|
@@ -4,27 +4,45 @@ import * as path$1 from "node:path";
|
|
|
4
4
|
import { parseSync } from "oxc-parser";
|
|
5
5
|
import MagicString from "magic-string";
|
|
6
6
|
//#region packages/vite-plugin-angular/src/lib/compiler/resource-inliner.ts
|
|
7
|
+
/** Whether a node is an inline style value (string literal or single-quasi template). */
|
|
8
|
+
function isInlineStyleValue(node) {
|
|
9
|
+
return node?.type === "Literal" && typeof node.value === "string" || node?.type === "TemplateLiteral" && node.quasis?.length === 1;
|
|
10
|
+
}
|
|
11
|
+
/** Count the elements of a `styles: [...]` array that {@link extractInlineStyles} would emit. */
|
|
12
|
+
function countInlineStyleLiterals(arrayExpr) {
|
|
13
|
+
let count = 0;
|
|
14
|
+
for (const el of arrayExpr.elements ?? []) if (el?.type === "Literal" && typeof el.value === "string") count++;
|
|
15
|
+
else if (el?.type === "TemplateLiteral" && el.quasis?.length === 1) count++;
|
|
16
|
+
return count;
|
|
17
|
+
}
|
|
7
18
|
/**
|
|
8
19
|
* Inline external templateUrl and styleUrl/styleUrls into the source code
|
|
9
20
|
* using OXC parser for precise AST-based rewriting.
|
|
10
21
|
*
|
|
11
22
|
* Replaces:
|
|
12
23
|
* templateUrl: './file.html' → template: "...file contents..."
|
|
13
|
-
* styleUrl: './file.
|
|
14
|
-
* styleUrls: ['./a.
|
|
24
|
+
* styleUrl: './file.scss' → styles: ["...file contents..."]
|
|
25
|
+
* styleUrls: ['./a.scss'] → styles: ["...contents..."]
|
|
15
26
|
*
|
|
16
27
|
* When the decorator already has a `styles: [...]` array, inlined CSS is
|
|
17
28
|
* merged into that existing array instead of emitting a second `styles`
|
|
18
29
|
* property (which would be a duplicate object literal key).
|
|
19
30
|
*
|
|
20
|
-
* Returns the modified source code
|
|
31
|
+
* Returns the modified source code plus the per-style-index extension map
|
|
32
|
+
* (see {@link InlineResourceResult}).
|
|
21
33
|
*/
|
|
22
|
-
function inlineResourceUrls(code, fileName) {
|
|
23
|
-
|
|
34
|
+
async function inlineResourceUrls(code, fileName) {
|
|
35
|
+
const styleExtensions = /* @__PURE__ */ new Map();
|
|
36
|
+
if (!code.includes("templateUrl") && !code.includes("styleUrl")) return {
|
|
37
|
+
code,
|
|
38
|
+
styleExtensions,
|
|
39
|
+
resourceDependencies: []
|
|
40
|
+
};
|
|
24
41
|
const { program } = parseSync(fileName, code);
|
|
25
42
|
const ms = new MagicString(code);
|
|
26
43
|
let changed = false;
|
|
27
44
|
const dir = path$1.dirname(fileName);
|
|
45
|
+
const plans = [];
|
|
28
46
|
for (const node of program.body) {
|
|
29
47
|
const decl = node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration" ? node.declaration : node;
|
|
30
48
|
if (!decl || decl.type !== "ClassDeclaration") continue;
|
|
@@ -34,85 +52,145 @@ function inlineResourceUrls(code, fileName) {
|
|
|
34
52
|
if (expr.callee?.name !== "Component") continue;
|
|
35
53
|
const arg = expr.arguments?.[0];
|
|
36
54
|
if (!arg || arg.type !== "ObjectExpression") continue;
|
|
55
|
+
let existingStylesProp = null;
|
|
37
56
|
let existingStylesArray = null;
|
|
38
57
|
for (const prop of arg.properties) {
|
|
39
58
|
if (prop.type !== "Property") continue;
|
|
40
|
-
if ((prop.key?.name ?? prop.key?.value) === "styles"
|
|
41
|
-
|
|
59
|
+
if ((prop.key?.name ?? prop.key?.value) === "styles") {
|
|
60
|
+
existingStylesProp = prop;
|
|
61
|
+
if (prop.value?.type === "ArrayExpression") existingStylesArray = prop.value;
|
|
42
62
|
break;
|
|
43
63
|
}
|
|
44
64
|
}
|
|
45
|
-
const
|
|
46
|
-
const
|
|
65
|
+
const existingInlineCount = existingStylesArray ? countInlineStyleLiterals(existingStylesArray) : isInlineStyleValue(existingStylesProp?.value) ? 1 : 0;
|
|
66
|
+
const resourceProps = [];
|
|
47
67
|
for (const prop of arg.properties) {
|
|
48
68
|
if (prop.type !== "Property") continue;
|
|
49
69
|
const key = prop.key?.name ?? prop.key?.value;
|
|
50
70
|
const val = prop.value;
|
|
51
71
|
if (key === "templateUrl" && val?.type === "Literal" && typeof val.value === "string") {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
content
|
|
58
|
-
});
|
|
59
|
-
} catch {}
|
|
72
|
+
resourceProps.push({
|
|
73
|
+
kind: "templateUrl",
|
|
74
|
+
prop,
|
|
75
|
+
resolvedPath: path$1.resolve(dir, val.value)
|
|
76
|
+
});
|
|
60
77
|
continue;
|
|
61
78
|
}
|
|
62
79
|
if (key === "styleUrl" && val?.type === "Literal" && typeof val.value === "string") {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
contents: [content]
|
|
69
|
-
});
|
|
70
|
-
} catch {}
|
|
80
|
+
resourceProps.push({
|
|
81
|
+
kind: "styleUrl",
|
|
82
|
+
prop,
|
|
83
|
+
resolvedPath: path$1.resolve(dir, val.value)
|
|
84
|
+
});
|
|
71
85
|
continue;
|
|
72
86
|
}
|
|
73
87
|
if (key === "styleUrls" && val?.type === "ArrayExpression") {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
contents.push(fs.readFileSync(filePath, "utf-8"));
|
|
79
|
-
} catch {
|
|
80
|
-
allRead = false;
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
if (allRead && contents.length > 0) cssProps.push({
|
|
88
|
+
const resolvedPaths = [];
|
|
89
|
+
for (const el of val.elements) if (el?.type === "Literal" && typeof el.value === "string") resolvedPaths.push(path$1.resolve(dir, el.value));
|
|
90
|
+
resourceProps.push({
|
|
91
|
+
kind: "styleUrls",
|
|
84
92
|
prop,
|
|
85
|
-
|
|
93
|
+
resolvedPaths
|
|
86
94
|
});
|
|
87
95
|
}
|
|
88
96
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
97
|
+
plans.push({
|
|
98
|
+
existingStylesProp,
|
|
99
|
+
existingStylesArray,
|
|
100
|
+
existingInlineCount,
|
|
101
|
+
resourceProps
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const pathsToRead = /* @__PURE__ */ new Set();
|
|
106
|
+
for (const plan of plans) for (const rp of plan.resourceProps) if (rp.kind === "styleUrls") for (const p of rp.resolvedPaths) pathsToRead.add(p);
|
|
107
|
+
else pathsToRead.add(rp.resolvedPath);
|
|
108
|
+
const fileContents = /* @__PURE__ */ new Map();
|
|
109
|
+
await Promise.all([...pathsToRead].map(async (p) => {
|
|
110
|
+
fileContents.set(p, await fs.promises.readFile(p, "utf-8").catch(() => void 0));
|
|
111
|
+
}));
|
|
112
|
+
let flatStyleBase = 0;
|
|
113
|
+
for (const plan of plans) {
|
|
114
|
+
const { existingStylesProp, existingStylesArray, existingInlineCount } = plan;
|
|
115
|
+
const templateUrlRewrites = [];
|
|
116
|
+
const cssProps = [];
|
|
117
|
+
for (const rp of plan.resourceProps) if (rp.kind === "templateUrl") {
|
|
118
|
+
const content = fileContents.get(rp.resolvedPath);
|
|
119
|
+
if (content !== void 0) templateUrlRewrites.push({
|
|
120
|
+
prop: rp.prop,
|
|
121
|
+
content
|
|
122
|
+
});
|
|
123
|
+
} else if (rp.kind === "styleUrl") {
|
|
124
|
+
const content = fileContents.get(rp.resolvedPath);
|
|
125
|
+
if (content !== void 0) cssProps.push({
|
|
126
|
+
prop: rp.prop,
|
|
127
|
+
contents: [content],
|
|
128
|
+
extensions: [extensionOf(rp.resolvedPath)]
|
|
129
|
+
});
|
|
130
|
+
} else {
|
|
131
|
+
const contents = [];
|
|
132
|
+
const extensions = [];
|
|
133
|
+
let allRead = true;
|
|
134
|
+
for (const p of rp.resolvedPaths) {
|
|
135
|
+
const content = fileContents.get(p);
|
|
136
|
+
if (content === void 0) {
|
|
137
|
+
allRead = false;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
contents.push(content);
|
|
141
|
+
extensions.push(extensionOf(p));
|
|
92
142
|
}
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
143
|
+
if (allRead && contents.length > 0) cssProps.push({
|
|
144
|
+
prop: rp.prop,
|
|
145
|
+
contents,
|
|
146
|
+
extensions
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
for (const { prop, content } of templateUrlRewrites) {
|
|
150
|
+
ms.overwrite(prop.start, prop.end, `template: ${JSON.stringify(content)}`);
|
|
151
|
+
changed = true;
|
|
152
|
+
}
|
|
153
|
+
if (cssProps.length > 0) {
|
|
154
|
+
const allContents = cssProps.flatMap((c) => c.contents);
|
|
155
|
+
const allExtensions = cssProps.flatMap((c) => c.extensions);
|
|
156
|
+
if (existingStylesArray) {
|
|
157
|
+
const realElements = existingStylesArray.elements.filter((e) => e != null);
|
|
158
|
+
if (realElements.length > 0) {
|
|
159
|
+
const lastElement = realElements[realElements.length - 1];
|
|
160
|
+
const insertion = allContents.map((c) => `, ${JSON.stringify(c)}`).join("");
|
|
161
|
+
ms.appendRight(lastElement.end, insertion);
|
|
106
162
|
} else {
|
|
107
|
-
const
|
|
108
|
-
ms.
|
|
109
|
-
for (const { prop } of rest) removePropertyWithSeparator(ms, code, prop.start, prop.end);
|
|
163
|
+
const insertion = allContents.map((c) => JSON.stringify(c)).join(", ");
|
|
164
|
+
ms.appendLeft(existingStylesArray.end - 1, insertion);
|
|
110
165
|
}
|
|
111
|
-
|
|
166
|
+
for (const { prop } of cssProps) removePropertyWithSeparator(ms, code, prop.start, prop.end);
|
|
167
|
+
} else if (isInlineStyleValue(existingStylesProp?.value)) {
|
|
168
|
+
const original = code.slice(existingStylesProp.value.start, existingStylesProp.value.end);
|
|
169
|
+
const merged = allContents.map((c) => `, ${JSON.stringify(c)}`).join("");
|
|
170
|
+
ms.overwrite(existingStylesProp.value.start, existingStylesProp.value.end, `[${original}${merged}]`);
|
|
171
|
+
for (const { prop } of cssProps) removePropertyWithSeparator(ms, code, prop.start, prop.end);
|
|
172
|
+
} else {
|
|
173
|
+
const [first, ...rest] = cssProps;
|
|
174
|
+
ms.overwrite(first.prop.start, first.prop.end, `styles: [${allContents.map((c) => JSON.stringify(c)).join(", ")}]`);
|
|
175
|
+
for (const { prop } of rest) removePropertyWithSeparator(ms, code, prop.start, prop.end);
|
|
112
176
|
}
|
|
113
|
-
|
|
177
|
+
changed = true;
|
|
178
|
+
const externalBase = flatStyleBase + existingInlineCount;
|
|
179
|
+
allExtensions.forEach((ext, k) => {
|
|
180
|
+
if (ext) styleExtensions.set(externalBase + k, ext);
|
|
181
|
+
});
|
|
182
|
+
flatStyleBase = externalBase + allContents.length;
|
|
183
|
+
} else flatStyleBase += existingInlineCount;
|
|
114
184
|
}
|
|
115
|
-
return
|
|
185
|
+
return {
|
|
186
|
+
code: changed ? ms.toString() : code,
|
|
187
|
+
styleExtensions,
|
|
188
|
+
resourceDependencies: [...pathsToRead].filter((p) => fileContents.get(p) !== void 0)
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/** Lower-cased file extension without the leading dot (e.g. `scss`), or `''`. */
|
|
192
|
+
function extensionOf(filePath) {
|
|
193
|
+
return path$1.extname(filePath).slice(1).toLowerCase();
|
|
116
194
|
}
|
|
117
195
|
/**
|
|
118
196
|
* Remove a property from an object literal while also eating one surrounding
|
|
@@ -142,9 +220,9 @@ function isWhitespace(ch) {
|
|
|
142
220
|
* Extract inline style strings from Angular @Component decorator using OXC parser.
|
|
143
221
|
* Returns an array of style string values for preprocessing.
|
|
144
222
|
*/
|
|
145
|
-
function extractInlineStyles(code, fileName) {
|
|
223
|
+
function extractInlineStyles(code, fileName, program) {
|
|
146
224
|
if (!code.includes("styles")) return [];
|
|
147
|
-
return extractInlineStyles$1(code, fileName);
|
|
225
|
+
return extractInlineStyles$1(code, fileName, program);
|
|
148
226
|
}
|
|
149
227
|
//#endregion
|
|
150
228
|
export { extractInlineStyles, inlineResourceUrls };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource-inliner.js","names":[],"sources":["../../../../src/lib/compiler/resource-inliner.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { parseSync } from 'oxc-parser';\nimport MagicString from 'magic-string';\nimport { extractInlineStyles as extractStylesFromAst } from './style-ast.js';\n\n/**\n * Inline external templateUrl and styleUrl/styleUrls into the source code\n * using OXC parser for precise AST-based rewriting.\n *\n * Replaces:\n * templateUrl: './file.html' → template: \"...file contents...\"\n * styleUrl: './file.css' → styles: [\"...file contents...\"]\n * styleUrls: ['./a.css'] → styles: [\"...contents...\"]\n *\n * When the decorator already has a `styles: [...]` array, inlined CSS is\n * merged into that existing array instead of emitting a second `styles`\n * property (which would be a duplicate object literal key).\n *\n * Returns the modified source code, or the original if no changes were needed.\n */\nexport function inlineResourceUrls(code: string, fileName: string): string {\n if (!code.includes('templateUrl') && !code.includes('styleUrl')) {\n return code;\n }\n\n const { program } = parseSync(fileName, code);\n const ms = new MagicString(code);\n let changed = false;\n const dir = path.dirname(fileName);\n\n for (const node of program.body) {\n const decl =\n node.type === 'ExportNamedDeclaration' ||\n node.type === 'ExportDefaultDeclaration'\n ? (node as any).declaration\n : 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 // First pass: locate an existing `styles: [...]` array in the same\n // decorator. Inlined CSS will be merged into it to avoid duplicate keys.\n let existingStylesArray: any = null;\n for (const prop of arg.properties) {\n if (prop.type !== 'Property') continue;\n const key: string = prop.key?.name ?? prop.key?.value;\n if (key === 'styles' && prop.value?.type === 'ArrayExpression') {\n existingStylesArray = prop.value;\n break;\n }\n }\n\n // Collect the props we want to rewrite. Contents from styleUrl /\n // styleUrls are accumulated so that multiple url-based props in one\n // decorator collapse into a single `styles` array write.\n const templateUrlRewrites: Array<{ prop: any; content: string }> = [];\n const cssProps: Array<{ prop: any; contents: string[] }> = [];\n\n for (const prop of arg.properties) {\n if (prop.type !== 'Property') continue;\n const key: string = prop.key?.name ?? prop.key?.value;\n const val = prop.value;\n\n if (\n key === 'templateUrl' &&\n val?.type === 'Literal' &&\n typeof val.value === 'string'\n ) {\n try {\n const filePath = path.resolve(dir, val.value);\n const content = fs.readFileSync(filePath, 'utf-8');\n templateUrlRewrites.push({ prop, content });\n } catch {\n // Keep original if file can't be read\n }\n continue;\n }\n\n if (\n key === 'styleUrl' &&\n val?.type === 'Literal' &&\n typeof val.value === 'string'\n ) {\n try {\n const filePath = path.resolve(dir, val.value);\n const content = fs.readFileSync(filePath, 'utf-8');\n cssProps.push({ prop, contents: [content] });\n } catch {\n // Keep original if file can't be read\n }\n continue;\n }\n\n if (key === 'styleUrls' && val?.type === 'ArrayExpression') {\n const contents: string[] = [];\n let allRead = true;\n for (const el of val.elements) {\n if (el?.type === 'Literal' && typeof el.value === 'string') {\n try {\n const filePath = path.resolve(dir, el.value);\n contents.push(fs.readFileSync(filePath, 'utf-8'));\n } catch {\n allRead = false;\n break;\n }\n }\n }\n if (allRead && contents.length > 0) {\n cssProps.push({ prop, contents });\n }\n }\n }\n\n for (const { prop, content } of templateUrlRewrites) {\n ms.overwrite(\n prop.start,\n prop.end,\n `template: ${JSON.stringify(content)}`,\n );\n changed = true;\n }\n\n if (cssProps.length > 0) {\n const allContents = cssProps.flatMap((c) => c.contents);\n\n if (existingStylesArray) {\n // Filter out null holes in case the source already has a sparse array.\n const realElements = (existingStylesArray.elements as any[]).filter(\n (e) => e != null,\n );\n\n if (realElements.length > 0) {\n // Insert right after the last real element. This preserves any\n // trailing comma the source may have (e.g. Prettier output) and\n // avoids producing a sparse `[existing, , \"new\"]` element, which\n // would crash downstream decorator metadata extraction.\n const lastElement = realElements[realElements.length - 1];\n const insertion = allContents\n .map((c) => `, ${JSON.stringify(c)}`)\n .join('');\n ms.appendRight(lastElement.end, insertion);\n } else {\n // Empty array — drop the leading comma.\n const insertion = allContents\n .map((c) => JSON.stringify(c))\n .join(', ');\n ms.appendLeft(existingStylesArray.end - 1, insertion);\n }\n\n for (const { prop } of cssProps) {\n removePropertyWithSeparator(ms, code, prop.start, prop.end);\n }\n } else {\n // No existing `styles` array — rewrite the first styleUrl/styleUrls\n // prop with the merged contents and drop any additional ones so we\n // don't emit multiple `styles` properties.\n const [first, ...rest] = cssProps;\n ms.overwrite(\n first.prop.start,\n first.prop.end,\n `styles: [${allContents.map((c) => JSON.stringify(c)).join(', ')}]`,\n );\n for (const { prop } of rest) {\n removePropertyWithSeparator(ms, code, prop.start, prop.end);\n }\n }\n changed = true;\n }\n }\n }\n\n return changed ? ms.toString() : code;\n}\n\n/**\n * Remove a property from an object literal while also eating one surrounding\n * comma so the resulting object stays syntactically valid. Prefers the\n * trailing comma; falls back to the leading comma when the property is the\n * last entry in the object.\n */\nfunction removePropertyWithSeparator(\n ms: MagicString,\n code: string,\n propStart: number,\n propEnd: number,\n): void {\n let i = propEnd;\n while (i < code.length && isWhitespace(code[i])) i++;\n if (code[i] === ',') {\n ms.remove(propStart, i + 1);\n return;\n }\n let j = propStart - 1;\n while (j >= 0 && isWhitespace(code[j])) j--;\n if (code[j] === ',') {\n ms.remove(j, propEnd);\n return;\n }\n ms.remove(propStart, propEnd);\n}\n\nfunction isWhitespace(ch: string): boolean {\n return (\n ch === ' ' ||\n ch === '\\t' ||\n ch === '\\n' ||\n ch === '\\r' ||\n ch === '\\f' ||\n ch === '\\v'\n );\n}\n\n/**\n * Extract inline style strings from Angular @Component decorator using OXC parser.\n * Returns an array of style string values for preprocessing.\n */\nexport function extractInlineStyles(code: string, fileName: string): string[] {\n if (!code.includes('styles')) return [];\n return extractStylesFromAst(code, fileName);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resource-inliner.js","names":[],"sources":["../../../../src/lib/compiler/resource-inliner.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { parseSync } from 'oxc-parser';\nimport MagicString from 'magic-string';\nimport { extractInlineStyles as extractStylesFromAst } from './style-ast.js';\n\nexport interface InlineResourceResult {\n /** The modified source code, or the original if no changes were needed. */\n code: string;\n /**\n * Source extension (without the leading dot, lower-cased — e.g. `scss`) of\n * each inlined external style, keyed by its position in the flat per-file\n * style list produced by {@link extractInlineStyles}. Lets the caller run\n * each external `styleUrl` through the right preprocessor by its own file\n * extension, independent of the `inlineStylesExtension` option (which governs\n * truly-inline `styles: [...]` template strings).\n */\n styleExtensions: Map<number, string>;\n /**\n * Absolute paths of every external resource file successfully read and\n * inlined. Callers record these so a later edit to the resource file can\n * invalidate the owning source module — the inlined copy is otherwise\n * invisible to the module graph.\n */\n resourceDependencies: string[];\n}\n\n/** Whether a node is an inline style value (string literal or single-quasi template). */\nfunction isInlineStyleValue(node: any): boolean {\n return (\n (node?.type === 'Literal' && typeof node.value === 'string') ||\n (node?.type === 'TemplateLiteral' && node.quasis?.length === 1)\n );\n}\n\n/** Count the elements of a `styles: [...]` array that {@link extractInlineStyles} would emit. */\nfunction countInlineStyleLiterals(arrayExpr: any): number {\n let count = 0;\n for (const el of arrayExpr.elements ?? []) {\n if (el?.type === 'Literal' && typeof el.value === 'string') count++;\n else if (el?.type === 'TemplateLiteral' && el.quasis?.length === 1) count++;\n }\n return count;\n}\n\n/**\n * Inline external templateUrl and styleUrl/styleUrls into the source code\n * using OXC parser for precise AST-based rewriting.\n *\n * Replaces:\n * templateUrl: './file.html' → template: \"...file contents...\"\n * styleUrl: './file.scss' → styles: [\"...file contents...\"]\n * styleUrls: ['./a.scss'] → styles: [\"...contents...\"]\n *\n * When the decorator already has a `styles: [...]` array, inlined CSS is\n * merged into that existing array instead of emitting a second `styles`\n * property (which would be a duplicate object literal key).\n *\n * Returns the modified source code plus the per-style-index extension map\n * (see {@link InlineResourceResult}).\n */\nexport async function inlineResourceUrls(\n code: string,\n fileName: string,\n): Promise<InlineResourceResult> {\n const styleExtensions = new Map<number, string>();\n\n if (!code.includes('templateUrl') && !code.includes('styleUrl')) {\n return { code, styleExtensions, resourceDependencies: [] };\n }\n\n const { program } = parseSync(fileName, code);\n const ms = new MagicString(code);\n let changed = false;\n const dir = path.dirname(fileName);\n\n // Phase 1: walk the AST and collect resource descriptors per decorator,\n // in source order — no I/O during the walk. A plan is recorded for every\n // `@Component` decorator (even without url props) because the flat style\n // index bookkeeping below advances per decorator.\n type ResourceProp =\n | { kind: 'templateUrl'; prop: any; resolvedPath: string }\n | { kind: 'styleUrl'; prop: any; resolvedPath: string }\n | { kind: 'styleUrls'; prop: any; resolvedPaths: string[] };\n interface DecoratorPlan {\n existingStylesProp: any;\n existingStylesArray: any;\n existingInlineCount: number;\n resourceProps: ResourceProp[];\n }\n const plans: DecoratorPlan[] = [];\n\n for (const node of program.body) {\n const decl =\n node.type === 'ExportNamedDeclaration' ||\n node.type === 'ExportDefaultDeclaration'\n ? (node as any).declaration\n : 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 // First pass: locate an existing `styles` property in the same decorator.\n // Inlined CSS is merged into it — converting a singular string/template\n // value to an array when needed — to avoid emitting a duplicate `styles`\n // key.\n let existingStylesProp: any = null;\n let existingStylesArray: any = null;\n for (const prop of arg.properties) {\n if (prop.type !== 'Property') continue;\n const key: string = prop.key?.name ?? prop.key?.value;\n if (key === 'styles') {\n existingStylesProp = prop;\n if (prop.value?.type === 'ArrayExpression') {\n existingStylesArray = prop.value;\n }\n break;\n }\n }\n\n // Inline `styles` entries already present come first in the flat list;\n // appended external styles follow them.\n const existingInlineCount = existingStylesArray\n ? countInlineStyleLiterals(existingStylesArray)\n : isInlineStyleValue(existingStylesProp?.value)\n ? 1\n : 0;\n\n const resourceProps: ResourceProp[] = [];\n\n for (const prop of arg.properties) {\n if (prop.type !== 'Property') continue;\n const key: string = prop.key?.name ?? prop.key?.value;\n const val = prop.value;\n\n if (\n key === 'templateUrl' &&\n val?.type === 'Literal' &&\n typeof val.value === 'string'\n ) {\n resourceProps.push({\n kind: 'templateUrl',\n prop,\n resolvedPath: path.resolve(dir, val.value),\n });\n continue;\n }\n\n if (\n key === 'styleUrl' &&\n val?.type === 'Literal' &&\n typeof val.value === 'string'\n ) {\n resourceProps.push({\n kind: 'styleUrl',\n prop,\n resolvedPath: path.resolve(dir, val.value),\n });\n continue;\n }\n\n if (key === 'styleUrls' && val?.type === 'ArrayExpression') {\n const resolvedPaths: string[] = [];\n for (const el of val.elements) {\n if (el?.type === 'Literal' && typeof el.value === 'string') {\n resolvedPaths.push(path.resolve(dir, el.value));\n }\n }\n resourceProps.push({ kind: 'styleUrls', prop, resolvedPaths });\n }\n }\n\n plans.push({\n existingStylesProp,\n existingStylesArray,\n existingInlineCount,\n resourceProps,\n });\n }\n }\n\n // Phase 2: read every referenced resource without blocking the event\n // loop. Each read catches its own failure to `undefined` — one missing\n // file must not abort sibling reads that succeed.\n const pathsToRead = new Set<string>();\n for (const plan of plans) {\n for (const rp of plan.resourceProps) {\n if (rp.kind === 'styleUrls') {\n for (const p of rp.resolvedPaths) pathsToRead.add(p);\n } else {\n pathsToRead.add(rp.resolvedPath);\n }\n }\n }\n const fileContents = new Map<string, string | undefined>();\n await Promise.all(\n [...pathsToRead].map(async (p) => {\n fileContents.set(\n p,\n await fs.promises.readFile(p, 'utf-8').catch(() => undefined),\n );\n }),\n );\n\n // Phase 3: apply MagicString rewrites in the original AST order so the\n // flat style index bookkeeping matches `extractInlineStyles`.\n\n // Running base index into the flat per-file style list (matching the order\n // `extractInlineStyles` walks classes/decorators/properties) so external\n // styles can be mapped to the index `resolvedInlineStyles` later keys on.\n let flatStyleBase = 0;\n\n for (const plan of plans) {\n const { existingStylesProp, existingStylesArray, existingInlineCount } =\n plan;\n\n // Collect the props we want to rewrite. Contents from styleUrl /\n // styleUrls are accumulated so that multiple url-based props in one\n // decorator collapse into a single `styles` array write. `extensions`\n // tracks each content's source extension in the same order.\n const templateUrlRewrites: Array<{ prop: any; content: string }> = [];\n const cssProps: Array<{\n prop: any;\n contents: string[];\n extensions: string[];\n }> = [];\n\n for (const rp of plan.resourceProps) {\n if (rp.kind === 'templateUrl') {\n // Keep original if file can't be read\n const content = fileContents.get(rp.resolvedPath);\n if (content !== undefined) {\n templateUrlRewrites.push({ prop: rp.prop, content });\n }\n } else if (rp.kind === 'styleUrl') {\n // Keep original if file can't be read\n const content = fileContents.get(rp.resolvedPath);\n if (content !== undefined) {\n cssProps.push({\n prop: rp.prop,\n contents: [content],\n extensions: [extensionOf(rp.resolvedPath)],\n });\n }\n } else {\n // styleUrls is all-or-nothing: keep the original property if any\n // entry failed to read.\n const contents: string[] = [];\n const extensions: string[] = [];\n let allRead = true;\n for (const p of rp.resolvedPaths) {\n const content = fileContents.get(p);\n if (content === undefined) {\n allRead = false;\n break;\n }\n contents.push(content);\n extensions.push(extensionOf(p));\n }\n if (allRead && contents.length > 0) {\n cssProps.push({ prop: rp.prop, contents, extensions });\n }\n }\n }\n\n for (const { prop, content } of templateUrlRewrites) {\n ms.overwrite(\n prop.start,\n prop.end,\n `template: ${JSON.stringify(content)}`,\n );\n changed = true;\n }\n\n if (cssProps.length > 0) {\n const allContents = cssProps.flatMap((c) => c.contents);\n const allExtensions = cssProps.flatMap((c) => c.extensions);\n\n if (existingStylesArray) {\n // Filter out null holes in case the source already has a sparse array.\n const realElements = (existingStylesArray.elements as any[]).filter(\n (e) => e != null,\n );\n\n if (realElements.length > 0) {\n // Insert right after the last real element. This preserves any\n // trailing comma the source may have (e.g. Prettier output) and\n // avoids producing a sparse `[existing, , \"new\"]` element, which\n // would crash downstream decorator metadata extraction.\n const lastElement = realElements[realElements.length - 1];\n const insertion = allContents\n .map((c) => `, ${JSON.stringify(c)}`)\n .join('');\n ms.appendRight(lastElement.end, insertion);\n } else {\n // Empty array — drop the leading comma.\n const insertion = allContents\n .map((c) => JSON.stringify(c))\n .join(', ');\n ms.appendLeft(existingStylesArray.end - 1, insertion);\n }\n\n for (const { prop } of cssProps) {\n removePropertyWithSeparator(ms, code, prop.start, prop.end);\n }\n } else if (isInlineStyleValue(existingStylesProp?.value)) {\n // Singular `styles: '...'` / `styles: \\`...\\`` — wrap the original\n // value into an array merged with the inlined styles, then drop the\n // styleUrl props so only one `styles` key remains.\n const original = code.slice(\n existingStylesProp.value.start,\n existingStylesProp.value.end,\n );\n const merged = allContents\n .map((c) => `, ${JSON.stringify(c)}`)\n .join('');\n ms.overwrite(\n existingStylesProp.value.start,\n existingStylesProp.value.end,\n `[${original}${merged}]`,\n );\n for (const { prop } of cssProps) {\n removePropertyWithSeparator(ms, code, prop.start, prop.end);\n }\n } else {\n // No existing `styles` array — rewrite the first styleUrl/styleUrls\n // prop with the merged contents and drop any additional ones so we\n // don't emit multiple `styles` properties.\n const [first, ...rest] = cssProps;\n ms.overwrite(\n first.prop.start,\n first.prop.end,\n `styles: [${allContents.map((c) => JSON.stringify(c)).join(', ')}]`,\n );\n for (const { prop } of rest) {\n removePropertyWithSeparator(ms, code, prop.start, prop.end);\n }\n }\n changed = true;\n\n // The appended external styles occupy the flat indices after any\n // pre-existing inline styles for this component.\n const externalBase = flatStyleBase + existingInlineCount;\n allExtensions.forEach((ext, k) => {\n if (ext) styleExtensions.set(externalBase + k, ext);\n });\n flatStyleBase = externalBase + allContents.length;\n } else {\n flatStyleBase += existingInlineCount;\n }\n }\n\n return {\n code: changed ? ms.toString() : code,\n styleExtensions,\n resourceDependencies: [...pathsToRead].filter(\n (p) => fileContents.get(p) !== undefined,\n ),\n };\n}\n\n/** Lower-cased file extension without the leading dot (e.g. `scss`), or `''`. */\nfunction extensionOf(filePath: string): string {\n return path.extname(filePath).slice(1).toLowerCase();\n}\n\n/**\n * Remove a property from an object literal while also eating one surrounding\n * comma so the resulting object stays syntactically valid. Prefers the\n * trailing comma; falls back to the leading comma when the property is the\n * last entry in the object.\n */\nfunction removePropertyWithSeparator(\n ms: MagicString,\n code: string,\n propStart: number,\n propEnd: number,\n): void {\n let i = propEnd;\n while (i < code.length && isWhitespace(code[i])) i++;\n if (code[i] === ',') {\n ms.remove(propStart, i + 1);\n return;\n }\n let j = propStart - 1;\n while (j >= 0 && isWhitespace(code[j])) j--;\n if (code[j] === ',') {\n ms.remove(j, propEnd);\n return;\n }\n ms.remove(propStart, propEnd);\n}\n\nfunction isWhitespace(ch: string): boolean {\n return (\n ch === ' ' ||\n ch === '\\t' ||\n ch === '\\n' ||\n ch === '\\r' ||\n ch === '\\f' ||\n ch === '\\v'\n );\n}\n\n/**\n * Extract inline style strings from Angular @Component decorator using OXC parser.\n * Returns an array of style string values for preprocessing.\n */\nexport function extractInlineStyles(\n code: string,\n fileName: string,\n program?: ReturnType<typeof parseSync>['program'],\n): string[] {\n if (!code.includes('styles')) return [];\n return extractStylesFromAst(code, fileName, program);\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseSync } from "oxc-parser";
|
|
1
2
|
/**
|
|
2
3
|
* Extract styleUrl/styleUrls values from Angular @Component decorators.
|
|
3
4
|
*/
|
|
@@ -5,4 +6,4 @@ export declare function extractStyleUrls(code: string, fileName: string): string
|
|
|
5
6
|
/**
|
|
6
7
|
* Extract inline style strings from Angular @Component decorators.
|
|
7
8
|
*/
|
|
8
|
-
export declare function extractInlineStyles(code: string, fileName: string): string[];
|
|
9
|
+
export declare function extractInlineStyles(code: string, fileName: string, program?: ReturnType<typeof parseSync>["program"]): string[];
|
|
@@ -13,9 +13,9 @@ function getTemplateLiteralText(node) {
|
|
|
13
13
|
/**
|
|
14
14
|
* Extract inline style strings from Angular @Component decorators.
|
|
15
15
|
*/
|
|
16
|
-
function extractInlineStyles(code, fileName) {
|
|
16
|
+
function extractInlineStyles(code, fileName, program) {
|
|
17
17
|
const styles = [];
|
|
18
|
-
|
|
18
|
+
program ??= parseSync(fileName, code).program;
|
|
19
19
|
for (const node of program.body) {
|
|
20
20
|
const decl = getClassDeclaration(node);
|
|
21
21
|
if (!decl || decl.type !== "ClassDeclaration") continue;
|
|
@@ -1 +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
|
|
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(\n code: string,\n fileName: string,\n program?: ReturnType<typeof parseSync>['program'],\n): string[] {\n const styles: string[] = [];\n program ??= parseSync(fileName, code).program;\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,oBACd,MACA,UACA,SACU;CACV,MAAM,SAAmB,EAAE;AAC3B,aAAY,UAAU,UAAU,KAAK,CAAC;AAEtC,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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import MagicString from "magic-string";
|
|
2
|
+
import { parseSync } from "oxc-parser";
|
|
2
3
|
/**
|
|
3
4
|
* Analyse compiled TypeScript source and return the set of imported names
|
|
4
5
|
* that are only referenced in type positions (type annotations, implements
|
|
@@ -6,7 +7,7 @@ import MagicString from "magic-string";
|
|
|
6
7
|
*
|
|
7
8
|
* Uses oxc-parser for fast, Rust-based AST analysis — no type-checker needed.
|
|
8
9
|
*/
|
|
9
|
-
export declare function detectTypeOnlyImportNames(code: string): Set<string>;
|
|
10
|
+
export declare function detectTypeOnlyImportNames(code: string, program?: ReturnType<typeof parseSync>["program"]): Set<string>;
|
|
10
11
|
/**
|
|
11
12
|
* Elide import specifiers (or entire import declarations) for names that are
|
|
12
13
|
* only used in type positions. Operates on the compiler's TypeScript output
|
|
@@ -21,6 +22,7 @@ export declare function elideTypeOnlyImports(code: string): string;
|
|
|
21
22
|
*
|
|
22
23
|
* Detects type-only names from `ms.toString()` (the fully-mutated code),
|
|
23
24
|
* but reads import positions from `ms.original` (the coordinate system
|
|
24
|
-
* MagicString expects).
|
|
25
|
+
* MagicString expects). `originalProgram`, when provided, must be a parse
|
|
26
|
+
* of `ms.original` — never of the mutated text.
|
|
25
27
|
*/
|
|
26
|
-
export declare function elideTypeOnlyImportsMagicString(ms: MagicString): void;
|
|
28
|
+
export declare function elideTypeOnlyImportsMagicString(ms: MagicString, originalProgram?: ReturnType<typeof parseSync>["program"]): void;
|
|
@@ -44,8 +44,8 @@ var TYPE_NODE_TYPES = new Set([
|
|
|
44
44
|
* Internal helper: parse code and return both the AST and the set of
|
|
45
45
|
* type-only imported names. Avoids double-parsing when both are needed.
|
|
46
46
|
*/
|
|
47
|
-
function analyzeTypeOnlyImports(code) {
|
|
48
|
-
const ast = parseSync("file.ts", code).program;
|
|
47
|
+
function analyzeTypeOnlyImports(code, program) {
|
|
48
|
+
const ast = program ?? parseSync("file.ts", code).program;
|
|
49
49
|
const importedNames = /* @__PURE__ */ new Set();
|
|
50
50
|
for (const node of ast.body) {
|
|
51
51
|
if (node.type !== "ImportDeclaration") continue;
|
|
@@ -147,8 +147,8 @@ function collectTypeReferenceNames(typeNode, importedNames, valueReferenced) {
|
|
|
147
147
|
*
|
|
148
148
|
* Uses oxc-parser for fast, Rust-based AST analysis — no type-checker needed.
|
|
149
149
|
*/
|
|
150
|
-
function detectTypeOnlyImportNames(code) {
|
|
151
|
-
return analyzeTypeOnlyImports(code).typeOnlyNames;
|
|
150
|
+
function detectTypeOnlyImportNames(code, program) {
|
|
151
|
+
return analyzeTypeOnlyImports(code, program).typeOnlyNames;
|
|
152
152
|
}
|
|
153
153
|
/**
|
|
154
154
|
* Apply import elision edits to a MagicString instance using positions from
|
|
@@ -197,13 +197,13 @@ function applyElisionEdits(ms, ast, src, typeOnlyNames) {
|
|
|
197
197
|
*
|
|
198
198
|
* Detects type-only names from `ms.toString()` (the fully-mutated code),
|
|
199
199
|
* but reads import positions from `ms.original` (the coordinate system
|
|
200
|
-
* MagicString expects).
|
|
200
|
+
* MagicString expects). `originalProgram`, when provided, must be a parse
|
|
201
|
+
* of `ms.original` — never of the mutated text.
|
|
201
202
|
*/
|
|
202
|
-
function elideTypeOnlyImportsMagicString(ms) {
|
|
203
|
+
function elideTypeOnlyImportsMagicString(ms, originalProgram) {
|
|
203
204
|
const typeOnlyNames = detectTypeOnlyImportNames(ms.toString());
|
|
204
205
|
if (typeOnlyNames.size === 0) return;
|
|
205
|
-
|
|
206
|
-
applyElisionEdits(ms, ast, ms.original, typeOnlyNames);
|
|
206
|
+
applyElisionEdits(ms, originalProgram ?? parseSync("file.ts", ms.original).program, ms.original, typeOnlyNames);
|
|
207
207
|
}
|
|
208
208
|
//#endregion
|
|
209
209
|
export { detectTypeOnlyImportNames, elideTypeOnlyImportsMagicString };
|
|
@@ -1 +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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"}
|
|
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(\n code: string,\n program?: ReturnType<typeof parseSync>['program'],\n): {\n ast: any;\n typeOnlyNames: Set<string>;\n} {\n const ast = program ?? 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(\n code: string,\n program?: ReturnType<typeof parseSync>['program'],\n): Set<string> {\n return analyzeTypeOnlyImports(code, program).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). `originalProgram`, when provided, must be a parse\n * of `ms.original` — never of the mutated text.\n */\nexport function elideTypeOnlyImportsMagicString(\n ms: MagicString,\n originalProgram?: ReturnType<typeof parseSync>['program'],\n): 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 = originalProgram ?? parseSync('file.ts', ms.original).program;\n applyElisionEdits(ms, ast, ms.original, typeOnlyNames);\n}\n"],"mappings":"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"}
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { DepOptimizationConfig, Rolldown } from "vite";
|
|
9
9
|
import { CompilerPluginOptions } from "./utils/devkit.js";
|
|
10
|
+
import type { TransformCacheStore } from "./utils/transform-cache.js";
|
|
10
11
|
type EsbuildOptions = NonNullable<DepOptimizationConfig["esbuildOptions"]>;
|
|
11
12
|
type EsbuildPlugin = NonNullable<EsbuildOptions["plugins"]>[number];
|
|
12
|
-
export declare function createCompilerPlugin(pluginOptions: CompilerPluginOptions, isTest: boolean, closeTransformer: boolean): EsbuildPlugin;
|
|
13
|
-
export declare function createRolldownCompilerPlugin(pluginOptions: CompilerPluginOptions, isTest: boolean, closeTransformer: boolean): Rolldown.Plugin;
|
|
13
|
+
export declare function createCompilerPlugin(pluginOptions: CompilerPluginOptions, isTest: boolean, closeTransformer: boolean, cache?: TransformCacheStore): EsbuildPlugin;
|
|
14
|
+
export declare function createRolldownCompilerPlugin(pluginOptions: CompilerPluginOptions, isTest: boolean, closeTransformer: boolean, cache?: TransformCacheStore): Rolldown.Plugin;
|
|
14
15
|
export {};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jt } from "./utils/devkit.js";
|
|
2
2
|
//#region packages/vite-plugin-angular/src/lib/compiler-plugin.ts
|
|
3
|
-
function createCompilerPlugin(pluginOptions, isTest, closeTransformer) {
|
|
3
|
+
function createCompilerPlugin(pluginOptions, isTest, closeTransformer, cache) {
|
|
4
4
|
const javascriptTransformer = new jt({
|
|
5
5
|
...pluginOptions,
|
|
6
6
|
jit: true
|
|
7
|
-
}, 1);
|
|
7
|
+
}, 1, cache);
|
|
8
8
|
return {
|
|
9
9
|
name: "analogjs-angular-esbuild-deps-optimizer-plugin",
|
|
10
10
|
async setup(build) {
|
|
@@ -18,11 +18,11 @@ function createCompilerPlugin(pluginOptions, isTest, closeTransformer) {
|
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
-
function createRolldownCompilerPlugin(pluginOptions, isTest, closeTransformer) {
|
|
21
|
+
function createRolldownCompilerPlugin(pluginOptions, isTest, closeTransformer, cache) {
|
|
22
22
|
const javascriptTransformer = new jt({
|
|
23
23
|
...pluginOptions,
|
|
24
24
|
jit: true
|
|
25
|
-
}, 1);
|
|
25
|
+
}, 1, cache);
|
|
26
26
|
const plugin = { name: "analogjs-rolldown-deps-optimizer-plugin" };
|
|
27
27
|
if (!isTest) plugin.load = {
|
|
28
28
|
filter: { id: /\.[cm]?js$/ },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compiler-plugin.js","names":[],"sources":["../../../src/lib/compiler-plugin.ts"],"sourcesContent":["/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport type { DepOptimizationConfig, Rolldown } from 'vite';\nimport type { PluginBuild } from 'esbuild';\n\nimport {\n CompilerPluginOptions,\n JavaScriptTransformer,\n} from './utils/devkit.js';\n\ntype EsbuildOptions = NonNullable<DepOptimizationConfig['esbuildOptions']>;\ntype EsbuildPlugin = NonNullable<EsbuildOptions['plugins']>[number];\n\nexport function createCompilerPlugin(\n pluginOptions: CompilerPluginOptions,\n isTest: boolean,\n closeTransformer: boolean,\n): EsbuildPlugin {\n const javascriptTransformer = new JavaScriptTransformer(\n { ...pluginOptions, jit: true },\n 1,\n );\n\n return {\n name: 'analogjs-angular-esbuild-deps-optimizer-plugin',\n async setup(build: PluginBuild) {\n if (!isTest) {\n build.onLoad({ filter: /\\.[cm]?js$/ }, async (args) => {\n const contents = await javascriptTransformer.transformFile(args.path);\n\n return {\n contents,\n loader: 'js',\n };\n });\n }\n\n if (closeTransformer) {\n build.onEnd(() => javascriptTransformer.close());\n }\n },\n };\n}\n\nexport function createRolldownCompilerPlugin(\n pluginOptions: CompilerPluginOptions,\n isTest: boolean,\n closeTransformer: boolean,\n): Rolldown.Plugin {\n const javascriptTransformer = new JavaScriptTransformer(\n { ...pluginOptions, jit: true },\n 1,\n );\n\n const plugin: Rolldown.Plugin = {\n name: 'analogjs-rolldown-deps-optimizer-plugin',\n };\n\n if (!isTest) {\n plugin.load = {\n filter: {\n id: /\\.[cm]?js$/,\n },\n async handler(id) {\n const contents = await javascriptTransformer.transformFile(id);\n\n return {\n code: Buffer.from(contents).toString('utf-8'),\n loader: 'js',\n } as any;\n },\n };\n }\n\n if (closeTransformer) {\n plugin.buildEnd = () => javascriptTransformer.close();\n }\n\n return plugin;\n}\n"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"compiler-plugin.js","names":[],"sources":["../../../src/lib/compiler-plugin.ts"],"sourcesContent":["/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport type { DepOptimizationConfig, Rolldown } from 'vite';\nimport type { PluginBuild } from 'esbuild';\n\nimport {\n CompilerPluginOptions,\n JavaScriptTransformer,\n} from './utils/devkit.js';\nimport type { TransformCacheStore } from './utils/transform-cache.js';\n\ntype EsbuildOptions = NonNullable<DepOptimizationConfig['esbuildOptions']>;\ntype EsbuildPlugin = NonNullable<EsbuildOptions['plugins']>[number];\n\nexport function createCompilerPlugin(\n pluginOptions: CompilerPluginOptions,\n isTest: boolean,\n closeTransformer: boolean,\n cache?: TransformCacheStore,\n): EsbuildPlugin {\n const javascriptTransformer = new JavaScriptTransformer(\n { ...pluginOptions, jit: true },\n 1,\n cache,\n );\n\n return {\n name: 'analogjs-angular-esbuild-deps-optimizer-plugin',\n async setup(build: PluginBuild) {\n if (!isTest) {\n build.onLoad({ filter: /\\.[cm]?js$/ }, async (args) => {\n const contents = await javascriptTransformer.transformFile(args.path);\n\n return {\n contents,\n loader: 'js',\n };\n });\n }\n\n if (closeTransformer) {\n build.onEnd(() => javascriptTransformer.close());\n }\n },\n };\n}\n\nexport function createRolldownCompilerPlugin(\n pluginOptions: CompilerPluginOptions,\n isTest: boolean,\n closeTransformer: boolean,\n cache?: TransformCacheStore,\n): Rolldown.Plugin {\n const javascriptTransformer = new JavaScriptTransformer(\n { ...pluginOptions, jit: true },\n 1,\n cache,\n );\n\n const plugin: Rolldown.Plugin = {\n name: 'analogjs-rolldown-deps-optimizer-plugin',\n };\n\n if (!isTest) {\n plugin.load = {\n filter: {\n id: /\\.[cm]?js$/,\n },\n async handler(id) {\n const contents = await javascriptTransformer.transformFile(id);\n\n return {\n code: Buffer.from(contents).toString('utf-8'),\n loader: 'js',\n } as any;\n },\n };\n }\n\n if (closeTransformer) {\n plugin.buildEnd = () => javascriptTransformer.close();\n }\n\n return plugin;\n}\n"],"mappings":";;AAoBA,SAAgB,qBACd,eACA,QACA,kBACA,OACe;CACf,MAAM,wBAAwB,IAAI,GAChC;EAAE,GAAG;EAAe,KAAK;EAAM,EAC/B,GACA,MACD;AAED,QAAO;EACL,MAAM;EACN,MAAM,MAAM,OAAoB;AAC9B,OAAI,CAAC,OACH,OAAM,OAAO,EAAE,QAAQ,cAAc,EAAE,OAAO,SAAS;AAGrD,WAAO;KACL,UAHe,MAAM,sBAAsB,cAAc,KAAK,KAAK;KAInE,QAAQ;KACT;KACD;AAGJ,OAAI,iBACF,OAAM,YAAY,sBAAsB,OAAO,CAAC;;EAGrD;;AAGH,SAAgB,6BACd,eACA,QACA,kBACA,OACiB;CACjB,MAAM,wBAAwB,IAAI,GAChC;EAAE,GAAG;EAAe,KAAK;EAAM,EAC/B,GACA,MACD;CAED,MAAM,SAA0B,EAC9B,MAAM,2CACP;AAED,KAAI,CAAC,OACH,QAAO,OAAO;EACZ,QAAQ,EACN,IAAI,cACL;EACD,MAAM,QAAQ,IAAI;GAChB,MAAM,WAAW,MAAM,sBAAsB,cAAc,GAAG;AAE9D,UAAO;IACL,MAAM,OAAO,KAAK,SAAS,CAAC,SAAS,QAAQ;IAC7C,QAAQ;IACT;;EAEJ;AAGH,KAAI,iBACF,QAAO,iBAAiB,sBAAsB,OAAO;AAGvD,QAAO"}
|
|
@@ -127,15 +127,14 @@ function getTemplateUrls(code) {
|
|
|
127
127
|
var StyleUrlsResolver = class {
|
|
128
128
|
styleUrlsCache = /* @__PURE__ */ new Map();
|
|
129
129
|
resolve(code, id) {
|
|
130
|
-
const matchedStyleUrls = getStyleUrls(code);
|
|
131
130
|
const entry = this.styleUrlsCache.get(id);
|
|
132
|
-
if (entry
|
|
133
|
-
const styleUrls =
|
|
131
|
+
if (entry?.code === code) return entry.styleUrls;
|
|
132
|
+
const styleUrls = getStyleUrls(code).map((styleUrlPath) => {
|
|
134
133
|
return `${styleUrlPath}|${normalizePath(resolve(dirname(id), styleUrlPath))}`;
|
|
135
134
|
});
|
|
136
135
|
this.styleUrlsCache.set(id, {
|
|
137
|
-
|
|
138
|
-
|
|
136
|
+
code,
|
|
137
|
+
styleUrls
|
|
139
138
|
});
|
|
140
139
|
return styleUrls;
|
|
141
140
|
}
|