Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions crates/oxc_angular_compiler/src/component/decorator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,18 +492,19 @@ fn extract_identifier_array<'a>(
}

/// The class each element of an array literal names, by position: `X`, or
/// `forwardRef(() => X)` with `@angular/core`'s `forwardRef`.
/// `forwardRef(() => X)` with `@angular/core`'s `forwardRef`, with the span of
/// the identifier.
fn extract_import_entry_classes<'a>(
allocator: &'a Allocator,
expr: &'a Expression<'a>,
consts: &StringConsts<'_>,
) -> Vec<'a, Option<Ident<'a>>> {
) -> Vec<'a, Option<(Ident<'a>, Span)>> {
let mut result = Vec::new_in(&allocator);
if let Expression::ArrayExpression(arr) = expr {
for element in &arr.elements {
let class = element.as_expression().and_then(|element| {
match try_unwrap_forward_ref(element, Some(consts)).unwrap_or(element) {
Expression::Identifier(id) => Some(id.name),
Expression::Identifier(id) => Some((id.name, id.span)),
_ => None,
}
});
Expand Down
24 changes: 21 additions & 3 deletions crates/oxc_angular_compiler/src/component/defer_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ use crate::class_metadata::{
use crate::i18n::serializer::is_i18n_attribute;
use crate::output::ast::{
ArrowFunctionBody, ArrowFunctionExpr, LiteralArrayExpr, OutputExpression, ReadVarExpr,
WrappedNodeExpr,
};
use crate::parser::html::split_ns_name;
use crate::pipeline::selector::CssSelector;
Expand Down Expand Up @@ -286,10 +287,19 @@ pub(super) fn defer_block_dependencies<'a>(
/// `dependencies` that `deferred` lists, and a plain reference to the others.
/// `None` when the block has no dependencies (`compileDeferBlocks` in ngc's
/// component `handler.ts`).
///
/// ngc's plain reference is the `WrappedNodeExpr` its `LocalIdentifierStrategy`
/// emits (`Reference.getIdentityIn`): the name of a class declared in the
/// file, which is the same node for every component and stays a plain read
/// here, or for an imported class the identifier in this component's
/// `imports`, found in `import_entry_classes`. Wrapped nodes are equal only
/// when they are the same node, so the constant pool shares a function that
/// reads an imported class within this component only.
pub(super) fn compile_defer_block_resolver<'a>(
allocator: &'a Allocator,
dependencies: &[Ident<'a>],
deferred: &[Ident<'a>],
import_entry_classes: &[Option<(Ident<'a>, oxc_span::Span)>],
import_map: &ImportMap<'a>,
) -> Option<OutputExpression<'a>> {
if dependencies.is_empty() {
Expand All @@ -304,11 +314,19 @@ pub(super) fn compile_defer_block_resolver<'a>(
} else {
None
};
entries.push(deferrable.unwrap_or_else(|| {
OutputExpression::ReadVar(oxc_allocator::Box::new_in(
let imports_identifier = import_entry_classes
.iter()
.flatten()
.find(|(class, _)| class == name && import_map.contains_key(name));
entries.push(deferrable.unwrap_or_else(|| match imports_identifier {
Some((_, span)) => OutputExpression::WrappedNode(oxc_allocator::Box::new_in(
WrappedNodeExpr { node_id: *name, source_span: Some(*span) },
&allocator,
)),
None => OutputExpression::ReadVar(oxc_allocator::Box::new_in(
ReadVarExpr { name: *name, source_span: None },
&allocator,
))
)),
}));
}
let array = OutputExpression::LiteralArray(oxc_allocator::Box::new_in(
Expand Down
25 changes: 19 additions & 6 deletions crates/oxc_angular_compiler/src/component/definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ pub struct ComponentDefinitions<'a> {
/// * `job` - The compilation job with template compilation results
/// * `template_fn` - The compiled template function
/// * `host_binding_result` - Optional host binding compilation result (function, hostAttrs, hostVars)
/// * `attrs_ref` - Optional pre-pooled attrs constant reference (pooled before template compilation)
/// * `attrs_ref` - Optional pre-pooled attrs constant reference (pooled before template compilation, pre-v22 only)
/// * `view_query_fn` - Optional view query function (with pre-pooled predicates)
///
/// # Returns
Expand Down Expand Up @@ -282,7 +282,8 @@ fn generate_cmp_definition<'a>(

// 14. attrs: ["class", "..."] - only if first selector has attributes
// This is optional and only included if the first selector specifies attributes.
// Ported from Angular compiler.ts lines 195-212.
// Ported from Angular compiler.ts (v21.2.x); v22.0.0-next.4 dropped the field
// (angular/angular@d99ab0e040), so attrs_ref is only set for older versions.
// The attrs_ref is pre-pooled BEFORE template compilation to ensure correct constant ordering.
// TypeScript Angular adds attrs to the pool BEFORE template ingestion/compilation.
if let Some(attrs) = attrs_ref {
Expand Down Expand Up @@ -422,10 +423,22 @@ fn generate_cmp_definition<'a>(
}
let style_value = Ident::from_in(style.as_str(), &allocator);

style_entries.push(OutputExpression::Literal(Box::new_in(
LiteralExpr { value: LiteralValue::String(style_value), source_span: None },
&allocator,
)));
// A long style is shared once the file uses it twice (Angular's
// `constantPool.getConstLiteral(literal(style))`).
style_entries.push(job.pool.get_const_literal(
OutputExpression::Literal(Box::new_in(
LiteralExpr { value: LiteralValue::String(style_value), source_span: None },
&allocator,
)),
false,
));
}
// A style used twice here was inline on its first use; point that use
// at the constant too, like Angular's `FixupExpression` does.
for entry in &mut style_entries {
if let Some(shared) = job.pool.shared_literal(entry) {
*entry = shared;
}
}

if !style_entries.is_empty() {
Expand Down
42 changes: 40 additions & 2 deletions crates/oxc_angular_compiler/src/component/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,22 @@ impl AngularVersion {
self.is_at_least(22, 0, 0)
}

/// Check if this version emits the `attrs` field on `ɵcmp` (before
/// v22.0.0-next.4).
///
/// Until then `compileComponentFromMetadata` pooled the attributes of the
/// component's first selector (`[bitBadge]` gives `["bitBadge", ""]`) and set
/// `attrs` to that constant. No runtime ever read it, and
/// `angular/angular@d99ab0e040` ("fix(compiler): stop generating unused
/// field") removed it in v22.0.0-next.4; it was never backported. The
/// constant still takes a `_cN` index on older versions.
pub fn emits_selector_attrs(&self) -> bool {
if self.major == 22 && self.minor == 0 && self.patch == 0 {
return matches!(self.prerelease, Some(prerelease) if prerelease < Prerelease::Next(4));
}
self.major < 22
}

/// Check if this version compiles component templates in DOM-only mode
/// (v20.1.0-next.3+).
///
Expand Down Expand Up @@ -530,8 +546,9 @@ pub struct ComponentMetadata<'a> {
/// array literal, by position: `X`, or `forwardRef(() => X)` with
/// `@angular/core`'s `forwardRef` (which [`Self::imports`] leaves out),
/// as ngtsc resolves them through its `forwardRefResolver`; `None` for
/// anything else, such as `X as any`, which ngtsc keeps eager.
pub import_entry_classes: Vec<'a, Option<Ident<'a>>>,
/// anything else, such as `X as any`, which ngtsc keeps eager. Each class
/// comes with the span of its identifier in the array.
pub import_entry_classes: Vec<'a, Option<(Ident<'a>, Span)>>,

/// Exported names for template references.
///
Expand Down Expand Up @@ -1195,4 +1212,25 @@ mod tests {
assert!(!version.uses_shadow_css_polyfills(), "{version:?}");
}
}

#[test]
fn selector_attrs_end_at_22_0_0_next_4() {
let parse = |s| AngularVersion::parse(s).unwrap();

for version in
[parse("19.2.20"), parse("20.3.34"), parse("21.2.25"), parse("22.0.0-next.3")]
{
assert!(version.emits_selector_attrs(), "{version:?}");
}

for version in [
parse("22.0.0-next.4"),
parse("22.0.0-rc.0"),
parse("22.0.0"),
parse("22.2.2"),
AngularVersion::new(u32::MAX, 0, 0),
] {
assert!(!version.emits_selector_attrs(), "{version:?}");
}
}
}
Loading
Loading