From ae5a59fd8122bb91cc18c07e2e7aca79eab63194 Mon Sep 17 00:00:00 2001 From: "Adam R. Nelson" Date: Sun, 29 Jun 2025 21:48:52 -0400 Subject: [PATCH 01/10] transpile JSX into _jsxSorted and _jsxSplit --- optimizer/src/component/shared.rs | 1 + optimizer/src/transform.rs | 361 +++++++++++++++++++++++++++++- 2 files changed, 353 insertions(+), 9 deletions(-) diff --git a/optimizer/src/component/shared.rs b/optimizer/src/component/shared.rs index ab8540c..6d767b3 100644 --- a/optimizer/src/component/shared.rs +++ b/optimizer/src/component/shared.rs @@ -9,6 +9,7 @@ use std::convert::Into; use std::path::PathBuf; pub const QWIK_CORE_SOURCE: &str = "@qwik.dev/core"; +pub const JSX_NAME: &str = "_jsx"; pub const MARKER_SUFFIX: &str = "$"; pub const QRL: &str = "qrl"; pub const QRL_SUFFIX: &str = "Qrl"; diff --git a/optimizer/src/transform.rs b/optimizer/src/transform.rs index 9d61231..898a011 100644 --- a/optimizer/src/transform.rs +++ b/optimizer/src/transform.rs @@ -52,6 +52,9 @@ use crate::illegal_code::{IllegalCode, IllegalCodeType}; use crate::processing_failure::ProcessingFailure; use crate::transpiler::Transpiler; +const JSX_SORTED: &str = "_jsxSorted"; +const JSX_SPLIT: &str = "_jsxSplit"; + impl OptimizedApp { fn get_component(&self, name: String) -> Option<&QrlComponent> { self.components @@ -100,6 +103,19 @@ impl OptimizationResult { } } +struct JsxState<'gen> { + is_fn: bool, + is_text_only: bool, + is_segment: bool, + should_runtime_sort: bool, + static_listeners: bool, + static_subtree: bool, + key_prop: Option>, + var_props: OxcVec<'gen, ObjectPropertyKind<'gen>>, + const_props: OxcVec<'gen, ObjectPropertyKind<'gen>>, + children: OxcVec<'gen, ArrayExpressionElement<'gen>>, +} + pub struct TransformGenerator<'gen> { pub options: TransformOptions, @@ -109,6 +125,8 @@ pub struct TransformGenerator<'gen> { pub errors: Vec, + builder: AstBuilder<'gen>, + depth: usize, segment_stack: Vec, @@ -123,6 +141,8 @@ pub struct TransformGenerator<'gen> { import_stack: Vec>, + const_stack: Vec>, + import_by_symbol: HashMap, removed: HashMap, @@ -130,6 +150,19 @@ pub struct TransformGenerator<'gen> { source_info: &'gen SourceInfo, scope: Option, + + jsx_stack: Vec>, + + jsx_key_counter: u32, + + // Marks whether each JSX attribute in the stack is var (false) or const (true) + // An attribute is considered var if it: + // - calls a function + // - accesses a member + // - is a variable that is not an import, an export, or in the const stack + expr_is_const_stack: Vec, + + replace_expr: Option>, } impl<'gen> TransformGenerator<'gen> { @@ -137,12 +170,16 @@ impl<'gen> TransformGenerator<'gen> { source_info: &'gen SourceInfo, options: TransformOptions, scope: Option, + allocator: &'gen Allocator, ) -> Self { + let qwik_core_import_path = PathBuf::from("@qwik/core"); + let builder = AstBuilder::new(allocator); Self { options, components: Vec::new(), app: OptimizedApp::default(), errors: Vec::new(), + builder, depth: 0, segment_stack: Vec::new(), segment_builder: SegmentBuilder::new(), @@ -150,10 +187,15 @@ impl<'gen> TransformGenerator<'gen> { component_stack: Vec::new(), qrl_stack: Vec::new(), import_stack: vec![BTreeSet::new()], - import_by_symbol: Default::default(), + const_stack: vec![BTreeSet::new()], + import_by_symbol: HashMap::default(), removed: HashMap::new(), source_info, scope, + jsx_stack: Vec::new(), + jsx_key_counter: 0, + expr_is_const_stack: Vec::new(), + replace_expr: None, } } @@ -208,6 +250,14 @@ impl<'gen> TransformGenerator<'gen> { } } +fn move_expression<'gen>( + builder: &AstBuilder<'gen>, + expr: &mut Expression<'gen>, +) -> Expression<'gen> { + let span = expr.span().clone(); + std::mem::replace(expr, builder.expression_null_literal(span)) +} + const DEBUG: bool = true; const DUMP_FINAL_AST: bool = false; @@ -251,8 +301,14 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { self.ascend(); self.debug(format!("ENTER: CallExpression, {:?}", node), ctx); + if let Some(mut is_const) = self.expr_is_const_stack.last_mut() { + *is_const = false; + } + let name = node.callee_name().unwrap_or_default().to_string(); - if (name.ends_with(MARKER_SUFFIX)) { + if (name == JSX_NAME) { + println!("JSX FOUND IN CALLEXPRESSION, {:?}", node); + } else if (name.ends_with(MARKER_SUFFIX)) { self.import_stack.push(BTreeSet::new()); } @@ -312,6 +368,16 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { self.segment_stack.pop(); } + fn enter_member_expression( + &mut self, + node: &mut MemberExpression<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + if let Some(mut is_const) = self.expr_is_const_stack.last_mut() { + *is_const = false; + } + } + fn enter_function(&mut self, node: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) { let segment: Segment = node .name() @@ -360,6 +426,10 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { let s: Segment = self.new_segment(segment_name); self.segment_stack.push(s); + self.expr_is_const_stack.push(match node.kind { + VariableDeclarationKind::Const => true, + _ => false, + }); if let Some(name) = id.get_identifier_name() { /// Adds symbol and import information in the case this declaration ends up being referenced in @@ -398,10 +468,31 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { } } + // If this definition is constant, mark it as constant within the current scope + if self.expr_is_const_stack.pop().unwrap_or_default() { + if let Some(consts) = self.const_stack.last_mut() { + let symbol_id = node + .id + .get_binding_identifier() + .and_then(|b| b.symbol_id.get()); + if let Some(symbol_id) = symbol_id { + consts.insert(symbol_id); + } + } + } + let popped = self.segment_stack.pop(); println!("pop segment: {popped:?}"); } + fn enter_block_statement(&mut self, node: &mut BlockStatement<'a>, ctx: &mut TraverseCtx<'a>) { + self.const_stack.push(BTreeSet::new()); + } + + fn exit_block_statement(&mut self, node: &mut BlockStatement<'a>, ctx: &mut TraverseCtx<'a>) { + self.const_stack.pop(); + } + fn enter_expression_statement( &mut self, node: &mut ExpressionStatement<'a>, @@ -420,9 +511,39 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { self.descend(); } + fn exit_expression(&mut self, node: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + if let Some(expr) = self.replace_expr.take() { + self.debug("Replacing expression on exit", ctx); + *node = expr; + } + } + fn enter_jsx_element(&mut self, node: &mut JSXElement<'a>, ctx: &mut TraverseCtx<'a>) { - if let Some(name) = node.opening_element.name.get_identifier_name() { - let segment: Segment = self.new_segment(name); + let (segment, is_fn, is_text_only) = + if let Some(id) = node.opening_element.name.get_identifier() { + (Some(self.new_segment(id.name)), true, false) + } else if let Some(name) = node.opening_element.name.get_identifier_name() { + ( + Some(self.new_segment(name)), + false, + is_text_only(name.into()), + ) + } else { + (None, true, false) + }; + self.jsx_stack.push(JsxState { + is_fn, + is_text_only, + is_segment: segment.is_some(), + should_runtime_sort: false, + static_listeners: true, + static_subtree: true, + key_prop: None, + var_props: OxcVec::new_in(self.builder.allocator), + const_props: OxcVec::new_in(self.builder.allocator), + children: OxcVec::new_in(self.builder.allocator), + }); + if let Some(segment) = segment { self.debug(format!("ENTER: JSXElementName {segment}"), ctx); println!("push segment: {segment}"); self.segment_stack.push(segment); @@ -430,15 +551,168 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { } fn exit_jsx_element(&mut self, node: &mut JSXElement<'a>, ctx: &mut TraverseCtx<'a>) { - // JSX Elements should be treated as part of the segment scope. - if let Some(name) = node.opening_element.name.get_identifier_name() { - let popped = self.segment_stack.pop(); + if let Some(mut jsx) = self.jsx_stack.pop() { + if (!jsx.should_runtime_sort) { + jsx.var_props.sort_by_key(|prop| match prop { + ObjectPropertyKind::ObjectProperty(b) => match &(*b).key { + PropertyKey::StringLiteral(b) => (*b).to_string(), + _ => "".to_string(), + }, + _ => "".to_string(), + }); + } + let name = &node.opening_element.name; + let jsx_type = match name { + JSXElementName::Identifier(b) => { + self.builder + .expression_string_literal((*b).span, (*b).name, Some((*b).name)) + } + JSXElementName::IdentifierReference(b) => { + self.builder.expression_identifier((*b).span, (*b).name) + } + JSXElementName::NamespacedName(b) => { + panic!("namespaced names in JSX not implemented") + } + JSXElementName::MemberExpression(b) => { + fn process_member_expr<'b>( + builder: &AstBuilder<'b>, + expr: &JSXMemberExpressionObject<'b>, + ) -> Expression<'b> { + match expr { + JSXMemberExpressionObject::ThisExpression(b) => { + builder.expression_this((*b).span) + } + JSXMemberExpressionObject::IdentifierReference(b) => { + builder.expression_identifier((*b).span, (*b).name) + } + JSXMemberExpressionObject::MemberExpression(b) => builder + .member_expression_static( + (*b).span, + process_member_expr(builder, &(*b).object), + builder + .identifier_name((*b).property.span(), (*b).property.name), + false, + ) + .into(), + } + } + self.builder + .member_expression_static( + (*b).span(), + process_member_expr(&self.builder, &((*b).object)), + self.builder + .identifier_name((*b).property.span(), (*b).property.name), + false, + ) + .into() + } + JSXElementName::ThisExpression(b) => self.builder.expression_this((*b).span), + }; + let args: OxcVec> = OxcVec::from_array_in( + [ + // type + jsx_type.into(), + // varProps + self.builder + .expression_object(node.span(), jsx.var_props, None) + .into(), + // constProps + self.builder + .expression_object(node.span(), jsx.const_props, None) + .into(), + // children + self.builder + .expression_array(node.span(), jsx.children, None) + .into(), + // flags + self.builder + .expression_numeric_literal( + node.span(), + ((if jsx.static_subtree { 0b1 } else { 0 }) + | (if jsx.static_listeners { 0b01 } else { 0 })) + .into(), + None, + NumberBase::Binary, + ) + .into(), + // key + jsx.key_prop + .unwrap_or_else(|| -> Expression<'a> { + // TODO: Figure out how to replicate root_jsx_mode from old optimizer + // (this conditional should be is_fn || root_jsx_mode) + if jsx.is_fn { + if let Some(cmp) = self.component_stack.last() { + let new_key = format!( + "{}_{}", + cmp.id.hash.chars().take(2).collect::(), + self.jsx_key_counter + ); + self.jsx_key_counter += 1; + return self.builder.expression_string_literal( + Span::default(), + new_key, + None, + ); + } + } + self.builder.expression_null_literal(Span::default()) + }) + .into(), + ], + self.builder.allocator, + ); + self.replace_expr = Some(self.builder.expression_call_with_pure( + node.span, + self.builder.expression_identifier( + name.span(), + if (jsx.should_runtime_sort) { + JSX_SPLIT + } else { + JSX_SORTED + }, + ), + None::>>, + args, + false, + jsx.is_text_only, + )); + if jsx.is_segment { + let popped = self.segment_stack.pop(); + } } self.debug("EXIT: JSXElementName", ctx); self.descend(); } + fn exit_jsx_spread_attribute( + &mut self, + node: &mut JSXSpreadAttribute<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + // Reference: qwik build/v2 internal_handle_jsx_props_obj + // If we have spread props, all props that come before it are variable even if they're static + if let Some(jsx) = self.jsx_stack.last_mut() { + let range = 0..jsx.const_props.len(); + jsx.const_props + .drain(range) + .for_each(|p| jsx.var_props.push(p)); + jsx.should_runtime_sort = true; + jsx.static_subtree = false; + jsx.static_listeners = false; + jsx.var_props + .push(self.builder.object_property_kind_spread_property( + node.span(), + move_expression(&self.builder, &mut node.argument).into(), + )) + } + } + fn enter_jsx_attribute(&mut self, node: &mut JSXAttribute<'a>, ctx: &mut TraverseCtx<'a>) { + self.expr_is_const_stack.push( + self.jsx_stack + .last() + .map_or(false, |jsx| !jsx.should_runtime_sort), + ); self.ascend(); self.debug("ENTER: JSXAttribute", ctx); // JSX Attributes should be treated as part of the segment scope. @@ -447,8 +721,45 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { } fn exit_jsx_attribute(&mut self, node: &mut JSXAttribute<'a>, ctx: &mut TraverseCtx<'a>) { + if let Some(jsx) = self.jsx_stack.last_mut() { + let expr: Expression<'a> = { + let v = &mut node.value; + match v { + None => self.builder.expression_boolean_literal(node.span, true), + Some(JSXAttributeValue::Element(_)) => self.replace_expr.take().unwrap(), + Some(JSXAttributeValue::Fragment(_)) => self.replace_expr.take().unwrap(), + Some(JSXAttributeValue::StringLiteral(b)) => self + .builder + .expression_string_literal((*b).span, (*b).value, Some((*b).value)), + Some(JSXAttributeValue::ExpressionContainer(b)) => { + move_expression(&self.builder, (*b).expression.to_expression_mut()) + } + } + }; + let is_const = self.expr_is_const_stack.pop().unwrap_or_default(); + if node.is_key() { + jsx.key_prop = Some(expr); + } else { + let props = if is_const { + &mut jsx.const_props + } else { + &mut jsx.var_props + }; + props.push(self.builder.object_property_kind_object_property( + node.span, + PropertyKind::Init, + self.builder.property_key_static_identifier( + node.name.span(), + node.name.get_identifier().name, + ), + expr, + false, + false, + false, + )); + } + } let popped = self.segment_stack.pop(); - println!("pop segment: {popped:?}"); self.debug("EXIT: JSXAttribute", ctx); self.descend(); } @@ -471,6 +782,31 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { } } + fn exit_jsx_child(&mut self, node: &mut JSXChild<'a>, ctx: &mut TraverseCtx<'a>) { + if let Some(jsx) = self.jsx_stack.last_mut() { + jsx.children.push(match node { + JSXChild::Text(b) => self + .builder + .expression_string_literal((*b).span, (*b).value, Some((*b).value)) + .into(), + JSXChild::Element(_) => self.replace_expr.take().unwrap().into(), + JSXChild::Fragment(_) => self.replace_expr.take().unwrap().into(), + JSXChild::ExpressionContainer(b) => { + jsx.static_subtree = false; + move_expression(&self.builder, (*b).expression.to_expression_mut()).into() + } + JSXChild::Spread(b) => { + jsx.static_subtree = false; + let span = (*b).span.clone(); + self.builder.array_expression_element_spread_element( + span, + move_expression(&self.builder, &mut (*b).expression), + ) + } + }); + } + } + fn exit_return_statement(&mut self, node: &mut ReturnStatement<'a>, ctx: &mut TraverseCtx<'a>) { if let Some(expr) = &node.argument { if expr.is_qrl_replaceable() { @@ -658,6 +994,13 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { } } +fn is_text_only(node: &str) -> bool { + matches!( + node, + "text" | "textarea" | "title" | "option" | "script" | "style" | "noscript" + ) +} + pub struct TransformOptions { pub minify: bool, pub target: Target, @@ -703,7 +1046,7 @@ pub fn transform(script_source: Source, options: TransformOptions) -> Result Date: Sun, 29 Jun 2025 21:52:45 -0400 Subject: [PATCH 02/10] remove now-unused Transpiler module --- optimizer/src/component/component.rs | 5 ----- optimizer/src/lib.rs | 1 - optimizer/src/transform.rs | 5 ----- optimizer/src/transpiler.rs | 23 ----------------------- 4 files changed, 34 deletions(-) delete mode 100644 optimizer/src/transpiler.rs diff --git a/optimizer/src/component/component.rs b/optimizer/src/component/component.rs index d9ebebf..af09e41 100644 --- a/optimizer/src/component/component.rs +++ b/optimizer/src/component/component.rs @@ -2,7 +2,6 @@ use crate::component::Language; use crate::component::*; use crate::segment::Segment; use crate::transform::TransformOptions; -use crate::transpiler::Transpiler; use oxc_allocator::{Allocator, Box as OxcBox, CloneIn, IntoIn, Vec as OxcVec}; use oxc_ast::ast::*; use oxc_ast::*; @@ -118,10 +117,6 @@ impl QrlComponent { body, ); - if options.transpile_jsx { - Transpiler::transpile(allocator, &mut new_pgm, source_info); - } - let codegen = Codegen::new(); let codegen_options = CodegenOptions { annotation_comments: true, diff --git a/optimizer/src/lib.rs b/optimizer/src/lib.rs index 8fbcf04..324c955 100644 --- a/optimizer/src/lib.rs +++ b/optimizer/src/lib.rs @@ -17,4 +17,3 @@ mod processing_failure; mod ref_counter; mod segment; pub mod transform; -mod transpiler; diff --git a/optimizer/src/transform.rs b/optimizer/src/transform.rs index 898a011..c62ce23 100644 --- a/optimizer/src/transform.rs +++ b/optimizer/src/transform.rs @@ -50,7 +50,6 @@ pub struct OptimizedApp { use crate::ext::*; use crate::illegal_code::{IllegalCode, IllegalCodeType}; use crate::processing_failure::ProcessingFailure; -use crate::transpiler::Transpiler; const JSX_SORTED: &str = "_jsxSorted"; const JSX_SPLIT: &str = "_jsxSplit"; @@ -271,10 +270,6 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { ImportCleanUp::clean_up(node, ctx.ast.allocator); - if self.options.transpile_jsx { - Transpiler::transpile(ctx.ast.allocator, node, self.source_info); - } - let codegen_options = CodegenOptions { annotation_comments: true, minify: self.options.minify, diff --git a/optimizer/src/transpiler.rs b/optimizer/src/transpiler.rs deleted file mode 100644 index ff77c76..0000000 --- a/optimizer/src/transpiler.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::component::SourceInfo; -use oxc_allocator::Allocator; -use oxc_ast::ast::Program; -use oxc_semantic::SemanticBuilder; -use oxc_transformer::{TransformOptions, Transformer}; - -pub struct Transpiler; - -impl Transpiler { - pub fn transpile<'a>( - allocator: &'a Allocator, - pgm: &mut Program<'a>, - source_info: &SourceInfo, - ) { - // TODO Correctly handle errors. - let ret = SemanticBuilder::new().with_excess_capacity(2.0).build(&pgm); - let scoping = ret.semantic.into_scoping(); - let path = &source_info.rel_path; - // TODO Correctly handle errors. - let ret = Transformer::new(allocator, path, &TransformOptions::default()) - .build_with_scoping(scoping, pgm); - } -} From 3637e89e64d228b5421f2c63c6d58af3f4cd7857 Mon Sep 17 00:00:00 2001 From: "Adam R. Nelson" Date: Sun, 29 Jun 2025 22:00:52 -0400 Subject: [PATCH 03/10] conditionally transform JSX --- optimizer/src/transform.rs | 347 ++++++++++++++++++++----------------- 1 file changed, 184 insertions(+), 163 deletions(-) diff --git a/optimizer/src/transform.rs b/optimizer/src/transform.rs index c62ce23..827cc01 100644 --- a/optimizer/src/transform.rs +++ b/optimizer/src/transform.rs @@ -421,10 +421,12 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { let s: Segment = self.new_segment(segment_name); self.segment_stack.push(s); - self.expr_is_const_stack.push(match node.kind { - VariableDeclarationKind::Const => true, - _ => false, - }); + if (self.options.transpile_jsx) { + self.expr_is_const_stack.push(match node.kind { + VariableDeclarationKind::Const => true, + _ => false, + }); + } if let Some(name) = id.get_identifier_name() { /// Adds symbol and import information in the case this declaration ends up being referenced in @@ -464,7 +466,7 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { } // If this definition is constant, mark it as constant within the current scope - if self.expr_is_const_stack.pop().unwrap_or_default() { + if self.options.transpile_jsx && self.expr_is_const_stack.pop().unwrap_or_default() { if let Some(consts) = self.const_stack.last_mut() { let symbol_id = node .id @@ -481,11 +483,15 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { } fn enter_block_statement(&mut self, node: &mut BlockStatement<'a>, ctx: &mut TraverseCtx<'a>) { - self.const_stack.push(BTreeSet::new()); + if (self.options.transpile_jsx) { + self.const_stack.push(BTreeSet::new()); + } } fn exit_block_statement(&mut self, node: &mut BlockStatement<'a>, ctx: &mut TraverseCtx<'a>) { - self.const_stack.pop(); + if (self.options.transpile_jsx) { + self.const_stack.pop(); + } } fn enter_expression_statement( @@ -547,130 +553,135 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { fn exit_jsx_element(&mut self, node: &mut JSXElement<'a>, ctx: &mut TraverseCtx<'a>) { if let Some(mut jsx) = self.jsx_stack.pop() { - if (!jsx.should_runtime_sort) { - jsx.var_props.sort_by_key(|prop| match prop { - ObjectPropertyKind::ObjectProperty(b) => match &(*b).key { - PropertyKey::StringLiteral(b) => (*b).to_string(), + if (self.options.transpile_jsx) { + if (!jsx.should_runtime_sort) { + jsx.var_props.sort_by_key(|prop| match prop { + ObjectPropertyKind::ObjectProperty(b) => match &(*b).key { + PropertyKey::StringLiteral(b) => (*b).to_string(), + _ => "".to_string(), + }, _ => "".to_string(), - }, - _ => "".to_string(), - }); - } - let name = &node.opening_element.name; - let jsx_type = match name { - JSXElementName::Identifier(b) => { - self.builder - .expression_string_literal((*b).span, (*b).name, Some((*b).name)) - } - JSXElementName::IdentifierReference(b) => { - self.builder.expression_identifier((*b).span, (*b).name) + }); } - JSXElementName::NamespacedName(b) => { - panic!("namespaced names in JSX not implemented") - } - JSXElementName::MemberExpression(b) => { - fn process_member_expr<'b>( - builder: &AstBuilder<'b>, - expr: &JSXMemberExpressionObject<'b>, - ) -> Expression<'b> { - match expr { - JSXMemberExpressionObject::ThisExpression(b) => { - builder.expression_this((*b).span) - } - JSXMemberExpressionObject::IdentifierReference(b) => { - builder.expression_identifier((*b).span, (*b).name) + let name = &node.opening_element.name; + let jsx_type = match name { + JSXElementName::Identifier(b) => self.builder.expression_string_literal( + (*b).span, + (*b).name, + Some((*b).name), + ), + JSXElementName::IdentifierReference(b) => { + self.builder.expression_identifier((*b).span, (*b).name) + } + JSXElementName::NamespacedName(b) => { + panic!("namespaced names in JSX not implemented") + } + JSXElementName::MemberExpression(b) => { + fn process_member_expr<'b>( + builder: &AstBuilder<'b>, + expr: &JSXMemberExpressionObject<'b>, + ) -> Expression<'b> { + match expr { + JSXMemberExpressionObject::ThisExpression(b) => { + builder.expression_this((*b).span) + } + JSXMemberExpressionObject::IdentifierReference(b) => { + builder.expression_identifier((*b).span, (*b).name) + } + JSXMemberExpressionObject::MemberExpression(b) => builder + .member_expression_static( + (*b).span, + process_member_expr(builder, &(*b).object), + builder.identifier_name( + (*b).property.span(), + (*b).property.name, + ), + false, + ) + .into(), } - JSXMemberExpressionObject::MemberExpression(b) => builder - .member_expression_static( - (*b).span, - process_member_expr(builder, &(*b).object), - builder - .identifier_name((*b).property.span(), (*b).property.name), - false, - ) - .into(), } + self.builder + .member_expression_static( + (*b).span(), + process_member_expr(&self.builder, &((*b).object)), + self.builder + .identifier_name((*b).property.span(), (*b).property.name), + false, + ) + .into() } - self.builder - .member_expression_static( - (*b).span(), - process_member_expr(&self.builder, &((*b).object)), - self.builder - .identifier_name((*b).property.span(), (*b).property.name), - false, - ) - .into() - } - JSXElementName::ThisExpression(b) => self.builder.expression_this((*b).span), - }; - let args: OxcVec> = OxcVec::from_array_in( - [ - // type - jsx_type.into(), - // varProps - self.builder - .expression_object(node.span(), jsx.var_props, None) - .into(), - // constProps - self.builder - .expression_object(node.span(), jsx.const_props, None) - .into(), - // children - self.builder - .expression_array(node.span(), jsx.children, None) - .into(), - // flags - self.builder - .expression_numeric_literal( - node.span(), - ((if jsx.static_subtree { 0b1 } else { 0 }) - | (if jsx.static_listeners { 0b01 } else { 0 })) + JSXElementName::ThisExpression(b) => self.builder.expression_this((*b).span), + }; + let args: OxcVec> = OxcVec::from_array_in( + [ + // type + jsx_type.into(), + // varProps + self.builder + .expression_object(node.span(), jsx.var_props, None) + .into(), + // constProps + self.builder + .expression_object(node.span(), jsx.const_props, None) + .into(), + // children + self.builder + .expression_array(node.span(), jsx.children, None) .into(), - None, - NumberBase::Binary, - ) - .into(), - // key - jsx.key_prop - .unwrap_or_else(|| -> Expression<'a> { - // TODO: Figure out how to replicate root_jsx_mode from old optimizer - // (this conditional should be is_fn || root_jsx_mode) - if jsx.is_fn { - if let Some(cmp) = self.component_stack.last() { - let new_key = format!( - "{}_{}", - cmp.id.hash.chars().take(2).collect::(), - self.jsx_key_counter - ); - self.jsx_key_counter += 1; - return self.builder.expression_string_literal( - Span::default(), - new_key, - None, - ); + // flags + self.builder + .expression_numeric_literal( + node.span(), + ((if jsx.static_subtree { 0b1 } else { 0 }) + | (if jsx.static_listeners { 0b01 } else { 0 })) + .into(), + None, + NumberBase::Binary, + ) + .into(), + // key + jsx.key_prop + .unwrap_or_else(|| -> Expression<'a> { + // TODO: Figure out how to replicate root_jsx_mode from old optimizer + // (this conditional should be is_fn || root_jsx_mode) + if jsx.is_fn { + if let Some(cmp) = self.component_stack.last() { + let new_key = format!( + "{}_{}", + cmp.id.hash.chars().take(2).collect::(), + self.jsx_key_counter + ); + self.jsx_key_counter += 1; + return self.builder.expression_string_literal( + Span::default(), + new_key, + None, + ); + } } - } - self.builder.expression_null_literal(Span::default()) - }) - .into(), - ], - self.builder.allocator, - ); - self.replace_expr = Some(self.builder.expression_call_with_pure( - node.span, - self.builder.expression_identifier( - name.span(), - if (jsx.should_runtime_sort) { - JSX_SPLIT - } else { - JSX_SORTED - }, - ), - None::>>, - args, - false, - jsx.is_text_only, - )); + self.builder.expression_null_literal(Span::default()) + }) + .into(), + ], + self.builder.allocator, + ); + self.replace_expr = Some(self.builder.expression_call_with_pure( + node.span, + self.builder.expression_identifier( + name.span(), + if (jsx.should_runtime_sort) { + JSX_SPLIT + } else { + JSX_SORTED + }, + ), + None::>>, + args, + false, + jsx.is_text_only, + )); + } if jsx.is_segment { let popped = self.segment_stack.pop(); } @@ -684,6 +695,9 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { node: &mut JSXSpreadAttribute<'a>, ctx: &mut TraverseCtx<'a>, ) { + if (!self.options.transpile_jsx) { + return; + } // Reference: qwik build/v2 internal_handle_jsx_props_obj // If we have spread props, all props that come before it are variable even if they're static if let Some(jsx) = self.jsx_stack.last_mut() { @@ -703,11 +717,13 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { } fn enter_jsx_attribute(&mut self, node: &mut JSXAttribute<'a>, ctx: &mut TraverseCtx<'a>) { - self.expr_is_const_stack.push( - self.jsx_stack - .last() - .map_or(false, |jsx| !jsx.should_runtime_sort), - ); + if (self.options.transpile_jsx) { + self.expr_is_const_stack.push( + self.jsx_stack + .last() + .map_or(false, |jsx| !jsx.should_runtime_sort), + ); + } self.ascend(); self.debug("ENTER: JSXAttribute", ctx); // JSX Attributes should be treated as part of the segment scope. @@ -716,42 +732,44 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { } fn exit_jsx_attribute(&mut self, node: &mut JSXAttribute<'a>, ctx: &mut TraverseCtx<'a>) { - if let Some(jsx) = self.jsx_stack.last_mut() { - let expr: Expression<'a> = { - let v = &mut node.value; - match v { - None => self.builder.expression_boolean_literal(node.span, true), - Some(JSXAttributeValue::Element(_)) => self.replace_expr.take().unwrap(), - Some(JSXAttributeValue::Fragment(_)) => self.replace_expr.take().unwrap(), - Some(JSXAttributeValue::StringLiteral(b)) => self - .builder - .expression_string_literal((*b).span, (*b).value, Some((*b).value)), - Some(JSXAttributeValue::ExpressionContainer(b)) => { - move_expression(&self.builder, (*b).expression.to_expression_mut()) + if (self.options.transpile_jsx) { + if let Some(jsx) = self.jsx_stack.last_mut() { + let expr: Expression<'a> = { + let v = &mut node.value; + match v { + None => self.builder.expression_boolean_literal(node.span, true), + Some(JSXAttributeValue::Element(_)) => self.replace_expr.take().unwrap(), + Some(JSXAttributeValue::Fragment(_)) => self.replace_expr.take().unwrap(), + Some(JSXAttributeValue::StringLiteral(b)) => self + .builder + .expression_string_literal((*b).span, (*b).value, Some((*b).value)), + Some(JSXAttributeValue::ExpressionContainer(b)) => { + move_expression(&self.builder, (*b).expression.to_expression_mut()) + } } - } - }; - let is_const = self.expr_is_const_stack.pop().unwrap_or_default(); - if node.is_key() { - jsx.key_prop = Some(expr); - } else { - let props = if is_const { - &mut jsx.const_props - } else { - &mut jsx.var_props }; - props.push(self.builder.object_property_kind_object_property( - node.span, - PropertyKind::Init, - self.builder.property_key_static_identifier( - node.name.span(), - node.name.get_identifier().name, - ), - expr, - false, - false, - false, - )); + let is_const = self.expr_is_const_stack.pop().unwrap_or_default(); + if node.is_key() { + jsx.key_prop = Some(expr); + } else { + let props = if is_const { + &mut jsx.const_props + } else { + &mut jsx.var_props + }; + props.push(self.builder.object_property_kind_object_property( + node.span, + PropertyKind::Init, + self.builder.property_key_static_identifier( + node.name.span(), + node.name.get_identifier().name, + ), + expr, + false, + false, + false, + )); + } } } let popped = self.segment_stack.pop(); @@ -778,6 +796,9 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { } fn exit_jsx_child(&mut self, node: &mut JSXChild<'a>, ctx: &mut TraverseCtx<'a>) { + if (!self.options.transpile_jsx) { + return; + } if let Some(jsx) = self.jsx_stack.last_mut() { jsx.children.push(match node { JSXChild::Text(b) => self From 5ea0d2f8d000e87e051f70b6187a97f2ce234d1f Mon Sep 17 00:00:00 2001 From: "Adam R. Nelson" Date: Sun, 29 Jun 2025 22:32:13 -0400 Subject: [PATCH 04/10] fix fragments --- ..._lib_interface__tests__test_project_1.snap | 18 ++-- ...ansform__tests__test_example_jsx_body.snap | 2 +- optimizer/src/transform.rs | 99 ++++++++++++++----- 3 files changed, 85 insertions(+), 34 deletions(-) diff --git a/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap b/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap index 9c0de31..d4b76a5 100644 --- a/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap +++ b/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap @@ -4,7 +4,7 @@ expression: result --- modules: - path: "./src/test_input/test_project_1/src/routes/index.tsx" - code: "import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from \"react/jsx-runtime\";\nexport const component_3DYNZg0ikgU = () => {\n\treturn /* @__PURE__ */ _jsxs(_Fragment, { children: [/* @__PURE__ */ _jsx(\"h1\", { children: \"Hi πŸ‘‹\" }), /* @__PURE__ */ _jsxs(\"div\", { children: [\n\t\t\"Can't wait to see what you build with qwik!\",\n\t\t/* @__PURE__ */ _jsx(\"br\", {}),\n\t\t\"Happy coding.\"\n\t] })] });\n};\n" + code: "export const component_3DYNZg0ikgU = () => {\n\treturn [\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"h1\", {}, {}, [\"Hi πŸ‘‹\"], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [\n\t\t\t\"\\n Can't wait to see what you build with qwik!\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"br\", {}, {}, [], 1, null),\n\t\t\t\"\\n Happy coding.\\n \"\n\t\t], 1, null),\n\t\t\"\\n \"\n\t];\n};\n" map: ~ segment: origin: "./src/test_input/test_project_1/src/routes/index.tsx" @@ -29,7 +29,7 @@ modules: segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/root.tsx" - code: "import { QwikCityProvider } from \"@qwik.dev/router\";\nimport { RouterHead } from \"./components/router-head/router-head\";\nimport { RouterOutlet } from \"@qwik.dev/router\";\nimport { ServiceWorkerRegister } from \"@qwik.dev/router\";\nimport { isDev } from \"@qwik.dev/core\";\nimport { jsx as _jsx, jsxs as _jsxs } from \"react/jsx-runtime\";\nexport const component_cQDlEL2LM0Q = () => {\n\treturn /* @__PURE__ */ _jsxs(QwikCityProvider, { children: [/* @__PURE__ */ _jsxs(\"head\", { children: [\n\t\t/* @__PURE__ */ _jsx(\"meta\", { charset: \"utf-8\" }),\n\t\t!isDev && /* @__PURE__ */ _jsx(\"link\", {\n\t\t\trel: \"manifest\",\n\t\t\thref: `${import.meta.env.BASE_URL}manifest.json`\n\t\t}),\n\t\t/* @__PURE__ */ _jsx(RouterHead, {})\n\t] }), /* @__PURE__ */ _jsxs(\"body\", {\n\t\tlang: \"en\",\n\t\tchildren: [/* @__PURE__ */ _jsx(RouterOutlet, {}), !isDev && /* @__PURE__ */ _jsx(ServiceWorkerRegister, {})]\n\t})] });\n};\n" + code: "import { QwikCityProvider } from \"@qwik.dev/router\";\nimport { RouterHead } from \"./components/router-head/router-head\";\nimport { RouterOutlet } from \"@qwik.dev/router\";\nimport { ServiceWorkerRegister } from \"@qwik.dev/router\";\nimport { isDev } from \"@qwik.dev/core\";\nexport const component_cQDlEL2LM0Q = () => {\n\treturn _jsxSorted(QwikCityProvider, {}, {}, [\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"head\", {}, {}, [\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"meta\", {}, { charset: \"utf-8\" }, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t!isDev && /* @__PURE__ */ _jsxSorted(\"link\", { href: `${import.meta.env.BASE_URL}manifest.json` }, { rel: \"manifest\" }, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t_jsxSorted(RouterHead, {}, {}, [], 1, null),\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"body\", {}, { lang: \"en\" }, [\n\t\t\t\"\\n \",\n\t\t\t_jsxSorted(RouterOutlet, {}, {}, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t!isDev && _jsxSorted(ServiceWorkerRegister, {}, {}, [], 1, null),\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \"\n\t], 1, null);\n};\n" map: ~ segment: origin: "./src/test_input/test_project_1/src/root.tsx" @@ -49,7 +49,7 @@ modules: - 0 isEntry: true - path: "./src/test_input/test_project_1/src/entry.ssr.tsx" - code: "import Root from \"./root\";\nimport { renderToStream } from \"@qwik.dev/core/server\";\nimport { manifest } from \"@qwik-client-manifest\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default function(opts) {\n\treturn renderToStream(/* @__PURE__ */ _jsx(Root, {}), {\n\t\tmanifest,\n\t\t...opts,\n\t\tcontainerAttributes: {\n\t\t\tlang: \"en-us\",\n\t\t\t...opts.containerAttributes\n\t\t},\n\t\tserverData: { ...opts.serverData }\n\t});\n}\n" + code: "import Root from \"./root\";\nimport { renderToStream } from \"@qwik.dev/core/server\";\nimport { manifest } from \"@qwik-client-manifest\";\nimport { RenderToStreamOptions } from \"@qwik.dev/core/server\";\nexport default function(opts: RenderToStreamOptions) {\n\treturn renderToStream(_jsxSorted(Root, {}, {}, [], 1, null), {\n\t\tmanifest,\n\t\t...opts,\n\t\tcontainerAttributes: {\n\t\t\tlang: \"en-us\",\n\t\t\t...opts.containerAttributes\n\t\t},\n\t\tserverData: { ...opts.serverData }\n\t});\n}\n" map: ~ segment: ~ isEntry: false @@ -59,7 +59,7 @@ modules: segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/routes/layout.tsx" - code: "import { Slot } from \"@qwik.dev/core\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport const component_a1SxCR8N64g = () => {\n\treturn /* @__PURE__ */ _jsx(Slot, {});\n};\n" + code: "import { Slot } from \"@qwik.dev/core\";\nexport const component_a1SxCR8N64g = () => {\n\treturn _jsxSorted(Slot, {}, {}, [], 1, null);\n};\n" map: ~ segment: origin: "./src/test_input/test_project_1/src/routes/layout.tsx" @@ -79,22 +79,22 @@ modules: - 0 isEntry: true - path: "./src/test_input/test_project_1/src/routes/index.tsx" - code: "import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/index.tsx.tsx_component_3DYNZg0ikgU\"), \"component_3DYNZg0ikgU\"));\nexport const head = componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/index.tsx.tsx_component_3DYNZg0ikgU\"), \"component_3DYNZg0ikgU\"));\n" + code: "import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nimport { DocumentHead } from \"@qwik.dev/router\";\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/index.tsx.tsx_component_3DYNZg0ikgU\"), \"component_3DYNZg0ikgU\"));\nexport const head: DocumentHead = componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/index.tsx.tsx_component_3DYNZg0ikgU\"), \"component_3DYNZg0ikgU\"));\n" map: ~ segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/routes/layout.tsx" - code: "import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport const onGet = async ({ cacheControl }) => {\n\tcacheControl({\n\t\tstaleWhileRevalidate: 60 * 60 * 24 * 7,\n\t\tmaxAge: 5\n\t});\n};\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/layout.tsx.tsx_component_a1SxCR8N64g\"), \"component_a1SxCR8N64g\"));\n" + code: "import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nimport { RequestHandler } from \"@qwik.dev/router\";\nexport const onGet: RequestHandler = async ({ cacheControl }) => {\n\tcacheControl({\n\t\tstaleWhileRevalidate: 60 * 60 * 24 * 7,\n\t\tmaxAge: 5\n\t});\n};\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/layout.tsx.tsx_component_a1SxCR8N64g\"), \"component_a1SxCR8N64g\"));\n" map: ~ segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/entry.dev.tsx" - code: "import Root from \"./root\";\nimport { render } from \"@qwik.dev/core\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default function(opts) {\n\treturn render(document, /* @__PURE__ */ _jsx(Root, {}), opts);\n}\n" + code: "import Root from \"./root\";\nimport { render } from \"@qwik.dev/core\";\nimport { RenderOptions } from \"@qwik.dev/core\";\nexport default function(opts: RenderOptions) {\n\treturn render(document, _jsxSorted(Root, {}, {}, [], 1, null), opts);\n}\n" map: ~ segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/routes/service-worker.ts" - code: "import { setupServiceWorker } from \"@qwik.dev/router/service-worker\";\nsetupServiceWorker();\naddEventListener(\"install\", () => self.skipWaiting());\naddEventListener(\"activate\", () => self.clients.claim());\n" + code: "import { setupServiceWorker } from \"@qwik.dev/router/service-worker\";\nsetupServiceWorker();\naddEventListener(\"install\", () => self.skipWaiting());\naddEventListener(\"activate\", () => self.clients.claim());\ndeclare const self: ServiceWorkerGlobalScope;\n" map: ~ segment: ~ isEntry: false @@ -104,7 +104,7 @@ modules: segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/components/router-head/router-head.tsx" - code: "import { useDocumentHead } from \"@qwik.dev/router\";\nimport { useLocation } from \"@qwik.dev/router\";\nimport { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from \"react/jsx-runtime\";\nexport const RouterHead_component_9vp071E6SfU = () => {\n\tconst head = useDocumentHead();\n\tconst loc = useLocation();\n\treturn /* @__PURE__ */ _jsxs(_Fragment, { children: [\n\t\t/* @__PURE__ */ _jsx(\"title\", { children: head.title }),\n\t\t/* @__PURE__ */ _jsx(\"link\", {\n\t\t\trel: \"canonical\",\n\t\t\thref: loc.url.href\n\t\t}),\n\t\t/* @__PURE__ */ _jsx(\"meta\", {\n\t\t\tname: \"viewport\",\n\t\t\tcontent: \"width=device-width, initial-scale=1.0\"\n\t\t}),\n\t\t/* @__PURE__ */ _jsx(\"link\", {\n\t\t\trel: \"icon\",\n\t\t\ttype: \"image/svg+xml\",\n\t\t\thref: \"/favicon.svg\"\n\t\t}),\n\t\thead.meta.map((m) => /* @__PURE__ */ _jsx(\"meta\", { ...m }, m.key)),\n\t\thead.links.map((l) => /* @__PURE__ */ _jsx(\"link\", { ...l }, l.key)),\n\t\thead.styles.map((s) => /* @__PURE__ */ _jsx(\"style\", {\n\t\t\t...s.props,\n\t\t\t...s.props?.dangerouslySetInnerHTML ? {} : { dangerouslySetInnerHTML: s.style }\n\t\t}, s.key)),\n\t\thead.scripts.map((s) => /* @__PURE__ */ _jsx(\"script\", {\n\t\t\t...s.props,\n\t\t\t...s.props?.dangerouslySetInnerHTML ? {} : { dangerouslySetInnerHTML: s.script }\n\t\t}, s.key))\n\t] });\n};\n" + code: "import { useDocumentHead } from \"@qwik.dev/router\";\nimport { useLocation } from \"@qwik.dev/router\";\nexport const RouterHead_component_9vp071E6SfU = () => {\n\tconst head = useDocumentHead();\n\tconst loc = useLocation();\n\treturn [\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"title\", {}, {}, [head.title], 1, null),\n\t\t\"\\n\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"link\", { href: loc.url.href }, { rel: \"canonical\" }, [], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"meta\", {}, {\n\t\t\tname: \"viewport\",\n\t\t\tcontent: \"width=device-width, initial-scale=1.0\"\n\t\t}, [], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"link\", {}, {\n\t\t\trel: \"icon\",\n\t\t\ttype: \"image/svg+xml\",\n\t\t\thref: \"/favicon.svg\"\n\t\t}, [], 1, null),\n\t\t\"\\n\\n \",\n\t\thead.meta.map((m) => /* @__PURE__ */ _jsxSplit(\"meta\", { ...m }, {}, [], 0, m.key)),\n\t\t\"\\n\\n \",\n\t\thead.links.map((l) => /* @__PURE__ */ _jsxSplit(\"link\", { ...l }, {}, [], 0, l.key)),\n\t\t\"\\n\\n \",\n\t\thead.styles.map((s) => /* @__PURE__ */ _jsxSplit(\"style\", {\n\t\t\t...s.props,\n\t\t\t...s.props?.dangerouslySetInnerHTML ? {} : { dangerouslySetInnerHTML: s.style }\n\t\t}, {}, [], 0, s.key)),\n\t\t\"\\n\\n \",\n\t\thead.scripts.map((s) => /* @__PURE__ */ _jsxSplit(\"script\", {\n\t\t\t...s.props,\n\t\t\t...s.props?.dangerouslySetInnerHTML ? {} : { dangerouslySetInnerHTML: s.script }\n\t\t}, {}, [], 0, s.key)),\n\t\t\"\\n \"\n\t];\n};\n" map: ~ segment: origin: "./src/test_input/test_project_1/src/components/router-head/router-head.tsx" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_body.snap index 814939a..cc8ff55 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_body.snap @@ -2,4 +2,4 @@ source: src/transform.rs expression: result.body --- -"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nimport { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from \"react/jsx-runtime\";\nexport const Lightweight = (props) => {\n\treturn /* @__PURE__ */ _jsx(\"div\", { children: /* @__PURE__ */ _jsxs(_Fragment, { children: [/* @__PURE__ */ _jsx(\"div\", {}), /* @__PURE__ */ _jsx(\"button\", { ...props })] }) });\n};\nexport const Foo = componentQrl(qrl(() => import(\"./test.jsx_Foo_component_XXLiwMim708\"), \"Foo_component_XXLiwMim708\"));\n" +"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport const Lightweight = (props) => {\n\treturn /* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [\n\t\t\"\\n \",\n\t\t[\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSplit(\"button\", { ...props }, {}, [], 0, null),\n\t\t\t\"\\n \"\n\t\t],\n\t\t\"\\n \"\n\t], 1, null);\n};\nexport const Foo = componentQrl(qrl(() => import(\"./test.jsx_Foo_component_XXLiwMim708\"), \"Foo_component_XXLiwMim708\"));\n" diff --git a/optimizer/src/transform.rs b/optimizer/src/transform.rs index 827cc01..77cfc4a 100644 --- a/optimizer/src/transform.rs +++ b/optimizer/src/transform.rs @@ -514,7 +514,7 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { fn exit_expression(&mut self, node: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { if let Some(expr) = self.replace_expr.take() { - self.debug("Replacing expression on exit", ctx); + println!("Replacing expression on exit"); *node = expr; } } @@ -564,15 +564,19 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { }); } let name = &node.opening_element.name; - let jsx_type = match name { - JSXElementName::Identifier(b) => self.builder.expression_string_literal( - (*b).span, - (*b).name, - Some((*b).name), + let (jsx_type, pure) = match name { + JSXElementName::Identifier(b) => ( + self.builder.expression_string_literal( + (*b).span, + (*b).name, + Some((*b).name), + ), + true, + ), + JSXElementName::IdentifierReference(b) => ( + self.builder.expression_identifier((*b).span, (*b).name), + false, ), - JSXElementName::IdentifierReference(b) => { - self.builder.expression_identifier((*b).span, (*b).name) - } JSXElementName::NamespacedName(b) => { panic!("namespaced names in JSX not implemented") } @@ -601,17 +605,22 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { .into(), } } - self.builder - .member_expression_static( - (*b).span(), - process_member_expr(&self.builder, &((*b).object)), - self.builder - .identifier_name((*b).property.span(), (*b).property.name), - false, - ) - .into() + ( + self.builder + .member_expression_static( + (*b).span(), + process_member_expr(&self.builder, &((*b).object)), + self.builder + .identifier_name((*b).property.span(), (*b).property.name), + false, + ) + .into(), + false, + ) + } + JSXElementName::ThisExpression(b) => { + (self.builder.expression_this((*b).span), false) } - JSXElementName::ThisExpression(b) => self.builder.expression_this((*b).span), }; let args: OxcVec> = OxcVec::from_array_in( [ @@ -679,7 +688,7 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { None::>>, args, false, - jsx.is_text_only, + pure, )); } if jsx.is_segment { @@ -690,6 +699,35 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { self.descend(); } + fn enter_jsx_fragment(&mut self, node: &mut JSXFragment<'a>, ctx: &mut TraverseCtx<'a>) { + self.jsx_stack.push(JsxState { + is_fn: false, + is_text_only: false, + is_segment: false, + should_runtime_sort: false, + static_listeners: true, + static_subtree: true, + key_prop: None, + var_props: OxcVec::new_in(self.builder.allocator), + const_props: OxcVec::new_in(self.builder.allocator), + children: OxcVec::new_in(self.builder.allocator), + }); + self.debug("ENTER: JSXFragment", ctx); + } + + fn exit_jsx_fragment(&mut self, node: &mut JSXFragment<'a>, ctx: &mut TraverseCtx<'a>) { + if let Some(mut jsx) = self.jsx_stack.pop() { + if (self.options.transpile_jsx) { + self.replace_expr = Some(self.builder.expression_array( + node.span(), + jsx.children, + None, + )); + } + } + self.debug("EXIT: JSXFragment", ctx); + } + fn exit_jsx_spread_attribute( &mut self, node: &mut JSXSpreadAttribute<'a>, @@ -738,8 +776,14 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { let v = &mut node.value; match v { None => self.builder.expression_boolean_literal(node.span, true), - Some(JSXAttributeValue::Element(_)) => self.replace_expr.take().unwrap(), - Some(JSXAttributeValue::Fragment(_)) => self.replace_expr.take().unwrap(), + Some(JSXAttributeValue::Element(_)) => { + println!("Replacing JSX attribute element on exit"); + self.replace_expr.take().unwrap() + } + Some(JSXAttributeValue::Fragment(_)) => { + println!("Replacing JSX attribute fragment on exit"); + self.replace_expr.take().unwrap() + } Some(JSXAttributeValue::StringLiteral(b)) => self .builder .expression_string_literal((*b).span, (*b).value, Some((*b).value)), @@ -799,14 +843,21 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { if (!self.options.transpile_jsx) { return; } + self.debug("EXIT: JSX child", ctx); if let Some(jsx) = self.jsx_stack.last_mut() { jsx.children.push(match node { JSXChild::Text(b) => self .builder .expression_string_literal((*b).span, (*b).value, Some((*b).value)) .into(), - JSXChild::Element(_) => self.replace_expr.take().unwrap().into(), - JSXChild::Fragment(_) => self.replace_expr.take().unwrap().into(), + JSXChild::Element(_) => { + println!("Replacing JSX child element on exit"); + self.replace_expr.take().unwrap().into() + } + JSXChild::Fragment(_) => { + println!("Replacing JSX child fragment on exit"); + self.replace_expr.take().unwrap().into() + } JSXChild::ExpressionContainer(b) => { jsx.static_subtree = false; move_expression(&self.builder, (*b).expression.to_expression_mut()).into() From 7b806efe4471b30143f899b02add16f9a38dcb2c Mon Sep 17 00:00:00 2001 From: "Adam R. Nelson" Date: Mon, 30 Jun 2025 10:54:49 -0400 Subject: [PATCH 05/10] more test fixes and logging --- ..._tests__test_example_jsx_Foo_component_1_dBtHyQ8Btps.snap | 2 +- optimizer/src/transform.rs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_Foo_component_1_dBtHyQ8Btps.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_Foo_component_1_dBtHyQ8Btps.snap index 13c5b6a..1ea2da5 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_Foo_component_1_dBtHyQ8Btps.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_Foo_component_1_dBtHyQ8Btps.snap @@ -2,4 +2,4 @@ source: src/transform.rs expression: comp.code --- -"import { Lightweight } from \"./test.jsx\";\nimport { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from \"react/jsx-runtime\";\nexport const Foo_component_1_dBtHyQ8Btps = () => {\n\treturn /* @__PURE__ */ _jsxs(\"div\", { children: [\n\t\t/* @__PURE__ */ _jsxs(_Fragment, { children: [\n\t\t\t/* @__PURE__ */ _jsx(\"div\", { class: \"class\" }),\n\t\t\t/* @__PURE__ */ _jsx(\"div\", { class: \"class\" }),\n\t\t\t/* @__PURE__ */ _jsx(\"div\", {\n\t\t\t\tclass: \"class\",\n\t\t\t\tchildren: \"12\"\n\t\t\t})\n\t\t] }),\n\t\t/* @__PURE__ */ _jsx(\"div\", {\n\t\t\tclass: \"class\",\n\t\t\tchildren: /* @__PURE__ */ _jsx(Lightweight, { ...props })\n\t\t}),\n\t\t/* @__PURE__ */ _jsxs(\"div\", {\n\t\t\tclass: \"class\",\n\t\t\tchildren: [\n\t\t\t\t/* @__PURE__ */ _jsx(\"div\", {}),\n\t\t\t\t/* @__PURE__ */ _jsx(\"div\", {}),\n\t\t\t\t/* @__PURE__ */ _jsx(\"div\", {})\n\t\t\t]\n\t\t}),\n\t\t/* @__PURE__ */ _jsx(\"div\", {\n\t\t\tclass: \"class\",\n\t\t\tchildren\n\t\t})\n\t] });\n};\n" +"import { Lightweight } from \"./test.jsx\";\nexport const Foo_component_1_dBtHyQ8Btps = () => {\n\treturn /* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [\n\t\t\"\\n \",\n\t\t[\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [\"12\"], 1, null),\n\t\t\t\"\\n \"\n\t\t],\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [\n\t\t\t\"\\n \",\n\t\t\t_jsxSplit(Lightweight, { ...props }, {}, [], 0, null),\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [\n\t\t\t\"\\n \",\n\t\t\tchildren,\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \"\n\t], 1, null);\n};\n" diff --git a/optimizer/src/transform.rs b/optimizer/src/transform.rs index 77cfc4a..4b68a50 100644 --- a/optimizer/src/transform.rs +++ b/optimizer/src/transform.rs @@ -261,7 +261,12 @@ const DEBUG: bool = true; const DUMP_FINAL_AST: bool = false; impl<'a> Traverse<'a> for TransformGenerator<'a> { + fn enter_program(&mut self, node: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { + println!("ENTERING PROGRAM {}", self.source_info.file_name); + } + fn exit_program(&mut self, node: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { + println!("EXITING PROGRAM {}", self.source_info.file_name); if let Some(tree) = self.import_stack.pop() { tree.iter().for_each(|import| { node.body.insert(0, import.into_in(ctx.ast.allocator)); From 783aa29f18353d7951096a96598e7e11c3074800 Mon Sep 17 00:00:00 2001 From: "Adam R. Nelson" Date: Thu, 3 Jul 2025 13:19:52 -0400 Subject: [PATCH 06/10] insert _jsx* imports --- optimizer/src/component/shared.rs | 3 +- optimizer/src/import_clean_up.rs | 2 +- ..._lib_interface__tests__test_project_1.snap | 14 +++---- ...ample_jsx_Foo_component_1_dBtHyQ8Btps.snap | 4 +- ...ansform__tests__test_example_jsx_body.snap | 4 +- optimizer/src/transform.rs | 37 +++++++++---------- 6 files changed, 32 insertions(+), 32 deletions(-) diff --git a/optimizer/src/component/shared.rs b/optimizer/src/component/shared.rs index 6d767b3..bd0b2ca 100644 --- a/optimizer/src/component/shared.rs +++ b/optimizer/src/component/shared.rs @@ -9,7 +9,8 @@ use std::convert::Into; use std::path::PathBuf; pub const QWIK_CORE_SOURCE: &str = "@qwik.dev/core"; -pub const JSX_NAME: &str = "_jsx"; +pub const JSX_SORTED_NAME: &str = "_jsxSorted"; +pub const JSX_SPLIT_NAME: &str = "_jsxSplit"; pub const MARKER_SUFFIX: &str = "$"; pub const QRL: &str = "qrl"; pub const QRL_SUFFIX: &str = "Qrl"; diff --git a/optimizer/src/import_clean_up.rs b/optimizer/src/import_clean_up.rs index 0e60d74..948e590 100644 --- a/optimizer/src/import_clean_up.rs +++ b/optimizer/src/import_clean_up.rs @@ -64,7 +64,7 @@ struct Key(String); impl From<&ImportDeclaration<'_>> for Key { fn from(import: &ImportDeclaration) -> Self { let mut key = String::new(); - for specifiers in &import.specifiers { + if let Some(specifiers) = &import.specifiers { for specifier in specifiers { let local = specifier.local(); let local_name = local.name; diff --git a/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap b/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap index d4b76a5..582f217 100644 --- a/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap +++ b/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap @@ -1,10 +1,10 @@ --- -source: src/js_lib_interface.rs +source: optimizer/src/js_lib_interface.rs expression: result --- modules: - path: "./src/test_input/test_project_1/src/routes/index.tsx" - code: "export const component_3DYNZg0ikgU = () => {\n\treturn [\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"h1\", {}, {}, [\"Hi πŸ‘‹\"], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [\n\t\t\t\"\\n Can't wait to see what you build with qwik!\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"br\", {}, {}, [], 1, null),\n\t\t\t\"\\n Happy coding.\\n \"\n\t\t], 1, null),\n\t\t\"\\n \"\n\t];\n};\n" + code: "import { _jsxSorted } from \"@qwik.dev/core\";\nexport const component_3DYNZg0ikgU = () => {\n\treturn [\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"h1\", {}, {}, [\"Hi πŸ‘‹\"], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [\n\t\t\t\"\\n Can't wait to see what you build with qwik!\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"br\", {}, {}, [], 1, null),\n\t\t\t\"\\n Happy coding.\\n \"\n\t\t], 1, null),\n\t\t\"\\n \"\n\t];\n};\n" map: ~ segment: origin: "./src/test_input/test_project_1/src/routes/index.tsx" @@ -29,7 +29,7 @@ modules: segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/root.tsx" - code: "import { QwikCityProvider } from \"@qwik.dev/router\";\nimport { RouterHead } from \"./components/router-head/router-head\";\nimport { RouterOutlet } from \"@qwik.dev/router\";\nimport { ServiceWorkerRegister } from \"@qwik.dev/router\";\nimport { isDev } from \"@qwik.dev/core\";\nexport const component_cQDlEL2LM0Q = () => {\n\treturn _jsxSorted(QwikCityProvider, {}, {}, [\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"head\", {}, {}, [\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"meta\", {}, { charset: \"utf-8\" }, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t!isDev && /* @__PURE__ */ _jsxSorted(\"link\", { href: `${import.meta.env.BASE_URL}manifest.json` }, { rel: \"manifest\" }, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t_jsxSorted(RouterHead, {}, {}, [], 1, null),\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"body\", {}, { lang: \"en\" }, [\n\t\t\t\"\\n \",\n\t\t\t_jsxSorted(RouterOutlet, {}, {}, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t!isDev && _jsxSorted(ServiceWorkerRegister, {}, {}, [], 1, null),\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \"\n\t], 1, null);\n};\n" + code: "import { QwikCityProvider } from \"@qwik.dev/router\";\nimport { RouterHead } from \"./components/router-head/router-head\";\nimport { RouterOutlet } from \"@qwik.dev/router\";\nimport { ServiceWorkerRegister } from \"@qwik.dev/router\";\nimport { _jsxSorted } from \"@qwik.dev/core\";\nimport { isDev } from \"@qwik.dev/core\";\nexport const component_cQDlEL2LM0Q = () => {\n\treturn _jsxSorted(QwikCityProvider, {}, {}, [\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"head\", {}, {}, [\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"meta\", {}, { charset: \"utf-8\" }, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t!isDev && /* @__PURE__ */ _jsxSorted(\"link\", { href: `${import.meta.env.BASE_URL}manifest.json` }, { rel: \"manifest\" }, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t_jsxSorted(RouterHead, {}, {}, [], 1, null),\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"body\", {}, { lang: \"en\" }, [\n\t\t\t\"\\n \",\n\t\t\t_jsxSorted(RouterOutlet, {}, {}, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t!isDev && _jsxSorted(ServiceWorkerRegister, {}, {}, [], 1, null),\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \"\n\t], 1, null);\n};\n" map: ~ segment: origin: "./src/test_input/test_project_1/src/root.tsx" @@ -49,7 +49,7 @@ modules: - 0 isEntry: true - path: "./src/test_input/test_project_1/src/entry.ssr.tsx" - code: "import Root from \"./root\";\nimport { renderToStream } from \"@qwik.dev/core/server\";\nimport { manifest } from \"@qwik-client-manifest\";\nimport { RenderToStreamOptions } from \"@qwik.dev/core/server\";\nexport default function(opts: RenderToStreamOptions) {\n\treturn renderToStream(_jsxSorted(Root, {}, {}, [], 1, null), {\n\t\tmanifest,\n\t\t...opts,\n\t\tcontainerAttributes: {\n\t\t\tlang: \"en-us\",\n\t\t\t...opts.containerAttributes\n\t\t},\n\t\tserverData: { ...opts.serverData }\n\t});\n}\n" + code: "import Root from \"./root\";\nimport { renderToStream } from \"@qwik.dev/core/server\";\nimport { manifest } from \"@qwik-client-manifest\";\nimport { _jsxSorted } from \"@qwik.dev/core\";\nimport { RenderToStreamOptions } from \"@qwik.dev/core/server\";\nexport default function(opts: RenderToStreamOptions) {\n\treturn renderToStream(_jsxSorted(Root, {}, {}, [], 1, null), {\n\t\tmanifest,\n\t\t...opts,\n\t\tcontainerAttributes: {\n\t\t\tlang: \"en-us\",\n\t\t\t...opts.containerAttributes\n\t\t},\n\t\tserverData: { ...opts.serverData }\n\t});\n}\n" map: ~ segment: ~ isEntry: false @@ -59,7 +59,7 @@ modules: segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/routes/layout.tsx" - code: "import { Slot } from \"@qwik.dev/core\";\nexport const component_a1SxCR8N64g = () => {\n\treturn _jsxSorted(Slot, {}, {}, [], 1, null);\n};\n" + code: "import { Slot } from \"@qwik.dev/core\";\nimport { _jsxSorted } from \"@qwik.dev/core\";\nexport const component_a1SxCR8N64g = () => {\n\treturn _jsxSorted(Slot, {}, {}, [], 1, null);\n};\n" map: ~ segment: origin: "./src/test_input/test_project_1/src/routes/layout.tsx" @@ -89,7 +89,7 @@ modules: segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/entry.dev.tsx" - code: "import Root from \"./root\";\nimport { render } from \"@qwik.dev/core\";\nimport { RenderOptions } from \"@qwik.dev/core\";\nexport default function(opts: RenderOptions) {\n\treturn render(document, _jsxSorted(Root, {}, {}, [], 1, null), opts);\n}\n" + code: "import Root from \"./root\";\nimport { render } from \"@qwik.dev/core\";\nimport { _jsxSorted } from \"@qwik.dev/core\";\nimport { RenderOptions } from \"@qwik.dev/core\";\nexport default function(opts: RenderOptions) {\n\treturn render(document, _jsxSorted(Root, {}, {}, [], 1, null), opts);\n}\n" map: ~ segment: ~ isEntry: false @@ -104,7 +104,7 @@ modules: segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/components/router-head/router-head.tsx" - code: "import { useDocumentHead } from \"@qwik.dev/router\";\nimport { useLocation } from \"@qwik.dev/router\";\nexport const RouterHead_component_9vp071E6SfU = () => {\n\tconst head = useDocumentHead();\n\tconst loc = useLocation();\n\treturn [\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"title\", {}, {}, [head.title], 1, null),\n\t\t\"\\n\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"link\", { href: loc.url.href }, { rel: \"canonical\" }, [], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"meta\", {}, {\n\t\t\tname: \"viewport\",\n\t\t\tcontent: \"width=device-width, initial-scale=1.0\"\n\t\t}, [], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"link\", {}, {\n\t\t\trel: \"icon\",\n\t\t\ttype: \"image/svg+xml\",\n\t\t\thref: \"/favicon.svg\"\n\t\t}, [], 1, null),\n\t\t\"\\n\\n \",\n\t\thead.meta.map((m) => /* @__PURE__ */ _jsxSplit(\"meta\", { ...m }, {}, [], 0, m.key)),\n\t\t\"\\n\\n \",\n\t\thead.links.map((l) => /* @__PURE__ */ _jsxSplit(\"link\", { ...l }, {}, [], 0, l.key)),\n\t\t\"\\n\\n \",\n\t\thead.styles.map((s) => /* @__PURE__ */ _jsxSplit(\"style\", {\n\t\t\t...s.props,\n\t\t\t...s.props?.dangerouslySetInnerHTML ? {} : { dangerouslySetInnerHTML: s.style }\n\t\t}, {}, [], 0, s.key)),\n\t\t\"\\n\\n \",\n\t\thead.scripts.map((s) => /* @__PURE__ */ _jsxSplit(\"script\", {\n\t\t\t...s.props,\n\t\t\t...s.props?.dangerouslySetInnerHTML ? {} : { dangerouslySetInnerHTML: s.script }\n\t\t}, {}, [], 0, s.key)),\n\t\t\"\\n \"\n\t];\n};\n" + code: "import { _jsxSorted } from \"@qwik.dev/core\";\nimport { _jsxSplit } from \"@qwik.dev/core\";\nimport { useDocumentHead } from \"@qwik.dev/router\";\nimport { useLocation } from \"@qwik.dev/router\";\nexport const RouterHead_component_9vp071E6SfU = () => {\n\tconst head = useDocumentHead();\n\tconst loc = useLocation();\n\treturn [\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"title\", {}, {}, [head.title], 1, null),\n\t\t\"\\n\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"link\", { href: loc.url.href }, { rel: \"canonical\" }, [], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"meta\", {}, {\n\t\t\tname: \"viewport\",\n\t\t\tcontent: \"width=device-width, initial-scale=1.0\"\n\t\t}, [], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"link\", {}, {\n\t\t\trel: \"icon\",\n\t\t\ttype: \"image/svg+xml\",\n\t\t\thref: \"/favicon.svg\"\n\t\t}, [], 1, null),\n\t\t\"\\n\\n \",\n\t\thead.meta.map((m) => /* @__PURE__ */ _jsxSplit(\"meta\", { ...m }, {}, [], 0, m.key)),\n\t\t\"\\n\\n \",\n\t\thead.links.map((l) => /* @__PURE__ */ _jsxSplit(\"link\", { ...l }, {}, [], 0, l.key)),\n\t\t\"\\n\\n \",\n\t\thead.styles.map((s) => /* @__PURE__ */ _jsxSplit(\"style\", {\n\t\t\t...s.props,\n\t\t\t...s.props?.dangerouslySetInnerHTML ? {} : { dangerouslySetInnerHTML: s.style }\n\t\t}, {}, [], 0, s.key)),\n\t\t\"\\n\\n \",\n\t\thead.scripts.map((s) => /* @__PURE__ */ _jsxSplit(\"script\", {\n\t\t\t...s.props,\n\t\t\t...s.props?.dangerouslySetInnerHTML ? {} : { dangerouslySetInnerHTML: s.script }\n\t\t}, {}, [], 0, s.key)),\n\t\t\"\\n \"\n\t];\n};\n" map: ~ segment: origin: "./src/test_input/test_project_1/src/components/router-head/router-head.tsx" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_Foo_component_1_dBtHyQ8Btps.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_Foo_component_1_dBtHyQ8Btps.snap index 1ea2da5..ad8912d 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_Foo_component_1_dBtHyQ8Btps.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_Foo_component_1_dBtHyQ8Btps.snap @@ -1,5 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: comp.code --- -"import { Lightweight } from \"./test.jsx\";\nexport const Foo_component_1_dBtHyQ8Btps = () => {\n\treturn /* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [\n\t\t\"\\n \",\n\t\t[\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [\"12\"], 1, null),\n\t\t\t\"\\n \"\n\t\t],\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [\n\t\t\t\"\\n \",\n\t\t\t_jsxSplit(Lightweight, { ...props }, {}, [], 0, null),\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [\n\t\t\t\"\\n \",\n\t\t\tchildren,\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \"\n\t], 1, null);\n};\n" +"import { Lightweight } from \"./test.jsx\";\nimport { _jsxSorted } from \"@qwik.dev/core\";\nimport { _jsxSplit } from \"@qwik.dev/core\";\nexport const Foo_component_1_dBtHyQ8Btps = () => {\n\treturn /* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [\n\t\t\"\\n \",\n\t\t[\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [\"12\"], 1, null),\n\t\t\t\"\\n \"\n\t\t],\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [\n\t\t\t\"\\n \",\n\t\t\t_jsxSplit(Lightweight, { ...props }, {}, [], 0, null),\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [\n\t\t\t\"\\n \",\n\t\t\tchildren,\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \"\n\t], 1, null);\n};\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_body.snap index cc8ff55..b378d95 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_body.snap @@ -1,5 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body --- -"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport const Lightweight = (props) => {\n\treturn /* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [\n\t\t\"\\n \",\n\t\t[\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSplit(\"button\", { ...props }, {}, [], 0, null),\n\t\t\t\"\\n \"\n\t\t],\n\t\t\"\\n \"\n\t], 1, null);\n};\nexport const Foo = componentQrl(qrl(() => import(\"./test.jsx_Foo_component_XXLiwMim708\"), \"Foo_component_XXLiwMim708\"));\n" +"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nimport { _jsxSplit } from \"@qwik.dev/core\";\nimport { _jsxSorted } from \"@qwik.dev/core\";\nexport const Lightweight = (props) => {\n\treturn /* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [\n\t\t\"\\n \",\n\t\t[\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSplit(\"button\", { ...props }, {}, [], 0, null),\n\t\t\t\"\\n \"\n\t\t],\n\t\t\"\\n \"\n\t], 1, null);\n};\nexport const Foo = componentQrl(qrl(() => import(\"./test.jsx_Foo_component_XXLiwMim708\"), \"Foo_component_XXLiwMim708\"));\n" diff --git a/optimizer/src/transform.rs b/optimizer/src/transform.rs index 4b68a50..f1bbc46 100644 --- a/optimizer/src/transform.rs +++ b/optimizer/src/transform.rs @@ -51,9 +51,6 @@ use crate::ext::*; use crate::illegal_code::{IllegalCode, IllegalCodeType}; use crate::processing_failure::ProcessingFailure; -const JSX_SORTED: &str = "_jsxSorted"; -const JSX_SPLIT: &str = "_jsxSplit"; - impl OptimizedApp { fn get_component(&self, name: String) -> Option<&QrlComponent> { self.components @@ -154,13 +151,16 @@ pub struct TransformGenerator<'gen> { jsx_key_counter: u32, - // Marks whether each JSX attribute in the stack is var (false) or const (true) - // An attribute is considered var if it: - // - calls a function - // - accesses a member - // - is a variable that is not an import, an export, or in the const stack + /// Marks whether each JSX attribute in the stack is var (false) or const (true). + /// An attribute is considered var if it: + /// - calls a function + /// - accesses a member + /// - is a variable that is not an import, an export, or in the const stack expr_is_const_stack: Vec, + /// Used to replace the current expression in the AST. Should be set when exiting a specific + /// type of expression (e.g., `exit_jsx_element`); this will be picked up in `exit_expression`, + /// which will replace the entire expression with the contents of this field. replace_expr: Option>, } @@ -306,9 +306,7 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { } let name = node.callee_name().unwrap_or_default().to_string(); - if (name == JSX_NAME) { - println!("JSX FOUND IN CALLEXPRESSION, {:?}", node); - } else if (name.ends_with(MARKER_SUFFIX)) { + if (name.ends_with(MARKER_SUFFIX)) { self.import_stack.push(BTreeSet::new()); } @@ -680,21 +678,22 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { ], self.builder.allocator, ); + let callee = if (jsx.should_runtime_sort) { + JSX_SPLIT_NAME + } else { + JSX_SORTED_NAME + }; self.replace_expr = Some(self.builder.expression_call_with_pure( node.span, - self.builder.expression_identifier( - name.span(), - if (jsx.should_runtime_sort) { - JSX_SPLIT - } else { - JSX_SORTED - }, - ), + self.builder.expression_identifier(name.span(), callee), None::>>, args, false, pure, )); + if let Some(imports) = self.import_stack.last_mut() { + imports.insert(Import::new(vec![callee.into()], QWIK_CORE_SOURCE)); + } } if jsx.is_segment { let popped = self.segment_stack.pop(); From 6a4d265fc3fed5268087a8960af9696ddee76c93 Mon Sep 17 00:00:00 2001 From: "Adam R. Nelson" Date: Thu, 3 Jul 2025 13:46:01 -0400 Subject: [PATCH 07/10] merge imports from the same module --- optimizer/src/import_clean_up.rs | 57 +++++++++++++++---- ..._lib_interface__tests__test_project_1.snap | 12 ++-- ...ransform__tests__test_example_11_body.snap | 5 +- ...transform__tests__test_example_1_body.snap | 5 +- ...transform__tests__test_example_2_body.snap | 5 +- ...transform__tests__test_example_3_body.snap | 5 +- ...transform__tests__test_example_4_body.snap | 5 +- ...transform__tests__test_example_5_body.snap | 5 +- ...transform__tests__test_example_7_body.snap | 5 +- ...transform__tests__test_example_8_body.snap | 5 +- ...ts__test_example_capture_imports_body.snap | 5 +- ..._test_example_capturing_fn_class_body.snap | 5 +- ...ansform__tests__test_example_jsx_body.snap | 2 +- 13 files changed, 73 insertions(+), 48 deletions(-) diff --git a/optimizer/src/import_clean_up.rs b/optimizer/src/import_clean_up.rs index 948e590..162df56 100644 --- a/optimizer/src/import_clean_up.rs +++ b/optimizer/src/import_clean_up.rs @@ -1,9 +1,9 @@ -use crate::component::{Import, QWIK_CORE_SOURCE}; +use crate::component::{Import, ImportId, QWIK_CORE_SOURCE}; use oxc_allocator::Allocator; use oxc_ast::ast::{ImportDeclaration, ImportOrExportKind, Program, Statement}; use oxc_semantic::{SemanticBuilder, SemanticBuilderReturn}; use oxc_traverse::{traverse_mut, Traverse, TraverseCtx}; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; /// This struct is used to clean up unused imports in the AST. pub(crate) struct ImportCleanUp; @@ -94,28 +94,38 @@ impl<'a> Traverse<'a> for ImportCleanUp { node: &mut oxc_allocator::Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>, ) { - let mut imports: BTreeSet = BTreeSet::new(); + let mut imports: BTreeMap<&str, BTreeSet> = BTreeMap::new(); + // TODO: This will probably fail on `import { foo asΒ bar } from "qux"` + // Needs a special case for rename imports node.retain_mut(|node| match node { Statement::ImportDeclaration(import) => { let source = import.source.clone(); - let specifiers = &mut import.specifiers; - if let Some(specifiers) = specifiers { + if let Some(specifiers) = &import.specifiers { for specifier in specifiers { if ctx.scoping().symbol_is_used(specifier.local().symbol_id()) { - imports.insert(Import::from_import_declaration_specifier( - specifier, &source, - )); + if let Some(existing_set) = imports.get_mut(source.value.into()) { + existing_set.insert(specifier.into()); + } else { + let mut set: BTreeSet = BTreeSet::new(); + set.insert(specifier.into()); + imports.insert(source.value.into(), set); + } } } + false + } else { + true } - false } _ => true, }); - imports.iter().for_each(|import| { - node.insert(0, import.into_statement(ctx.ast.allocator)); + imports.into_iter().rev().for_each(|(module, names)| { + node.insert( + 0, + Import::new(names.into_iter().collect(), module).into_statement(ctx.ast.allocator), + ); }) } } @@ -153,6 +163,31 @@ mod tests { assert_eq!(lines[1], r#"b.foo();"#); } + #[test] + fn test_merge_imports() { + let allocator = Allocator::new(); + let source = r#" + import { a } from '@qwik.dev/core'; + import { b } from '@qwik.dev/core'; + import { c } from '@qwik.dev/router'; + + a.foo(b, c); + "#; + + let parse_return = Parser::new(&allocator, source, SourceType::tsx()).parse(); + let mut program = parse_return.program; + ImportCleanUp::clean_up(&mut program, &allocator); + + let codegen = Codegen::default(); + let raw = codegen.build(&program).code; + let lines: Vec<&str> = raw.lines().collect(); + assert_eq!(program.body.len(), 3); + assert_eq!(lines.len(), 3); + assert_eq!(lines[0], r#"import { a, b } from "@qwik.dev/core";"#); + assert_eq!(lines[1], r#"import { c } from "@qwik.dev/router";"#); + assert_eq!(lines[2], r#"a.foo(b, c);"#); + } + #[test] fn test_rename_qwik_imports() { let source = "@builder.io/qwik-city/foo"; diff --git a/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap b/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap index 582f217..e2fe819 100644 --- a/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap +++ b/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap @@ -49,12 +49,12 @@ modules: - 0 isEntry: true - path: "./src/test_input/test_project_1/src/entry.ssr.tsx" - code: "import Root from \"./root\";\nimport { renderToStream } from \"@qwik.dev/core/server\";\nimport { manifest } from \"@qwik-client-manifest\";\nimport { _jsxSorted } from \"@qwik.dev/core\";\nimport { RenderToStreamOptions } from \"@qwik.dev/core/server\";\nexport default function(opts: RenderToStreamOptions) {\n\treturn renderToStream(_jsxSorted(Root, {}, {}, [], 1, null), {\n\t\tmanifest,\n\t\t...opts,\n\t\tcontainerAttributes: {\n\t\t\tlang: \"en-us\",\n\t\t\t...opts.containerAttributes\n\t\t},\n\t\tserverData: { ...opts.serverData }\n\t});\n}\n" + code: "import Root from \"./root\";\nimport { manifest } from \"@qwik-client-manifest\";\nimport { _jsxSorted } from \"@qwik.dev/core\";\nimport { RenderToStreamOptions, renderToStream } from \"@qwik.dev/core/server\";\nexport default function(opts: RenderToStreamOptions) {\n\treturn renderToStream(_jsxSorted(Root, {}, {}, [], 1, null), {\n\t\tmanifest,\n\t\t...opts,\n\t\tcontainerAttributes: {\n\t\t\tlang: \"en-us\",\n\t\t\t...opts.containerAttributes\n\t\t},\n\t\tserverData: { ...opts.serverData }\n\t});\n}\n" map: ~ segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/root.tsx" - code: "import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/root.tsx.tsx_component_cQDlEL2LM0Q\"), \"component_cQDlEL2LM0Q\"));\n" + code: "import { componentQrl, qrl } from \"@qwik.dev/core\";\nimport \"./global.css\";\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/root.tsx.tsx_component_cQDlEL2LM0Q\"), \"component_cQDlEL2LM0Q\"));\n" map: ~ segment: ~ isEntry: false @@ -79,17 +79,17 @@ modules: - 0 isEntry: true - path: "./src/test_input/test_project_1/src/routes/index.tsx" - code: "import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nimport { DocumentHead } from \"@qwik.dev/router\";\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/index.tsx.tsx_component_3DYNZg0ikgU\"), \"component_3DYNZg0ikgU\"));\nexport const head: DocumentHead = componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/index.tsx.tsx_component_3DYNZg0ikgU\"), \"component_3DYNZg0ikgU\"));\n" + code: "import { componentQrl, qrl } from \"@qwik.dev/core\";\nimport { DocumentHead } from \"@qwik.dev/router\";\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/index.tsx.tsx_component_3DYNZg0ikgU\"), \"component_3DYNZg0ikgU\"));\nexport const head: DocumentHead = componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/index.tsx.tsx_component_3DYNZg0ikgU\"), \"component_3DYNZg0ikgU\"));\n" map: ~ segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/routes/layout.tsx" - code: "import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nimport { RequestHandler } from \"@qwik.dev/router\";\nexport const onGet: RequestHandler = async ({ cacheControl }) => {\n\tcacheControl({\n\t\tstaleWhileRevalidate: 60 * 60 * 24 * 7,\n\t\tmaxAge: 5\n\t});\n};\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/layout.tsx.tsx_component_a1SxCR8N64g\"), \"component_a1SxCR8N64g\"));\n" + code: "import { componentQrl, qrl } from \"@qwik.dev/core\";\nimport { RequestHandler } from \"@qwik.dev/router\";\nexport const onGet: RequestHandler = async ({ cacheControl }) => {\n\tcacheControl({\n\t\tstaleWhileRevalidate: 60 * 60 * 24 * 7,\n\t\tmaxAge: 5\n\t});\n};\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/layout.tsx.tsx_component_a1SxCR8N64g\"), \"component_a1SxCR8N64g\"));\n" map: ~ segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/entry.dev.tsx" - code: "import Root from \"./root\";\nimport { render } from \"@qwik.dev/core\";\nimport { _jsxSorted } from \"@qwik.dev/core\";\nimport { RenderOptions } from \"@qwik.dev/core\";\nexport default function(opts: RenderOptions) {\n\treturn render(document, _jsxSorted(Root, {}, {}, [], 1, null), opts);\n}\n" + code: "import Root from \"./root\";\nimport { RenderOptions, _jsxSorted, render } from \"@qwik.dev/core\";\nexport default function(opts: RenderOptions) {\n\treturn render(document, _jsxSorted(Root, {}, {}, [], 1, null), opts);\n}\n" map: ~ segment: ~ isEntry: false @@ -99,7 +99,7 @@ modules: segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/components/router-head/router-head.tsx" - code: "import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\n/**\n* The RouterHead component is placed inside of the document `` element.\n*/\nexport const RouterHead = componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/components/router-head/router-head.tsx.tsx_RouterHead_component_9vp071E6SfU\"), \"RouterHead_component_9vp071E6SfU\"));\n" + code: "import { componentQrl, qrl } from \"@qwik.dev/core\";\n/**\n* The RouterHead component is placed inside of the document `` element.\n*/\nexport const RouterHead = componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/components/router-head/router-head.tsx.tsx_RouterHead_component_9vp071E6SfU\"), \"RouterHead_component_9vp071E6SfU\"));\n" map: ~ segment: ~ isEntry: false diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_body.snap index 8fac317..fb57cb6 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_body.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body -snapshot_kind: text --- -"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport const Header = componentQrl(qrl(() => import(\"./test.tsx_Header_component_J4uyIhaBNR4\"), \"Header_component_J4uyIhaBNR4\"));\nexport const App = componentQrl(qrl(() => import(\"./test.tsx_App_component_ckEPmXZlub0\"), \"App_component_ckEPmXZlub0\"));\n" +"import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport const Header = componentQrl(qrl(() => import(\"./test.tsx_Header_component_J4uyIhaBNR4\"), \"Header_component_J4uyIhaBNR4\"));\nexport const App = componentQrl(qrl(() => import(\"./test.tsx_App_component_ckEPmXZlub0\"), \"App_component_ckEPmXZlub0\"));\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_1_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_1_body.snap index 54c93f2..eeab627 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_1_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_1_body.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body -snapshot_kind: text --- -"import { qrl } from \"@qwik.dev/core\";\nimport { component } from \"@qwik.dev/core\";\nexport const renderHeader = qrl(() => import(\"./test.tsx_renderHeader_zBbHWn4e8Cg\"), \"renderHeader_zBbHWn4e8Cg\");\nconst renderHeader = component(qrl(() => import(\"./test.tsx_renderHeader_component_U6Kkv07sbpQ\"), \"renderHeader_component_U6Kkv07sbpQ\"));\n" +"import { component, qrl } from \"@qwik.dev/core\";\nexport const renderHeader = qrl(() => import(\"./test.tsx_renderHeader_zBbHWn4e8Cg\"), \"renderHeader_zBbHWn4e8Cg\");\nconst renderHeader = component(qrl(() => import(\"./test.tsx_renderHeader_component_U6Kkv07sbpQ\"), \"renderHeader_component_U6Kkv07sbpQ\"));\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_2_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_2_body.snap index 54c93f2..eeab627 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_2_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_2_body.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body -snapshot_kind: text --- -"import { qrl } from \"@qwik.dev/core\";\nimport { component } from \"@qwik.dev/core\";\nexport const renderHeader = qrl(() => import(\"./test.tsx_renderHeader_zBbHWn4e8Cg\"), \"renderHeader_zBbHWn4e8Cg\");\nconst renderHeader = component(qrl(() => import(\"./test.tsx_renderHeader_component_U6Kkv07sbpQ\"), \"renderHeader_component_U6Kkv07sbpQ\"));\n" +"import { component, qrl } from \"@qwik.dev/core\";\nexport const renderHeader = qrl(() => import(\"./test.tsx_renderHeader_zBbHWn4e8Cg\"), \"renderHeader_zBbHWn4e8Cg\");\nconst renderHeader = component(qrl(() => import(\"./test.tsx_renderHeader_component_U6Kkv07sbpQ\"), \"renderHeader_component_U6Kkv07sbpQ\"));\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_3_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_3_body.snap index 50f1d52..a7a1077 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_3_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_3_body.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body -snapshot_kind: text --- -"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport const App = () => {\n\tconst Header = componentQrl(qrl(() => import(\"./test.tsx_App_Header_component_B9F3YeqcO1w\"), \"App_Header_component_B9F3YeqcO1w\"));\n\treturn Header;\n};\n" +"import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport const App = () => {\n\tconst Header = componentQrl(qrl(() => import(\"./test.tsx_App_Header_component_B9F3YeqcO1w\"), \"App_Header_component_B9F3YeqcO1w\"));\n\treturn Header;\n};\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_4_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_4_body.snap index 46b2941..5eb0eaa 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_4_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_4_body.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body -snapshot_kind: text --- -"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport function App() {\n\tconst Header = componentQrl(qrl(() => import(\"./test.tsx_App_Header_component_B9F3YeqcO1w\"), \"App_Header_component_B9F3YeqcO1w\"));\n\treturn Header;\n}\n" +"import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport function App() {\n\tconst Header = componentQrl(qrl(() => import(\"./test.tsx_App_Header_component_B9F3YeqcO1w\"), \"App_Header_component_B9F3YeqcO1w\"));\n\treturn Header;\n}\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_5_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_5_body.snap index 104dc9f..1879937 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_5_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_5_body.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body -snapshot_kind: text --- -"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport const Header = componentQrl(qrl(() => import(\"./test.tsx_Header_component_J4uyIhaBNR4\"), \"Header_component_J4uyIhaBNR4\"));\n" +"import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport const Header = componentQrl(qrl(() => import(\"./test.tsx_Header_component_J4uyIhaBNR4\"), \"Header_component_J4uyIhaBNR4\"));\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_7_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_7_body.snap index cf6b85d..6c2d0e5 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_7_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_7_body.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body -snapshot_kind: text --- -"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport const Header = componentQrl(qrl(() => import(\"./test.tsx_Header_component_J4uyIhaBNR4\"), \"Header_component_J4uyIhaBNR4\"));\ncomponentQrl(qrl(() => import(\"./test.tsx_App_component_ckEPmXZlub0\"), \"App_component_ckEPmXZlub0\"));\n" +"import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport const Header = componentQrl(qrl(() => import(\"./test.tsx_Header_component_J4uyIhaBNR4\"), \"Header_component_J4uyIhaBNR4\"));\ncomponentQrl(qrl(() => import(\"./test.tsx_App_component_ckEPmXZlub0\"), \"App_component_ckEPmXZlub0\"));\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_8_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_8_body.snap index 104dc9f..1879937 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_8_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_8_body.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body -snapshot_kind: text --- -"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport const Header = componentQrl(qrl(() => import(\"./test.tsx_Header_component_J4uyIhaBNR4\"), \"Header_component_J4uyIhaBNR4\"));\n" +"import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport const Header = componentQrl(qrl(() => import(\"./test.tsx_Header_component_J4uyIhaBNR4\"), \"Header_component_J4uyIhaBNR4\"));\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capture_imports_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capture_imports_body.snap index 45b6b39..f7812f3 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capture_imports_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capture_imports_body.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body -snapshot_kind: text --- -"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport const App = componentQrl(qrl(() => import(\"./test.jsx_App_component_MB7xrsoro5g\"), \"App_component_MB7xrsoro5g\"));\n" +"import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport const App = componentQrl(qrl(() => import(\"./test.jsx_App_component_MB7xrsoro5g\"), \"App_component_MB7xrsoro5g\"));\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capturing_fn_class_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capturing_fn_class_body.snap index 45b6b39..f7812f3 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capturing_fn_class_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capturing_fn_class_body.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: result.body -snapshot_kind: text --- -"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nexport const App = componentQrl(qrl(() => import(\"./test.jsx_App_component_MB7xrsoro5g\"), \"App_component_MB7xrsoro5g\"));\n" +"import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport const App = componentQrl(qrl(() => import(\"./test.jsx_App_component_MB7xrsoro5g\"), \"App_component_MB7xrsoro5g\"));\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_body.snap index b378d95..e7c0832 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_body.snap @@ -2,4 +2,4 @@ source: optimizer/src/transform.rs expression: result.body --- -"import { qrl } from \"@qwik.dev/core\";\nimport { componentQrl } from \"@qwik.dev/core\";\nimport { _jsxSplit } from \"@qwik.dev/core\";\nimport { _jsxSorted } from \"@qwik.dev/core\";\nexport const Lightweight = (props) => {\n\treturn /* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [\n\t\t\"\\n \",\n\t\t[\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSplit(\"button\", { ...props }, {}, [], 0, null),\n\t\t\t\"\\n \"\n\t\t],\n\t\t\"\\n \"\n\t], 1, null);\n};\nexport const Foo = componentQrl(qrl(() => import(\"./test.jsx_Foo_component_XXLiwMim708\"), \"Foo_component_XXLiwMim708\"));\n" +"import { _jsxSorted, _jsxSplit, componentQrl, qrl } from \"@qwik.dev/core\";\nexport const Lightweight = (props) => {\n\treturn /* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [\n\t\t\"\\n \",\n\t\t[\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSplit(\"button\", { ...props }, {}, [], 0, null),\n\t\t\t\"\\n \"\n\t\t],\n\t\t\"\\n \"\n\t], 1, null);\n};\nexport const Foo = componentQrl(qrl(() => import(\"./test.jsx_Foo_component_XXLiwMim708\"), \"Foo_component_XXLiwMim708\"));\n" From b0aa5856ee3aa716371234e67ebd572e1d18a49f Mon Sep 17 00:00:00 2001 From: "Adam R. Nelson" Date: Mon, 21 Jul 2025 13:23:31 -0400 Subject: [PATCH 08/10] transpile typescript --- optimizer/src/js_lib_interface.rs | 1 + ..._lib_interface__tests__test_project_1.snap | 10 +++--- ...e_ts_App_Header_component_B9F3YeqcO1w.snap | 5 +++ ...der_component_div_onClick_aO7uI7Iw6oQ.snap | 5 +++ ...ransform__tests__test_example_ts_body.snap | 5 +++ optimizer/src/test_input/test_example_ts.tsx | 11 +++++++ optimizer/src/transform.rs | 32 +++++++++++++++++++ 7 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_App_Header_component_B9F3YeqcO1w.snap create mode 100644 optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_App_Header_component_div_onClick_aO7uI7Iw6oQ.snap create mode 100644 optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_body.snap create mode 100644 optimizer/src/test_input/test_example_ts.tsx diff --git a/optimizer/src/js_lib_interface.rs b/optimizer/src/js_lib_interface.rs index 00e2898..81c3f99 100644 --- a/optimizer/src/js_lib_interface.rs +++ b/optimizer/src/js_lib_interface.rs @@ -253,6 +253,7 @@ pub fn transform_modules(config: TransformModulesOptions) -> Result false, }, target: config.mode, + transpile_ts: config.transpile_ts, transpile_jsx: config.transpile_jsx, }, )?; diff --git a/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap b/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap index e2fe819..0bcf0c4 100644 --- a/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap +++ b/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap @@ -49,7 +49,7 @@ modules: - 0 isEntry: true - path: "./src/test_input/test_project_1/src/entry.ssr.tsx" - code: "import Root from \"./root\";\nimport { manifest } from \"@qwik-client-manifest\";\nimport { _jsxSorted } from \"@qwik.dev/core\";\nimport { RenderToStreamOptions, renderToStream } from \"@qwik.dev/core/server\";\nexport default function(opts: RenderToStreamOptions) {\n\treturn renderToStream(_jsxSorted(Root, {}, {}, [], 1, null), {\n\t\tmanifest,\n\t\t...opts,\n\t\tcontainerAttributes: {\n\t\t\tlang: \"en-us\",\n\t\t\t...opts.containerAttributes\n\t\t},\n\t\tserverData: { ...opts.serverData }\n\t});\n}\n" + code: "import Root from \"./root\";\nimport { manifest } from \"@qwik-client-manifest\";\nimport { _jsxSorted } from \"@qwik.dev/core\";\nimport { renderToStream } from \"@qwik.dev/core/server\";\nexport default function(opts) {\n\treturn renderToStream(_jsxSorted(Root, {}, {}, [], 1, null), {\n\t\tmanifest,\n\t\t...opts,\n\t\tcontainerAttributes: {\n\t\t\tlang: \"en-us\",\n\t\t\t...opts.containerAttributes\n\t\t},\n\t\tserverData: { ...opts.serverData }\n\t});\n}\n" map: ~ segment: ~ isEntry: false @@ -79,22 +79,22 @@ modules: - 0 isEntry: true - path: "./src/test_input/test_project_1/src/routes/index.tsx" - code: "import { componentQrl, qrl } from \"@qwik.dev/core\";\nimport { DocumentHead } from \"@qwik.dev/router\";\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/index.tsx.tsx_component_3DYNZg0ikgU\"), \"component_3DYNZg0ikgU\"));\nexport const head: DocumentHead = componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/index.tsx.tsx_component_3DYNZg0ikgU\"), \"component_3DYNZg0ikgU\"));\n" + code: "import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/index.tsx.tsx_component_3DYNZg0ikgU\"), \"component_3DYNZg0ikgU\"));\nexport const head = componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/index.tsx.tsx_component_3DYNZg0ikgU\"), \"component_3DYNZg0ikgU\"));\n" map: ~ segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/routes/layout.tsx" - code: "import { componentQrl, qrl } from \"@qwik.dev/core\";\nimport { RequestHandler } from \"@qwik.dev/router\";\nexport const onGet: RequestHandler = async ({ cacheControl }) => {\n\tcacheControl({\n\t\tstaleWhileRevalidate: 60 * 60 * 24 * 7,\n\t\tmaxAge: 5\n\t});\n};\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/layout.tsx.tsx_component_a1SxCR8N64g\"), \"component_a1SxCR8N64g\"));\n" + code: "import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport const onGet = async ({ cacheControl }) => {\n\tcacheControl({\n\t\tstaleWhileRevalidate: 60 * 60 * 24 * 7,\n\t\tmaxAge: 5\n\t});\n};\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/layout.tsx.tsx_component_a1SxCR8N64g\"), \"component_a1SxCR8N64g\"));\n" map: ~ segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/entry.dev.tsx" - code: "import Root from \"./root\";\nimport { RenderOptions, _jsxSorted, render } from \"@qwik.dev/core\";\nexport default function(opts: RenderOptions) {\n\treturn render(document, _jsxSorted(Root, {}, {}, [], 1, null), opts);\n}\n" + code: "import Root from \"./root\";\nimport { _jsxSorted, render } from \"@qwik.dev/core\";\nexport default function(opts) {\n\treturn render(document, _jsxSorted(Root, {}, {}, [], 1, null), opts);\n}\n" map: ~ segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/routes/service-worker.ts" - code: "import { setupServiceWorker } from \"@qwik.dev/router/service-worker\";\nsetupServiceWorker();\naddEventListener(\"install\", () => self.skipWaiting());\naddEventListener(\"activate\", () => self.clients.claim());\ndeclare const self: ServiceWorkerGlobalScope;\n" + code: "import { setupServiceWorker } from \"@qwik.dev/router/service-worker\";\nsetupServiceWorker();\naddEventListener(\"install\", () => self.skipWaiting());\naddEventListener(\"activate\", () => self.clients.claim());\n" map: ~ segment: ~ isEntry: false diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_App_Header_component_B9F3YeqcO1w.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_App_Header_component_B9F3YeqcO1w.snap new file mode 100644 index 0000000..2c3975a --- /dev/null +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_App_Header_component_B9F3YeqcO1w.snap @@ -0,0 +1,5 @@ +--- +source: optimizer/src/transform.rs +expression: comp.code +--- +"import { qrl } from \"@qwik.dev/core\";\nexport const App_Header_component_B9F3YeqcO1w = () => {\n\tconsole.log(\"mount\");\n\treturn
import(\"./test.tsx_App_Header_component_div_onClick_aO7uI7Iw6oQ\"), \"App_Header_component_div_onClick_aO7uI7Iw6oQ\")} />;\n};\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_App_Header_component_div_onClick_aO7uI7Iw6oQ.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_App_Header_component_div_onClick_aO7uI7Iw6oQ.snap new file mode 100644 index 0000000..be1b93c --- /dev/null +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_App_Header_component_div_onClick_aO7uI7Iw6oQ.snap @@ -0,0 +1,5 @@ +--- +source: optimizer/src/transform.rs +expression: comp.code +--- +"export const App_Header_component_div_onClick_aO7uI7Iw6oQ = (ctx) => console.log(ctx);\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_body.snap new file mode 100644 index 0000000..a7a1077 --- /dev/null +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_ts_body.snap @@ -0,0 +1,5 @@ +--- +source: optimizer/src/transform.rs +expression: result.body +--- +"import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport const App = () => {\n\tconst Header = componentQrl(qrl(() => import(\"./test.tsx_App_Header_component_B9F3YeqcO1w\"), \"App_Header_component_B9F3YeqcO1w\"));\n\treturn Header;\n};\n" diff --git a/optimizer/src/test_input/test_example_ts.tsx b/optimizer/src/test_input/test_example_ts.tsx new file mode 100644 index 0000000..940e973 --- /dev/null +++ b/optimizer/src/test_input/test_example_ts.tsx @@ -0,0 +1,11 @@ +import { $, component$, type Component } from '@builder.io/qwik'; + +export const App = () => { + const Header: Component = component$(() => { + console.log("mount"); + return ( +
console.log(ctx))}/> + ); + }); + return Header; +}; diff --git a/optimizer/src/transform.rs b/optimizer/src/transform.rs index f1bbc46..5bdc086 100644 --- a/optimizer/src/transform.rs +++ b/optimizer/src/transform.rs @@ -16,6 +16,7 @@ use oxc_ast::{match_member_expression, AstBuilder, AstType, Comment, CommentKind use oxc_ast_visit::{Visit, VisitMut}; use oxc_codegen::{Codegen, CodegenOptions, Context, Gen}; use oxc_index::Idx; +use oxc_transformer::JsxOptions; use std::borrow::{Borrow, Cow}; use std::collections::{BTreeSet, HashMap, HashSet}; @@ -29,6 +30,7 @@ use oxc_semantic::{ SymbolId, }; use oxc_span::*; +use oxc_transformer::{TransformOptions as OxcTransformOptions, Transformer, TypeScriptOptions}; use oxc_traverse::{traverse_mut, Ancestor, Traverse, TraverseCtx}; use serde::{Deserialize, Serialize}; use std::cell::{Cell, RefCell}; @@ -1075,10 +1077,16 @@ fn is_text_only(node: &str) -> bool { pub struct TransformOptions { pub minify: bool, pub target: Target, + pub transpile_ts: bool, pub transpile_jsx: bool, } impl TransformOptions { + pub fn with_transpile_ts(mut self, transpile_ts: bool) -> Self { + self.transpile_ts = transpile_ts; + self + } + pub fn with_transpile_jsx(mut self, transpile_jsx: bool) -> Self { self.transpile_jsx = transpile_jsx; self @@ -1090,6 +1098,7 @@ impl Default for TransformOptions { TransformOptions { minify: false, target: Target::Dev, + transpile_ts: false, transpile_jsx: false, } } @@ -1108,6 +1117,24 @@ pub fn transform(script_source: Source, options: TransformOptions) -> Result Date: Thu, 24 Jul 2025 23:04:12 -0400 Subject: [PATCH 09/10] fix module naming issues --- optimizer/src/component/shared.rs | 2 +- optimizer/src/import_clean_up.rs | 52 +++++----- optimizer/src/js_lib_interface.rs | 6 +- ..._lib_interface__tests__test_project_1.snap | 96 +++++++++---------- 4 files changed, 82 insertions(+), 74 deletions(-) diff --git a/optimizer/src/component/shared.rs b/optimizer/src/component/shared.rs index bd0b2ca..6b26c0a 100644 --- a/optimizer/src/component/shared.rs +++ b/optimizer/src/component/shared.rs @@ -16,7 +16,7 @@ pub const QRL: &str = "qrl"; pub const QRL_SUFFIX: &str = "Qrl"; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(crate) enum ImportId { +pub enum ImportId { Named(String), NamedWithAlias(String, String), Default(String), diff --git a/optimizer/src/import_clean_up.rs b/optimizer/src/import_clean_up.rs index 162df56..d03c558 100644 --- a/optimizer/src/import_clean_up.rs +++ b/optimizer/src/import_clean_up.rs @@ -6,11 +6,15 @@ use oxc_traverse::{traverse_mut, Traverse, TraverseCtx}; use std::collections::{BTreeMap, BTreeSet}; /// This struct is used to clean up unused imports in the AST. -pub(crate) struct ImportCleanUp; +pub(crate) struct ImportCleanUp<'a> { + imports: BTreeMap<&'a str, BTreeSet>, +} -impl ImportCleanUp { +impl ImportCleanUp<'_> { pub fn new() -> Self { - ImportCleanUp + ImportCleanUp { + imports: BTreeMap::new(), + } } pub fn clean_up<'a>(program: &mut Program<'a>, allocator: &'a Allocator) { @@ -25,9 +29,20 @@ impl ImportCleanUp { let scoping = semantic.into_scoping(); - let transform = &mut ImportCleanUp::new(); + let mut transform = ImportCleanUp::new(); + + traverse_mut(&mut transform, allocator, program, scoping); - traverse_mut(transform, allocator, program, scoping); + transform + .imports + .into_iter() + .rev() + .for_each(|(module, names)| { + program.body.insert( + 0, + Import::new(names.into_iter().collect(), module).into_statement(allocator), + ); + }) } /// This function renames the Qwik imports to the new qwik.dev imports. @@ -88,28 +103,24 @@ impl From<&ImportDeclaration<'_>> for Key { } } -impl<'a> Traverse<'a> for ImportCleanUp { - fn enter_statements( +impl<'a> Traverse<'a> for ImportCleanUp<'a> { + fn exit_statements( &mut self, node: &mut oxc_allocator::Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>, ) { - let mut imports: BTreeMap<&str, BTreeSet> = BTreeMap::new(); - - // TODO: This will probably fail on `import { foo asΒ bar } from "qux"` - // Needs a special case for rename imports node.retain_mut(|node| match node { Statement::ImportDeclaration(import) => { let source = import.source.clone(); if let Some(specifiers) = &import.specifiers { for specifier in specifiers { if ctx.scoping().symbol_is_used(specifier.local().symbol_id()) { - if let Some(existing_set) = imports.get_mut(source.value.into()) { + if let Some(existing_set) = self.imports.get_mut(source.value.into()) { existing_set.insert(specifier.into()); } else { let mut set: BTreeSet = BTreeSet::new(); set.insert(specifier.into()); - imports.insert(source.value.into(), set); + self.imports.insert(source.value.into(), set); } } } @@ -120,13 +131,6 @@ impl<'a> Traverse<'a> for ImportCleanUp { } _ => true, }); - - imports.into_iter().rev().for_each(|(module, names)| { - node.insert( - 0, - Import::new(names.into_iter().collect(), module).into_statement(ctx.ast.allocator), - ); - }) } } @@ -167,11 +171,11 @@ mod tests { fn test_merge_imports() { let allocator = Allocator::new(); let source = r#" - import { a } from '@qwik.dev/core'; + import { a as A } from '@qwik.dev/core'; import { b } from '@qwik.dev/core'; import { c } from '@qwik.dev/router'; - a.foo(b, c); + A.foo(b, c); "#; let parse_return = Parser::new(&allocator, source, SourceType::tsx()).parse(); @@ -183,9 +187,9 @@ mod tests { let lines: Vec<&str> = raw.lines().collect(); assert_eq!(program.body.len(), 3); assert_eq!(lines.len(), 3); - assert_eq!(lines[0], r#"import { a, b } from "@qwik.dev/core";"#); + assert_eq!(lines[0], r#"import { b, a as A } from "@qwik.dev/core";"#); assert_eq!(lines[1], r#"import { c } from "@qwik.dev/router";"#); - assert_eq!(lines[2], r#"a.foo(b, c);"#); + assert_eq!(lines[2], r#"A.foo(b, c);"#); } #[test] diff --git a/optimizer/src/js_lib_interface.rs b/optimizer/src/js_lib_interface.rs index 81c3f99..ef52a4d 100644 --- a/optimizer/src/js_lib_interface.rs +++ b/optimizer/src/js_lib_interface.rs @@ -246,7 +246,11 @@ pub fn transform_modules(config: TransformModulesOptions) -> Result true, diff --git a/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap b/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap index 0bcf0c4..8fe64d6 100644 --- a/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap +++ b/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap @@ -3,46 +3,26 @@ source: optimizer/src/js_lib_interface.rs expression: result --- modules: - - path: "./src/test_input/test_project_1/src/routes/index.tsx" - code: "import { _jsxSorted } from \"@qwik.dev/core\";\nexport const component_3DYNZg0ikgU = () => {\n\treturn [\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"h1\", {}, {}, [\"Hi πŸ‘‹\"], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [\n\t\t\t\"\\n Can't wait to see what you build with qwik!\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"br\", {}, {}, [], 1, null),\n\t\t\t\"\\n Happy coding.\\n \"\n\t\t], 1, null),\n\t\t\"\\n \"\n\t];\n};\n" - map: ~ - segment: - origin: "./src/test_input/test_project_1/src/routes/index.tsx" - name: component_3DYNZg0ikgU - entry: ~ - displayName: index.tsx.tsx_component - hash: 3DYNZg0ikgU - canonicalFilename: "././src/test_input/test_project_1/src/routes/index.tsx.tsx_component_3DYNZg0ikgU" - path: "./src/test_input/test_project_1/src/routes/index.tsx" - extension: tsx - parent: ~ - ctxKind: function - ctxName: component_3DYNZg0ikgU - captures: false - loc: - - 0 - - 0 - isEntry: true - path: "./src/test_input/test_project_1/src/entry.preview.tsx" code: "import render from \"./entry.ssr\";\nimport qwikCityPlan from \"@qwik-city-plan\";\nimport { createQwikCity } from \"@qwik.dev/router/middleware/node\";\n/**\n* The default export is the QwikCity adapter used by Vite preview.\n*/\nexport default createQwikCity({\n\trender,\n\tqwikCityPlan\n});\n" map: ~ segment: ~ isEntry: false - - path: "./src/test_input/test_project_1/src/root.tsx" - code: "import { QwikCityProvider } from \"@qwik.dev/router\";\nimport { RouterHead } from \"./components/router-head/router-head\";\nimport { RouterOutlet } from \"@qwik.dev/router\";\nimport { ServiceWorkerRegister } from \"@qwik.dev/router\";\nimport { _jsxSorted } from \"@qwik.dev/core\";\nimport { isDev } from \"@qwik.dev/core\";\nexport const component_cQDlEL2LM0Q = () => {\n\treturn _jsxSorted(QwikCityProvider, {}, {}, [\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"head\", {}, {}, [\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"meta\", {}, { charset: \"utf-8\" }, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t!isDev && /* @__PURE__ */ _jsxSorted(\"link\", { href: `${import.meta.env.BASE_URL}manifest.json` }, { rel: \"manifest\" }, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t_jsxSorted(RouterHead, {}, {}, [], 1, null),\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"body\", {}, { lang: \"en\" }, [\n\t\t\t\"\\n \",\n\t\t\t_jsxSorted(RouterOutlet, {}, {}, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t!isDev && _jsxSorted(ServiceWorkerRegister, {}, {}, [], 1, null),\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \"\n\t], 1, null);\n};\n" + - path: "./src/test_input/test_project_1/src/routes/layout.tsx" + code: "import { Slot } from \"@qwik.dev/core\";\nimport { _jsxSorted } from \"@qwik.dev/core\";\nexport const component_e0ZOSHqXHEo = () => {\n\treturn _jsxSorted(Slot, {}, {}, [], 1, null);\n};\n" map: ~ segment: - origin: "./src/test_input/test_project_1/src/root.tsx" - name: component_cQDlEL2LM0Q + origin: "./src/test_input/test_project_1/src/routes/layout.tsx" + name: component_e0ZOSHqXHEo entry: ~ - displayName: root.tsx.tsx_component - hash: cQDlEL2LM0Q - canonicalFilename: "././src/test_input/test_project_1/src/root.tsx.tsx_component_cQDlEL2LM0Q" - path: "./src/test_input/test_project_1/src/root.tsx" + displayName: layout.tsx_component + hash: e0ZOSHqXHEo + canonicalFilename: "././src/test_input/test_project_1/src/routes/layout.tsx_component_e0ZOSHqXHEo" + path: "./src/test_input/test_project_1/src/routes/layout.tsx" extension: tsx parent: ~ ctxKind: function - ctxName: component_cQDlEL2LM0Q + ctxName: component_e0ZOSHqXHEo captures: false loc: - 0 @@ -54,37 +34,37 @@ modules: segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/root.tsx" - code: "import { componentQrl, qrl } from \"@qwik.dev/core\";\nimport \"./global.css\";\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/root.tsx.tsx_component_cQDlEL2LM0Q\"), \"component_cQDlEL2LM0Q\"));\n" + code: "import { componentQrl, qrl } from \"@qwik.dev/core\";\nimport \"./global.css\";\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/root.tsx_component_fZ4L0pYApnM\"), \"component_fZ4L0pYApnM\"));\n" map: ~ segment: ~ isEntry: false - - path: "./src/test_input/test_project_1/src/routes/layout.tsx" - code: "import { Slot } from \"@qwik.dev/core\";\nimport { _jsxSorted } from \"@qwik.dev/core\";\nexport const component_a1SxCR8N64g = () => {\n\treturn _jsxSorted(Slot, {}, {}, [], 1, null);\n};\n" + - path: "./src/test_input/test_project_1/src/root.tsx" + code: "import { QwikCityProvider } from \"@qwik.dev/router\";\nimport { RouterHead } from \"./components/router-head/router-head\";\nimport { RouterOutlet } from \"@qwik.dev/router\";\nimport { ServiceWorkerRegister } from \"@qwik.dev/router\";\nimport { _jsxSorted } from \"@qwik.dev/core\";\nimport { isDev } from \"@qwik.dev/core\";\nexport const component_fZ4L0pYApnM = () => {\n\treturn _jsxSorted(QwikCityProvider, {}, {}, [\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"head\", {}, {}, [\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"meta\", {}, { charset: \"utf-8\" }, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t!isDev && /* @__PURE__ */ _jsxSorted(\"link\", { href: `${import.meta.env.BASE_URL}manifest.json` }, { rel: \"manifest\" }, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t_jsxSorted(RouterHead, {}, {}, [], 1, null),\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"body\", {}, { lang: \"en\" }, [\n\t\t\t\"\\n \",\n\t\t\t_jsxSorted(RouterOutlet, {}, {}, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t!isDev && _jsxSorted(ServiceWorkerRegister, {}, {}, [], 1, null),\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \"\n\t], 1, null);\n};\n" map: ~ segment: - origin: "./src/test_input/test_project_1/src/routes/layout.tsx" - name: component_a1SxCR8N64g + origin: "./src/test_input/test_project_1/src/root.tsx" + name: component_fZ4L0pYApnM entry: ~ - displayName: layout.tsx.tsx_component - hash: a1SxCR8N64g - canonicalFilename: "././src/test_input/test_project_1/src/routes/layout.tsx.tsx_component_a1SxCR8N64g" - path: "./src/test_input/test_project_1/src/routes/layout.tsx" + displayName: root.tsx_component + hash: fZ4L0pYApnM + canonicalFilename: "././src/test_input/test_project_1/src/root.tsx_component_fZ4L0pYApnM" + path: "./src/test_input/test_project_1/src/root.tsx" extension: tsx parent: ~ ctxKind: function - ctxName: component_a1SxCR8N64g + ctxName: component_fZ4L0pYApnM captures: false loc: - 0 - 0 isEntry: true - path: "./src/test_input/test_project_1/src/routes/index.tsx" - code: "import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/index.tsx.tsx_component_3DYNZg0ikgU\"), \"component_3DYNZg0ikgU\"));\nexport const head = componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/index.tsx.tsx_component_3DYNZg0ikgU\"), \"component_3DYNZg0ikgU\"));\n" + code: "import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/index.tsx_component_4HLI2RMDcP8\"), \"component_4HLI2RMDcP8\"));\nexport const head = componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/index.tsx_component_4HLI2RMDcP8\"), \"component_4HLI2RMDcP8\"));\n" map: ~ segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/routes/layout.tsx" - code: "import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport const onGet = async ({ cacheControl }) => {\n\tcacheControl({\n\t\tstaleWhileRevalidate: 60 * 60 * 24 * 7,\n\t\tmaxAge: 5\n\t});\n};\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/layout.tsx.tsx_component_a1SxCR8N64g\"), \"component_a1SxCR8N64g\"));\n" + code: "import { componentQrl, qrl } from \"@qwik.dev/core\";\nexport const onGet = async ({ cacheControl }) => {\n\tcacheControl({\n\t\tstaleWhileRevalidate: 60 * 60 * 24 * 7,\n\t\tmaxAge: 5\n\t});\n};\nexport default componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/routes/layout.tsx_component_e0ZOSHqXHEo\"), \"component_e0ZOSHqXHEo\"));\n" map: ~ segment: ~ isEntry: false @@ -99,25 +79,45 @@ modules: segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/components/router-head/router-head.tsx" - code: "import { componentQrl, qrl } from \"@qwik.dev/core\";\n/**\n* The RouterHead component is placed inside of the document `` element.\n*/\nexport const RouterHead = componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/components/router-head/router-head.tsx.tsx_RouterHead_component_9vp071E6SfU\"), \"RouterHead_component_9vp071E6SfU\"));\n" + code: "import { componentQrl, qrl } from \"@qwik.dev/core\";\n/**\n* The RouterHead component is placed inside of the document `` element.\n*/\nexport const RouterHead = componentQrl(qrl(() => import(\"././src/test_input/test_project_1/src/components/router-head/router-head.tsx_RouterHead_component_VtXR96RQWfE\"), \"RouterHead_component_VtXR96RQWfE\"));\n" map: ~ segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/components/router-head/router-head.tsx" - code: "import { _jsxSorted } from \"@qwik.dev/core\";\nimport { _jsxSplit } from \"@qwik.dev/core\";\nimport { useDocumentHead } from \"@qwik.dev/router\";\nimport { useLocation } from \"@qwik.dev/router\";\nexport const RouterHead_component_9vp071E6SfU = () => {\n\tconst head = useDocumentHead();\n\tconst loc = useLocation();\n\treturn [\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"title\", {}, {}, [head.title], 1, null),\n\t\t\"\\n\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"link\", { href: loc.url.href }, { rel: \"canonical\" }, [], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"meta\", {}, {\n\t\t\tname: \"viewport\",\n\t\t\tcontent: \"width=device-width, initial-scale=1.0\"\n\t\t}, [], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"link\", {}, {\n\t\t\trel: \"icon\",\n\t\t\ttype: \"image/svg+xml\",\n\t\t\thref: \"/favicon.svg\"\n\t\t}, [], 1, null),\n\t\t\"\\n\\n \",\n\t\thead.meta.map((m) => /* @__PURE__ */ _jsxSplit(\"meta\", { ...m }, {}, [], 0, m.key)),\n\t\t\"\\n\\n \",\n\t\thead.links.map((l) => /* @__PURE__ */ _jsxSplit(\"link\", { ...l }, {}, [], 0, l.key)),\n\t\t\"\\n\\n \",\n\t\thead.styles.map((s) => /* @__PURE__ */ _jsxSplit(\"style\", {\n\t\t\t...s.props,\n\t\t\t...s.props?.dangerouslySetInnerHTML ? {} : { dangerouslySetInnerHTML: s.style }\n\t\t}, {}, [], 0, s.key)),\n\t\t\"\\n\\n \",\n\t\thead.scripts.map((s) => /* @__PURE__ */ _jsxSplit(\"script\", {\n\t\t\t...s.props,\n\t\t\t...s.props?.dangerouslySetInnerHTML ? {} : { dangerouslySetInnerHTML: s.script }\n\t\t}, {}, [], 0, s.key)),\n\t\t\"\\n \"\n\t];\n};\n" + code: "import { _jsxSorted } from \"@qwik.dev/core\";\nimport { _jsxSplit } from \"@qwik.dev/core\";\nimport { useDocumentHead } from \"@qwik.dev/router\";\nimport { useLocation } from \"@qwik.dev/router\";\nexport const RouterHead_component_VtXR96RQWfE = () => {\n\tconst head = useDocumentHead();\n\tconst loc = useLocation();\n\treturn [\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"title\", {}, {}, [head.title], 1, null),\n\t\t\"\\n\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"link\", { href: loc.url.href }, { rel: \"canonical\" }, [], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"meta\", {}, {\n\t\t\tname: \"viewport\",\n\t\t\tcontent: \"width=device-width, initial-scale=1.0\"\n\t\t}, [], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"link\", {}, {\n\t\t\trel: \"icon\",\n\t\t\ttype: \"image/svg+xml\",\n\t\t\thref: \"/favicon.svg\"\n\t\t}, [], 1, null),\n\t\t\"\\n\\n \",\n\t\thead.meta.map((m) => /* @__PURE__ */ _jsxSplit(\"meta\", { ...m }, {}, [], 0, m.key)),\n\t\t\"\\n\\n \",\n\t\thead.links.map((l) => /* @__PURE__ */ _jsxSplit(\"link\", { ...l }, {}, [], 0, l.key)),\n\t\t\"\\n\\n \",\n\t\thead.styles.map((s) => /* @__PURE__ */ _jsxSplit(\"style\", {\n\t\t\t...s.props,\n\t\t\t...s.props?.dangerouslySetInnerHTML ? {} : { dangerouslySetInnerHTML: s.style }\n\t\t}, {}, [], 0, s.key)),\n\t\t\"\\n\\n \",\n\t\thead.scripts.map((s) => /* @__PURE__ */ _jsxSplit(\"script\", {\n\t\t\t...s.props,\n\t\t\t...s.props?.dangerouslySetInnerHTML ? {} : { dangerouslySetInnerHTML: s.script }\n\t\t}, {}, [], 0, s.key)),\n\t\t\"\\n \"\n\t];\n};\n" map: ~ segment: origin: "./src/test_input/test_project_1/src/components/router-head/router-head.tsx" - name: RouterHead_component_9vp071E6SfU + name: RouterHead_component_VtXR96RQWfE entry: ~ - displayName: router-head.tsx.tsx_RouterHead_component - hash: 9vp071E6SfU - canonicalFilename: "././src/test_input/test_project_1/src/components/router-head/router-head.tsx.tsx_RouterHead_component_9vp071E6SfU" + displayName: router-head.tsx_RouterHead_component + hash: VtXR96RQWfE + canonicalFilename: "././src/test_input/test_project_1/src/components/router-head/router-head.tsx_RouterHead_component_VtXR96RQWfE" path: "./src/test_input/test_project_1/src/components/router-head/router-head.tsx" extension: tsx parent: ~ ctxKind: function - ctxName: RouterHead_component_9vp071E6SfU + ctxName: RouterHead_component_VtXR96RQWfE + captures: false + loc: + - 0 + - 0 + isEntry: true + - path: "./src/test_input/test_project_1/src/routes/index.tsx" + code: "import { _jsxSorted } from \"@qwik.dev/core\";\nexport const component_4HLI2RMDcP8 = () => {\n\treturn [\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"h1\", {}, {}, [\"Hi πŸ‘‹\"], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [\n\t\t\t\"\\n Can't wait to see what you build with qwik!\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"br\", {}, {}, [], 1, null),\n\t\t\t\"\\n Happy coding.\\n \"\n\t\t], 1, null),\n\t\t\"\\n \"\n\t];\n};\n" + map: ~ + segment: + origin: "./src/test_input/test_project_1/src/routes/index.tsx" + name: component_4HLI2RMDcP8 + entry: ~ + displayName: index.tsx_component + hash: 4HLI2RMDcP8 + canonicalFilename: "././src/test_input/test_project_1/src/routes/index.tsx_component_4HLI2RMDcP8" + path: "./src/test_input/test_project_1/src/routes/index.tsx" + extension: tsx + parent: ~ + ctxKind: function + ctxName: component_4HLI2RMDcP8 captures: false loc: - 0 From f8f420bc2db08079ecb2da34ba4b1051d7d385a6 Mon Sep 17 00:00:00 2001 From: "Adam R. Nelson" Date: Fri, 25 Jul 2025 00:42:31 -0400 Subject: [PATCH 10/10] fix component imports and JSX whitespace --- optimizer/src/component/component.rs | 4 +- optimizer/src/import_clean_up.rs | 6 +-- ..._lib_interface__tests__test_project_1.snap | 8 ++-- ..._example_11_App_component_ckEPmXZlub0.snap | 5 +-- ...ample_11_Header_component_J4uyIhaBNR4.snap | 5 +-- ...ure_imports_App_component_MB7xrsoro5g.snap | 5 +-- ...ample_jsx_Foo_component_1_dBtHyQ8Btps.snap | 2 +- ...ansform__tests__test_example_jsx_body.snap | 2 +- optimizer/src/transform.rs | 43 +++++++++++-------- 9 files changed, 42 insertions(+), 38 deletions(-) diff --git a/optimizer/src/component/component.rs b/optimizer/src/component/component.rs index af09e41..9690335 100644 --- a/optimizer/src/component/component.rs +++ b/optimizer/src/component/component.rs @@ -1,7 +1,7 @@ -use crate::component::Language; use crate::component::*; use crate::segment::Segment; use crate::transform::TransformOptions; +use crate::{component::Language, import_clean_up::ImportCleanUp}; use oxc_allocator::{Allocator, Box as OxcBox, CloneIn, IntoIn, Vec as OxcVec}; use oxc_ast::ast::*; use oxc_ast::*; @@ -117,6 +117,8 @@ impl QrlComponent { body, ); + ImportCleanUp::clean_up(&mut new_pgm, allocator); + let codegen = Codegen::new(); let codegen_options = CodegenOptions { annotation_comments: true, diff --git a/optimizer/src/import_clean_up.rs b/optimizer/src/import_clean_up.rs index d03c558..d369c86 100644 --- a/optimizer/src/import_clean_up.rs +++ b/optimizer/src/import_clean_up.rs @@ -21,11 +21,7 @@ impl ImportCleanUp<'_> { let SemanticBuilderReturn { semantic, errors: semantic_errors, - } = SemanticBuilder::new() - .with_check_syntax_error(true) // Enable extra syntax error checking - .with_build_jsdoc(true) // Enable JSDoc parsing - .with_cfg(true) // Build a Control Flow Graph - .build(program); + } = SemanticBuilder::new().build(program); let scoping = semantic.into_scoping(); diff --git a/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap b/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap index 8fe64d6..c1d0f39 100644 --- a/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap +++ b/optimizer/src/snapshots/qwik_optimizer__js_lib_interface__tests__test_project_1.snap @@ -9,7 +9,7 @@ modules: segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/routes/layout.tsx" - code: "import { Slot } from \"@qwik.dev/core\";\nimport { _jsxSorted } from \"@qwik.dev/core\";\nexport const component_e0ZOSHqXHEo = () => {\n\treturn _jsxSorted(Slot, {}, {}, [], 1, null);\n};\n" + code: "import { Slot, _jsxSorted } from \"@qwik.dev/core\";\nexport const component_e0ZOSHqXHEo = () => {\n\treturn _jsxSorted(Slot, {}, {}, [], 1, null);\n};\n" map: ~ segment: origin: "./src/test_input/test_project_1/src/routes/layout.tsx" @@ -39,7 +39,7 @@ modules: segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/root.tsx" - code: "import { QwikCityProvider } from \"@qwik.dev/router\";\nimport { RouterHead } from \"./components/router-head/router-head\";\nimport { RouterOutlet } from \"@qwik.dev/router\";\nimport { ServiceWorkerRegister } from \"@qwik.dev/router\";\nimport { _jsxSorted } from \"@qwik.dev/core\";\nimport { isDev } from \"@qwik.dev/core\";\nexport const component_fZ4L0pYApnM = () => {\n\treturn _jsxSorted(QwikCityProvider, {}, {}, [\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"head\", {}, {}, [\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"meta\", {}, { charset: \"utf-8\" }, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t!isDev && /* @__PURE__ */ _jsxSorted(\"link\", { href: `${import.meta.env.BASE_URL}manifest.json` }, { rel: \"manifest\" }, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t_jsxSorted(RouterHead, {}, {}, [], 1, null),\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"body\", {}, { lang: \"en\" }, [\n\t\t\t\"\\n \",\n\t\t\t_jsxSorted(RouterOutlet, {}, {}, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t!isDev && _jsxSorted(ServiceWorkerRegister, {}, {}, [], 1, null),\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \"\n\t], 1, null);\n};\n" + code: "import { RouterHead } from \"./components/router-head/router-head\";\nimport { _jsxSorted, isDev } from \"@qwik.dev/core\";\nimport { QwikCityProvider, RouterOutlet, ServiceWorkerRegister } from \"@qwik.dev/router\";\nexport const component_fZ4L0pYApnM = () => {\n\treturn _jsxSorted(QwikCityProvider, {}, {}, [/* @__PURE__ */ _jsxSorted(\"head\", {}, {}, [\n\t\t/* @__PURE__ */ _jsxSorted(\"meta\", {}, { charset: \"utf-8\" }, [], 1, null),\n\t\t!isDev && /* @__PURE__ */ _jsxSorted(\"link\", { href: `${import.meta.env.BASE_URL}manifest.json` }, { rel: \"manifest\" }, [], 1, null),\n\t\t_jsxSorted(RouterHead, {}, {}, [], 1, null)\n\t], 1, null), /* @__PURE__ */ _jsxSorted(\"body\", {}, { lang: \"en\" }, [_jsxSorted(RouterOutlet, {}, {}, [], 1, null), !isDev && _jsxSorted(ServiceWorkerRegister, {}, {}, [], 1, null)], 1, null)], 1, null);\n};\n" map: ~ segment: origin: "./src/test_input/test_project_1/src/root.tsx" @@ -84,7 +84,7 @@ modules: segment: ~ isEntry: false - path: "./src/test_input/test_project_1/src/components/router-head/router-head.tsx" - code: "import { _jsxSorted } from \"@qwik.dev/core\";\nimport { _jsxSplit } from \"@qwik.dev/core\";\nimport { useDocumentHead } from \"@qwik.dev/router\";\nimport { useLocation } from \"@qwik.dev/router\";\nexport const RouterHead_component_VtXR96RQWfE = () => {\n\tconst head = useDocumentHead();\n\tconst loc = useLocation();\n\treturn [\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"title\", {}, {}, [head.title], 1, null),\n\t\t\"\\n\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"link\", { href: loc.url.href }, { rel: \"canonical\" }, [], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"meta\", {}, {\n\t\t\tname: \"viewport\",\n\t\t\tcontent: \"width=device-width, initial-scale=1.0\"\n\t\t}, [], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"link\", {}, {\n\t\t\trel: \"icon\",\n\t\t\ttype: \"image/svg+xml\",\n\t\t\thref: \"/favicon.svg\"\n\t\t}, [], 1, null),\n\t\t\"\\n\\n \",\n\t\thead.meta.map((m) => /* @__PURE__ */ _jsxSplit(\"meta\", { ...m }, {}, [], 0, m.key)),\n\t\t\"\\n\\n \",\n\t\thead.links.map((l) => /* @__PURE__ */ _jsxSplit(\"link\", { ...l }, {}, [], 0, l.key)),\n\t\t\"\\n\\n \",\n\t\thead.styles.map((s) => /* @__PURE__ */ _jsxSplit(\"style\", {\n\t\t\t...s.props,\n\t\t\t...s.props?.dangerouslySetInnerHTML ? {} : { dangerouslySetInnerHTML: s.style }\n\t\t}, {}, [], 0, s.key)),\n\t\t\"\\n\\n \",\n\t\thead.scripts.map((s) => /* @__PURE__ */ _jsxSplit(\"script\", {\n\t\t\t...s.props,\n\t\t\t...s.props?.dangerouslySetInnerHTML ? {} : { dangerouslySetInnerHTML: s.script }\n\t\t}, {}, [], 0, s.key)),\n\t\t\"\\n \"\n\t];\n};\n" + code: "import { _jsxSorted, _jsxSplit } from \"@qwik.dev/core\";\nimport { useDocumentHead, useLocation } from \"@qwik.dev/router\";\nexport const RouterHead_component_VtXR96RQWfE = () => {\n\tconst head = useDocumentHead();\n\tconst loc = useLocation();\n\treturn [\n\t\t/* @__PURE__ */ _jsxSorted(\"title\", {}, {}, [head.title], 1, null),\n\t\t/* @__PURE__ */ _jsxSorted(\"link\", { href: loc.url.href }, { rel: \"canonical\" }, [], 1, null),\n\t\t/* @__PURE__ */ _jsxSorted(\"meta\", {}, {\n\t\t\tname: \"viewport\",\n\t\t\tcontent: \"width=device-width, initial-scale=1.0\"\n\t\t}, [], 1, null),\n\t\t/* @__PURE__ */ _jsxSorted(\"link\", {}, {\n\t\t\trel: \"icon\",\n\t\t\ttype: \"image/svg+xml\",\n\t\t\thref: \"/favicon.svg\"\n\t\t}, [], 1, null),\n\t\thead.meta.map((m) => /* @__PURE__ */ _jsxSplit(\"meta\", { ...m }, {}, [], 0, m.key)),\n\t\thead.links.map((l) => /* @__PURE__ */ _jsxSplit(\"link\", { ...l }, {}, [], 0, l.key)),\n\t\thead.styles.map((s) => /* @__PURE__ */ _jsxSplit(\"style\", {\n\t\t\t...s.props,\n\t\t\t...s.props?.dangerouslySetInnerHTML ? {} : { dangerouslySetInnerHTML: s.style }\n\t\t}, {}, [], 0, s.key)),\n\t\thead.scripts.map((s) => /* @__PURE__ */ _jsxSplit(\"script\", {\n\t\t\t...s.props,\n\t\t\t...s.props?.dangerouslySetInnerHTML ? {} : { dangerouslySetInnerHTML: s.script }\n\t\t}, {}, [], 0, s.key))\n\t];\n};\n" map: ~ segment: origin: "./src/test_input/test_project_1/src/components/router-head/router-head.tsx" @@ -104,7 +104,7 @@ modules: - 0 isEntry: true - path: "./src/test_input/test_project_1/src/routes/index.tsx" - code: "import { _jsxSorted } from \"@qwik.dev/core\";\nexport const component_4HLI2RMDcP8 = () => {\n\treturn [\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"h1\", {}, {}, [\"Hi πŸ‘‹\"], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [\n\t\t\t\"\\n Can't wait to see what you build with qwik!\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"br\", {}, {}, [], 1, null),\n\t\t\t\"\\n Happy coding.\\n \"\n\t\t], 1, null),\n\t\t\"\\n \"\n\t];\n};\n" + code: "import { _jsxSorted } from \"@qwik.dev/core\";\nexport const component_4HLI2RMDcP8 = () => {\n\treturn [/* @__PURE__ */ _jsxSorted(\"h1\", {}, {}, [\"Hi πŸ‘‹\"], 1, null), /* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [\n\t\t\"Can't wait to see what you build with qwik!\",\n\t\t/* @__PURE__ */ _jsxSorted(\"br\", {}, {}, [], 1, null),\n\t\t\"Happy coding.\"\n\t], 1, null)];\n};\n" map: ~ segment: origin: "./src/test_input/test_project_1/src/routes/index.tsx" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_App_component_ckEPmXZlub0.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_App_component_ckEPmXZlub0.snap index f7e2d3e..51603c5 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_App_component_ckEPmXZlub0.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_App_component_ckEPmXZlub0.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: comp.code -snapshot_kind: text --- -"import { Header } from \"./test\";\nimport { foo } from \"../state\";\nexport const App_component_ckEPmXZlub0 = () => {\n\treturn
{foo()}
;\n};\n" +"import { foo } from \"../state\";\nimport { Header } from \"./test\";\nexport const App_component_ckEPmXZlub0 = () => {\n\treturn
{foo()}
;\n};\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_Header_component_J4uyIhaBNR4.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_Header_component_J4uyIhaBNR4.snap index 1f7d2f8..132a47e 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_Header_component_J4uyIhaBNR4.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_11_Header_component_J4uyIhaBNR4.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: comp.code -snapshot_kind: text --- -"import { Header } from \"./test\";\nimport { qrl } from \"@qwik.dev/core\";\nimport { bar as bbar } from \"../state\";\nimport * as dep2 from \"dep2\";\nexport const Header_component_J4uyIhaBNR4 = () => {\n\treturn
import(\"./test.tsx_Header_component_Header_onClick_oNOlojcAk6Q\"), \"Header_component_Header_onClick_oNOlojcAk6Q\")}>\n {dep2.stuff()}{bbar()}\n
;\n};\n" +"import { bar as bbar } from \"../state\";\nimport { Header } from \"./test\";\nimport { qrl } from \"@qwik.dev/core\";\nimport * as dep2 from \"dep2\";\nexport const Header_component_J4uyIhaBNR4 = () => {\n\treturn
import(\"./test.tsx_Header_component_Header_onClick_oNOlojcAk6Q\"), \"Header_component_Header_onClick_oNOlojcAk6Q\")}>\n {dep2.stuff()}{bbar()}\n
;\n};\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capture_imports_App_component_MB7xrsoro5g.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capture_imports_App_component_MB7xrsoro5g.snap index 20228cb..1194300 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capture_imports_App_component_MB7xrsoro5g.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_capture_imports_App_component_MB7xrsoro5g.snap @@ -1,6 +1,5 @@ --- -source: src/transform.rs +source: optimizer/src/transform.rs expression: comp.code -snapshot_kind: text --- -"import { useStylesQrl, qrl } from \"@qwik.dev/core\";\nexport const App_component_MB7xrsoro5g = () => {\n\tuseStylesQrl(qrl(() => import(\"./test.jsx_App_component_useStyles_yj4sK3KtjRE\"), \"App_component_useStyles_yj4sK3KtjRE\"));\n\tuseStylesQrl(qrl(() => import(\"./test.jsx_App_component_useStyles_1_QaDDTipqmN4\"), \"App_component_useStyles_1_QaDDTipqmN4\"));\n};\n" +"import { qrl, useStylesQrl } from \"@qwik.dev/core\";\nexport const App_component_MB7xrsoro5g = () => {\n\tuseStylesQrl(qrl(() => import(\"./test.jsx_App_component_useStyles_yj4sK3KtjRE\"), \"App_component_useStyles_yj4sK3KtjRE\"));\n\tuseStylesQrl(qrl(() => import(\"./test.jsx_App_component_useStyles_1_QaDDTipqmN4\"), \"App_component_useStyles_1_QaDDTipqmN4\"));\n};\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_Foo_component_1_dBtHyQ8Btps.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_Foo_component_1_dBtHyQ8Btps.snap index ad8912d..b91a334 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_Foo_component_1_dBtHyQ8Btps.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_Foo_component_1_dBtHyQ8Btps.snap @@ -2,4 +2,4 @@ source: optimizer/src/transform.rs expression: comp.code --- -"import { Lightweight } from \"./test.jsx\";\nimport { _jsxSorted } from \"@qwik.dev/core\";\nimport { _jsxSplit } from \"@qwik.dev/core\";\nexport const Foo_component_1_dBtHyQ8Btps = () => {\n\treturn /* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [\n\t\t\"\\n \",\n\t\t[\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [\"12\"], 1, null),\n\t\t\t\"\\n \"\n\t\t],\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [\n\t\t\t\"\\n \",\n\t\t\t_jsxSplit(Lightweight, { ...props }, {}, [], 0, null),\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \",\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [\n\t\t\t\"\\n \",\n\t\t\tchildren,\n\t\t\t\"\\n \"\n\t\t], 1, null),\n\t\t\"\\n \"\n\t], 1, null);\n};\n" +"import { Lightweight } from \"./test.jsx\";\nimport { _jsxSorted, _jsxSplit } from \"@qwik.dev/core\";\nexport const Foo_component_1_dBtHyQ8Btps = () => {\n\treturn /* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [\n\t\t[\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [], 1, null),\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [], 1, null),\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [\"12\"], 1, null)\n\t\t],\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [_jsxSplit(Lightweight, { ...props }, {}, [], 0, null)], 1, null),\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null)\n\t\t], 1, null),\n\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, { class: \"class\" }, [children], 1, null)\n\t], 1, null);\n};\n" diff --git a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_body.snap b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_body.snap index e7c0832..f12cf29 100644 --- a/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_body.snap +++ b/optimizer/src/snapshots/qwik_optimizer__transform__tests__test_example_jsx_body.snap @@ -2,4 +2,4 @@ source: optimizer/src/transform.rs expression: result.body --- -"import { _jsxSorted, _jsxSplit, componentQrl, qrl } from \"@qwik.dev/core\";\nexport const Lightweight = (props) => {\n\treturn /* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [\n\t\t\"\\n \",\n\t\t[\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null),\n\t\t\t\"\\n \",\n\t\t\t/* @__PURE__ */ _jsxSplit(\"button\", { ...props }, {}, [], 0, null),\n\t\t\t\"\\n \"\n\t\t],\n\t\t\"\\n \"\n\t], 1, null);\n};\nexport const Foo = componentQrl(qrl(() => import(\"./test.jsx_Foo_component_XXLiwMim708\"), \"Foo_component_XXLiwMim708\"));\n" +"import { _jsxSorted, _jsxSplit, componentQrl, qrl } from \"@qwik.dev/core\";\nexport const Lightweight = (props) => {\n\treturn /* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [[/* @__PURE__ */ _jsxSorted(\"div\", {}, {}, [], 1, null), /* @__PURE__ */ _jsxSplit(\"button\", { ...props }, {}, [], 0, null)]], 1, null);\n};\nexport const Foo = componentQrl(qrl(() => import(\"./test.jsx_Foo_component_XXLiwMim708\"), \"Foo_component_XXLiwMim708\"));\n" diff --git a/optimizer/src/transform.rs b/optimizer/src/transform.rs index 5bdc086..943237f 100644 --- a/optimizer/src/transform.rs +++ b/optimizer/src/transform.rs @@ -851,32 +851,43 @@ impl<'a> Traverse<'a> for TransformGenerator<'a> { } self.debug("EXIT: JSX child", ctx); if let Some(jsx) = self.jsx_stack.last_mut() { - jsx.children.push(match node { - JSXChild::Text(b) => self - .builder - .expression_string_literal((*b).span, (*b).value, Some((*b).value)) - .into(), + let maybe_child = match node { + JSXChild::Text(b) => { + let text: &'a str = self.builder.allocator.alloc_str(b.value.trim()); + if (text.is_empty()) { + None + } else { + Some( + self.builder + .expression_string_literal((*b).span, text, Some(text.into())) + .into(), + ) + } + } JSXChild::Element(_) => { println!("Replacing JSX child element on exit"); - self.replace_expr.take().unwrap().into() + Some(self.replace_expr.take().unwrap().into()) } JSXChild::Fragment(_) => { println!("Replacing JSX child fragment on exit"); - self.replace_expr.take().unwrap().into() + Some(self.replace_expr.take().unwrap().into()) } JSXChild::ExpressionContainer(b) => { jsx.static_subtree = false; - move_expression(&self.builder, (*b).expression.to_expression_mut()).into() + Some(move_expression(&self.builder, (*b).expression.to_expression_mut()).into()) } JSXChild::Spread(b) => { jsx.static_subtree = false; let span = (*b).span.clone(); - self.builder.array_expression_element_spread_element( + Some(self.builder.array_expression_element_spread_element( span, move_expression(&self.builder, &mut (*b).expression), - ) + )) } - }); + }; + if let Some(child) = maybe_child { + jsx.children.push(child); + } } } @@ -1144,17 +1155,15 @@ pub fn transform(script_source: Source, options: TransformOptions) -> Result