Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
optimize_barrel
SWC transform and new optimizePackageImports
conf…
…ig (#54572) ## Implementation Base on #54530, we're implementing a `optimize_barrel` transform to optimize barrel files to only export the names we need. If the transformed file isn't a "barrel file", we just re-export the names from it without any transformation. Take `lucide-react` as an example, with #54530 we are able to transform ```js import { IceCream } from 'lucide-react' ``` to ```js import { IceCream } from '__barrel_optimize__?names=IceCream!=!lucide-react?__barrel_optimize_noop__=IceCream' ``` And then, we apply that new request with a new Webpack module rule to use the SWC loader with option `optimizeBarrelExports: ['IceCream']`, which eventually got passed to this new `optimize_barrel` transform and does the optimization. ## Notes We'll have to add a new `getModularizeImportAliases` alias list to map `lucide-react` to the ESM version, as we have the `['main', 'module']` resolve order for the server compiler. Otherwise this optimization doesn't work in that compiler. There's no e2e test added because it's already covered by the `modularize-imports` test as we removed the default `lucide-react` transform rules and it still works. We'll need to test other libs before migrating them to the new `optimizePackageImports` option. --- Closes #54571, closes #53605, closes #53789, closes #53894, closes #54063.
- Loading branch information
Showing
26 changed files
with
449 additions
and
87 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,268 @@ | ||
use serde::Deserialize; | ||
use turbopack_binding::swc::core::{ | ||
common::{FileName, DUMMY_SP}, | ||
ecma::{ | ||
ast::*, | ||
utils::{private_ident, quote_str}, | ||
visit::Fold, | ||
}, | ||
}; | ||
|
||
#[derive(Clone, Debug, Deserialize)] | ||
pub struct Config { | ||
pub names: Vec<String>, | ||
} | ||
|
||
pub fn optimize_barrel(filename: FileName, config: Config) -> impl Fold { | ||
OptimizeBarrel { | ||
filepath: filename.to_string(), | ||
names: config.names, | ||
} | ||
} | ||
|
||
#[derive(Debug, Default)] | ||
struct OptimizeBarrel { | ||
filepath: String, | ||
names: Vec<String>, | ||
} | ||
|
||
impl Fold for OptimizeBarrel { | ||
fn fold_module_items(&mut self, items: Vec<ModuleItem>) -> Vec<ModuleItem> { | ||
// One pre-pass to find all the local idents that we are referencing. | ||
let mut local_idents = vec![]; | ||
for item in &items { | ||
if let ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(export_named)) = item { | ||
if export_named.src.is_none() { | ||
for spec in &export_named.specifiers { | ||
if let ExportSpecifier::Named(s) = spec { | ||
let str_name; | ||
if let Some(name) = &s.exported { | ||
str_name = match &name { | ||
ModuleExportName::Ident(n) => n.sym.to_string(), | ||
ModuleExportName::Str(n) => n.value.to_string(), | ||
}; | ||
} else { | ||
str_name = match &s.orig { | ||
ModuleExportName::Ident(n) => n.sym.to_string(), | ||
ModuleExportName::Str(n) => n.value.to_string(), | ||
}; | ||
} | ||
|
||
// If the exported name needs to be kept, track the local ident. | ||
if self.names.contains(&str_name) { | ||
if let ModuleExportName::Ident(i) = &s.orig { | ||
local_idents.push(i.sym.clone()); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
// The second pass to rebuild the module items. | ||
let mut new_items = vec![]; | ||
|
||
// We only apply this optimization to barrel files. Here we consider | ||
// a barrel file to be a file that only exports from other modules. | ||
// Besides that, lit expressions are allowed as well ("use client", etc.). | ||
let mut is_barrel = true; | ||
for item in &items { | ||
match item { | ||
ModuleItem::ModuleDecl(decl) => { | ||
match decl { | ||
// export { foo } from './foo'; | ||
ModuleDecl::ExportNamed(export_named) => { | ||
for spec in &export_named.specifiers { | ||
match spec { | ||
ExportSpecifier::Namespace(s) => { | ||
let name_str = match &s.name { | ||
ModuleExportName::Ident(n) => n.sym.to_string(), | ||
ModuleExportName::Str(n) => n.value.to_string(), | ||
}; | ||
if self.names.contains(&name_str) { | ||
new_items.push(item.clone()); | ||
} | ||
} | ||
ExportSpecifier::Named(s) => { | ||
if let Some(name) = &s.exported { | ||
let name_str = match &name { | ||
ModuleExportName::Ident(n) => n.sym.to_string(), | ||
ModuleExportName::Str(n) => n.value.to_string(), | ||
}; | ||
|
||
if self.names.contains(&name_str) { | ||
new_items.push(ModuleItem::ModuleDecl( | ||
ModuleDecl::ExportNamed(NamedExport { | ||
span: DUMMY_SP, | ||
specifiers: vec![ExportSpecifier::Named( | ||
ExportNamedSpecifier { | ||
span: DUMMY_SP, | ||
orig: s.orig.clone(), | ||
exported: Some( | ||
ModuleExportName::Ident( | ||
Ident::new( | ||
name_str.into(), | ||
DUMMY_SP, | ||
), | ||
), | ||
), | ||
is_type_only: false, | ||
}, | ||
)], | ||
src: export_named.src.clone(), | ||
type_only: false, | ||
asserts: None, | ||
}), | ||
)); | ||
} | ||
} else { | ||
let name_str = match &s.orig { | ||
ModuleExportName::Ident(n) => n.sym.to_string(), | ||
ModuleExportName::Str(n) => n.value.to_string(), | ||
}; | ||
|
||
if self.names.contains(&name_str) { | ||
new_items.push(ModuleItem::ModuleDecl( | ||
ModuleDecl::ExportNamed(NamedExport { | ||
span: DUMMY_SP, | ||
specifiers: vec![ExportSpecifier::Named( | ||
ExportNamedSpecifier { | ||
span: DUMMY_SP, | ||
orig: s.orig.clone(), | ||
exported: None, | ||
is_type_only: false, | ||
}, | ||
)], | ||
src: export_named.src.clone(), | ||
type_only: false, | ||
asserts: None, | ||
}), | ||
)); | ||
} | ||
} | ||
} | ||
_ => { | ||
is_barrel = false; | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
// Keep import statements that create the local idents we need. | ||
ModuleDecl::Import(import_decl) => { | ||
for spec in &import_decl.specifiers { | ||
match spec { | ||
ImportSpecifier::Named(s) => { | ||
if local_idents.contains(&s.local.sym) { | ||
new_items.push(ModuleItem::ModuleDecl( | ||
ModuleDecl::Import(ImportDecl { | ||
span: DUMMY_SP, | ||
specifiers: vec![ImportSpecifier::Named( | ||
ImportNamedSpecifier { | ||
span: DUMMY_SP, | ||
local: s.local.clone(), | ||
imported: s.imported.clone(), | ||
is_type_only: false, | ||
}, | ||
)], | ||
src: import_decl.src.clone(), | ||
type_only: false, | ||
asserts: None, | ||
}), | ||
)); | ||
} | ||
} | ||
ImportSpecifier::Default(s) => { | ||
if local_idents.contains(&s.local.sym) { | ||
new_items.push(ModuleItem::ModuleDecl( | ||
ModuleDecl::Import(ImportDecl { | ||
span: DUMMY_SP, | ||
specifiers: vec![ImportSpecifier::Default( | ||
ImportDefaultSpecifier { | ||
span: DUMMY_SP, | ||
local: s.local.clone(), | ||
}, | ||
)], | ||
src: import_decl.src.clone(), | ||
type_only: false, | ||
asserts: None, | ||
}), | ||
)); | ||
} | ||
} | ||
ImportSpecifier::Namespace(s) => { | ||
if local_idents.contains(&s.local.sym) { | ||
new_items.push(ModuleItem::ModuleDecl( | ||
ModuleDecl::Import(ImportDecl { | ||
span: DUMMY_SP, | ||
specifiers: vec![ImportSpecifier::Namespace( | ||
ImportStarAsSpecifier { | ||
span: DUMMY_SP, | ||
local: s.local.clone(), | ||
}, | ||
)], | ||
src: import_decl.src.clone(), | ||
type_only: false, | ||
asserts: None, | ||
}), | ||
)); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
_ => { | ||
// Export expressions are not allowed in barrel files. | ||
is_barrel = false; | ||
break; | ||
} | ||
} | ||
} | ||
ModuleItem::Stmt(stmt) => match stmt { | ||
Stmt::Expr(expr) => match &*expr.expr { | ||
Expr::Lit(_) => { | ||
new_items.push(item.clone()); | ||
} | ||
_ => { | ||
is_barrel = false; | ||
break; | ||
} | ||
}, | ||
_ => { | ||
is_barrel = false; | ||
break; | ||
} | ||
}, | ||
} | ||
} | ||
|
||
// If the file is not a barrel file, we need to create a new module that | ||
// re-exports from the original file. | ||
// This is to avoid creating multiple instances of the original module. | ||
if !is_barrel { | ||
new_items = vec![ModuleItem::ModuleDecl(ModuleDecl::ExportNamed( | ||
NamedExport { | ||
span: DUMMY_SP, | ||
specifiers: self | ||
.names | ||
.iter() | ||
.map(|name| { | ||
ExportSpecifier::Named(ExportNamedSpecifier { | ||
span: DUMMY_SP, | ||
orig: ModuleExportName::Ident(private_ident!(name.clone())), | ||
exported: None, | ||
is_type_only: false, | ||
}) | ||
}) | ||
.collect(), | ||
src: Some(Box::new(quote_str!(self.filepath.to_string()))), | ||
type_only: false, | ||
asserts: None, | ||
}, | ||
))]; | ||
} | ||
|
||
new_items | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
packages/next-swc/crates/core/tests/fixture/named-import-transform/1/output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
import { A, B, C as F } from "barrel-optimize-loader?names=A,B,C!foo"; | ||
import { A, B, C as F } from "__barrel_optimize__?names=A,B,C!=!foo?__barrel_optimize_noop__=A,B,C"; | ||
import D from 'bar'; | ||
import E from 'baz'; |
4 changes: 2 additions & 2 deletions
4
packages/next-swc/crates/core/tests/fixture/named-import-transform/2/output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
import { A, B, C as F } from "barrel-optimize-loader?names=A,B,C!foo"; | ||
import { D } from "barrel-optimize-loader?names=D!bar"; | ||
import { A, B, C as F } from "__barrel_optimize__?names=A,B,C!=!foo?__barrel_optimize_noop__=A,B,C"; | ||
import { D } from "__barrel_optimize__?names=D!=!bar?__barrel_optimize_noop__=D"; | ||
import E from 'baz'; |
3 changes: 3 additions & 0 deletions
3
packages/next-swc/crates/core/tests/fixture/optimize-barrel/1/input.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export { foo, b as y } from './1' | ||
export { x, a } from './2' | ||
export { z } |
3 changes: 3 additions & 0 deletions
3
packages/next-swc/crates/core/tests/fixture/optimize-barrel/1/output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export { b as y } from './1'; | ||
export { x } from './2'; | ||
export { z }; |
6 changes: 6 additions & 0 deletions
6
packages/next-swc/crates/core/tests/fixture/optimize-barrel/2/input.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// De-optimize this file | ||
const foo = 1 | ||
|
||
export { foo, b as y } from './1' | ||
export { x, a } from './2' | ||
export { z } |
2 changes: 2 additions & 0 deletions
2
packages/next-swc/crates/core/tests/fixture/optimize-barrel/2/output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// De-optimize this file | ||
export { x, y, z } from "/some-project/node_modules/foo/file.js"; |
6 changes: 6 additions & 0 deletions
6
packages/next-swc/crates/core/tests/fixture/optimize-barrel/3/input.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// De-optimize this file | ||
export * from 'x' | ||
|
||
export { foo, b as y } from './1' | ||
export { x, a } from './2' | ||
export { z } |
2 changes: 2 additions & 0 deletions
2
packages/next-swc/crates/core/tests/fixture/optimize-barrel/3/output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// De-optimize this file | ||
export { x, y, z } from "/some-project/node_modules/foo/file.js"; |
Oops, something went wrong.