@analogjs/vite-plugin-angular 3.0.0-alpha.99 → 3.0.0-beta.2

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 (85) hide show
  1. package/migrations/migrate-setup-vitest/migrate-setup-vitest.js.map +1 -1
  2. package/migrations/update-3-0-0/migrate-setup-vitest.js +3 -2
  3. package/migrations/update-3-0-0/migrate-setup-vitest.js.map +1 -1
  4. package/package.json +1 -1
  5. package/src/lib/analog-plugin-interop.js +1 -1
  6. package/src/lib/analog-plugin-interop.js.map +1 -1
  7. package/src/lib/angular-build-optimizer-plugin.js +1 -1
  8. package/src/lib/angular-build-optimizer-plugin.js.map +1 -1
  9. package/src/lib/angular-jit-plugin.js.map +1 -1
  10. package/src/lib/angular-vite-plugin.js +20 -7
  11. package/src/lib/angular-vite-plugin.js.map +1 -1
  12. package/src/lib/angular-vitest-plugin.js +1 -1
  13. package/src/lib/angular-vitest-plugin.js.map +1 -1
  14. package/src/lib/compilation-api/compilation-api-plugin.js +6 -5
  15. package/src/lib/compilation-api/compilation-api-plugin.js.map +1 -1
  16. package/src/lib/compilation-api/index.js +2 -1
  17. package/src/lib/compiler/angular-version.js +1 -1
  18. package/src/lib/compiler/angular-version.js.map +1 -1
  19. package/src/lib/compiler/class-field-lowering.js.map +1 -1
  20. package/src/lib/compiler/compile.js +22 -19
  21. package/src/lib/compiler/compile.js.map +1 -1
  22. package/src/lib/compiler/constants.js +4 -4
  23. package/src/lib/compiler/constants.js.map +1 -1
  24. package/src/lib/compiler/debug.js.map +1 -1
  25. package/src/lib/compiler/defer.js +1 -1
  26. package/src/lib/compiler/defer.js.map +1 -1
  27. package/src/lib/compiler/dts-reader.js +1 -1
  28. package/src/lib/compiler/dts-reader.js.map +1 -1
  29. package/src/lib/compiler/hmr.js.map +1 -1
  30. package/src/lib/compiler/index.js +9 -8
  31. package/src/lib/compiler/jit-metadata.js.map +1 -1
  32. package/src/lib/compiler/jit-transform.js +3 -5
  33. package/src/lib/compiler/jit-transform.js.map +1 -1
  34. package/src/lib/compiler/js-emitter.js +2 -4
  35. package/src/lib/compiler/js-emitter.js.map +1 -1
  36. package/src/lib/compiler/metadata.js +10 -12
  37. package/src/lib/compiler/metadata.js.map +1 -1
  38. package/src/lib/compiler/registry.js.map +1 -1
  39. package/src/lib/compiler/resource-inliner.js.map +1 -1
  40. package/src/lib/compiler/style-ast.js.map +1 -1
  41. package/src/lib/compiler/type-elision.js +2 -2
  42. package/src/lib/compiler/type-elision.js.map +1 -1
  43. package/src/lib/compiler/utils.js +7 -5
  44. package/src/lib/compiler/utils.js.map +1 -1
  45. package/src/lib/compiler-plugin.js +7 -0
  46. package/src/lib/compiler-plugin.js.map +1 -1
  47. package/src/lib/component-resolvers.js +1 -1
  48. package/src/lib/component-resolvers.js.map +1 -1
  49. package/src/lib/encapsulation-plugin.js +3 -2
  50. package/src/lib/encapsulation-plugin.js.map +1 -1
  51. package/src/lib/fast-compile-plugin.js +3 -2
  52. package/src/lib/fast-compile-plugin.js.map +1 -1
  53. package/src/lib/host.js.map +1 -1
  54. package/src/lib/hot-update-batch.js +1 -1
  55. package/src/lib/hot-update-batch.js.map +1 -1
  56. package/src/lib/live-reload-plugin.js.map +1 -1
  57. package/src/lib/nx-folder-plugin.js.map +1 -1
  58. package/src/lib/plugins/file-replacements.plugin.js.map +1 -1
  59. package/src/lib/router-plugin.js.map +1 -1
  60. package/src/lib/style-preprocessor.js.map +1 -1
  61. package/src/lib/stylesheet-registry.js.map +1 -1
  62. package/src/lib/tools/src/builders/vite/vite-build.impl.js +2 -1
  63. package/src/lib/tools/src/builders/vite/vite-build.impl.js.map +1 -1
  64. package/src/lib/tools/src/builders/vite-dev-server/dev-server.impl.js +2 -1
  65. package/src/lib/tools/src/builders/vite-dev-server/dev-server.impl.js.map +1 -1
  66. package/src/lib/utils/compilation-shared.js.map +1 -1
  67. package/src/lib/utils/css-extension-resolver.js.map +1 -1
  68. package/src/lib/utils/css-preprocessor-workers.js.map +1 -1
  69. package/src/lib/utils/debug-harness.js.map +1 -1
  70. package/src/lib/utils/debug-log-file.js +2 -1
  71. package/src/lib/utils/debug-log-file.js.map +1 -1
  72. package/src/lib/utils/debug.js.map +1 -1
  73. package/src/lib/utils/devkit.js +1 -1
  74. package/src/lib/utils/devkit.js.map +1 -1
  75. package/src/lib/utils/jit-inline-styles.js.map +1 -1
  76. package/src/lib/utils/plugin-config.js.map +1 -1
  77. package/src/lib/utils/rolldown.js.map +1 -1
  78. package/src/lib/utils/safe-module-paths.js.map +1 -1
  79. package/src/lib/utils/transform-cache.js.map +1 -1
  80. package/src/lib/utils/tsconfig-resolver.js +2 -1
  81. package/src/lib/utils/tsconfig-resolver.js.map +1 -1
  82. package/src/lib/utils/virtual-ids.js.map +1 -1
  83. package/src/lib/utils/virtual-resources.js.map +1 -1
  84. package/src/lib/virtual-modules-plugin.js +4 -1
  85. package/src/lib/virtual-modules-plugin.js.map +1 -1
@@ -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\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
+ {"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 +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(\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
+ {"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;CACnD,OAAO,KAAK,SAAS,4BACnB,KAAK,SAAS,6BACX,KAAa,cACd;AACN;AAEA,SAAS,eAAe,MAA+B;CACrD,OAAO,KAAK,KAAK,QAAQ,KAAK,KAAK;AACrC;AAEA,SAAS,uBAAuB,MAA+B;CAC7D,IAAI,MAAM,SAAS,qBAAqB,KAAK,QAAQ,WAAW,GAC9D;CAGF,OAAO,KAAK,OAAO,EAAE,CAAC,MAAM,UAAU,KAAK,OAAO,EAAE,CAAC,MAAM;AAC7D;;;;AAmDA,SAAgB,oBACd,MACA,UACA,SACU;CACV,MAAM,SAAmB,CAAC;CAC1B,YAAY,UAAU,UAAU,IAAI,CAAC,CAAC;CAEtC,KAAK,MAAM,QAAQ,QAAQ,MAAM;EAC/B,MAAM,OAAO,oBAAoB,IAAI;EACrC,IAAI,CAAC,QAAQ,KAAK,SAAS,oBAAoB;EAE/C,KAAK,MAAM,OAAO,KAAK,cAAc,CAAC,GAAG;GACvC,MAAM,OAAO,IAAI;GACjB,IAAI,CAAC,QAAQ,KAAK,SAAS,kBAAkB;GAC7C,IAAI,KAAK,QAAQ,SAAS,aAAa;GAEvC,MAAM,MAAM,KAAK,YAAY;GAC7B,IAAI,CAAC,OAAO,IAAI,SAAS,oBAAoB;GAE7C,KAAK,MAAM,QAAQ,IAAI,YAAY;IACjC,IAAI,KAAK,SAAS,YAAY;IAC9B,MAAM,MAAM,eAAe,IAAI;IAC/B,MAAM,MAAM,KAAK;IAEjB,IAAI,QAAQ,UAAU;IAEtB,IAAI,KAAK,SAAS,mBAChB,KAAK,MAAM,MAAM,IAAI,UAAU;KAC7B,IAAI,IAAI,SAAS,aAAa,OAAO,GAAG,UAAU,UAAU;MAC1D,OAAO,KAAK,GAAG,KAAK;MACpB;KACF;KAEA,MAAM,eAAe,uBAAuB,EAAE;KAC9C,IAAI,iBAAiB,KAAA,GACnB,OAAO,KAAK,YAAY;IAE5B;SACK,IAAI,KAAK,SAAS,aAAa,OAAO,IAAI,UAAU,UACzD,OAAO,KAAK,IAAI,KAAK;SAChB;KACL,MAAM,eAAe,uBAAuB,GAAG;KAC/C,IAAI,iBAAiB,KAAA,GACnB,OAAO,KAAK,YAAY;IAE5B;GACF;EACF;CACF;CAEA,OAAO;AACT"}
@@ -5,7 +5,7 @@ import "magic-string";
5
5
  * AST property keys that represent type-only positions in TypeScript.
6
6
  * Identifiers found only under these keys are never emitted as runtime values.
7
7
  */
8
- var TYPE_POSITION_KEYS = new Set([
8
+ var TYPE_POSITION_KEYS = /* @__PURE__ */ new Set([
9
9
  "typeAnnotation",
10
10
  "typeParameters",
11
11
  "superTypeParameters",
@@ -17,7 +17,7 @@ var TYPE_POSITION_KEYS = new Set([
17
17
  * AST node types that represent type-level constructs.
18
18
  * Any identifier nested inside one of these nodes is in a type position.
19
19
  */
20
- var TYPE_NODE_TYPES = new Set([
20
+ var TYPE_NODE_TYPES = /* @__PURE__ */ new Set([
21
21
  "TSTypeAnnotation",
22
22
  "TSTypeReference",
23
23
  "TSTypeParameterInstantiation",
@@ -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(\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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"}
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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"}
@@ -16,10 +16,12 @@ function findAllClasses(sf) {
16
16
  function unwrapForwardRefOxc(node) {
17
17
  if (node?.type === "CallExpression" && node.callee?.type === "Identifier" && node.callee.name === "forwardRef") {
18
18
  const arg = node.arguments?.[0];
19
- if (arg?.type === "ArrowFunctionExpression") if (arg.body?.type === "FunctionBody" || arg.body?.type === "BlockStatement") {
20
- const stmt = (arg.body.statements || arg.body.body || [])[0];
21
- if (stmt?.type === "ReturnStatement" && stmt.argument) return stmt.argument;
22
- } else return arg.body;
19
+ if (arg?.type === "ArrowFunctionExpression") {
20
+ if (arg.body?.type === "FunctionBody" || arg.body?.type === "BlockStatement") {
21
+ const stmt = (arg.body.statements || arg.body.body || [])[0];
22
+ if (stmt?.type === "ReturnStatement" && stmt.argument) return stmt.argument;
23
+ } else return arg.body;
24
+ }
23
25
  if (arg?.type === "FunctionExpression") {
24
26
  const stmts = arg.body?.statements || arg.body?.body || [];
25
27
  if (stmts.length === 1) {
@@ -71,6 +73,6 @@ function deriveRelativeImportPath(fromFile, toFile) {
71
73
  return rel;
72
74
  }
73
75
  //#endregion
74
- export { findAllClasses, resolveSyntheticImportSpecifier, unwrapForwardRefOxc };
76
+ export { deriveRelativeImportPath, findAllClasses, resolveSyntheticImportSpecifier, unwrapForwardRefOxc };
75
77
 
76
78
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","names":[],"sources":["../../../../src/lib/compiler/utils.ts"],"sourcesContent":["import * as ts from 'typescript';\nimport * as path from 'node:path';\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\n/**\n * Resolve the module specifier to use for a synthetic import of a registry\n * declaration referenced from `currentFileName`.\n *\n * Order of preference:\n * 1. `entry.sourcePackage` — external packages scanned from `.d.ts` resolve\n * via their package name (proper barrel exports).\n * 2. A relative path derived from `currentFileName` to `entry.fileName` — for\n * local workspace classes scanned from real source files. This is what\n * NgModule/tuple export expansion must use: the class lives in its own\n * file, not the module/barrel file that re-exports it, so importing from\n * the module specifier fails with\n * `\"X\" is not exported by \".../the.module.ts\"`.\n * 3. `fallbackSpecifier` — the import path of the module/tuple itself, used\n * for `.d.ts` entries (whose file path is not a usable import target) and\n * when the registry has no file location for the class.\n *\n * Returns `undefined` when the class is declared in `currentFileName` itself\n * (already in scope — no synthetic import needed).\n */\nexport function resolveSyntheticImportSpecifier(\n currentFileName: string,\n entry: { sourcePackage?: string; fileName?: string },\n fallbackSpecifier: string | undefined,\n): string | undefined {\n if (entry.sourcePackage) return entry.sourcePackage;\n // `.d.ts` entries come from scanning external package type declarations;\n // their file path is not a usable relative import target, so defer to the\n // module/tuple specifier the consumer already imports from.\n if (entry.fileName && !entry.fileName.endsWith('.d.ts')) {\n if (entry.fileName === currentFileName) return undefined;\n return deriveRelativeImportPath(currentFileName, entry.fileName);\n }\n return fallbackSpecifier;\n}\n\n/**\n * Derive a relative module specifier from `fromFile` to `toFile`, suitable for\n * a synthetic `import { … } from '…'` statement: strips the file extension,\n * normalizes to POSIX separators, and ensures an explicit `./` prefix.\n */\nexport function deriveRelativeImportPath(\n fromFile: string,\n toFile: string,\n): string {\n let rel = path.relative(path.dirname(fromFile), toFile);\n rel = rel.replace(/\\.[cm]?[jt]sx?$/, '');\n rel = rel.split(path.sep).join('/');\n if (!rel.startsWith('.')) rel = './' + rel;\n return rel;\n}\n\nexport { ANGULAR_DECORATORS } from './constants.js';\n"],"mappings":";;;;;AA4BA,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;;;;;;;;;;;;;;;;;;;;;;AAuBT,SAAgB,gCACd,iBACA,OACA,mBACoB;AACpB,KAAI,MAAM,cAAe,QAAO,MAAM;AAItC,KAAI,MAAM,YAAY,CAAC,MAAM,SAAS,SAAS,QAAQ,EAAE;AACvD,MAAI,MAAM,aAAa,gBAAiB,QAAO,KAAA;AAC/C,SAAO,yBAAyB,iBAAiB,MAAM,SAAS;;AAElE,QAAO;;;;;;;AAQT,SAAgB,yBACd,UACA,QACQ;CACR,IAAI,MAAM,OAAK,SAAS,OAAK,QAAQ,SAAS,EAAE,OAAO;AACvD,OAAM,IAAI,QAAQ,mBAAmB,GAAG;AACxC,OAAM,IAAI,MAAM,OAAK,IAAI,CAAC,KAAK,IAAI;AACnC,KAAI,CAAC,IAAI,WAAW,IAAI,CAAE,OAAM,OAAO;AACvC,QAAO"}
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../../../src/lib/compiler/utils.ts"],"sourcesContent":["import * as ts from 'typescript';\nimport * as path from 'node:path';\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\n/**\n * Resolve the module specifier to use for a synthetic import of a registry\n * declaration referenced from `currentFileName`.\n *\n * Order of preference:\n * 1. `entry.sourcePackage` — external packages scanned from `.d.ts` resolve\n * via their package name (proper barrel exports).\n * 2. A relative path derived from `currentFileName` to `entry.fileName` — for\n * local workspace classes scanned from real source files. This is what\n * NgModule/tuple export expansion must use: the class lives in its own\n * file, not the module/barrel file that re-exports it, so importing from\n * the module specifier fails with\n * `\"X\" is not exported by \".../the.module.ts\"`.\n * 3. `fallbackSpecifier` — the import path of the module/tuple itself, used\n * for `.d.ts` entries (whose file path is not a usable import target) and\n * when the registry has no file location for the class.\n *\n * Returns `undefined` when the class is declared in `currentFileName` itself\n * (already in scope — no synthetic import needed).\n */\nexport function resolveSyntheticImportSpecifier(\n currentFileName: string,\n entry: { sourcePackage?: string; fileName?: string },\n fallbackSpecifier: string | undefined,\n): string | undefined {\n if (entry.sourcePackage) return entry.sourcePackage;\n // `.d.ts` entries come from scanning external package type declarations;\n // their file path is not a usable relative import target, so defer to the\n // module/tuple specifier the consumer already imports from.\n if (entry.fileName && !entry.fileName.endsWith('.d.ts')) {\n if (entry.fileName === currentFileName) return undefined;\n return deriveRelativeImportPath(currentFileName, entry.fileName);\n }\n return fallbackSpecifier;\n}\n\n/**\n * Derive a relative module specifier from `fromFile` to `toFile`, suitable for\n * a synthetic `import { … } from '…'` statement: strips the file extension,\n * normalizes to POSIX separators, and ensures an explicit `./` prefix.\n */\nexport function deriveRelativeImportPath(\n fromFile: string,\n toFile: string,\n): string {\n let rel = path.relative(path.dirname(fromFile), toFile);\n rel = rel.replace(/\\.[cm]?[jt]sx?$/, '');\n rel = rel.split(path.sep).join('/');\n if (!rel.startsWith('.')) rel = './' + rel;\n return rel;\n}\n\nexport { ANGULAR_DECORATORS } from './constants.js';\n"],"mappings":";;;;;AA4BA,SAAgB,eAAe,IAA0C;CACvE,MAAM,SAAgC,CAAC;CACvC,SAAS,KAAK,MAAe;EAC3B,IAAI,GAAG,mBAAmB,IAAI,GAAG,OAAO,KAAK,IAAI;EACjD,GAAG,aAAa,MAAM,IAAI;CAC5B;CACA,KAAK,EAAE;CACP,OAAO;AACT;;AAgCA,SAAgB,oBAAoB,MAAgB;CAClD,IACE,MAAM,SAAS,oBACf,KAAK,QAAQ,SAAS,gBACtB,KAAK,OAAO,SAAS,cACrB;EACA,MAAM,MAAM,KAAK,YAAY;EAC7B,IAAI,KAAK,SAAS,2BAA2B;GAC3C,IACE,IAAI,MAAM,SAAS,kBACnB,IAAI,MAAM,SAAS,kBACnB;IAEA,MAAM,QADQ,IAAI,KAAK,cAAc,IAAI,KAAK,QAAQ,CAAC,EAAA,CACpC;IACnB,IAAI,MAAM,SAAS,qBAAqB,KAAK,UAC3C,OAAO,KAAK;GAChB,OAEE,OAAO,IAAI;EAEf;EACA,IAAI,KAAK,SAAS,sBAAsB;GACtC,MAAM,QAAQ,IAAI,MAAM,cAAc,IAAI,MAAM,QAAQ,CAAC;GACzD,IAAI,MAAM,WAAW,GAAG;IACtB,MAAM,OAAO,MAAM;IACnB,IAAI,MAAM,SAAS,qBAAqB,KAAK,UAC3C,OAAO,KAAK;GAChB;EACF;CACF;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,gCACd,iBACA,OACA,mBACoB;CACpB,IAAI,MAAM,eAAe,OAAO,MAAM;CAItC,IAAI,MAAM,YAAY,CAAC,MAAM,SAAS,SAAS,OAAO,GAAG;EACvD,IAAI,MAAM,aAAa,iBAAiB,OAAO,KAAA;EAC/C,OAAO,yBAAyB,iBAAiB,MAAM,QAAQ;CACjE;CACA,OAAO;AACT;;;;;;AAOA,SAAgB,yBACd,UACA,QACQ;CACR,IAAI,MAAM,OAAK,SAAS,OAAK,QAAQ,QAAQ,GAAG,MAAM;CACtD,MAAM,IAAI,QAAQ,mBAAmB,EAAE;CACvC,MAAM,IAAI,MAAM,OAAK,GAAG,CAAC,CAAC,KAAK,GAAG;CAClC,IAAI,CAAC,IAAI,WAAW,GAAG,GAAG,MAAM,OAAO;CACvC,OAAO;AACT"}
@@ -1,5 +1,12 @@
1
1
  import { JavaScriptTransformer } from "./utils/devkit.js";
2
2
  //#region packages/vite-plugin-angular/src/lib/compiler-plugin.ts
3
+ /**
4
+ * @license
5
+ * Copyright Google LLC All Rights Reserved.
6
+ *
7
+ * Use of this source code is governed by an MIT-style license that can be
8
+ * found in the LICENSE file at https://angular.dev/license
9
+ */
3
10
  function createCompilerPlugin(pluginOptions, isTest, closeTransformer, cache) {
4
11
  let javascriptTransformer;
5
12
  function getTransformer() {
@@ -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';\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 let javascriptTransformer: InstanceType<typeof JavaScriptTransformer>;\n function getTransformer() {\n return (javascriptTransformer ??= new JavaScriptTransformer(\n { ...pluginOptions, jit: true },\n 1,\n cache,\n ));\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 getTransformer().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 let javascriptTransformer: InstanceType<typeof JavaScriptTransformer>;\n function getTransformer() {\n return (javascriptTransformer ??= new JavaScriptTransformer(\n { ...pluginOptions, jit: true },\n 1,\n cache,\n ));\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 getTransformer().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,IAAI;CACJ,SAAS,iBAAiB;AACxB,SAAQ,0BAA0B,IAAI,sBACpC;GAAE,GAAG;GAAe,KAAK;GAAM,EAC/B,GACA,MACD;;AAGH,QAAO;EACL,MAAM;EACN,MAAM,MAAM,OAAoB;AAC9B,OAAI,CAAC,OACH,OAAM,OAAO,EAAE,QAAQ,cAAc,EAAE,OAAO,SAAS;AAGrD,WAAO;KACL,UAHe,MAAM,gBAAgB,CAAC,cAAc,KAAK,KAAK;KAI9D,QAAQ;KACT;KACD;AAGJ,OAAI,iBACF,OAAM,YAAY,uBAAuB,OAAO,CAAC;;EAGtD;;AAGH,SAAgB,6BACd,eACA,QACA,kBACA,OACiB;CACjB,IAAI;CACJ,SAAS,iBAAiB;AACxB,SAAQ,0BAA0B,IAAI,sBACpC;GAAE,GAAG;GAAe,KAAK;GAAM,EAC/B,GACA,MACD;;CAGH,MAAM,SAA0B,EAC9B,MAAM,2CACP;AAED,KAAI,CAAC,OACH,QAAO,OAAO;EACZ,QAAQ,EACN,IAAI,cACL;EACD,MAAM,QAAQ,IAAI;GAChB,MAAM,WAAW,MAAM,gBAAgB,CAAC,cAAc,GAAG;AAEzD,UAAO;IACL,MAAM,OAAO,KAAK,SAAS,CAAC,SAAS,QAAQ;IAC7C,QAAQ;IACT;;EAEJ;AAGH,KAAI,iBACF,QAAO,iBAAiB,uBAAuB,OAAO;AAGxD,QAAO"}
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 let javascriptTransformer: InstanceType<typeof JavaScriptTransformer>;\n function getTransformer() {\n return (javascriptTransformer ??= new JavaScriptTransformer(\n { ...pluginOptions, jit: true },\n 1,\n cache,\n ));\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 getTransformer().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 let javascriptTransformer: InstanceType<typeof JavaScriptTransformer>;\n function getTransformer() {\n return (javascriptTransformer ??= new JavaScriptTransformer(\n { ...pluginOptions, jit: true },\n 1,\n cache,\n ));\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 getTransformer().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,IAAI;CACJ,SAAS,iBAAiB;EACxB,OAAQ,0BAA0B,IAAI,sBACpC;GAAE,GAAG;GAAe,KAAK;EAAK,GAC9B,GACA,KACF;CACF;CAEA,OAAO;EACL,MAAM;EACN,MAAM,MAAM,OAAoB;GAC9B,IAAI,CAAC,QACH,MAAM,OAAO,EAAE,QAAQ,aAAa,GAAG,OAAO,SAAS;IAGrD,OAAO;KACL,UAAA,MAHqB,eAAe,CAAC,CAAC,cAAc,KAAK,IAAI;KAI7D,QAAQ;IACV;GACF,CAAC;GAGH,IAAI,kBACF,MAAM,YAAY,uBAAuB,MAAM,CAAC;EAEpD;CACF;AACF;AAEA,SAAgB,6BACd,eACA,QACA,kBACA,OACiB;CACjB,IAAI;CACJ,SAAS,iBAAiB;EACxB,OAAQ,0BAA0B,IAAI,sBACpC;GAAE,GAAG;GAAe,KAAK;EAAK,GAC9B,GACA,KACF;CACF;CAEA,MAAM,SAA0B,EAC9B,MAAM,0CACR;CAEA,IAAI,CAAC,QACH,OAAO,OAAO;EACZ,QAAQ,EACN,IAAI,aACN;EACA,MAAM,QAAQ,IAAI;GAChB,MAAM,WAAW,MAAM,eAAe,CAAC,CAAC,cAAc,EAAE;GAExD,OAAO;IACL,MAAM,OAAO,KAAK,QAAQ,CAAC,CAAC,SAAS,OAAO;IAC5C,QAAQ;GACV;EACF;CACF;CAGF,IAAI,kBACF,OAAO,iBAAiB,uBAAuB,MAAM;CAGvD,OAAO;AACT"}
@@ -107,6 +107,6 @@ var TemplateUrlsResolver = class {
107
107
  }
108
108
  };
109
109
  //#endregion
110
- export { StyleUrlsResolver, TemplateUrlsResolver };
110
+ export { StyleUrlsResolver, TemplateUrlsResolver, getStyleUrls, getTemplateUrls };
111
111
 
112
112
  //# sourceMappingURL=component-resolvers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"component-resolvers.js","names":[],"sources":["../../../src/lib/component-resolvers.ts"],"sourcesContent":["import { dirname, resolve } from 'node:path';\n// OXC parser (native Rust, NAPI-RS) replaces ts-morph for AST extraction.\n// It is ~10-50x faster for the narrow task of pulling property values from\n// Angular component decorators. The Visitor helper from Rolldown walks the\n// ESTree-compatible AST that OXC produces.\nimport { parseSync } from 'oxc-parser';\nimport { Visitor } from 'rolldown/utils';\nimport { normalizePath } from 'vite';\n\n// ---------------------------------------------------------------------------\n// AST helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Extracts a string value from an ESTree AST node.\n *\n * Handles three forms that Angular decorators may use:\n * - `Literal` with a string value → `'./foo.css'` / `\"./foo.css\"`\n * - `StringLiteral` (OXC-specific) → same representation\n * - `TemplateLiteral` with zero expressions → `` `./foo.css` ``\n *\n * Uses `any` because OXC's AST mixes standard ESTree nodes with\n * OXC-specific variants (e.g. `StringLiteral`), and the project's\n * tsconfig enforces `noPropertyAccessFromIndexSignature`.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction getStringValue(node: any): string | undefined {\n if (!node) return undefined;\n // Standard ESTree Literal (string value)\n if (node.type === 'Literal' && typeof node.value === 'string') {\n return node.value;\n }\n // OXC-specific StringLiteral node\n if (node.type === 'StringLiteral') {\n return node.value;\n }\n // Template literal with no interpolation (e.g., `./foo.css`)\n if (\n node.type === 'TemplateLiteral' &&\n node.expressions.length === 0 &&\n node.quasis.length === 1\n ) {\n return node.quasis[0].value.cooked ?? node.quasis[0].value.raw;\n }\n return undefined;\n}\n\n/**\n * Parses TypeScript/JS source with OXC and collects `styleUrl`, `styleUrls`,\n * and `templateUrl` property values from Angular `@Component()` decorators\n * in a single AST pass.\n *\n * This replaces the previous ts-morph implementation — OXC parses natively\n * via Rust NAPI bindings, avoiding the overhead of spinning up a full\n * TypeScript `Project` for each file.\n */\nfunction collectComponentUrls(code: string): {\n styleUrls: string[];\n templateUrls: string[];\n inlineTemplates: string[];\n} {\n const { program } = parseSync('cmp.ts', code);\n const styleUrls: string[] = [];\n const templateUrls: string[] = [];\n const inlineTemplates: string[] = [];\n\n const visitor = new Visitor({\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ClassDeclaration(node: any) {\n const decorators = node.decorators ?? [];\n for (const decorator of decorators) {\n const expression = decorator.expression;\n if (\n expression?.type !== 'CallExpression' ||\n expression.callee?.type !== 'Identifier' ||\n expression.callee.name !== 'Component'\n ) {\n continue;\n }\n\n const componentArg = expression.arguments?.[0];\n if (componentArg?.type !== 'ObjectExpression') {\n continue;\n }\n\n for (const property of componentArg.properties ?? []) {\n if (\n property?.type !== 'Property' ||\n property.key?.type !== 'Identifier'\n ) {\n continue;\n }\n\n const name = property.key.name;\n\n if (\n name === 'styleUrls' &&\n property.value?.type === 'ArrayExpression'\n ) {\n for (const el of property.value.elements) {\n const val = getStringValue(el);\n if (val !== undefined) styleUrls.push(val);\n }\n }\n\n if (name === 'styleUrl') {\n const val = getStringValue(property.value);\n if (val !== undefined) styleUrls.push(val);\n }\n\n if (name === 'templateUrl') {\n const val = getStringValue(property.value);\n if (val !== undefined) templateUrls.push(val);\n }\n\n if (name === 'template') {\n const val = getStringValue(property.value);\n if (val !== undefined) inlineTemplates.push(val);\n }\n }\n }\n },\n });\n visitor.visit(program);\n\n return { styleUrls, templateUrls, inlineTemplates };\n}\n\nexport interface AngularComponentMetadata {\n className: string;\n selector?: string;\n styleUrls: string[];\n templateUrls: string[];\n inlineTemplates: string[];\n}\n\n/**\n * Extract Angular component identities from raw source code before Angular's\n * compilation pipeline strips decorators. This is used for dev-time\n * diagnostics such as duplicate selectors, duplicate component class names,\n * selectorless shared components, and inline-template validation.\n */\nexport function getAngularComponentMetadata(\n code: string,\n): AngularComponentMetadata[] {\n const { program } = parseSync('cmp.ts', code);\n const components: AngularComponentMetadata[] = [];\n\n const visitor = new Visitor({\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ClassDeclaration(node: any) {\n const decorators = node.decorators ?? [];\n for (const decorator of decorators) {\n const expression = decorator.expression;\n if (\n expression?.type !== 'CallExpression' ||\n expression.callee?.type !== 'Identifier' ||\n expression.callee.name !== 'Component'\n ) {\n continue;\n }\n\n const componentArg = expression.arguments?.[0];\n if (componentArg?.type !== 'ObjectExpression') {\n continue;\n }\n\n const metadata: AngularComponentMetadata = {\n className: node.id?.name ?? '(anonymous)',\n styleUrls: [],\n templateUrls: [],\n inlineTemplates: [],\n };\n\n for (const property of componentArg.properties ?? []) {\n if (\n property?.type !== 'Property' ||\n property.key?.type !== 'Identifier'\n ) {\n continue;\n }\n\n const name = property.key.name;\n if (name === 'selector') {\n metadata.selector = getStringValue(property.value);\n } else if (name === 'styleUrl') {\n const val = getStringValue(property.value);\n if (val !== undefined) {\n metadata.styleUrls.push(val);\n }\n } else if (\n name === 'styleUrls' &&\n property.value?.type === 'ArrayExpression'\n ) {\n for (const el of property.value.elements ?? []) {\n const val = getStringValue(el);\n if (val !== undefined) {\n metadata.styleUrls.push(val);\n }\n }\n } else if (name === 'templateUrl') {\n const val = getStringValue(property.value);\n if (val !== undefined) {\n metadata.templateUrls.push(val);\n }\n } else if (name === 'template') {\n const val = getStringValue(property.value);\n if (val !== undefined) {\n metadata.inlineTemplates.push(val);\n }\n }\n }\n\n components.push(metadata);\n }\n },\n });\n visitor.visit(program);\n\n return components;\n}\n\n/** Extract all `styleUrl` / `styleUrls` values from Angular component source. */\nexport function getStyleUrls(code: string): string[] {\n return collectComponentUrls(code).styleUrls;\n}\n\n/** Extract all `templateUrl` values from Angular component source. */\nexport function getTemplateUrls(code: string): string[] {\n return collectComponentUrls(code).templateUrls;\n}\n\n/** Extract inline `template` strings from Angular component source. */\nexport function getInlineTemplates(code: string): string[] {\n return collectComponentUrls(code).inlineTemplates;\n}\n\n// ---------------------------------------------------------------------------\n// Resolver caches\n// ---------------------------------------------------------------------------\n\ninterface StyleUrlsCacheEntry {\n code: string;\n styleUrls: string[];\n}\n\nexport class StyleUrlsResolver {\n // These resolvers may be called multiple times during the same\n // compilation for the same files. Caching is required because these\n // resolvers use synchronous system calls to the filesystem, which can\n // degrade performance when running compilations for multiple files.\n private readonly styleUrlsCache = new Map<string, StyleUrlsCacheEntry>();\n\n resolve(code: string, id: string): string[] {\n const entry = this.styleUrlsCache.get(id);\n if (entry?.code === code) {\n return entry.styleUrls;\n }\n\n const styleUrls = getStyleUrls(code).map((styleUrlPath) => {\n return `${styleUrlPath}|${normalizePath(\n resolve(dirname(id), styleUrlPath),\n )}`;\n });\n\n this.styleUrlsCache.set(id, { code, styleUrls });\n return styleUrls;\n }\n}\n\ninterface TemplateUrlsCacheEntry {\n code: string;\n templateUrlPaths: string[];\n}\n\nexport class TemplateUrlsResolver {\n private readonly templateUrlsCache = new Map<\n string,\n TemplateUrlsCacheEntry\n >();\n\n resolve(code: string, id: string): string[] {\n const entry = this.templateUrlsCache.get(id);\n if (entry?.code === code) {\n return entry.templateUrlPaths;\n }\n\n const templateUrlPaths = getTemplateUrls(code).map(\n (url) => `${url}|${normalizePath(resolve(dirname(id), url))}`,\n );\n\n this.templateUrlsCache.set(id, { code, templateUrlPaths });\n return templateUrlPaths;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA0BA,SAAS,eAAe,MAA+B;AACrD,KAAI,CAAC,KAAM,QAAO,KAAA;AAElB,KAAI,KAAK,SAAS,aAAa,OAAO,KAAK,UAAU,SACnD,QAAO,KAAK;AAGd,KAAI,KAAK,SAAS,gBAChB,QAAO,KAAK;AAGd,KACE,KAAK,SAAS,qBACd,KAAK,YAAY,WAAW,KAC5B,KAAK,OAAO,WAAW,EAEvB,QAAO,KAAK,OAAO,GAAG,MAAM,UAAU,KAAK,OAAO,GAAG,MAAM;;;;;;;;;;;AAc/D,SAAS,qBAAqB,MAI5B;CACA,MAAM,EAAE,YAAY,UAAU,UAAU,KAAK;CAC7C,MAAM,YAAsB,EAAE;CAC9B,MAAM,eAAyB,EAAE;CACjC,MAAM,kBAA4B,EAAE;AAEpB,KAAI,QAAQ,EAE1B,iBAAiB,MAAW;EAC1B,MAAM,aAAa,KAAK,cAAc,EAAE;AACxC,OAAK,MAAM,aAAa,YAAY;GAClC,MAAM,aAAa,UAAU;AAC7B,OACE,YAAY,SAAS,oBACrB,WAAW,QAAQ,SAAS,gBAC5B,WAAW,OAAO,SAAS,YAE3B;GAGF,MAAM,eAAe,WAAW,YAAY;AAC5C,OAAI,cAAc,SAAS,mBACzB;AAGF,QAAK,MAAM,YAAY,aAAa,cAAc,EAAE,EAAE;AACpD,QACE,UAAU,SAAS,cACnB,SAAS,KAAK,SAAS,aAEvB;IAGF,MAAM,OAAO,SAAS,IAAI;AAE1B,QACE,SAAS,eACT,SAAS,OAAO,SAAS,kBAEzB,MAAK,MAAM,MAAM,SAAS,MAAM,UAAU;KACxC,MAAM,MAAM,eAAe,GAAG;AAC9B,SAAI,QAAQ,KAAA,EAAW,WAAU,KAAK,IAAI;;AAI9C,QAAI,SAAS,YAAY;KACvB,MAAM,MAAM,eAAe,SAAS,MAAM;AAC1C,SAAI,QAAQ,KAAA,EAAW,WAAU,KAAK,IAAI;;AAG5C,QAAI,SAAS,eAAe;KAC1B,MAAM,MAAM,eAAe,SAAS,MAAM;AAC1C,SAAI,QAAQ,KAAA,EAAW,cAAa,KAAK,IAAI;;AAG/C,QAAI,SAAS,YAAY;KACvB,MAAM,MAAM,eAAe,SAAS,MAAM;AAC1C,SAAI,QAAQ,KAAA,EAAW,iBAAgB,KAAK,IAAI;;;;IAKzD,CAAC,CACM,MAAM,QAAQ;AAEtB,QAAO;EAAE;EAAW;EAAc;EAAiB;;;AAkGrD,SAAgB,aAAa,MAAwB;AACnD,QAAO,qBAAqB,KAAK,CAAC;;;AAIpC,SAAgB,gBAAgB,MAAwB;AACtD,QAAO,qBAAqB,KAAK,CAAC;;AAiBpC,IAAa,oBAAb,MAA+B;CAK7B,iCAAkC,IAAI,KAAkC;CAExE,QAAQ,MAAc,IAAsB;EAC1C,MAAM,QAAQ,KAAK,eAAe,IAAI,GAAG;AACzC,MAAI,OAAO,SAAS,KAClB,QAAO,MAAM;EAGf,MAAM,YAAY,aAAa,KAAK,CAAC,KAAK,iBAAiB;AACzD,UAAO,GAAG,aAAa,GAAG,cACxB,QAAQ,QAAQ,GAAG,EAAE,aAAa,CACnC;IACD;AAEF,OAAK,eAAe,IAAI,IAAI;GAAE;GAAM;GAAW,CAAC;AAChD,SAAO;;;AASX,IAAa,uBAAb,MAAkC;CAChC,oCAAqC,IAAI,KAGtC;CAEH,QAAQ,MAAc,IAAsB;EAC1C,MAAM,QAAQ,KAAK,kBAAkB,IAAI,GAAG;AAC5C,MAAI,OAAO,SAAS,KAClB,QAAO,MAAM;EAGf,MAAM,mBAAmB,gBAAgB,KAAK,CAAC,KAC5C,QAAQ,GAAG,IAAI,GAAG,cAAc,QAAQ,QAAQ,GAAG,EAAE,IAAI,CAAC,GAC5D;AAED,OAAK,kBAAkB,IAAI,IAAI;GAAE;GAAM;GAAkB,CAAC;AAC1D,SAAO"}
1
+ {"version":3,"file":"component-resolvers.js","names":[],"sources":["../../../src/lib/component-resolvers.ts"],"sourcesContent":["import { dirname, resolve } from 'node:path';\n// OXC parser (native Rust, NAPI-RS) replaces ts-morph for AST extraction.\n// It is ~10-50x faster for the narrow task of pulling property values from\n// Angular component decorators. The Visitor helper from Rolldown walks the\n// ESTree-compatible AST that OXC produces.\nimport { parseSync } from 'oxc-parser';\nimport { Visitor } from 'rolldown/utils';\nimport { normalizePath } from 'vite';\n\n// ---------------------------------------------------------------------------\n// AST helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Extracts a string value from an ESTree AST node.\n *\n * Handles three forms that Angular decorators may use:\n * - `Literal` with a string value → `'./foo.css'` / `\"./foo.css\"`\n * - `StringLiteral` (OXC-specific) → same representation\n * - `TemplateLiteral` with zero expressions → `` `./foo.css` ``\n *\n * Uses `any` because OXC's AST mixes standard ESTree nodes with\n * OXC-specific variants (e.g. `StringLiteral`), and the project's\n * tsconfig enforces `noPropertyAccessFromIndexSignature`.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction getStringValue(node: any): string | undefined {\n if (!node) return undefined;\n // Standard ESTree Literal (string value)\n if (node.type === 'Literal' && typeof node.value === 'string') {\n return node.value;\n }\n // OXC-specific StringLiteral node\n if (node.type === 'StringLiteral') {\n return node.value;\n }\n // Template literal with no interpolation (e.g., `./foo.css`)\n if (\n node.type === 'TemplateLiteral' &&\n node.expressions.length === 0 &&\n node.quasis.length === 1\n ) {\n return node.quasis[0].value.cooked ?? node.quasis[0].value.raw;\n }\n return undefined;\n}\n\n/**\n * Parses TypeScript/JS source with OXC and collects `styleUrl`, `styleUrls`,\n * and `templateUrl` property values from Angular `@Component()` decorators\n * in a single AST pass.\n *\n * This replaces the previous ts-morph implementation — OXC parses natively\n * via Rust NAPI bindings, avoiding the overhead of spinning up a full\n * TypeScript `Project` for each file.\n */\nfunction collectComponentUrls(code: string): {\n styleUrls: string[];\n templateUrls: string[];\n inlineTemplates: string[];\n} {\n const { program } = parseSync('cmp.ts', code);\n const styleUrls: string[] = [];\n const templateUrls: string[] = [];\n const inlineTemplates: string[] = [];\n\n const visitor = new Visitor({\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ClassDeclaration(node: any) {\n const decorators = node.decorators ?? [];\n for (const decorator of decorators) {\n const expression = decorator.expression;\n if (\n expression?.type !== 'CallExpression' ||\n expression.callee?.type !== 'Identifier' ||\n expression.callee.name !== 'Component'\n ) {\n continue;\n }\n\n const componentArg = expression.arguments?.[0];\n if (componentArg?.type !== 'ObjectExpression') {\n continue;\n }\n\n for (const property of componentArg.properties ?? []) {\n if (\n property?.type !== 'Property' ||\n property.key?.type !== 'Identifier'\n ) {\n continue;\n }\n\n const name = property.key.name;\n\n if (\n name === 'styleUrls' &&\n property.value?.type === 'ArrayExpression'\n ) {\n for (const el of property.value.elements) {\n const val = getStringValue(el);\n if (val !== undefined) styleUrls.push(val);\n }\n }\n\n if (name === 'styleUrl') {\n const val = getStringValue(property.value);\n if (val !== undefined) styleUrls.push(val);\n }\n\n if (name === 'templateUrl') {\n const val = getStringValue(property.value);\n if (val !== undefined) templateUrls.push(val);\n }\n\n if (name === 'template') {\n const val = getStringValue(property.value);\n if (val !== undefined) inlineTemplates.push(val);\n }\n }\n }\n },\n });\n visitor.visit(program);\n\n return { styleUrls, templateUrls, inlineTemplates };\n}\n\nexport interface AngularComponentMetadata {\n className: string;\n selector?: string;\n styleUrls: string[];\n templateUrls: string[];\n inlineTemplates: string[];\n}\n\n/**\n * Extract Angular component identities from raw source code before Angular's\n * compilation pipeline strips decorators. This is used for dev-time\n * diagnostics such as duplicate selectors, duplicate component class names,\n * selectorless shared components, and inline-template validation.\n */\nexport function getAngularComponentMetadata(\n code: string,\n): AngularComponentMetadata[] {\n const { program } = parseSync('cmp.ts', code);\n const components: AngularComponentMetadata[] = [];\n\n const visitor = new Visitor({\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ClassDeclaration(node: any) {\n const decorators = node.decorators ?? [];\n for (const decorator of decorators) {\n const expression = decorator.expression;\n if (\n expression?.type !== 'CallExpression' ||\n expression.callee?.type !== 'Identifier' ||\n expression.callee.name !== 'Component'\n ) {\n continue;\n }\n\n const componentArg = expression.arguments?.[0];\n if (componentArg?.type !== 'ObjectExpression') {\n continue;\n }\n\n const metadata: AngularComponentMetadata = {\n className: node.id?.name ?? '(anonymous)',\n styleUrls: [],\n templateUrls: [],\n inlineTemplates: [],\n };\n\n for (const property of componentArg.properties ?? []) {\n if (\n property?.type !== 'Property' ||\n property.key?.type !== 'Identifier'\n ) {\n continue;\n }\n\n const name = property.key.name;\n if (name === 'selector') {\n metadata.selector = getStringValue(property.value);\n } else if (name === 'styleUrl') {\n const val = getStringValue(property.value);\n if (val !== undefined) {\n metadata.styleUrls.push(val);\n }\n } else if (\n name === 'styleUrls' &&\n property.value?.type === 'ArrayExpression'\n ) {\n for (const el of property.value.elements ?? []) {\n const val = getStringValue(el);\n if (val !== undefined) {\n metadata.styleUrls.push(val);\n }\n }\n } else if (name === 'templateUrl') {\n const val = getStringValue(property.value);\n if (val !== undefined) {\n metadata.templateUrls.push(val);\n }\n } else if (name === 'template') {\n const val = getStringValue(property.value);\n if (val !== undefined) {\n metadata.inlineTemplates.push(val);\n }\n }\n }\n\n components.push(metadata);\n }\n },\n });\n visitor.visit(program);\n\n return components;\n}\n\n/** Extract all `styleUrl` / `styleUrls` values from Angular component source. */\nexport function getStyleUrls(code: string): string[] {\n return collectComponentUrls(code).styleUrls;\n}\n\n/** Extract all `templateUrl` values from Angular component source. */\nexport function getTemplateUrls(code: string): string[] {\n return collectComponentUrls(code).templateUrls;\n}\n\n/** Extract inline `template` strings from Angular component source. */\nexport function getInlineTemplates(code: string): string[] {\n return collectComponentUrls(code).inlineTemplates;\n}\n\n// ---------------------------------------------------------------------------\n// Resolver caches\n// ---------------------------------------------------------------------------\n\ninterface StyleUrlsCacheEntry {\n code: string;\n styleUrls: string[];\n}\n\nexport class StyleUrlsResolver {\n // These resolvers may be called multiple times during the same\n // compilation for the same files. Caching is required because these\n // resolvers use synchronous system calls to the filesystem, which can\n // degrade performance when running compilations for multiple files.\n private readonly styleUrlsCache = new Map<string, StyleUrlsCacheEntry>();\n\n resolve(code: string, id: string): string[] {\n const entry = this.styleUrlsCache.get(id);\n if (entry?.code === code) {\n return entry.styleUrls;\n }\n\n const styleUrls = getStyleUrls(code).map((styleUrlPath) => {\n return `${styleUrlPath}|${normalizePath(\n resolve(dirname(id), styleUrlPath),\n )}`;\n });\n\n this.styleUrlsCache.set(id, { code, styleUrls });\n return styleUrls;\n }\n}\n\ninterface TemplateUrlsCacheEntry {\n code: string;\n templateUrlPaths: string[];\n}\n\nexport class TemplateUrlsResolver {\n private readonly templateUrlsCache = new Map<\n string,\n TemplateUrlsCacheEntry\n >();\n\n resolve(code: string, id: string): string[] {\n const entry = this.templateUrlsCache.get(id);\n if (entry?.code === code) {\n return entry.templateUrlPaths;\n }\n\n const templateUrlPaths = getTemplateUrls(code).map(\n (url) => `${url}|${normalizePath(resolve(dirname(id), url))}`,\n );\n\n this.templateUrlsCache.set(id, { code, templateUrlPaths });\n return templateUrlPaths;\n }\n}\n"],"mappings":"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"}
@@ -57,12 +57,13 @@ function encapsulationPlugin() {
57
57
  function encapsulateViteCssOutput(code, componentId) {
58
58
  const match = /const __vite__css\s*=\s*("(?:\\.|[^"\\])*")/.exec(code);
59
59
  if (match) {
60
- const encapsulated = encapsulateStyle(JSON.parse(match[1]), componentId);
60
+ const css = JSON.parse(match[1]);
61
+ const encapsulated = encapsulateStyle(css, componentId);
61
62
  return code.slice(0, match.index) + `const __vite__css = ${JSON.stringify(encapsulated)}` + code.slice(match.index + match[0].length);
62
63
  }
63
64
  return encapsulateStyle(code, componentId);
64
65
  }
65
66
  //#endregion
66
- export { encapsulationPlugin, getComponentStyleSheetMeta, isComponentStyleSheet };
67
+ export { encapsulateViteCssOutput, encapsulationPlugin, getComponentStyleSheetMeta, isComponentStyleSheet };
67
68
 
68
69
  //# sourceMappingURL=encapsulation-plugin.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"encapsulation-plugin.js","names":[],"sources":["../../../src/lib/encapsulation-plugin.ts"],"sourcesContent":["import { encapsulateStyle } from '@angular/compiler';\nimport { Plugin } from 'vite';\nimport { debugStylesV } from './utils/debug.js';\n\nexport function isComponentStyleSheet(id: string): boolean {\n return id.includes('ngcomp=');\n}\n\nexport function getComponentStyleSheetMeta(id: string): {\n componentId: string;\n encapsulation: 'emulated' | 'shadow' | 'none' | undefined;\n} {\n const queryIndex = id.indexOf('?');\n const params = new URLSearchParams(\n queryIndex === -1 ? '' : id.slice(queryIndex + 1),\n );\n const encapsulationMapping = {\n '0': 'emulated',\n '2': 'none',\n '3': 'shadow',\n } as const;\n const encapsulationKey = params.get('e') as\n | keyof typeof encapsulationMapping\n | null;\n\n return {\n // Angular component IDs may contain `^`. Vite ids sometimes keep the\n // percent-encoded form (`%5E`) even after query parsing.\n componentId: (params.get('ngcomp') ?? '').replaceAll('%5E', '^'),\n encapsulation: encapsulationKey\n ? encapsulationMapping[encapsulationKey]\n : undefined,\n };\n}\n\n/**\n * Encapsulation runs in enforce: 'post' so that @tailwindcss/vite\n * (enforce: 'pre') fully resolves @apply directives — including those\n * inside :host {} — before Angular's ShadowCss rewrites selectors.\n * (#2293)\n *\n * The `ngcomp=` query only appears on requests Angular's runtime makes for\n * externalized component styles, so it is the only gate needed. This plugin\n * is shared by the ngtsc and Angular Compilation API paths and must not\n * depend on either path's private state.\n */\nexport function encapsulationPlugin(): Plugin {\n return {\n name: '@analogjs/vite-plugin-angular:encapsulation',\n enforce: 'post',\n transform(code: string, id: string) {\n if (isComponentStyleSheet(id)) {\n const { encapsulation, componentId } = getComponentStyleSheetMeta(id);\n if (encapsulation === 'emulated' && componentId) {\n debugStylesV('applying emulated view encapsulation (post)', {\n stylesheet: id.split('?')[0],\n componentId,\n });\n const encapsulated = encapsulateViteCssOutput(code, componentId);\n return {\n code: encapsulated,\n map: null,\n };\n }\n }\n },\n };\n}\n\n/**\n * Encapsulate either a Vite CSS JS module (`const __vite__css = \"...\"`)\n * or a raw CSS string produced by `?direct` stylesheet requests.\n */\nexport function encapsulateViteCssOutput(\n code: string,\n componentId: string,\n): string {\n const VITE_CSS_CONST_RE = /const __vite__css\\s*=\\s*(\"(?:\\\\.|[^\"\\\\])*\")/;\n const match = VITE_CSS_CONST_RE.exec(code);\n if (match) {\n const css = JSON.parse(match[1]) as string;\n const encapsulated = encapsulateStyle(css, componentId);\n return (\n code.slice(0, match.index) +\n `const __vite__css = ${JSON.stringify(encapsulated)}` +\n code.slice(match.index + match[0].length)\n );\n }\n\n return encapsulateStyle(code, componentId);\n}\n"],"mappings":";;;AAIA,SAAgB,sBAAsB,IAAqB;AACzD,QAAO,GAAG,SAAS,UAAU;;AAG/B,SAAgB,2BAA2B,IAGzC;CACA,MAAM,aAAa,GAAG,QAAQ,IAAI;CAClC,MAAM,SAAS,IAAI,gBACjB,eAAe,KAAK,KAAK,GAAG,MAAM,aAAa,EAAE,CAClD;CACD,MAAM,uBAAuB;EAC3B,KAAK;EACL,KAAK;EACL,KAAK;EACN;CACD,MAAM,mBAAmB,OAAO,IAAI,IAAI;AAIxC,QAAO;EAGL,cAAc,OAAO,IAAI,SAAS,IAAI,IAAI,WAAW,OAAO,IAAI;EAChE,eAAe,mBACX,qBAAqB,oBACrB,KAAA;EACL;;;;;;;;;;;;;AAcH,SAAgB,sBAA8B;AAC5C,QAAO;EACL,MAAM;EACN,SAAS;EACT,UAAU,MAAc,IAAY;AAClC,OAAI,sBAAsB,GAAG,EAAE;IAC7B,MAAM,EAAE,eAAe,gBAAgB,2BAA2B,GAAG;AACrE,QAAI,kBAAkB,cAAc,aAAa;AAC/C,kBAAa,+CAA+C;MAC1D,YAAY,GAAG,MAAM,IAAI,CAAC;MAC1B;MACD,CAAC;AAEF,YAAO;MACL,MAFmB,yBAAyB,MAAM,YAAY;MAG9D,KAAK;MACN;;;;EAIR;;;;;;AAOH,SAAgB,yBACd,MACA,aACQ;CAER,MAAM,QADoB,8CACM,KAAK,KAAK;AAC1C,KAAI,OAAO;EAET,MAAM,eAAe,iBADT,KAAK,MAAM,MAAM,GAAG,EACW,YAAY;AACvD,SACE,KAAK,MAAM,GAAG,MAAM,MAAM,GAC1B,uBAAuB,KAAK,UAAU,aAAa,KACnD,KAAK,MAAM,MAAM,QAAQ,MAAM,GAAG,OAAO;;AAI7C,QAAO,iBAAiB,MAAM,YAAY"}
1
+ {"version":3,"file":"encapsulation-plugin.js","names":[],"sources":["../../../src/lib/encapsulation-plugin.ts"],"sourcesContent":["import { encapsulateStyle } from '@angular/compiler';\nimport { Plugin } from 'vite';\nimport { debugStylesV } from './utils/debug.js';\n\nexport function isComponentStyleSheet(id: string): boolean {\n return id.includes('ngcomp=');\n}\n\nexport function getComponentStyleSheetMeta(id: string): {\n componentId: string;\n encapsulation: 'emulated' | 'shadow' | 'none' | undefined;\n} {\n const queryIndex = id.indexOf('?');\n const params = new URLSearchParams(\n queryIndex === -1 ? '' : id.slice(queryIndex + 1),\n );\n const encapsulationMapping = {\n '0': 'emulated',\n '2': 'none',\n '3': 'shadow',\n } as const;\n const encapsulationKey = params.get('e') as\n | keyof typeof encapsulationMapping\n | null;\n\n return {\n // Angular component IDs may contain `^`. Vite ids sometimes keep the\n // percent-encoded form (`%5E`) even after query parsing.\n componentId: (params.get('ngcomp') ?? '').replaceAll('%5E', '^'),\n encapsulation: encapsulationKey\n ? encapsulationMapping[encapsulationKey]\n : undefined,\n };\n}\n\n/**\n * Encapsulation runs in enforce: 'post' so that @tailwindcss/vite\n * (enforce: 'pre') fully resolves @apply directives — including those\n * inside :host {} — before Angular's ShadowCss rewrites selectors.\n * (#2293)\n *\n * The `ngcomp=` query only appears on requests Angular's runtime makes for\n * externalized component styles, so it is the only gate needed. This plugin\n * is shared by the ngtsc and Angular Compilation API paths and must not\n * depend on either path's private state.\n */\nexport function encapsulationPlugin(): Plugin {\n return {\n name: '@analogjs/vite-plugin-angular:encapsulation',\n enforce: 'post',\n transform(code: string, id: string) {\n if (isComponentStyleSheet(id)) {\n const { encapsulation, componentId } = getComponentStyleSheetMeta(id);\n if (encapsulation === 'emulated' && componentId) {\n debugStylesV('applying emulated view encapsulation (post)', {\n stylesheet: id.split('?')[0],\n componentId,\n });\n const encapsulated = encapsulateViteCssOutput(code, componentId);\n return {\n code: encapsulated,\n map: null,\n };\n }\n }\n },\n };\n}\n\n/**\n * Encapsulate either a Vite CSS JS module (`const __vite__css = \"...\"`)\n * or a raw CSS string produced by `?direct` stylesheet requests.\n */\nexport function encapsulateViteCssOutput(\n code: string,\n componentId: string,\n): string {\n const VITE_CSS_CONST_RE = /const __vite__css\\s*=\\s*(\"(?:\\\\.|[^\"\\\\])*\")/;\n const match = VITE_CSS_CONST_RE.exec(code);\n if (match) {\n const css = JSON.parse(match[1]) as string;\n const encapsulated = encapsulateStyle(css, componentId);\n return (\n code.slice(0, match.index) +\n `const __vite__css = ${JSON.stringify(encapsulated)}` +\n code.slice(match.index + match[0].length)\n );\n }\n\n return encapsulateStyle(code, componentId);\n}\n"],"mappings":";;;AAIA,SAAgB,sBAAsB,IAAqB;CACzD,OAAO,GAAG,SAAS,SAAS;AAC9B;AAEA,SAAgB,2BAA2B,IAGzC;CACA,MAAM,aAAa,GAAG,QAAQ,GAAG;CACjC,MAAM,SAAS,IAAI,gBACjB,eAAe,KAAK,KAAK,GAAG,MAAM,aAAa,CAAC,CAClD;CACA,MAAM,uBAAuB;EAC3B,KAAK;EACL,KAAK;EACL,KAAK;CACP;CACA,MAAM,mBAAmB,OAAO,IAAI,GAAG;CAIvC,OAAO;EAGL,cAAc,OAAO,IAAI,QAAQ,KAAK,GAAA,CAAI,WAAW,OAAO,GAAG;EAC/D,eAAe,mBACX,qBAAqB,oBACrB,KAAA;CACN;AACF;;;;;;;;;;;;AAaA,SAAgB,sBAA8B;CAC5C,OAAO;EACL,MAAM;EACN,SAAS;EACT,UAAU,MAAc,IAAY;GAClC,IAAI,sBAAsB,EAAE,GAAG;IAC7B,MAAM,EAAE,eAAe,gBAAgB,2BAA2B,EAAE;IACpE,IAAI,kBAAkB,cAAc,aAAa;KAC/C,aAAa,+CAA+C;MAC1D,YAAY,GAAG,MAAM,GAAG,CAAC,CAAC;MAC1B;KACF,CAAC;KAED,OAAO;MACL,MAFmB,yBAAyB,MAAM,WAE5C;MACN,KAAK;KACP;IACF;GACF;EACF;CACF;AACF;;;;;AAMA,SAAgB,yBACd,MACA,aACQ;CAER,MAAM,QAAQ,8CAAkB,KAAK,IAAI;CACzC,IAAI,OAAO;EACT,MAAM,MAAM,KAAK,MAAM,MAAM,EAAE;EAC/B,MAAM,eAAe,iBAAiB,KAAK,WAAW;EACtD,OACE,KAAK,MAAM,GAAG,MAAM,KAAK,IACzB,uBAAuB,KAAK,UAAU,YAAY,MAClD,KAAK,MAAM,MAAM,QAAQ,MAAM,EAAE,CAAC,MAAM;CAE5C;CAEA,OAAO,iBAAiB,MAAM,WAAW;AAC3C"}
@@ -340,8 +340,9 @@ function fastCompilePlugin(pluginOptions) {
340
340
  isProd,
341
341
  isLib: !!config?.build?.lib
342
342
  };
343
+ const preliminaryTsConfigPath = resolveTsConfigPath();
343
344
  const depOptimizer = createDepOptimizerConfig({
344
- tsconfig: resolveTsConfigPath(),
345
+ tsconfig: preliminaryTsConfigPath,
345
346
  isProd,
346
347
  jit: pluginOptions.jit,
347
348
  watchMode,
@@ -427,6 +428,6 @@ function fastCompilePlugin(pluginOptions) {
427
428
  };
428
429
  }
429
430
  //#endregion
430
- export { fastCompilePlugin };
431
+ export { fastCompilePlugin, getPathsBasePath };
431
432
 
432
433
  //# sourceMappingURL=fast-compile-plugin.js.map