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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions optimizer/src/component/component.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use crate::component::Language;
use crate::component::*;
use crate::segment::Segment;
use crate::transform::TransformOptions;
use crate::transpiler::Transpiler;
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::*;
Expand Down Expand Up @@ -118,9 +117,7 @@ impl QrlComponent {
body,
);

if options.transpile_jsx {
Transpiler::transpile(allocator, &mut new_pgm, source_info);
}
ImportCleanUp::clean_up(&mut new_pgm, allocator);

let codegen = Codegen::new();
let codegen_options = CodegenOptions {
Expand Down
4 changes: 3 additions & 1 deletion optimizer/src/component/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ use std::convert::Into;
use std::path::PathBuf;

pub const QWIK_CORE_SOURCE: &str = "@qwik.dev/core";
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";

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) enum ImportId {
pub enum ImportId {
Named(String),
NamedWithAlias(String, String),
Default(String),
Expand Down
89 changes: 62 additions & 27 deletions optimizer/src/import_clean_up.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,44 @@
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;
pub(crate) struct ImportCleanUp<'a> {
imports: BTreeMap<&'a str, BTreeSet<ImportId>>,
}

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) {
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();

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.
Expand Down Expand Up @@ -64,7 +75,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;
Expand All @@ -88,35 +99,34 @@ 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: BTreeSet<Import> = BTreeSet::new();

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) = self.imports.get_mut(source.value.into()) {
existing_set.insert(specifier.into());
} else {
let mut set: BTreeSet<ImportId> = BTreeSet::new();
set.insert(specifier.into());
self.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));
})
}
}

Expand Down Expand Up @@ -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 as 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 { 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);"#);
}

#[test]
fn test_rename_qwik_imports() {
let source = "@builder.io/qwik-city/foo";
Expand Down
7 changes: 6 additions & 1 deletion optimizer/src/js_lib_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,13 +246,18 @@ pub fn transform_modules(config: TransformModulesOptions) -> Result<TransformOut
optimized_app,
errors,
} = transform(
Source::from_source(input.code, language, Some(relative_path.clone()))?,
Source::from_source(
input.code,
language,
Some(path.with_extension("").to_string_lossy().to_string()),
)?,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Path Resolution Fails with Incorrect Source Handling

The Source::from_source call now incorrectly passes an absolute path with its file extension removed, instead of the intended path relative to the source directory. This breaks path resolution and module identification, as downstream code expects relative paths with extensions.

Locations (1)

Fix in CursorFix in Web

TransformOptions {
minify: match config.minify {
MinifyMode::Simplify => true,
MinifyMode::None => false,
},
target: config.mode,
transpile_ts: config.transpile_ts,
transpile_jsx: config.transpile_jsx,
},
)?;
Expand Down
1 change: 0 additions & 1 deletion optimizer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,3 @@ mod processing_failure;
mod ref_counter;
mod segment;
pub mod transform;
mod transpiler;
Loading