-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
fix(es/minifier): Fix merge_imports to preserve import order #11258
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
This commit fixes the `merge_imports` option in the compression transform to preserve the original import order and prevent imports from being placed after their usage. **Problem:** When `merge_imports` was enabled, imports from the same module were being merged correctly, but the merged imports were being added at the end of the module body, causing them to appear after their usage statements. This broke the execution order and made the output not aesthetically pleasing. **Solution:** - Track the index of the first occurrence of each import group - Insert merged imports at the position of the first occurrence - Maintain the original import order by processing items sequentially **Test:** Added test case for issue #11257 that verifies imports are merged and placed in the correct position. Fixes #11257 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
|
|
1 similar comment
|
|
|
|
🤖 This pull request has been linked to DevBird Task #1621 View the task details and manage the automated development workflow in DevBird. Learn more about DevBird here or the announcement blog post here. |
📋 DevBird Task PromptFix #11257 This comment was automatically added by DevBird. You can disable this feature in DevBird Settings. |
|
Claude finished @DeLino[bot]'s task —— View job PR ReviewI've completed a comprehensive review of this PR that fixes import merging order preservation. Here's my detailed feedback: Todo List:
Code Quality Assessment✅ Positive Aspects
Issues Found🔴 Critical Issues1. Inefficient Data Structure Usage ( let mut seen_named: FxHashMap<SpecifierKey, ()> = FxHashMap::default();This should be 2. Potential Panic Risk ( for merged in inserts_at.get(first_idx).expect(
"Invariant violated: first_idx should always be present in inserts_at \
due to import group construction",
) {While the expect message is descriptive, this creates a potential panic point. The reviewer correctly identified this could be improved with better error handling. 🟡 Minor Issues3. Redundant Data Structure ( let mut processed_indices = FxHashSet::default();The 4. Multiple Cloning Operations Performance Considerations✅ Good
|
Binary Sizes
Commit: 5c6d2d7 |
Update the expected output for test 11133 to match the corrected behavior from the merge_imports fix. The merged imports now correctly appear at the position of their first occurrence, preserving the original import order and keeping imports before their usage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
CodSpeed Performance ReportMerging #11258 will not alter performanceComparing Summary
|
After fixing the merge_imports feature to preserve import order correctly, these test expectations needed to be updated. Imports now properly appear before exports and other statements, as they should. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR fixes an issue where merged imports were being appended at the end of the module instead of maintaining their original position. The fix ensures that merged imports are placed at the location of the first import occurrence, preserving the original import order.
Key changes:
- Modified import grouping to track the first occurrence index of each import group
- Replaced the retain-and-append approach with a new approach that inserts merged imports at their first occurrence position
- Ensures import statements maintain their relative position in the module
Reviewed Changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/swc_ecma_minifier/src/pass/postcompress.rs | Refactored merge_imports_in_module to track first occurrence index and insert merged imports at the correct position |
| crates/swc_ecma_minifier/tests/fixture/issues/11257/input.js | Test input demonstrating imports from multiple sources that should be merged |
| crates/swc_ecma_minifier/tests/fixture/issues/11257/output.js | Expected output showing merged imports maintaining original position |
| crates/swc_ecma_minifier/tests/fixture/issues/11133/output.js | Updated test expectations to reflect proper import positioning after merge |
| crates/swc/tests/tsc-references/exportNamespace12.2.minified.js | Updated test showing imports now appear before usage |
| crates/swc/tests/tsc-references/esnextmodulekindWithES5Target9.2.minified.js | Updated test showing imports now appear before exports |
| crates/swc/tests/tsc-references/es6modulekindWithES5Target9.2.minified.js | Updated test showing imports now appear before exports |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // This import needs to be merged | ||
| if idx == *first_idx && processed_indices.insert(*first_idx, ()).is_none() { | ||
| // This is the first occurrence - insert merged imports here | ||
| for merged in inserts_at.get(first_idx).unwrap() { |
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using unwrap() without proper error handling could cause a panic. Since first_idx is guaranteed to be in inserts_at by the logic at line 160, consider using expect() with a descriptive message to clarify this invariant, or restructure to avoid the lookup by storing the merged imports with the group data.
| for merged in inserts_at.get(first_idx).unwrap() { | |
| for merged in inserts_at.get(first_idx).expect("Invariant violated: first_idx should always be present in inserts_at due to import group construction") { |
| module.body.retain(|item| { | ||
| // and insert merged imports at the position of the first occurrence | ||
| let mut new_body = Vec::new(); | ||
| let mut processed_indices = FxHashMap::default(); |
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using a FxHashMap to track processed indices is inefficient when a FxHashSet<usize> would be more appropriate. The map stores unit values () which provides no benefit over a set's simpler API and slightly better performance.
kdy1
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reflect #11258 (review)
- Replace FxHashMap with FxHashSet for processed_indices tracking - Replace unwrap() with expect() with descriptive error message 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for (idx, item) in module.body.iter().enumerate() { | ||
| if let ModuleItem::ModuleDecl(ModuleDecl::Import(import_decl)) = item { | ||
| // Keep side-effect imports | ||
| if import_decl.specifiers.is_empty() { | ||
| return true; | ||
| new_body.push(item.clone()); | ||
| continue; | ||
| } | ||
|
|
||
| let key = ImportKey::from_import_decl(import_decl); | ||
| // Only keep if there's just one import for this key (no merging needed) | ||
| import_groups.get(&key).map_or(true, |v| v.len() <= 1) | ||
| } else { | ||
| true | ||
| } | ||
| }); | ||
|
|
||
| // Create merged imports and add them back | ||
| for (key, import_decls) in import_groups.iter() { | ||
| if import_decls.len() <= 1 { | ||
| // No merging needed, already retained above | ||
| continue; | ||
| } | ||
| // Check if this import is part of a merge group | ||
| if let Some((first_idx, decls)) = import_groups.get(&key) { | ||
| if decls.len() > 1 { | ||
| // This import needs to be merged | ||
| if idx == *first_idx && processed_indices.insert(*first_idx) { | ||
| // This is the first occurrence - insert merged imports here | ||
| for merged in inserts_at.get(first_idx).expect( | ||
| "Invariant violated: first_idx should always be present in inserts_at \ | ||
| due to import group construction", | ||
| ) { | ||
| new_body | ||
| .push(ModuleItem::ModuleDecl(ModuleDecl::Import(merged.clone()))); | ||
| } | ||
| } | ||
| // Skip this individual import (it's been merged) | ||
| continue; | ||
| } | ||
| } | ||
|
|
||
| let merged_imports = merge_import_decls(import_decls, key); | ||
| for merged in merged_imports { | ||
| module | ||
| .body | ||
| .push(ModuleItem::ModuleDecl(ModuleDecl::Import(merged))); | ||
| // Keep imports that don't need merging | ||
| new_body.push(item.clone()); | ||
| } else { | ||
| // Keep all non-import items | ||
| new_body.push(item.clone()); | ||
| } | ||
| } |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The loop clones every item in module.body, including potentially large non-import items. Consider taking ownership of module.body (using std::mem::take or similar) and moving items instead of cloning them. For example: let old_body = std::mem::take(&mut module.body); followed by for (idx, item) in old_body.into_iter().enumerate() and using items directly without cloning.
Summary
This PR fixes the
merge_importsoption in the compression transform to preserve the original import order and prevent imports from being placed after their usage.Problem
When
merge_importswas enabled, imports from the same module were being merged correctly, but the merged imports were being added at the end of the module body. This caused imports to appear after their usage statements, which:Example
Input:
Before this fix:
After this fix:
Solution
The fix modifies the
merge_imports_in_modulefunction incrates/swc_ecma_minifier/src/pass/postcompress.rs:Changes
merge_imports_in_moduleto track first import positionsretain + pushapproach to a full rebuild ofmodule.bodymerge_importscompression reorders imports incorrectly and places them after usage #11257Test Plan
tests/fixture/issues/11257/Fixes #11257
🤖 Generated with Claude Code