Skip to content

Commit 898265a

Browse files
committed
vite plugin fix
1 parent b712931 commit 898265a

29 files changed

Lines changed: 1716 additions & 481 deletions

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/oxc_angular_compiler/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ doctest = false
1717
[dependencies]
1818
oxc_allocator = { workspace = true }
1919
oxc_ast = { workspace = true }
20-
oxc_codegen = { workspace = true, features = ["sourcemap"] }
2120
oxc_diagnostics = { workspace = true }
2221
oxc_parser = { workspace = true }
2322
oxc_span = { workspace = true }

crates/oxc_angular_compiler/src/component/decorator.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use oxc_allocator::{Allocator, Vec};
77
use oxc_ast::ast::{
88
Argument, ArrayExpressionElement, Class, Decorator, Expression, ObjectPropertyKind, PropertyKey,
99
};
10-
use oxc_span::Atom;
10+
use oxc_span::{Atom, Span};
1111

1212
use super::metadata::{
1313
ChangeDetectionStrategy, ComponentMetadata, HostMetadata, ViewEncapsulation,
@@ -132,6 +132,14 @@ fn find_component_decorator<'a>(decorators: &'a [Decorator<'a>]) -> Option<&'a D
132132
})
133133
}
134134

135+
/// Find the span of the @Component decorator on a class.
136+
///
137+
/// Returns the span including any leading whitespace/newlines that should be removed
138+
/// along with the decorator.
139+
pub fn find_component_decorator_span(class: &Class<'_>) -> Option<Span> {
140+
find_component_decorator(&class.decorators).map(|d| d.span)
141+
}
142+
135143
/// Check if a callee expression is a call to 'Component'.
136144
fn is_component_call(callee: &Expression<'_>) -> bool {
137145
match callee {

crates/oxc_angular_compiler/src/component/metadata.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ impl<'a> ComponentMetadata<'a> {
120120
template_url: None,
121121
styles: Vec::new_in(allocator),
122122
style_urls: Vec::new_in(allocator),
123-
standalone: false,
123+
standalone: true, // Angular v17+ defaults to standalone
124124
encapsulation: ViewEncapsulation::default(),
125125
change_detection: ChangeDetectionStrategy::default(),
126126
host: None,

crates/oxc_angular_compiler/src/component/transform.rs

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,14 @@
44
//! containing Angular components into compiled JavaScript.
55
66
use std::collections::HashMap;
7-
use std::path::PathBuf;
87

98
use oxc_allocator::Allocator;
109
use oxc_ast::ast::{Declaration, ExportDefaultDeclarationKind, Statement};
11-
use oxc_codegen::{Codegen, CodegenOptions};
1210
use oxc_diagnostics::OxcDiagnostic;
1311
use oxc_parser::Parser;
14-
use oxc_span::{Atom, SourceType};
12+
use oxc_span::{Atom, SourceType, Span};
1513

16-
use super::decorator::extract_component_metadata;
14+
use super::decorator::{extract_component_metadata, find_component_decorator_span};
1715
use super::definition::generate_component_definitions;
1816
use super::metadata::ComponentMetadata;
1917
use crate::output::ast::FunctionExpr;
@@ -162,8 +160,9 @@ pub fn transform_angular_file(
162160
// Still continue to try to generate output for partial results
163161
}
164162

165-
// Collect component definitions to append after codegen
163+
// Collect component definitions to append and decorator spans to remove
166164
let mut component_definitions: Vec<String> = Vec::new();
165+
let mut decorator_spans_to_remove: Vec<Span> = Vec::new();
167166
let mut needs_angular_core_import = false;
168167

169168
// 2. Walk AST to find @Component decorated classes and extract metadata
@@ -201,9 +200,16 @@ pub fn transform_angular_file(
201200
);
202201
}
203202

203+
// Track the decorator span to remove
204+
if let Some(span) = find_component_decorator_span(class) {
205+
decorator_spans_to_remove.push(span);
206+
}
207+
204208
// Store the ɵcmp/ɵfac definitions to append later
209+
// Include declarations (child view functions) before ɵcmp
205210
component_definitions.push(format!(
206-
"{}.ɵcmp = {};\n{}.ɵfac = {};",
211+
"{}{}.ɵcmp = {};\n{}.ɵfac = {};",
212+
compilation_result.declarations_js,
207213
class_name,
208214
compilation_result.cmp_js,
209215
class_name,
@@ -236,13 +242,36 @@ pub fn transform_angular_file(
236242
}
237243
}
238244

239-
// 5. Generate output code using oxc_codegen
240-
let codegen_options = CodegenOptions {
241-
single_quote: true,
242-
source_map_path: if options.sourcemap { Some(PathBuf::from(path)) } else { None },
243-
..Default::default()
244-
};
245-
let codegen_ret = Codegen::new().with_options(codegen_options).build(&parser_ret.program);
245+
// 5. Generate output code by modifying the original source
246+
// We use string manipulation instead of oxc_codegen to preserve
247+
// the original code structure and avoid decorator placement issues.
248+
249+
// Sort decorator spans in reverse order so we can remove them from back to front
250+
decorator_spans_to_remove.sort_by(|a, b| b.start.cmp(&a.start));
251+
252+
// Start with the original source
253+
let mut modified_source = source.to_string();
254+
255+
// Remove each decorator span from the source
256+
for span in decorator_spans_to_remove {
257+
let start = span.start as usize;
258+
let end = span.end as usize;
259+
260+
// Find any trailing whitespace/newline after the decorator to also remove
261+
let mut actual_end = end;
262+
let bytes = modified_source.as_bytes();
263+
while actual_end < bytes.len() {
264+
let c = bytes[actual_end];
265+
if c == b' ' || c == b'\t' || c == b'\n' || c == b'\r' {
266+
actual_end += 1;
267+
} else {
268+
break;
269+
}
270+
}
271+
272+
// Remove the decorator and trailing whitespace
273+
modified_source.replace_range(start..actual_end, "");
274+
}
246275

247276
// Build final code with imports and definitions
248277
let mut code = String::new();
@@ -252,8 +281,8 @@ pub fn transform_angular_file(
252281
code.push_str("import * as i0 from '@angular/core';\n");
253282
}
254283

255-
// Append the original code
256-
code.push_str(&codegen_ret.code);
284+
// Append the modified original code
285+
code.push_str(&modified_source);
257286

258287
// Append ɵcmp/ɵfac definitions after the generated code
259288
if !component_definitions.is_empty() {
@@ -265,7 +294,8 @@ pub fn transform_angular_file(
265294
}
266295

267296
result.code = code;
268-
result.map = codegen_ret.map.map(|m| m.to_json_string());
297+
// Note: source maps not supported with string manipulation approach
298+
result.map = None;
269299

270300
result
271301
}
@@ -280,6 +310,9 @@ struct FullCompilationResult {
280310

281311
/// ɵfac factory function as JavaScript.
282312
fac_js: String,
313+
314+
/// Additional declarations (child view functions, constants).
315+
declarations_js: String,
283316
}
284317

285318
/// Compile a component template and generate ɵcmp/ɵfac definitions.
@@ -332,6 +365,13 @@ fn compile_component_full<'a>(
332365
// Emit JavaScript
333366
let emitter = JsEmitter::new();
334367

368+
// Emit declarations (child view functions, constants)
369+
let mut declarations_js = String::new();
370+
for decl in compiled.declarations.iter() {
371+
declarations_js.push_str(&emitter.emit_statement(decl));
372+
declarations_js.push('\n');
373+
}
374+
335375
// For HMR, we emit the template separately using compile_template_to_js
336376
// The ɵcmp already contains the template function inline
337377
let template_js =
@@ -354,7 +394,7 @@ fn compile_component_full<'a>(
354394
// Errors would have been caught earlier in parsing/transformation
355395
}
356396

357-
Ok(FullCompilationResult { template_js, cmp_js, fac_js })
397+
Ok(FullCompilationResult { template_js, cmp_js, fac_js, declarations_js })
358398
}
359399

360400
/// Resolve template content from inline or external source.

crates/oxc_angular_compiler/src/pipeline/phases/generate_advance.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,12 @@ fn get_slot_dependency_target(op: &UpdateOp<'_>) -> Option<XrefId> {
118118
UpdateOp::Animation(anim) => Some(anim.target),
119119
UpdateOp::AnimationBinding(anim) => Some(anim.target),
120120
UpdateOp::Control(ctrl) => Some(ctrl.target),
121+
UpdateOp::Repeater(rep) => Some(rep.target),
122+
UpdateOp::Conditional(cond) => Some(cond.target),
121123
// These operations don't depend on slot context
122124
UpdateOp::ListEnd(_)
123125
| UpdateOp::Advance(_)
124-
| UpdateOp::Repeater(_)
125126
| UpdateOp::StoreLet(_)
126-
| UpdateOp::Conditional(_)
127127
| UpdateOp::I18nApply(_)
128128
| UpdateOp::Variable(_)
129129
| UpdateOp::DeferWhen(_)

crates/oxc_angular_compiler/src/pipeline/phases/generate_variables.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use oxc_allocator::Box;
1616
use oxc_span::Atom;
1717

1818
use crate::ir::enums::{SemanticVariableKind, VariableFlags};
19-
use crate::ir::expression::{ContextExpr, GetCurrentViewExpr, IrExpression};
19+
use crate::ir::expression::{ContextExpr, GetCurrentViewExpr, IrExpression, NextContextExpr};
2020
use crate::ir::ops::{CreateOp, UpdateOp, UpdateVariableOp, VariableOp, XrefId};
2121
use crate::pipeline::compilation::ComponentCompilationJob;
2222

@@ -48,12 +48,14 @@ pub fn generate_variables(job: &mut ComponentCompilationJob<'_>) {
4848
};
4949

5050
// Generate context variable for embedded views
51+
// This goes in the UPDATE phase, not create, following Angular's pattern
5152
if needs_context {
5253
let ctx_var_xref = job.allocate_xref_id();
53-
let context_var = create_context_variable(allocator, ctx_var_xref, root_xref);
54+
let context_var =
55+
create_context_variable_for_update(allocator, ctx_var_xref, root_xref);
5456

5557
if let Some(view) = job.views.get_mut(&view_xref) {
56-
view.create.push_front(context_var);
58+
view.update.push_front(context_var);
5759
}
5860
}
5961

@@ -92,22 +94,23 @@ fn has_listeners(create_ops: &crate::ir::list::CreateOpList<'_>) -> bool {
9294
false
9395
}
9496

95-
/// Creates a context variable operation.
97+
/// Creates a context variable operation for the UPDATE phase.
9698
///
97-
/// Context variables store a reference to the component context for use in
98-
/// embedded views that need to access component properties.
99-
fn create_context_variable<'a>(
99+
/// Context variables in embedded views use `NextContextExpr` to navigate to the
100+
/// parent view's context. This is added to the update phase, not create.
101+
fn create_context_variable_for_update<'a>(
100102
allocator: &'a oxc_allocator::Allocator,
101103
xref: XrefId,
102104
context_view: XrefId,
103-
) -> CreateOp<'a> {
104-
// Create context expression as initializer
105-
let initializer = IrExpression::Context(Box::new_in(
106-
ContextExpr { view: context_view, source_span: None },
105+
) -> UpdateOp<'a> {
106+
// Use NextContextExpr to navigate to parent context
107+
// This is what Angular uses for embedded views accessing parent context
108+
let initializer = IrExpression::NextContext(Box::new_in(
109+
NextContextExpr { steps: 1, source_span: None },
107110
allocator,
108111
));
109112

110-
CreateOp::Variable(VariableOp {
113+
UpdateOp::Variable(UpdateVariableOp {
111114
base: Default::default(),
112115
xref,
113116
kind: SemanticVariableKind::Context,

0 commit comments

Comments
 (0)