Skip to content

Commit 4356c38

Browse files
Brooooooklynclaude
andcommitted
feat: generate .d.ts Ivy type declarations for Angular library builds
Add `dts_declarations` field to `TransformResult` that generates the static type declarations (ɵfac, ɵcmp, ɵdir, ɵpipe, ɵmod, ɵinj, ɵprov) needed in `.d.ts` files for Angular library consumers to perform template type-checking. This enables build tools like tsdown to post-process `.d.ts` output and inject Ivy declarations, solving the "Component imports must be standalone" error when publishing Angular libraries compiled with Oxc. Supports all Angular decorator types: @component, @directive, @pipe, @NgModule, and @Injectable, including input/output maps, signal inputs, exportAs, host directives, and constructor @Attribute() dependencies. - Close #86 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c380a55 commit 4356c38

File tree

6 files changed

+1224
-0
lines changed

6 files changed

+1224
-0
lines changed

crates/oxc_angular_compiler/src/component/transform.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ use crate::directive::{
3838
extract_content_queries, extract_directive_metadata, extract_view_queries,
3939
find_directive_decorator_span, generate_directive_definitions,
4040
};
41+
use crate::dts;
4142
use crate::injectable::{
4243
extract_injectable_metadata, find_injectable_decorator_span,
4344
generate_injectable_definition_from_decorator,
@@ -276,6 +277,20 @@ pub struct TransformResult {
276277

277278
/// Number of components found in the file.
278279
pub component_count: usize,
280+
281+
/// `.d.ts` type declarations for Angular classes.
282+
///
283+
/// Each entry contains the class name and the static member declarations
284+
/// that should be injected into the corresponding `.d.ts` class body.
285+
/// This enables library builds to include proper Ivy type declarations
286+
/// for template type-checking by consumers.
287+
///
288+
/// The declarations use `i0` as the namespace alias for `@angular/core`.
289+
/// Consumers must ensure their `.d.ts` files include:
290+
/// ```typescript
291+
/// import * as i0 from "@angular/core";
292+
/// ```
293+
pub dts_declarations: Vec<crate::dts::DtsDeclaration>,
279294
}
280295

281296
impl TransformResult {
@@ -1565,6 +1580,11 @@ pub fn transform_angular_file(
15651580
// Signal-based queries (contentChild(), contentChildren()) are also detected here
15661581
let content_queries = extract_content_queries(allocator, class);
15671582

1583+
// Collect content query property names for .d.ts generation
1584+
// (before content_queries is moved into compile_component_full)
1585+
let content_query_names: Vec<String> =
1586+
content_queries.iter().map(|q| q.property_name.to_string()).collect();
1587+
15681588
// 4. Compile the template and generate ɵcmp/ɵfac
15691589
// Pass the shared pool index to ensure unique constant names
15701590
// Pass the file-level namespace registry to ensure consistent namespace assignments
@@ -1736,6 +1756,20 @@ pub fn transform_angular_file(
17361756
result.dependencies.push(style_url.to_string());
17371757
}
17381758

1759+
// Generate .d.ts type declaration for this component
1760+
let type_argument_count = class
1761+
.type_parameters
1762+
.as_ref()
1763+
.map_or(0, |tp| tp.params.len() as u32);
1764+
let has_injectable =
1765+
extract_injectable_metadata(allocator, class).is_some();
1766+
result.dts_declarations.push(dts::generate_component_dts(
1767+
&metadata,
1768+
type_argument_count,
1769+
&content_query_names,
1770+
has_injectable,
1771+
));
1772+
17391773
result.component_count += 1;
17401774
}
17411775
Err(diags) => {
@@ -1830,6 +1864,12 @@ pub fn transform_angular_file(
18301864
}
18311865
}
18321866

1867+
// Generate .d.ts type declaration for this directive
1868+
let has_injectable = extract_injectable_metadata(allocator, class).is_some();
1869+
result
1870+
.dts_declarations
1871+
.push(dts::generate_directive_dts(&directive_metadata, has_injectable));
1872+
18331873
class_positions.push((
18341874
class_name.clone(),
18351875
compute_effective_start(class, &decorator_spans_to_remove, stmt_start),
@@ -1896,6 +1936,13 @@ pub fn transform_angular_file(
18961936
}
18971937
}
18981938

1939+
// Generate .d.ts type declaration for this pipe
1940+
let has_injectable =
1941+
extract_injectable_metadata(allocator, class).is_some();
1942+
result
1943+
.dts_declarations
1944+
.push(dts::generate_pipe_dts(&pipe_metadata, has_injectable));
1945+
18991946
class_positions.push((
19001947
class_name.clone(),
19011948
compute_effective_start(class, &decorator_spans_to_remove, stmt_start),
@@ -1977,6 +2024,13 @@ pub fn transform_angular_file(
19772024
external_decls.push_str(&emitter.emit_statement(stmt));
19782025
}
19792026

2027+
// Generate .d.ts type declaration for this NgModule
2028+
let has_injectable =
2029+
extract_injectable_metadata(allocator, class).is_some();
2030+
result
2031+
.dts_declarations
2032+
.push(dts::generate_ng_module_dts(&ng_module_metadata, has_injectable));
2033+
19802034
// NgModule: external_decls go AFTER the class (they reference the class name)
19812035
class_positions.push((
19822036
class_name.clone(),
@@ -2028,6 +2082,11 @@ pub fn transform_angular_file(
20282082
emitter.emit_expression(&definition.prov_definition)
20292083
);
20302084

2085+
// Generate .d.ts type declaration for this injectable
2086+
result
2087+
.dts_declarations
2088+
.push(dts::generate_injectable_dts(&injectable_metadata));
2089+
20312090
class_positions.push((
20322091
class_name.clone(),
20332092
compute_effective_start(class, &decorator_spans_to_remove, stmt_start),

0 commit comments

Comments
 (0)