Skip to content

Commit 61cb132

Browse files
authored
fix: render external re-exports (#12089)
* fix: render external re-exports * feat: support deep reexport from externals
1 parent e2f02f1 commit 61cb132

File tree

14 files changed

+412
-51
lines changed

14 files changed

+412
-51
lines changed

crates/rspack_plugin_esm_library/src/chunk_link.rs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
use std::{borrow::Cow, sync::Arc};
22

3-
use rspack_collections::{
4-
IdentifierIndexMap, IdentifierIndexSet, IdentifierMap, IdentifierSet, UkeyIndexMap,
5-
};
3+
use rspack_collections::{IdentifierIndexMap, IdentifierIndexSet, IdentifierMap, IdentifierSet};
64
use rspack_core::{
75
BoxChunkInitFragment, ChunkGraph, ChunkUkey, Compilation, ImportSpec, ModuleIdentifier,
86
RuntimeGlobals, find_new_name,
@@ -253,6 +251,12 @@ impl ExternalInterop {
253251
}
254252
}
255253

254+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
255+
pub enum ReExportFrom {
256+
Chunk(ChunkUkey),
257+
Request(String),
258+
}
259+
256260
#[derive(Debug, Clone)]
257261
pub struct ChunkLinkContext {
258262
pub chunk: ChunkUkey,
@@ -272,12 +276,12 @@ pub struct ChunkLinkContext {
272276
exports that need to be re-exported
273277
Map<chunk, Map<local_name, export_name>>
274278
*/
275-
re_exports: UkeyIndexMap<ChunkUkey, FxHashMap<Atom, FxHashSet<Atom>>>,
279+
re_exports: FxIndexMap<ReExportFrom, FxHashMap<Atom, FxHashSet<Atom>>>,
276280

277281
/**
278282
* re exports in raw form, used for rendering export * from 'module'
279283
*/
280-
pub raw_star_exports: FxIndexSet<String>,
284+
pub raw_star_exports: FxIndexMap<String, FxIndexSet<Atom>>,
281285

282286
/**
283287
import order matters, it affects execution order
@@ -370,7 +374,22 @@ impl ChunkLinkContext {
370374
set.get(&exported).expect("just inserted")
371375
}
372376

373-
pub fn star_re_exports() {}
377+
pub fn add_re_export_from_request(
378+
&mut self,
379+
request: String,
380+
imported_name: Atom,
381+
export_name: Atom,
382+
) {
383+
self.exported_symbols.insert(export_name.clone());
384+
385+
self
386+
.re_exports
387+
.entry(ReExportFrom::Request(request))
388+
.or_default()
389+
.entry(imported_name)
390+
.or_default()
391+
.insert(export_name);
392+
}
374393

375394
pub fn add_re_export(&mut self, chunk: ChunkUkey, local_name: Atom, export_name: Atom) -> &Atom {
376395
let export_name = if self.exported_symbols.insert(export_name.clone()) {
@@ -384,7 +403,7 @@ impl ChunkLinkContext {
384403

385404
let set = self
386405
.re_exports
387-
.entry(chunk)
406+
.entry(ReExportFrom::Chunk(chunk))
388407
.or_default()
389408
.entry(local_name)
390409
.or_default();
@@ -401,7 +420,7 @@ impl ChunkLinkContext {
401420
&mut self.exports
402421
}
403422

404-
pub fn re_exports(&self) -> &UkeyIndexMap<ChunkUkey, FxHashMap<Atom, FxHashSet<Atom>>> {
423+
pub fn re_exports(&self) -> &FxIndexMap<ReExportFrom, FxHashMap<Atom, FxHashSet<Atom>>> {
405424
&self.re_exports
406425
}
407426
}

crates/rspack_plugin_esm_library/src/link.rs

Lines changed: 149 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{
22
collections::{self},
33
hash::BuildHasher,
4-
sync::Arc,
4+
sync::{Arc, LazyLock},
55
};
66

77
use rayon::prelude::*;
@@ -11,18 +11,19 @@ use rspack_collections::{
1111
use rspack_core::{
1212
BuildMetaDefaultObject, BuildMetaExportsType, ChunkGraph, ChunkInitFragments, ChunkUkey,
1313
CodeGenerationPublicPathAutoReplace, Compilation, ConcatenatedModuleIdent, DependencyType,
14-
ExportMode, ExportProvided, ExportsInfoGetter, ExportsType, FindTargetResult, GetUsedNameParam,
15-
IdentCollector, InitFragmentKey, InitFragmentStage, MaybeDynamicTargetExportInfoHashKey,
16-
ModuleGraph, ModuleGraphCacheArtifact, ModuleIdentifier, ModuleInfo, NAMESPACE_OBJECT_EXPORT,
17-
NormalInitFragment, PathData, PrefetchExportsInfoMode, RuntimeGlobals, SourceType, URLStaticMode,
18-
UsageState, UsedName, UsedNameItem, escape_name, find_new_name, get_cached_readable_identifier,
19-
get_js_chunk_filename_template, property_access, property_name, reserved_names::RESERVED_NAMES,
20-
returning_function, rspack_sources::ReplaceSource, split_readable_identifier, to_normal_comment,
14+
ExportMode, ExportProvided, ExportsInfoGetter, ExportsType, ExternalModule, FindTargetResult,
15+
GetUsedNameParam, IdentCollector, InitFragmentKey, InitFragmentStage,
16+
MaybeDynamicTargetExportInfoHashKey, ModuleGraph, ModuleGraphCacheArtifact, ModuleIdentifier,
17+
ModuleInfo, NAMESPACE_OBJECT_EXPORT, NormalInitFragment, PathData, PrefetchExportsInfoMode,
18+
RuntimeGlobals, SourceType, URLStaticMode, UsageState, UsedName, UsedNameItem, escape_name,
19+
find_new_name, get_cached_readable_identifier, get_js_chunk_filename_template, property_access,
20+
property_name, reserved_names::RESERVED_NAMES, returning_function, rspack_sources::ReplaceSource,
21+
split_readable_identifier, to_normal_comment,
2122
};
2223
use rspack_error::{Diagnostic, Result};
2324
use rspack_javascript_compiler::ast::Ast;
2425
use rspack_plugin_javascript::{
25-
JsPlugin, RenderSource, dependency::ESMExportImportedSpecifierDependency,
26+
JS_DEFAULT_KEYWORD, JsPlugin, RenderSource, dependency::ESMExportImportedSpecifierDependency,
2627
visitors::swc_visitor::resolver,
2728
};
2829
use rspack_util::{
@@ -63,6 +64,8 @@ impl<V> GetMut<ModuleIdentifier, V> for IdentifierIndexMap<V> {
6364
}
6465
}
6566

67+
static START_EXPORTS: LazyLock<Atom> = LazyLock::new(|| "*".into());
68+
6669
#[derive(Default)]
6770
struct ExportsContext {
6871
pub exports: FxHashMap<Atom, FxIndexSet<Atom>>,
@@ -1056,6 +1059,22 @@ impl EsmLibraryPlugin {
10561059
continue;
10571060
}
10581061

1062+
if export_info.is_reexport()
1063+
&& let Some((Some(dep), _)) = export_info.target().iter().next()
1064+
{
1065+
let module_id = module_graph
1066+
.module_identifier_by_dependency_id(dep)
1067+
.expect("should be ESMExportImportedSpecifierDependency");
1068+
let module = module_graph
1069+
.module_by_identifier(module_id)
1070+
.expect("should have module");
1071+
if module.as_external_module().is_some() {
1072+
// ignore re-exported symbol from external module
1073+
// we have special handle for external module re-exports
1074+
continue;
1075+
}
1076+
}
1077+
10591078
let local = match export_info.used_name() {
10601079
Some(UsedNameItem::Inlined(inlined)) => inlined.render().into(),
10611080
Some(UsedNameItem::Str(name)) => {
@@ -1091,6 +1110,83 @@ impl EsmLibraryPlugin {
10911110
errors
10921111
}
10931112

1113+
fn re_export_from_external_module(
1114+
module: &ExternalModule,
1115+
current_chunk: ChunkUkey,
1116+
mode: &ExportMode,
1117+
link: &mut UkeyMap<ChunkUkey, ChunkLinkContext>,
1118+
export_dep: &ESMExportImportedSpecifierDependency,
1119+
) {
1120+
match mode {
1121+
// render export * from 'external module'
1122+
ExportMode::DynamicReexport(_) | ExportMode::EmptyStar(_) => {
1123+
let chunk_link = link.get_mut_unwrap(&current_chunk);
1124+
chunk_link
1125+
.raw_star_exports
1126+
.entry(export_dep.request.to_string())
1127+
.or_default()
1128+
.insert(START_EXPORTS.clone());
1129+
}
1130+
1131+
ExportMode::Unused(mode) if mode.name == "*" => {
1132+
let chunk_link = link.get_mut_unwrap(&current_chunk);
1133+
chunk_link
1134+
.raw_star_exports
1135+
.entry(export_dep.request.to_string())
1136+
.or_default()
1137+
.insert(START_EXPORTS.clone());
1138+
}
1139+
1140+
ExportMode::ReexportUndefined(_)
1141+
| ExportMode::Missing
1142+
| ExportMode::LazyMake
1143+
| ExportMode::Unused(_) => {}
1144+
1145+
ExportMode::ReexportDynamicDefault(_) => {
1146+
let chunk_link = link.get_mut_unwrap(&current_chunk);
1147+
chunk_link.add_re_export_from_request(
1148+
module.user_request().to_string(),
1149+
JS_DEFAULT_KEYWORD.clone(),
1150+
JS_DEFAULT_KEYWORD.clone(),
1151+
);
1152+
}
1153+
ExportMode::ReexportNamedDefault(mode) => {
1154+
let chunk_link = link.get_mut_unwrap(&current_chunk);
1155+
chunk_link.add_re_export_from_request(
1156+
module.user_request().to_string(),
1157+
JS_DEFAULT_KEYWORD.clone(),
1158+
mode.name.clone(),
1159+
);
1160+
}
1161+
ExportMode::ReexportNamespaceObject(mode) => {
1162+
let chunk_link = link.get_mut_unwrap(&current_chunk);
1163+
chunk_link
1164+
.raw_star_exports
1165+
.entry(module.user_request().to_string())
1166+
.or_default()
1167+
.insert(mode.name.clone());
1168+
}
1169+
ExportMode::ReexportFakeNamespaceObject(mode) => {
1170+
let chunk_link = link.get_mut_unwrap(&current_chunk);
1171+
chunk_link
1172+
.raw_star_exports
1173+
.entry(module.user_request().to_string())
1174+
.or_default()
1175+
.insert(mode.name.clone());
1176+
}
1177+
ExportMode::NormalReexport(normal) => {
1178+
let chunk_link = link.get_mut_unwrap(&current_chunk);
1179+
for item in &normal.items {
1180+
chunk_link.add_re_export_from_request(
1181+
module.user_request().into(),
1182+
item.ids.first().unwrap_or(&item.name).clone(),
1183+
item.name.clone(),
1184+
);
1185+
}
1186+
}
1187+
}
1188+
}
1189+
10941190
// export * from 'target'
10951191
// export * as n from 'target'
10961192
#[allow(clippy::too_many_arguments)]
@@ -1119,8 +1215,15 @@ impl EsmLibraryPlugin {
11191215
.and_then(|dep| dep.downcast_ref::<ESMExportImportedSpecifierDependency>())
11201216
.and_then(|dep| {
11211217
module_graph
1122-
.get_module_by_dependency_id(&dep.id)
1123-
.map(|module| (dep, module))
1218+
.connection_by_dependency_id(&dep.id)
1219+
.map(|conn| {
1220+
(
1221+
dep,
1222+
module_graph
1223+
.module_by_identifier(conn.module_identifier())
1224+
.expect("should have module"),
1225+
)
1226+
})
11241227
})
11251228
})
11261229
.map(|(export_imported_dep, module)| {
@@ -1148,33 +1251,28 @@ impl EsmLibraryPlugin {
11481251
for (export_dep, re_exports) in deps_and_modes {
11491252
// reset ref_module for each dep
11501253
let ref_module = orig_ref_module;
1151-
// optimize `export * from 'external module'`
1152-
let possible_to_optimize_mode = matches!(
1153-
&re_exports,
1154-
ExportMode::DynamicReexport(_) | ExportMode::EmptyStar(_)
1155-
) || matches!(&re_exports, ExportMode::Unused(mode) if &mode.name == "*");
11561254

11571255
let ref_box_module = module_graph
11581256
.module_by_identifier(&ref_module)
11591257
.expect("should have mode");
11601258

1161-
let optimize_reexport_star = possible_to_optimize_mode
1162-
&& export_dep.name.is_none()
1163-
&& ref_box_module
1164-
.as_external_module()
1165-
.is_some_and(|m| matches!(m.get_external_type().as_str(), "module-import" | "module"));
1166-
1167-
if optimize_reexport_star {
1168-
// render export * from 'external module'
1169-
let chunk_link = link.get_mut_unwrap(&current_chunk);
1170-
chunk_link
1171-
.raw_star_exports
1172-
.insert(export_dep.request.to_string());
1259+
if let Some(external_module) = ref_box_module.as_external_module()
1260+
&& matches!(
1261+
external_module.get_external_type().as_str(),
1262+
"module-import" | "module"
1263+
)
1264+
{
1265+
Self::re_export_from_external_module(
1266+
external_module,
1267+
current_chunk,
1268+
&re_exports,
1269+
link,
1270+
export_dep,
1271+
);
11731272
continue;
11741273
}
11751274

11761275
let chunk_link = link.get_mut_unwrap(&current_chunk);
1177-
11781276
match re_exports {
11791277
rspack_core::ExportMode::Missing
11801278
| rspack_core::ExportMode::LazyMake
@@ -1388,6 +1486,27 @@ impl EsmLibraryPlugin {
13881486
export_info = target_export_info;
13891487
}
13901488

1489+
if ref_module != orig_ref_module
1490+
&& let Some(external_module) = module_graph
1491+
.module_by_identifier(&ref_module)
1492+
.expect("should have module")
1493+
.as_external_module()
1494+
&& matches!(
1495+
external_module.get_external_type().as_str(),
1496+
"module-import" | "module"
1497+
)
1498+
{
1499+
// handle external module
1500+
Self::re_export_from_external_module(
1501+
external_module,
1502+
current_chunk,
1503+
&rspack_core::ExportMode::NormalReexport(mode.clone()),
1504+
link,
1505+
export_dep,
1506+
);
1507+
continue;
1508+
}
1509+
13911510
let chunk_link = link.get_mut_unwrap(&current_chunk);
13921511

13931512
let used_name = if unknown_export_info {
@@ -1974,7 +2093,7 @@ impl EsmLibraryPlugin {
19742093
we can remove the empty raw_import if there is star reexport.
19752094
*/
19762095
for chunk_link in link.values_mut() {
1977-
for source in &chunk_link.raw_star_exports {
2096+
for (source, _) in &chunk_link.raw_star_exports {
19782097
let key = (source.clone(), None);
19792098
if let Some(import_spec) = chunk_link.raw_import_stmts.get(&key)
19802099
&& import_spec.atoms.is_empty()

crates/rspack_plugin_esm_library/src/render.rs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -462,20 +462,29 @@ impl EsmLibraryPlugin {
462462
}
463463

464464
// render star exports
465-
for source in &chunk_link.raw_star_exports {
466-
final_source.add(RawStringSource::from(format!(
467-
"export * from {};\n",
468-
serde_json::to_string(source).expect("should have correct request")
469-
)));
465+
for (source, export_names) in &chunk_link.raw_star_exports {
466+
for name in export_names {
467+
if name == "*" {
468+
final_source.add(RawStringSource::from(format!(
469+
"export * from {};\n",
470+
serde_json::to_string(source).expect("should have correct request")
471+
)));
472+
} else {
473+
final_source.add(RawStringSource::from(format!(
474+
"export * as {name} from {};\n",
475+
serde_json::to_string(source).expect("should have correct request")
476+
)));
477+
}
478+
}
470479
}
471480

472481
// render re-exports
473-
for (chunk, export_symbols) in chunk_link.re_exports() {
482+
for (re_export_from, export_symbols) in chunk_link.re_exports() {
474483
let mut export_symbols = export_symbols.iter().collect::<Vec<_>>();
475484
export_symbols.sort_by(|a, b| a.0.cmp(b.0));
476485

477486
final_source.add(RawStringSource::from(format!(
478-
"export {{ {} }} from \"__RSPACK_ESM_CHUNK_{}\";\n",
487+
"export {{ {} }} from \"{}\";\n",
479488
export_symbols
480489
.iter()
481490
.flat_map(|(imported, exports)| {
@@ -491,7 +500,14 @@ impl EsmLibraryPlugin {
491500
})
492501
.collect::<Vec<_>>()
493502
.join(", "),
494-
chunk.as_u32()
503+
match re_export_from {
504+
crate::chunk_link::ReExportFrom::Chunk(chunk_ukey) => {
505+
Cow::Owned(format!("__RSPACK_ESM_CHUNK_{}", chunk_ukey.as_u32()))
506+
}
507+
crate::chunk_link::ReExportFrom::Request(request) => {
508+
Cow::Borrowed(request)
509+
}
510+
}
495511
)));
496512
}
497513

0 commit comments

Comments
 (0)