diff --git a/crates/rspack_core/src/exports_info.rs b/crates/rspack_core/src/exports_info.rs index c14455badb1f..8630eb0f6a64 100644 --- a/crates/rspack_core/src/exports_info.rs +++ b/crates/rspack_core/src/exports_info.rs @@ -607,7 +607,7 @@ impl From for ExportInfoId { #[derive(Debug, Clone, Default)] #[allow(unused)] pub struct ExportInfo { - name: JsWord, + pub name: JsWord, module_identifier: Option, pub usage_state: UsageState, used_name: Option, @@ -1146,11 +1146,15 @@ pub fn process_export_info( runtime: Option<&RuntimeSpec>, referenced_export: &mut Vec>, prefix: Vec, - export_info: Option<&ExportInfo>, + export_info: Option, default_points_to_self: bool, already_visited: &mut HashSet, ) { - if let Some(export_info) = export_info { + if let Some(export_info_id) = export_info { + let export_info = module_graph + .export_info_map + .get(&export_info_id) + .expect("should have export info"); let used = export_info.get_used(runtime); if used == UsageState::Unused { return; @@ -1185,7 +1189,7 @@ pub fn process_export_info( value.push(export_info.name.clone()); value }, - Some(export_info), + Some(export_info.id), false, already_visited, ); diff --git a/crates/rspack_core/src/module.rs b/crates/rspack_core/src/module.rs index a4eae53db8c2..a7418582e988 100644 --- a/crates/rspack_core/src/module.rs +++ b/crates/rspack_core/src/module.rs @@ -10,12 +10,14 @@ use rspack_identifier::{Identifiable, Identifier}; use rspack_sources::Source; use rspack_util::ext::{AsAny, DynEq, DynHash}; use rustc_hash::FxHashSet as HashSet; +use swc_core::ecma::atoms::JsWord; use crate::tree_shaking::visitor::OptimizeAnalyzeResult; use crate::{ BoxDependency, ChunkUkey, CodeGenerationResult, Compilation, CompilerContext, CompilerOptions, - ConnectionState, Context, ContextModule, DependencyTemplate, ExternalModule, ModuleDependency, - ModuleGraph, ModuleType, NormalModule, RawModule, Resolve, SharedPluginDriver, SourceType, + ConnectionState, Context, ContextModule, DependencyId, DependencyTemplate, ExternalModule, + ModuleDependency, ModuleGraph, ModuleType, NormalModule, RawModule, Resolve, SharedPluginDriver, + SourceType, }; pub struct BuildContext<'a> { @@ -42,6 +44,8 @@ pub struct BuildInfo { pub missing_dependencies: HashSet, pub build_dependencies: HashSet, pub asset_filenames: HashSet, + pub harmony_named_exports: HashSet, + pub all_star_exports: Vec, } #[derive(Debug, Default, Clone, Hash)] diff --git a/crates/rspack_plugin_javascript/src/dependency/esm/harmony_export_imported_specifier_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/esm/harmony_export_imported_specifier_dependency.rs index 9a6165fbc64f..0d72b9c49663 100644 --- a/crates/rspack_plugin_javascript/src/dependency/esm/harmony_export_imported_specifier_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/esm/harmony_export_imported_specifier_dependency.rs @@ -1,9 +1,9 @@ use rspack_core::{ create_exports_object_referenced, create_no_exports_referenced, export_from_import, get_exports_type, process_export_info, ConnectionState, Dependency, DependencyCategory, - DependencyId, DependencyTemplate, DependencyType, ErrorSpan, ExportInfo, ExportsType, - ExtendedReferencedExport, HarmonyExportInitFragment, ModuleDependency, ModuleGraph, - ModuleIdentifier, RuntimeSpec, TemplateContext, TemplateReplaceSource, + DependencyId, DependencyTemplate, DependencyType, ErrorSpan, ExportInfoId, ExportInfoProvided, + ExportsType, ExtendedReferencedExport, HarmonyExportInitFragment, ModuleDependency, ModuleGraph, + ModuleIdentifier, RuntimeSpec, TemplateContext, TemplateReplaceSource, UsageState, }; use rustc_hash::FxHashSet as HashSet; use swc_core::ecma::atoms::JsWord; @@ -11,7 +11,9 @@ use swc_core::ecma::atoms::JsWord; use super::create_resource_identifier_for_esm_dependency; // Create _webpack_require__.d(__webpack_exports__, {}). -// import { a } from 'a'; export { a } +// case1: `import { a } from 'a'; export { a }` +// case2: `export { a } from 'a';` +// TODO case3: `export * from 'a'` #[derive(Debug, Clone)] pub struct HarmonyExportImportedSpecifierDependency { pub id: DependencyId, @@ -19,19 +21,353 @@ pub struct HarmonyExportImportedSpecifierDependency { pub ids: Vec<(JsWord, Option)>, name: Option, resource_identifier: String, + // Because it is shared by multiply HarmonyExportImportedSpecifierDependency, so put it to `BuildInfo` + // pub active_exports: HashSet, + // pub all_star_exports: Option>, + pub other_star_exports: Option>, // look like it is unused } impl HarmonyExportImportedSpecifierDependency { - pub fn new(request: JsWord, ids: Vec<(JsWord, Option)>) -> Self { + pub fn new(request: JsWord, ids: Vec<(JsWord, Option)>, name: Option) -> Self { let resource_identifier = create_resource_identifier_for_esm_dependency(&request); Self { id: DependencyId::new(), - name: None, + name, request, ids, resource_identifier, + other_star_exports: None, } } + + pub fn active_exports<'a>(&self, module_graph: &'a ModuleGraph) -> &'a HashSet { + let build_info = module_graph + .module_graph_module_by_dependency_id(&self.id) + .expect("should have mgm") + .build_info + .as_ref() + .expect("should have build info"); + &build_info.harmony_named_exports + } + + pub fn all_star_exports<'a>(&self, module_graph: &'a ModuleGraph) -> &'a Vec { + let build_info = module_graph + .module_graph_module_by_dependency_id(&self.id) + .expect("should have mgm") + .build_info + .as_ref() + .expect("should have build info"); + &build_info.all_star_exports + } + + // TODO cache get_mode result + #[allow(unused)] + pub fn get_mode( + &self, + name: Option, + ids: &Vec, + module_graph: &ModuleGraph, + id: &DependencyId, + runtime: Option<&RuntimeSpec>, + ) -> ExportMode { + let imported_module_identifier = if let Some(imported_module_identifier) = + module_graph.module_identifier_by_dependency_id(id) + { + imported_module_identifier + } else { + return ExportMode { + kind: ExportModeType::Missing, + ..Default::default() + }; + }; + + let parent_module = module_graph + .parent_module_by_dependency_id(id) + .expect("should have parent module"); + let exports_type = get_exports_type(module_graph, id, &parent_module); + let exports_info = module_graph.get_exports_info(&parent_module); + + // Special handling for reexporting the default export + // from non-namespace modules + if let Some(name) = name.as_ref() && !ids.is_empty() && let Some(id) = ids.get(0) && id == "default" { + match exports_type { + ExportsType::Dynamic => { + return ExportMode { kind: ExportModeType::ReexportDynamicDefault, name: Some(name.clone()), ..Default::default() } + }, + ExportsType::DefaultOnly | ExportsType::DefaultWithNamed => { + let export_info = exports_info.id.get_read_only_export_info(name, module_graph).id; + return ExportMode { kind: ExportModeType::ReexportNamedDefault, name: Some(name.clone()), partial_namespace_export_info: Some(export_info), ..Default::default() } + }, + _ => {} + } + } + + // reexporting with a fixed name + if let Some(name) = name { + let export_info = exports_info + .id + .get_read_only_export_info(&name, module_graph) + .id; + if !ids.is_empty() { + // export { name as name } + match exports_type { + ExportsType::DefaultOnly => { + return ExportMode { + kind: ExportModeType::ReexportUndefined, + name: Some(name), + ..Default::default() + } + } + _ => { + return ExportMode { + kind: ExportModeType::NormalReexport, + items: Some(vec![NormalReexportItem { + name, + ids: ids.to_vec(), + hidden: false, + checked: false, + export_info, + }]), + ..Default::default() + } + } + } + } else { + // export * as name + match exports_type { + ExportsType::DefaultOnly => { + return ExportMode { + kind: ExportModeType::ReexportFakeNamespaceObject, + name: Some(name), + partial_namespace_export_info: Some(export_info), + fake_type: Some(0), + ..Default::default() + } + } + ExportsType::DefaultWithNamed => { + return ExportMode { + kind: ExportModeType::ReexportFakeNamespaceObject, + name: Some(name), + partial_namespace_export_info: Some(export_info), + fake_type: Some(2), + ..Default::default() + } + } + _ => { + return ExportMode { + kind: ExportModeType::ReexportNamespaceObject, + name: Some(name), + partial_namespace_export_info: Some(export_info), + ..Default::default() + } + } + } + } + } + + let StarReexportsInfo { + exports, + checked, + ignored_exports, + hidden, + } = self.get_star_reexports(module_graph, runtime, imported_module_identifier); + + if let Some(exports) = exports { + if exports.is_empty() { + return ExportMode { + kind: ExportModeType::EmptyStar, + hidden, + ..Default::default() + }; + } + + let mut items = exports + .into_iter() + .map(|export_name| NormalReexportItem { + name: export_name.clone(), + ids: vec![export_name.clone()], + hidden: false, + checked: checked.as_ref().map(|c| c.contains(&export_name)).is_some(), + export_info: exports_info + .id + .get_read_only_export_info(&export_name, module_graph) + .id, + }) + .collect::>(); + + if let Some(hidden) = &hidden { + for export_name in hidden.iter() { + items.push(NormalReexportItem { + name: export_name.clone(), + ids: vec![export_name.clone()], + hidden: true, + checked: false, + export_info: exports_info + .id + .get_read_only_export_info(export_name, module_graph) + .id, + }); + } + } + + ExportMode { + kind: ExportModeType::NormalReexport, + items: Some(items), + ..Default::default() + } + } else { + ExportMode { + kind: ExportModeType::DynamicReexport, + ignored: Some(ignored_exports), + hidden, + ..Default::default() + } + } + } + + pub fn get_star_reexports( + &self, + module_graph: &ModuleGraph, + runtime: Option<&RuntimeSpec>, + imported_module_identifier: &ModuleIdentifier, + ) -> StarReexportsInfo { + let imported_exports_info = module_graph.get_exports_info(imported_module_identifier); + let other_export_info = module_graph + .export_info_map + .get(&imported_exports_info.other_exports_info) + .expect("should have export info"); + let no_extra_exports = matches!(other_export_info.provided, Some(ExportInfoProvided::False)); + let no_extra_imports = matches!(other_export_info.get_used(runtime), UsageState::Unused); + let ignored_exports: HashSet = { + let mut e = self.active_exports(module_graph).clone(); + e.insert("default".into()); + e + }; + let mut hidden_exports = self.discover_active_exports_from_other_star_exports(module_graph); + if !no_extra_exports && !no_extra_imports { + if let Some(hidden_exports) = hidden_exports.as_mut() { + for e in ignored_exports.iter() { + hidden_exports.remove(e); + } + } + return StarReexportsInfo { + ignored_exports, + hidden: hidden_exports, + ..Default::default() + }; + } + + let mut exports = HashSet::default(); + let mut checked = HashSet::default(); + let mut hidden = if hidden_exports.is_some() { + Some(HashSet::default()) + } else { + None + }; + + let parent_module = module_graph + .parent_module_by_dependency_id(&self.id) + .expect("should have parent module"); + let exports_info = module_graph.get_exports_info(&parent_module); + if no_extra_imports { + for export_info_id in exports_info.get_ordered_exports() { + let export_info = module_graph + .export_info_map + .get(export_info_id) + .expect("should have export info"); + if ignored_exports.contains(&export_info.name) + || matches!(export_info.get_used(runtime), UsageState::Unused) + { + continue; + } + let imported_export_info = imported_exports_info + .id + .get_read_only_export_info(&export_info.name, module_graph); + if matches!( + imported_export_info.provided, + Some(ExportInfoProvided::False) + ) { + continue; + } + if let Some(hidden) = hidden.as_mut() && hidden_exports.as_ref() + .map(|hidden_exports| hidden_exports.contains(&export_info.name)) + .is_some() + { + hidden.insert(export_info.name.clone()); + continue; + } + exports.insert(export_info.name.clone()); + if matches!( + imported_export_info.provided, + Some(ExportInfoProvided::True) + ) { + continue; + } + checked.insert(export_info.name.clone()); + } + } else if no_extra_exports { + for import_export_info_id in imported_exports_info.get_ordered_exports() { + let import_export_info = module_graph + .export_info_map + .get(import_export_info_id) + .expect("should have export info"); + if ignored_exports.contains(&import_export_info.name) + || matches!(import_export_info.provided, Some(ExportInfoProvided::False)) + { + continue; + } + let export_info = exports_info + .id + .get_read_only_export_info(&import_export_info.name, module_graph); + if matches!(export_info.get_used(runtime), UsageState::Unused) { + continue; + } + if let Some(hidden) = hidden.as_mut() && hidden_exports.as_ref() + .map(|hidden_exports| hidden_exports.contains(&import_export_info.name)) + .is_some() + { + hidden.insert(import_export_info.name.clone()); + continue; + } + exports.insert(import_export_info.name.clone()); + if matches!(import_export_info.provided, Some(ExportInfoProvided::True)) { + continue; + } + checked.insert(import_export_info.name.clone()); + } + } + + StarReexportsInfo { + ignored_exports, + exports: Some(exports), + checked: Some(checked), + hidden, + } + } + + pub fn discover_active_exports_from_other_star_exports( + &self, + module_graph: &ModuleGraph, + ) -> Option> { + if let Some(other_star_exports) = &self.other_star_exports { + if other_star_exports.is_empty() { + return None; + } + } + + let all_star_exports = self.all_star_exports(module_graph); + if !all_star_exports.is_empty() { + let names = determine_export_assignments(module_graph, all_star_exports.clone(), None); + return Some(names); + } + + if let Some(other_star_exports) = &self.other_star_exports { + let names = + determine_export_assignments(module_graph, other_star_exports.clone(), Some(self.id)); + return Some(names); + } + None + } } impl DependencyTemplate for HarmonyExportImportedSpecifierDependency { @@ -132,11 +468,12 @@ impl ModuleDependency for HarmonyExportImportedSpecifierDependency { module_graph: &ModuleGraph, runtime: Option<&RuntimeSpec>, ) -> Vec { - let mode = get_mode( + let mode = self.get_mode( self.name.clone(), &self.ids.iter().map(|id| id.0.clone()).collect::>(), module_graph, &self.id, + runtime, ); match mode.kind { ExportModeType::Missing @@ -156,7 +493,7 @@ impl ModuleDependency for HarmonyExportImportedSpecifierDependency { runtime, &mut referenced_exports, vec![], - Some(partial_namespace_export_info), + Some(*partial_namespace_export_info), mode.kind == ExportModeType::ReexportFakeNamespaceObject, &mut Default::default(), ); @@ -216,108 +553,61 @@ pub enum ExportModeType { } #[derive(Debug)] -pub struct NormalReexportItem<'a> { +pub struct NormalReexportItem { pub name: JsWord, pub ids: Vec, pub hidden: bool, pub checked: bool, - pub export_info: &'a ExportInfo, + pub export_info: ExportInfoId, } #[derive(Debug, Default)] -pub struct ExportMode<'a> { +pub struct ExportMode { pub kind: ExportModeType, - pub items: Option>>, + pub items: Option>, pub name: Option, pub fake_type: Option, - pub partial_namespace_export_info: Option, + pub partial_namespace_export_info: Option, + pub ignored: Option>, + pub hidden: Option>, } -// TODO cache get_mode result -#[allow(unused)] -pub fn get_mode<'a>( - name: Option, - ids: &Vec, - module_graph: &'a ModuleGraph, - id: &DependencyId, -) -> ExportMode<'a> { - let parent_module = module_graph - .parent_module_by_dependency_id(id) - .expect("should have parent module"); - let exports_type = get_exports_type(module_graph, id, &parent_module); - let exports_info = module_graph.get_exports_info(&parent_module); - if let Some(name) = name.as_ref() && !ids.is_empty() && let Some(id) = ids.get(0) && id == "default" { - match exports_type { - ExportsType::Dynamic => { - return ExportMode { kind: ExportModeType::ReexportDynamicDefault, name: Some(name.clone()), ..Default::default() } - }, - ExportsType::DefaultOnly | ExportsType::DefaultWithNamed => { - return ExportMode { kind: ExportModeType::ReexportNamedDefault, name: Some(name.clone()), ..Default::default() } - }, - _ => {} - } +#[derive(Debug, Default)] +pub struct StarReexportsInfo { + exports: Option>, + checked: Option>, + ignored_exports: HashSet, + hidden: Option>, +} + +fn determine_export_assignments( + module_graph: &ModuleGraph, + mut dependencies: Vec, + additional_dependency: Option, +) -> HashSet { + if let Some(additional_dependency) = additional_dependency { + dependencies.push(additional_dependency); } - if let Some(name) = name { - let export_info = exports_info - .id - .get_read_only_export_info(&name, module_graph); - if !ids.is_empty() { - match exports_type { - ExportsType::DefaultOnly => { - return ExportMode { - kind: ExportModeType::ReexportUndefined, - name: Some(name), - ..Default::default() - } - } - _ => { - return ExportMode { - kind: ExportModeType::NormalReexport, - items: Some(vec![NormalReexportItem { - name, - ids: ids.to_vec(), - hidden: false, - checked: false, - export_info, - }]), - ..Default::default() - } - } - } - } else { - match exports_type { - ExportsType::DefaultOnly => { - return ExportMode { - kind: ExportModeType::ReexportFakeNamespaceObject, - name: Some(name), - fake_type: Some(0), - ..Default::default() - } - } - ExportsType::DefaultWithNamed => { - return ExportMode { - kind: ExportModeType::ReexportFakeNamespaceObject, - name: Some(name), - fake_type: Some(2), - ..Default::default() - } - } - _ => { - return ExportMode { - kind: ExportModeType::ReexportNamespaceObject, - name: Some(name), - ..Default::default() - } + let mut names = HashSet::default(); + + for dependency in dependencies.iter() { + if let Some(module_identifier) = module_graph.module_identifier_by_dependency_id(dependency) { + let exports_info = module_graph.get_exports_info(module_identifier); + for export_info_id in exports_info.exports.values() { + let export_info = module_graph + .export_info_map + .get(export_info_id) + .expect("should have export info"); + if matches!(export_info.provided, Some(ExportInfoProvided::True)) + && &export_info.name != "default" + && !names.contains(&export_info.name) + { + names.insert(export_info.name.clone()); } } } } - // todo star reexporting - ExportMode { - kind: ExportModeType::NormalReexport, - items: Some(vec![]), - ..Default::default() - } + names } diff --git a/crates/rspack_plugin_javascript/src/dependency/esm/harmony_export_specifier_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/esm/harmony_export_specifier_dependency.rs index c22c6e437989..fc44da3b8e25 100644 --- a/crates/rspack_plugin_javascript/src/dependency/esm/harmony_export_specifier_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/esm/harmony_export_specifier_dependency.rs @@ -9,14 +9,16 @@ use swc_core::ecma::atoms::JsWord; #[derive(Debug, Clone)] pub struct HarmonyExportSpecifierDependency { id: DependencyId, - export: (JsWord, JsWord), + name: JsWord, + value: JsWord, // id } impl HarmonyExportSpecifierDependency { - pub fn new(export: (JsWord, JsWord)) -> Self { + pub fn new(name: JsWord, value: JsWord) -> Self { Self { id: DependencyId::new(), - export, + name, + value, } } } @@ -36,7 +38,7 @@ impl Dependency for HarmonyExportSpecifierDependency { fn get_exports(&self) -> Option { Some(ExportsSpec { - exports: ExportsOfExportsSpec::Array(vec![ExportNameOrSpec::String(self.export.0.clone())]), + exports: ExportsOfExportsSpec::Array(vec![ExportNameOrSpec::String(self.name.clone())]), priority: Some(1), can_mangle: None, terminal_binding: Some(true), @@ -68,14 +70,15 @@ impl DependencyTemplate for HarmonyExportSpecifierDependency { .module_graph .get_exports_info(&module.identifier()) .get_used_exports() - .contains(&self.export.0) + .contains(&self.name) } else { true }; if used { - init_fragments.push(Box::new(HarmonyExportInitFragment::new( - self.export.clone(), - ))); + init_fragments.push(Box::new(HarmonyExportInitFragment::new(( + self.name.clone(), + self.value.clone(), + )))); } } } diff --git a/crates/rspack_plugin_javascript/src/dependency/esm/harmony_import_specifier_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/esm/harmony_import_specifier_dependency.rs index 3907d6336d14..d9d1ca26ae4b 100644 --- a/crates/rspack_plugin_javascript/src/dependency/esm/harmony_import_specifier_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/esm/harmony_import_specifier_dependency.rs @@ -1,10 +1,11 @@ -use rspack_core::{create_exports_object_referenced, ExtendedReferencedExport}; use rspack_core::{ - export_from_import, get_dependency_used_by_exports_condition, get_exports_type, + create_exports_object_referenced, create_no_exports_referenced, export_from_import, + get_dependency_used_by_exports_condition, get_exports_type, tree_shaking::symbol::DEFAULT_JS_WORD, Compilation, ConnectionState, Dependency, DependencyCategory, DependencyCondition, DependencyId, DependencyTemplate, DependencyType, - ErrorSpan, ExportsType, ModuleDependency, ModuleGraph, ModuleGraphModule, ModuleIdentifier, - ReferencedExport, RuntimeSpec, TemplateContext, TemplateReplaceSource, UsedByExports, + ErrorSpan, ExportsType, ExtendedReferencedExport, ModuleDependency, ModuleGraph, + ModuleGraphModule, ModuleIdentifier, ReferencedExport, RuntimeSpec, TemplateContext, + TemplateReplaceSource, UsedByExports, }; use rustc_hash::FxHashSet as HashSet; use swc_core::ecma::atoms::JsWord; @@ -39,6 +40,7 @@ impl HarmonyImportSpecifierDependency { call: bool, direct_import: bool, specifier: Specifier, + referenced_properties_in_destructuring: Option>, ) -> Self { let resource_identifier = create_resource_identifier_for_esm_dependency(&request); Self { @@ -53,7 +55,7 @@ impl HarmonyImportSpecifierDependency { specifier, used_by_exports: UsedByExports::default(), namespace_object_as_context: false, - referenced_properties_in_destructuring: None, + referenced_properties_in_destructuring, resource_identifier, } } @@ -215,6 +217,7 @@ impl ModuleDependency for HarmonyImportSpecifierDependency { module_graph: &ModuleGraph, _runtime: Option<&RuntimeSpec>, ) -> Vec { + // namespace import if self.ids.is_empty() { return self.get_referenced_exports_in_destructuring(None); } @@ -233,7 +236,7 @@ impl ModuleDependency for HarmonyImportSpecifierDependency { namespace_object_as_context = true; } ExportsType::Dynamic => { - return vec![] + return create_no_exports_referenced(); } _ => {} } diff --git a/crates/rspack_plugin_javascript/src/visitors/dependency/harmony_export_dependency_scanner.rs b/crates/rspack_plugin_javascript/src/visitors/dependency/harmony_export_dependency_scanner.rs index 5e4023ae100a..4582bde80246 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/harmony_export_dependency_scanner.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/harmony_export_dependency_scanner.rs @@ -1,6 +1,6 @@ use rspack_core::{ - tree_shaking::symbol::DEFAULT_JS_WORD, BoxDependency, BoxDependencyTemplate, ConstDependency, - SpanExt, + tree_shaking::symbol::DEFAULT_JS_WORD, BoxDependency, BoxDependencyTemplate, BuildInfo, + ConstDependency, SpanExt, }; use swc_core::{ common::Spanned, @@ -24,6 +24,7 @@ pub struct HarmonyExportDependencyScanner<'a> { pub dependencies: &'a mut Vec, pub presentational_dependencies: &'a mut Vec, pub import_map: &'a mut ImportMap, + pub build_info: &'a mut BuildInfo, } impl<'a> HarmonyExportDependencyScanner<'a> { @@ -31,11 +32,13 @@ impl<'a> HarmonyExportDependencyScanner<'a> { dependencies: &'a mut Vec, presentational_dependencies: &'a mut Vec, import_map: &'a mut ImportMap, + build_info: &'a mut BuildInfo, ) -> Self { Self { dependencies, presentational_dependencies, import_map, + build_info, } } } @@ -52,10 +55,14 @@ impl Visit for HarmonyExportDependencyScanner<'_> { Decl::Class(ClassDecl { ident, .. }) | Decl::Fn(FnDecl { ident, .. }) => { self .dependencies - .push(Box::new(HarmonyExportSpecifierDependency::new(( + .push(Box::new(HarmonyExportSpecifierDependency::new( ident.sym.clone(), ident.sym.clone(), - )))); + ))); + self + .build_info + .harmony_named_exports + .insert(ident.sym.clone()); } Decl::Var(v) => { find_pat_ids::<_, Ident>(&v.decls) @@ -63,10 +70,11 @@ impl Visit for HarmonyExportDependencyScanner<'_> { .for_each(|ident| { self .dependencies - .push(Box::new(HarmonyExportSpecifierDependency::new(( + .push(Box::new(HarmonyExportSpecifierDependency::new( + ident.sym.clone(), ident.sym.clone(), - ident.sym, - )))) + ))); + self.build_info.harmony_named_exports.insert(ident.sym); }); } _ => {} @@ -96,15 +104,17 @@ impl Visit for HarmonyExportDependencyScanner<'_> { .dependencies .push(Box::new(HarmonyExportImportedSpecifierDependency::new( reference.request.clone(), - vec![(export, reference.names.clone())], + vec![(export.clone(), reference.names.clone())], + Some(export), ))); } else { self .dependencies - .push(Box::new(HarmonyExportSpecifierDependency::new(( - export, + .push(Box::new(HarmonyExportSpecifierDependency::new( + export.clone(), orig.sym.clone(), - )))); + ))); + self.build_info.harmony_named_exports.insert(export); } } } @@ -122,12 +132,13 @@ impl Visit for HarmonyExportDependencyScanner<'_> { } fn visit_export_default_expr(&mut self, export_default_expr: &'_ ExportDefaultExpr) { + // TODO this should be at `HarmonyExportExpressionDependency` self .dependencies - .push(Box::new(HarmonyExportSpecifierDependency::new(( + .push(Box::new(HarmonyExportSpecifierDependency::new( DEFAULT_JS_WORD.clone(), DEFAULT_EXPORT.into(), - )))); + ))); self .presentational_dependencies @@ -146,15 +157,16 @@ impl Visit for HarmonyExportDependencyScanner<'_> { _ => unreachable!(), }; + // TODO this should be at `HarmonyExportExpressionDependency` self .dependencies - .push(Box::new(HarmonyExportSpecifierDependency::new(( + .push(Box::new(HarmonyExportSpecifierDependency::new( DEFAULT_JS_WORD.clone(), match &ident { Some(ident) => ident.sym.clone(), None => DEFAULT_EXPORT.into(), }, - )))); + ))); self .presentational_dependencies diff --git a/crates/rspack_plugin_javascript/src/visitors/dependency/harmony_import_dependency_scanner.rs b/crates/rspack_plugin_javascript/src/visitors/dependency/harmony_import_dependency_scanner.rs index d324a3a078d5..141df218b505 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/harmony_import_dependency_scanner.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/harmony_import_dependency_scanner.rs @@ -1,21 +1,23 @@ use indexmap::IndexMap; use rspack_core::{ - tree_shaking::symbol::DEFAULT_JS_WORD, BoxDependency, BoxDependencyTemplate, ConstDependency, - DependencyType, SpanExt, + tree_shaking::symbol::DEFAULT_JS_WORD, BoxDependency, BoxDependencyTemplate, BuildInfo, + ConstDependency, DependencyType, SpanExt, }; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; use swc_core::{ common::Span, ecma::{ ast::{ - Callee, ExportAll, ExportSpecifier, Expr, Id, Ident, ImportDecl, ImportSpecifier, Lit, - MemberExpr, MemberProp, ModuleExportName, NamedExport, Program, Prop, + AssignExpr, AssignOp, Callee, ExportAll, ExportSpecifier, Expr, Id, Ident, ImportDecl, + ImportSpecifier, Lit, MemberExpr, MemberProp, ModuleExportName, NamedExport, Pat, PatOrExpr, + Program, Prop, }, atoms::JsWord, visit::{noop_visit_type, Visit, VisitWith}, }, }; +use super::collect_destructuring_assignment_properties; use crate::dependency::{ HarmonyExportImportedSpecifierDependency, HarmonyImportDependency, HarmonyImportSpecifierDependency, Specifier, @@ -37,7 +39,7 @@ impl ImporterReferenceInfo { } } -pub type ImportMap = FxHashMap; +pub type ImportMap = HashMap; pub struct ImporterInfo { pub span: Span, @@ -62,6 +64,7 @@ pub struct HarmonyImportDependencyScanner<'a> { pub presentational_dependencies: &'a mut Vec, pub import_map: &'a mut ImportMap, pub imports: Imports, + pub build_info: &'a mut BuildInfo, } impl<'a> HarmonyImportDependencyScanner<'a> { @@ -69,12 +72,14 @@ impl<'a> HarmonyImportDependencyScanner<'a> { dependencies: &'a mut Vec, presentational_dependencies: &'a mut Vec, import_map: &'a mut ImportMap, + build_info: &'a mut BuildInfo, ) -> Self { Self { dependencies, presentational_dependencies, import_map, imports: Default::default(), + build_info, } } } @@ -85,41 +90,52 @@ impl Visit for HarmonyImportDependencyScanner<'_> { fn visit_program(&mut self, program: &Program) { // collect import map info program.visit_children_with(self); - for ((request, dependency_type), importer_info) in std::mem::take(&mut self.imports).into_iter() { if matches!(dependency_type, DependencyType::EsmExport) && !importer_info.specifiers.is_empty() { - let ids = importer_info + importer_info .specifiers .iter() - .map(|specifier| match specifier { - Specifier::Namespace(n) => (n.clone(), None), + .for_each(|specifier| match specifier { + Specifier::Namespace(n) => { + self + .dependencies + .push(Box::new(HarmonyExportImportedSpecifierDependency::new( + request.clone(), + vec![(n.clone(), None)], + Some(n.clone()), + ))); + self.build_info.harmony_named_exports.insert(n.clone()); + } Specifier::Default(_) => { unreachable!() } Specifier::Named(orig, exported) => { - (exported.clone().unwrap_or(orig.clone()), Some(orig.clone())) + let name = exported.clone().unwrap_or(orig.clone()); + self + .dependencies + .push(Box::new(HarmonyExportImportedSpecifierDependency::new( + request.clone(), + vec![(name.clone(), Some(orig.clone()))], + Some(name.clone()), + ))); + self.build_info.harmony_named_exports.insert(name); } - }) - .collect::>(); - self - .dependencies - .push(Box::new(HarmonyExportImportedSpecifierDependency::new( - request.clone(), - ids, - ))); + }); } - self - .dependencies - .push(Box::new(HarmonyImportDependency::new( - request.clone(), - Some(importer_info.span.into()), - importer_info.specifiers, - dependency_type, - importer_info.exports_all, - ))); + let dependency = HarmonyImportDependency::new( + request.clone(), + Some(importer_info.span.into()), + importer_info.specifiers, + dependency_type, + importer_info.exports_all, + ); + if importer_info.exports_all { + self.build_info.all_star_exports.push(dependency.id); + } + self.dependencies.push(Box::new(dependency)); } // collect import reference info @@ -265,6 +281,7 @@ pub struct HarmonyImportRefDependencyScanner<'a> { pub enter_callee: bool, pub import_map: &'a ImportMap, pub dependencies: &'a mut Vec, + pub properties_in_destructuring: HashMap>, } impl<'a> HarmonyImportRefDependencyScanner<'a> { @@ -273,6 +290,7 @@ impl<'a> HarmonyImportRefDependencyScanner<'a> { import_map, dependencies, enter_callee: false, + properties_in_destructuring: HashMap::default(), } } } @@ -280,6 +298,18 @@ impl<'a> HarmonyImportRefDependencyScanner<'a> { impl Visit for HarmonyImportRefDependencyScanner<'_> { noop_visit_type!(); + // collect referenced properties in destructuring + // import * as a from 'a'; + // const { value } = a; + fn visit_assign_expr(&mut self, assign_expr: &AssignExpr) { + if let PatOrExpr::Pat(box Pat::Object(object_pat)) = &assign_expr.left && assign_expr.op == AssignOp::Assign && let box Expr::Ident(ident) = &assign_expr.right && let Some(reference) = self.import_map.get(&ident.to_id()) && matches!(reference.specifier, Specifier::Namespace(_)) { + if let Some(value) = collect_destructuring_assignment_properties(object_pat) { + self.properties_in_destructuring.entry(ident.sym.clone()).and_modify(|v| v.extend(value.clone())).or_insert(value); + } + } + assign_expr.visit_children_with(self); + } + fn visit_prop(&mut self, n: &Prop) { match n { Prop::Shorthand(shorthand) => { @@ -295,6 +325,7 @@ impl Visit for HarmonyImportRefDependencyScanner<'_> { false, false, reference.specifier.clone(), + None, ))); } } @@ -315,6 +346,7 @@ impl Visit for HarmonyImportRefDependencyScanner<'_> { self.enter_callee, true, // x() reference.specifier.clone(), + self.properties_in_destructuring.remove(&ident.sym), ))); } } @@ -347,6 +379,7 @@ impl Visit for HarmonyImportRefDependencyScanner<'_> { self.enter_callee, !self.enter_callee, // x.xx() reference.specifier.clone(), + None, ))); return; } diff --git a/crates/rspack_plugin_javascript/src/visitors/dependency/mod.rs b/crates/rspack_plugin_javascript/src/visitors/dependency/mod.rs index 66619f337d55..24c702aaf076 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/mod.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/mod.rs @@ -36,7 +36,6 @@ use self::{ node_stuff_scanner::NodeStuffScanner, require_context_scanner::RequireContextScanner, url_scanner::UrlScanner, worker_scanner::WorkerScanner, }; - pub type ScanDependenciesResult = (Vec, Vec); #[allow(clippy::too_many_arguments)] @@ -112,11 +111,13 @@ pub fn scan_dependencies( &mut dependencies, &mut presentational_dependencies, &mut import_map, + build_info, )); program.visit_with(&mut HarmonyExportDependencyScanner::new( &mut dependencies, &mut presentational_dependencies, &mut import_map, + build_info, )); let mut worker_syntax_scanner = rspack_core::needs_refactor::WorkerSyntaxScanner::new( rspack_core::needs_refactor::DEFAULT_WORKER_SYNTAX, diff --git a/crates/rspack_plugin_javascript/src/visitors/dependency/util.rs b/crates/rspack_plugin_javascript/src/visitors/dependency/util.rs index fcae8389fae7..df2d34d28e88 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/util.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/util.rs @@ -1,13 +1,35 @@ +use rustc_hash::FxHashSet as HashSet; use swc_core::{ - common::{pass::AstNodePath, SyntaxContext}, + common::SyntaxContext, ecma::{ - ast::{CallExpr, Expr, MemberExpr}, - visit::{AstParentKind, AstParentNodeRef}, + ast::{CallExpr, Expr, MemberExpr, ObjectPat, ObjectPatProp, PropName}, + atoms::JsWord, }, }; -pub fn as_parent_path(ast_path: &AstNodePath>) -> Vec { - ast_path.iter().map(|n| n.kind()).collect() +pub fn collect_destructuring_assignment_properties( + object_pat: &ObjectPat, +) -> Option> { + let mut properties = HashSet::default(); + + for property in &object_pat.props { + match property { + ObjectPatProp::Assign(assign) => { + properties.insert(assign.key.sym.clone()); + } + ObjectPatProp::KeyValue(key_value) => { + if let PropName::Ident(ident) = &key_value.key { + properties.insert(ident.sym.clone()); + } + } + ObjectPatProp::Rest(_) => {} + } + } + + if properties.is_empty() { + return None; + } + Some(properties) } pub(crate) mod expr_matcher {