Skip to content

Commit 4d772e7

Browse files
authored
feat: tree shaking nested exports for destructuring assignment (#11781)
1 parent f3e5797 commit 4d772e7

File tree

14 files changed

+241
-75
lines changed

14 files changed

+241
-75
lines changed

crates/rspack_plugin_javascript/src/dependency/esm/esm_import_specifier_dependency.rs

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ use rspack_core::{
1616
};
1717
use rspack_error::Diagnostic;
1818
use rspack_util::json_stringify;
19-
use rustc_hash::FxHashSet as HashSet;
2019
use swc_core::ecma::atoms::Atom;
2120

2221
use super::{
@@ -25,7 +24,7 @@ use super::{
2524
};
2625
use crate::{
2726
connection_active_inline_value_for_esm_import_specifier, connection_active_used_by_exports,
28-
is_export_inlined, visitors::DestructuringAssignmentProperty,
27+
is_export_inlined, visitors::DestructuringAssignmentProperties,
2928
};
3029

3130
#[cacheable]
@@ -45,8 +44,8 @@ pub struct ESMImportSpecifierDependency {
4544
call: bool,
4645
direct_import: bool,
4746
used_by_exports: Option<UsedByExports>,
48-
#[cacheable(with=AsOption<AsVec<AsCacheable>>)]
49-
referenced_properties_in_destructuring: Option<HashSet<DestructuringAssignmentProperty>>,
47+
#[cacheable(with=AsOption<AsCacheable>)]
48+
referenced_properties_in_destructuring: Option<DestructuringAssignmentProperties>,
5049
resource_identifier: String,
5150
export_presence_mode: ExportPresenceMode,
5251
attributes: Option<ImportAttributes>,
@@ -69,7 +68,7 @@ impl ESMImportSpecifierDependency {
6968
call: bool,
7069
direct_import: bool,
7170
export_presence_mode: ExportPresenceMode,
72-
referenced_properties_in_destructuring: Option<HashSet<DestructuringAssignmentProperty>>,
71+
referenced_properties_in_destructuring: Option<DestructuringAssignmentProperties>,
7372
attributes: Option<ImportAttributes>,
7473
source_map: Option<SharedSourceMap>,
7574
) -> Self {
@@ -110,17 +109,19 @@ impl ESMImportSpecifierDependency {
110109
ids: Option<&[Atom]>,
111110
) -> Vec<ExtendedReferencedExport> {
112111
if let Some(referenced_properties) = &self.referenced_properties_in_destructuring {
113-
referenced_properties
114-
.iter()
115-
.map(|prop| {
116-
if let Some(v) = ids {
117-
let mut value = v.to_vec();
118-
value.push(prop.id.clone());
119-
value
120-
} else {
121-
vec![prop.id.clone()]
122-
}
123-
})
112+
let mut refs = Vec::new();
113+
referenced_properties.traverse_on_left(&mut |stack| {
114+
let ids_in_destructuring = stack.iter().map(|p| p.id.clone());
115+
if let Some(ids) = ids {
116+
let mut ids = ids.to_vec();
117+
ids.extend(ids_in_destructuring);
118+
refs.push(ids);
119+
} else {
120+
refs.push(ids_in_destructuring.collect::<Vec<_>>());
121+
}
122+
});
123+
refs
124+
.into_iter()
124125
// Do not inline if there are any places where used as destructuring
125126
.map(|name| ExtendedReferencedExport::Export(ReferencedExport::new(name, true, false)))
126127
.collect::<Vec<_>>()
@@ -565,9 +566,10 @@ impl DependencyTemplate for ESMImportSpecifierDependencyTemplate {
565566
}
566567
}
567568

568-
for prop in referenced_properties {
569+
referenced_properties.traverse_on_enter(&mut |stack| {
570+
let prop = stack.last().expect("should have last");
569571
let mut concated_ids = prefixed_ids.clone();
570-
concated_ids.push(prop.id.clone());
572+
concated_ids.extend(stack.iter().map(|p| p.id.clone()));
571573
let Some(new_name) = ExportsInfoGetter::get_used_name(
572574
GetUsedNameParam::WithNames(&module_graph.get_prefetched_exports_info(
573575
&module.identifier(),
@@ -586,7 +588,7 @@ impl DependencyTemplate for ESMImportSpecifierDependencyTemplate {
586588
};
587589

588590
if new_name == prop.id {
589-
continue;
591+
return;
590592
}
591593

592594
let comment = to_normal_comment(prop.id.as_str());
@@ -597,7 +599,7 @@ impl DependencyTemplate for ESMImportSpecifierDependencyTemplate {
597599
key
598600
};
599601
source.replace(prop.range.start, prop.range.end, &content, None);
600-
}
602+
});
601603
}
602604
}
603605
}

crates/rspack_plugin_javascript/src/parser_plugin/define_plugin/utils.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ use std::{borrow::Cow, sync::LazyLock};
33
use itertools::Itertools as _;
44
use regex::Regex;
55
use rspack_core::{ConstDependency, RuntimeGlobals};
6-
use rustc_hash::FxHashSet;
76
use serde_json::{Value, json};
87

9-
use crate::visitors::{DestructuringAssignmentProperty, JavascriptParser};
8+
use crate::visitors::{DestructuringAssignmentProperties, JavascriptParser};
109

1110
static WEBPACK_REQUIRE_FUNCTION_REGEXP: LazyLock<Regex> = LazyLock::new(|| {
1211
Regex::new("__webpack_require__\\s*(!?\\.)")
@@ -47,7 +46,7 @@ pub fn gen_const_dep(
4746
pub fn code_to_string<'a>(
4847
code: &'a Value,
4948
asi_safe: Option<bool>,
50-
obj_keys: Option<&FxHashSet<DestructuringAssignmentProperty>>,
49+
obj_keys: Option<&DestructuringAssignmentProperties>,
5150
) -> Cow<'a, str> {
5251
fn wrap_ansi(code: Cow<str>, is_arr: bool, asi_safe: Option<bool>) -> Cow<str> {
5352
match asi_safe {

crates/rspack_plugin_javascript/src/parser_plugin/import_meta_plugin.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ impl JavascriptParserPlugin for ImportMetaPlugin {
170170
parser.destructuring_assignment_properties.get(&span)
171171
{
172172
let mut content = vec![];
173-
for prop in referenced_properties_in_destructuring {
173+
for prop in referenced_properties_in_destructuring.iter() {
174174
if prop.id == "url" {
175175
content.push(format!(r#"url: "{}""#, self.import_meta_url(parser)))
176176
} else if prop.id == "webpack" {

crates/rspack_plugin_javascript/src/parser_plugin/import_parser_plugin.rs

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::borrow::Cow;
22

3-
use itertools::Itertools;
43
use rspack_core::{
54
AsyncDependenciesBlock, ChunkGroupOptions, ContextDependency, ContextNameSpaceObject,
65
ContextOptions, DependencyCategory, DependencyRange, DependencyType, DynamicImportFetchPriority,
@@ -163,11 +162,15 @@ impl JavascriptParserPlugin for ImportParserPlugin {
163162
.destructuring_assignment_properties
164163
.get(&ident.span())
165164
{
166-
for key in keys {
165+
let mut refs = Vec::new();
166+
keys.traverse_on_left(&mut |stack| {
167+
refs.push(stack.iter().map(|p| p.id.clone()).collect());
168+
});
169+
for ids in refs {
167170
parser
168171
.dynamic_import_references
169172
.get_import_mut_expect(&data.import_span)
170-
.add_reference(vec![key.id.clone()]);
173+
.add_reference(ids);
171174
}
172175
} else {
173176
parser
@@ -291,20 +294,28 @@ impl JavascriptParserPlugin for ImportParserPlugin {
291294
.map(|name| vec![Atom::from(name.as_str())])
292295
.collect::<Vec<_>>()
293296
});
297+
let has_webpack_exports_comment = exports.is_some();
294298

295299
let referenced_in_destructuring = parser
296300
.destructuring_assignment_properties
297-
.get(&import_call_span)
298-
.cloned();
301+
.get(&import_call_span);
299302
let referenced_in_member = parser
300303
.dynamic_import_references
301304
.get_import(&import_call_span);
302305
let referenced_fulfilled_ns_obj =
303306
import_then.and_then(|import_then| get_fulfilled_callback_namespace_obj(import_then));
307+
if let Some(keys) = referenced_in_destructuring {
308+
let mut refs = Vec::new();
309+
keys.traverse_on_left(&mut |stack| {
310+
refs.push(stack.iter().map(|p| p.id.clone()).collect());
311+
});
312+
exports = Some(refs);
313+
}
314+
304315
let is_statical = referenced_in_destructuring.is_some()
305316
|| referenced_in_member.is_some()
306317
|| referenced_fulfilled_ns_obj.is_some();
307-
if is_statical && exports.is_some() {
318+
if is_statical && has_webpack_exports_comment {
308319
let mut error: Error = create_traceable_error(
309320
"Useless magic comments".into(),
310321
"You don't need `webpackExports` if the usage of dynamic import is statically analyse-able. You can safely remove the `webpackExports` magic comment.".into(),
@@ -315,15 +326,6 @@ impl JavascriptParserPlugin for ImportParserPlugin {
315326
error.hide_stack = Some(true);
316327
parser.add_warning(error.into());
317328
}
318-
if let Some(referenced_properties_in_destructuring) = referenced_in_destructuring {
319-
exports = Some(
320-
referenced_properties_in_destructuring
321-
.iter()
322-
.cloned()
323-
.map(|x| vec![x.id])
324-
.collect_vec(),
325-
);
326-
}
327329

328330
let attributes = get_attributes_from_call_expr(node);
329331
let param = parser.evaluate_expression(dyn_imported.expr.as_ref());
@@ -562,15 +564,21 @@ fn walk_import_then_fulfilled_callback(
562564
if let Some(ns_obj) = namespace_obj_arg.as_ident() {
563565
tag_dynamic_import_referenced(parser, import_call, ns_obj.id.sym.clone());
564566
} else if let Some(ns_obj) = namespace_obj_arg.as_object() {
565-
if let Some(keys) = parser.collect_destructuring_assignment_properties(ns_obj) {
567+
if let Some(keys) =
568+
parser.collect_destructuring_assignment_properties_from_object_pattern(ns_obj)
569+
{
566570
parser
567571
.dynamic_import_references
568572
.add_import(import_call.span());
569573
let import_references = parser
570574
.dynamic_import_references
571575
.get_import_mut_expect(&import_call.span());
572-
for key in keys {
573-
import_references.add_reference(vec![key.id.clone()]);
576+
let mut refs = Vec::new();
577+
keys.traverse_on_left(&mut |stack| {
578+
refs.push(stack.iter().map(|p| p.id.clone()).collect());
579+
});
580+
for ids in refs {
581+
import_references.add_reference(ids);
574582
}
575583
}
576584
} else {

crates/rspack_plugin_javascript/src/visitors/dependency/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ use swc_core::common::{BytePos, Mark, SourceFile, SourceMap, comments::Comments}
1717
pub use self::{
1818
context_dependency_helper::{ContextModuleScanResult, create_context_dependency},
1919
parser::{
20-
AllowedMemberTypes, CallExpressionInfo, CallHooksName, DestructuringAssignmentProperty,
21-
ExportedVariableInfo, JavascriptParser, MemberExpressionInfo, RootName, TagInfoData,
22-
TopLevelScope, estree::*,
20+
AllowedMemberTypes, CallExpressionInfo, CallHooksName, DestructuringAssignmentProperties,
21+
DestructuringAssignmentProperty, ExportedVariableInfo, JavascriptParser, MemberExpressionInfo,
22+
RootName, TagInfoData, TopLevelScope, estree::*,
2323
},
2424
util::*,
2525
};

crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs

Lines changed: 97 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,20 @@ mod walk_block_pre;
55
mod walk_module_pre;
66
mod walk_pre;
77

8-
use std::{borrow::Cow, fmt::Display, rc::Rc, sync::Arc};
8+
use std::{
9+
borrow::Cow,
10+
fmt::Display,
11+
hash::{Hash, Hasher},
12+
rc::Rc,
13+
sync::Arc,
14+
};
915

1016
use bitflags::bitflags;
1117
pub use call_hooks_name::CallHooksName;
12-
use rspack_cacheable::{cacheable, with::AsPreset};
18+
use rspack_cacheable::{
19+
cacheable,
20+
with::{AsCacheable, AsOption, AsPreset, AsVec},
21+
};
1322
use rspack_core::{
1423
AsyncDependenciesBlock, BoxDependency, BoxDependencyTemplate, BuildInfo, BuildMeta,
1524
CompilerOptions, DependencyRange, FactoryMeta, JavascriptParserCommonjsExportsOption,
@@ -113,15 +122,6 @@ pub struct ExpressionExpressionInfo {
113122
pub member_ranges: Vec<Span>,
114123
}
115124

116-
#[cacheable]
117-
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
118-
pub struct DestructuringAssignmentProperty {
119-
pub range: DependencyRange,
120-
#[cacheable(with=AsPreset)]
121-
pub id: Atom,
122-
pub shorthand: bool,
123-
}
124-
125125
#[derive(Debug, Clone)]
126126
pub enum ExportedVariableInfo {
127127
Name(Atom),
@@ -211,17 +211,96 @@ impl From<Span> for StatementPath {
211211
}
212212
}
213213

214-
#[derive(Debug, Default)]
214+
#[cacheable]
215+
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
216+
pub struct DestructuringAssignmentProperty {
217+
pub range: DependencyRange,
218+
#[cacheable(with=AsPreset)]
219+
pub id: Atom,
220+
#[cacheable(omit_bounds, with=AsOption<AsCacheable>)]
221+
pub pattern: Option<DestructuringAssignmentProperties>,
222+
pub shorthand: bool,
223+
}
224+
225+
#[cacheable]
226+
#[derive(Debug, Default, Clone, PartialEq, Eq)]
215227
pub struct DestructuringAssignmentProperties {
216-
inner: FxHashMap<Span, FxHashSet<DestructuringAssignmentProperty>>,
228+
#[cacheable(with=AsVec<AsCacheable>)]
229+
inner: FxHashSet<DestructuringAssignmentProperty>,
230+
}
231+
232+
impl Hash for DestructuringAssignmentProperties {
233+
fn hash<H: Hasher>(&self, state: &mut H) {
234+
for prop in &self.inner {
235+
prop.hash(state);
236+
}
237+
}
217238
}
218239

219240
impl DestructuringAssignmentProperties {
220-
pub fn add(&mut self, span: Span, props: FxHashSet<DestructuringAssignmentProperty>) {
241+
pub fn new(properties: FxHashSet<DestructuringAssignmentProperty>) -> Self {
242+
Self { inner: properties }
243+
}
244+
245+
pub fn insert(&mut self, prop: DestructuringAssignmentProperty) -> bool {
246+
self.inner.insert(prop)
247+
}
248+
249+
pub fn extend(&mut self, other: Self) {
250+
self.inner.extend(other.inner);
251+
}
252+
253+
pub fn iter(&self) -> impl Iterator<Item = &DestructuringAssignmentProperty> {
254+
self.inner.iter()
255+
}
256+
257+
pub fn traverse_on_left<'a, F>(&'a self, on_left_node: &mut F)
258+
where
259+
F: FnMut(&mut Vec<&'a DestructuringAssignmentProperty>),
260+
{
261+
self.traverse_impl(on_left_node, &mut |_| {}, &mut Vec::new());
262+
}
263+
264+
pub fn traverse_on_enter<'a, F>(&'a self, on_enter_node: &mut F)
265+
where
266+
F: FnMut(&mut Vec<&'a DestructuringAssignmentProperty>),
267+
{
268+
self.traverse_impl(&mut |_| {}, on_enter_node, &mut Vec::new());
269+
}
270+
271+
fn traverse_impl<'a, L, E>(
272+
&'a self,
273+
on_left_node: &mut L,
274+
on_enter_node: &mut E,
275+
stack: &mut Vec<&'a DestructuringAssignmentProperty>,
276+
) where
277+
L: FnMut(&mut Vec<&'a DestructuringAssignmentProperty>),
278+
E: FnMut(&mut Vec<&'a DestructuringAssignmentProperty>),
279+
{
280+
for prop in &self.inner {
281+
stack.push(prop);
282+
on_enter_node(stack);
283+
if let Some(pattern) = &prop.pattern {
284+
pattern.traverse_impl(on_left_node, on_enter_node, stack);
285+
} else {
286+
on_left_node(stack);
287+
}
288+
stack.pop();
289+
}
290+
}
291+
}
292+
293+
#[derive(Debug, Default)]
294+
pub struct DestructuringAssignmentPropertiesMap {
295+
inner: FxHashMap<Span, DestructuringAssignmentProperties>,
296+
}
297+
298+
impl DestructuringAssignmentPropertiesMap {
299+
pub fn add(&mut self, span: Span, props: DestructuringAssignmentProperties) {
221300
self.inner.entry(span).or_default().extend(props)
222301
}
223302

224-
pub fn get(&self, span: &Span) -> Option<&FxHashSet<DestructuringAssignmentProperty>> {
303+
pub fn get(&self, span: &Span) -> Option<&DestructuringAssignmentProperties> {
225304
self.inner.get(span)
226305
}
227306
}
@@ -264,7 +343,7 @@ pub struct JavascriptParser<'parser> {
264343
pub(crate) statement_path: Vec<StatementPath>,
265344
pub(crate) prev_statement: Option<StatementPath>,
266345
pub is_esm: bool,
267-
pub(crate) destructuring_assignment_properties: DestructuringAssignmentProperties,
346+
pub(crate) destructuring_assignment_properties: DestructuringAssignmentPropertiesMap,
268347
pub(crate) dynamic_import_references: ImportsReferencesState,
269348
pub(crate) worker_index: u32,
270349
pub(crate) parser_exports_state: Option<bool>,
@@ -1123,7 +1202,8 @@ impl<'parser> JavascriptParser<'parser> {
11231202
can_collect.then_some(expr)
11241203
};
11251204
if let Some(destructuring) = destructuring
1126-
&& let Some(keys) = self.collect_destructuring_assignment_properties(pattern)
1205+
&& let Some(keys) =
1206+
self.collect_destructuring_assignment_properties_from_object_pattern(pattern)
11271207
{
11281208
self
11291209
.destructuring_assignment_properties

0 commit comments

Comments
 (0)