Skip to content

Commit 76b952e

Browse files
committed
fix: should re-exports deep star reexports
1 parent 733261f commit 76b952e

File tree

12 files changed

+256
-38
lines changed

12 files changed

+256
-38
lines changed

crates/rspack_core/src/concatenated_module.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ use crate::{
4343
CodeGenerationExportsFinalNames, CodeGenerationPublicPathAutoReplace, CodeGenerationResult,
4444
Compilation, ConcatenatedModuleIdent, ConcatenationScope, ConditionalInitFragment,
4545
ConnectionState, Context, DEFAULT_EXPORT, DEFAULT_EXPORT_ATOM, DependenciesBlock, DependencyId,
46-
DependencyType, ExportMode, ExportProvided, ExportsArgument, ExportsInfoGetter, ExportsType,
47-
FactoryMeta, GetUsedNameParam, IdentCollector, InitFragment, InitFragmentStage, LibIdentOptions,
46+
DependencyType, ExportProvided, ExportsArgument, ExportsInfoGetter, ExportsType, FactoryMeta,
47+
GetUsedNameParam, IdentCollector, InitFragment, InitFragmentStage, LibIdentOptions,
4848
MaybeDynamicTargetExportInfoHashKey, Module, ModuleArgument, ModuleGraph,
4949
ModuleGraphCacheArtifact, ModuleGraphConnection, ModuleIdentifier, ModuleLayer,
5050
ModuleStaticCacheArtifact, ModuleType, NAMESPACE_OBJECT_EXPORT, ParserOptions,
@@ -201,7 +201,6 @@ pub struct ConcatenatedModuleInfo {
201201
pub global_scope_ident: Vec<ConcatenatedModuleIdent>,
202202
pub idents: Vec<ConcatenatedModuleIdent>,
203203
pub all_used_names: HashSet<Atom>,
204-
pub re_exports: IdentifierIndexMap<Vec<ExportMode>>,
205204
pub binding_to_ref: HashMap<(Atom, SyntaxContext), Vec<ConcatenatedModuleIdent>>,
206205

207206
pub public_path_auto_replacement: Option<bool>,

crates/rspack_plugin_esm_library/src/link.rs

Lines changed: 68 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use std::{collections::hash_map::Entry, sync::Arc};
22

33
use rayon::prelude::*;
4-
use rspack_collections::{IdentifierIndexMap, IdentifierIndexSet, IdentifierMap, UkeyMap};
4+
use rspack_collections::{
5+
IdentifierIndexMap, IdentifierIndexSet, IdentifierMap, IdentifierSet, UkeyMap,
6+
};
57
use rspack_core::{
68
BuildMetaDefaultObject, BuildMetaExportsType, ChunkGraph, ChunkInitFragments, ChunkUkey,
79
CodeGenerationPublicPathAutoReplace, Compilation, ConcatenatedModuleIdent, ExportMode,
@@ -805,12 +807,6 @@ impl EsmLibraryPlugin {
805807
concate_info.idents = idents;
806808
concate_info.all_used_names = all_used_names;
807809
concate_info.binding_to_ref = binding_to_ref;
808-
809-
let scope = codegen_res
810-
.concatenation_scope
811-
.as_ref()
812-
.expect("should have concatenation scope");
813-
concate_info.re_exports = scope.re_exports.clone();
814810
concate_info.has_ast = true;
815811
concate_info.source = Some(ReplaceSource::new(render_source.source.clone()));
816812
concate_info.internal_source = Some(render_source.source.clone());
@@ -967,6 +963,7 @@ impl EsmLibraryPlugin {
967963
.expect("should have mode");
968964

969965
let optimize_reexport_star = possible_to_optimize_mode
966+
&& export_dep.name.is_none()
970967
&& ref_box_module
971968
.as_external_module()
972969
.is_some_and(|m| matches!(m.get_external_type().as_str(), "module-import" | "module"));
@@ -979,17 +976,36 @@ impl EsmLibraryPlugin {
979976
chunk_link
980977
.raw_star_exports
981978
.insert(export_dep.request.to_string());
979+
continue;
982980
}
983981

984982
let chunk_link = link.get_mut(&current_chunk).expect("should have link");
983+
985984
match re_exports {
986985
rspack_core::ExportMode::Missing
987986
| rspack_core::ExportMode::LazyMake
988-
| rspack_core::ExportMode::DynamicReexport(_)
989987
| rspack_core::ExportMode::ReexportUndefined(_)
990988
| rspack_core::ExportMode::EmptyStar(_)
991989
| rspack_core::ExportMode::Unused(_) => {}
992990

991+
rspack_core::ExportMode::DynamicReexport(_) => {
992+
// special handling for export * from normal module
993+
if export_dep.name.is_none() && export_dep.get_ids(module_graph).is_empty() {
994+
Self::link_re_export(
995+
ref_module,
996+
current_chunk,
997+
compilation,
998+
concate_modules_map,
999+
required,
1000+
link,
1001+
module_graph,
1002+
needed_namespace_objects,
1003+
entry_imports,
1004+
exports,
1005+
);
1006+
}
1007+
}
1008+
9931009
rspack_core::ExportMode::ReexportDynamicDefault(mode) => {
9941010
let ref_info = concate_modules_map
9951011
.get(&ref_module)
@@ -1145,10 +1161,10 @@ impl EsmLibraryPlugin {
11451161
let exports_info = module_graph.get_exports_info(&ref_module);
11461162

11471163
for item in &mode.items {
1148-
// reset ref_module for each dep
11491164
let mut ref_module = orig_ref_module;
11501165

11511166
if item.hidden {
1167+
// ignore hidden
11521168
continue;
11531169
}
11541170

@@ -1161,40 +1177,57 @@ impl EsmLibraryPlugin {
11611177
continue;
11621178
}
11631179

1164-
let chunk_link = link.get_mut(&current_chunk).expect("should have link");
1180+
let name = item.ids.first().unwrap_or(&item.name);
1181+
let mut unknown_export_info = false;
11651182

1166-
let export_info = item
1167-
.ids
1168-
.first()
1169-
.and_then(|id| exports_info.as_data(module_graph).named_exports(id))
1170-
.unwrap_or(item.export_info.as_data(module_graph));
1183+
let mut export_info =
1184+
if let Some(export_info) = exports_info.as_data(module_graph).named_exports(name) {
1185+
export_info
1186+
} else {
1187+
unknown_export_info = true;
1188+
// export info not found, this is likely because the export is from unknown
1189+
item.export_info.as_data(module_graph)
1190+
};
11711191

1172-
if export_info.is_reexport() {
1173-
// should point to the real export
1192+
let mut visited_modules = IdentifierSet::default();
1193+
visited_modules.insert(ref_module);
1194+
while export_info.is_reexport() {
11741195
let targets = export_info.get_max_target();
1175-
let (Some(dep), _) = targets
1196+
let (dep, _) = targets
11761197
.iter()
11771198
.next()
1178-
.expect("should have target if export info is reexport")
1179-
else {
1180-
continue;
1181-
};
1199+
.expect("should have target if export info is reexport");
1200+
let dep = dep.expect("should have dependency for re-exported export info");
11821201

1183-
let Some(module) = module_graph.module_identifier_by_dependency_id(dep) else {
1184-
continue;
1202+
let Some(module) = module_graph.module_identifier_by_dependency_id(&dep) else {
1203+
unreachable!("should have module for re-exported dependency");
11851204
};
11861205
ref_module = *module;
1206+
if !visited_modules.insert(*module) {
1207+
break;
1208+
}
1209+
let Some(target_export_info) = module_graph
1210+
.get_exports_info(&ref_module)
1211+
.as_data(module_graph)
1212+
.named_exports(name)
1213+
else {
1214+
// unknown export info, break
1215+
unknown_export_info = true;
1216+
break;
1217+
};
1218+
export_info = target_export_info;
11871219
}
11881220

1189-
let exports_info = module_graph
1190-
.get_prefetched_exports_info(&ref_module, PrefetchExportsInfoMode::Default);
1191-
let export_info =
1192-
exports_info.get_read_only_export_info(item.ids.first().unwrap_or(&item.name));
1221+
let chunk_link = link.get_mut(&current_chunk).expect("should have link");
11931222

1194-
let used_name = export_info.get_used_name(None, None).unwrap_or_else(|| {
1195-
// dynamic export
1223+
let used_name = if unknown_export_info {
11961224
UsedNameItem::Str(item.name.clone())
1197-
});
1225+
} else {
1226+
export_info.get_used_name(None, None).unwrap_or_else(|| {
1227+
// dynamic export
1228+
UsedNameItem::Str(item.name.clone())
1229+
})
1230+
};
11981231

11991232
if let UsedNameItem::Inlined(inlined) = used_name {
12001233
let new_name = find_new_name(&item.name, &chunk_link.used_names, &vec![]);
@@ -1212,6 +1245,10 @@ impl EsmLibraryPlugin {
12121245
continue;
12131246
};
12141247

1248+
let UsedNameItem::Str(used_name) = used_name else {
1249+
unreachable!()
1250+
};
1251+
12151252
// if item is from other module
12161253
let ref_info = concate_modules_map
12171254
.get(&ref_module)
@@ -1234,10 +1271,6 @@ impl EsmLibraryPlugin {
12341271
chunk_link.used_names.insert(variable_to_export.clone());
12351272
entry_imports.entry(ref_info.module).or_default();
12361273

1237-
let UsedNameItem::Str(used_name) = used_name else {
1238-
unreachable!()
1239-
};
1240-
12411274
let variable_to_export =
12421275
interop_info.property_access(&used_name, &mut chunk_link.used_names);
12431276
Self::add_chunk_export(
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
```mjs title=main.mjs
2+
// ./lib3.js
3+
const lib3_lib3 = 42
4+
5+
// ./lib2.js
6+
7+
8+
const lib2_lib2 = 42
9+
10+
// ./lib.js
11+
12+
13+
const lib_lib = 42
14+
15+
// ./index.js
16+
17+
18+
it('should re-export esm correctly', async () => {
19+
const { lib, lib2, lib3 } = await import(/* webpackIgnore: true */ './main.mjs')
20+
expect(lib).toBe(42)
21+
expect(lib2).toBe(42)
22+
expect(lib3).toBe(42)
23+
})
24+
25+
export { lib2_lib2 as lib2, lib3_lib3 as lib3, lib_lib as lib };
26+
27+
```
28+
29+
```mjs title=runtime.mjs
30+
31+
32+
33+
```
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export * from './lib'
2+
3+
it('should re-export esm correctly', async () => {
4+
const { lib, lib2, lib3 } = await import(/* webpackIgnore: true */ './main.mjs')
5+
expect(lib).toBe(42)
6+
expect(lib2).toBe(42)
7+
expect(lib3).toBe(42)
8+
})
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './lib2'
2+
3+
export const lib = 42
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './lib3'
2+
3+
export const lib2 = 42
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const lib3 = 42
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
```mjs title=main.mjs
2+
import { __webpack_require__ } from "./runtime.mjs";
3+
__webpack_require__.add({
4+
"./lib.js":
5+
/*!****************!*\
6+
!*** ./lib.js ***!
7+
\****************/
8+
(function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
9+
__webpack_require__.d(__webpack_exports__, {
10+
BI: () => (/* reexport safe */ _lib2__WEBPACK_IMPORTED_MODULE_0__.B),
11+
cR: () => (lib),
12+
up: () => (/* reexport safe */ _lib2__WEBPACK_IMPORTED_MODULE_0__.u)
13+
});
14+
/* ESM import */var _lib2__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./lib2 */ "./lib2.js");
15+
16+
17+
const lib = 42
18+
19+
eval('')
20+
21+
22+
}),
23+
"./lib2.js":
24+
/*!*****************!*\
25+
!*** ./lib2.js ***!
26+
\*****************/
27+
(function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
28+
__webpack_require__.d(__webpack_exports__, {
29+
B: () => (/* reexport safe */ _lib3__WEBPACK_IMPORTED_MODULE_0__.B),
30+
u: () => (lib2)
31+
});
32+
/* ESM import */var _lib3__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./lib3 */ "./lib3.js");
33+
34+
35+
const lib2 = 42
36+
37+
38+
}),
39+
"./lib3.js":
40+
/*!*****************!*\
41+
!*** ./lib3.js ***!
42+
\*****************/
43+
(function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
44+
__webpack_require__.d(__webpack_exports__, {
45+
B: () => (lib3)
46+
});
47+
const lib3 = 42
48+
49+
50+
}),
51+
});
52+
// ./index.js
53+
const lib_0 = __webpack_require__("./lib.js");
54+
var cR = lib_0.cR;
55+
56+
57+
it('should re-export esm correctly', async () => {
58+
const { lib, lib2, lib3 } = await import(/* webpackIgnore: true */ './main.mjs')
59+
expect(lib).toBe(42)
60+
expect(lib2).toBe(42)
61+
expect(lib3).toBe(42)
62+
})
63+
64+
const lib2_0 = __webpack_require__("./lib2.js");
65+
var u = lib2_0.u;
66+
67+
const lib3_0 = __webpack_require__("./lib3.js");
68+
var B = lib3_0.B;
69+
70+
export { B as lib3, cR as lib, u as lib2 };
71+
72+
```
73+
74+
```mjs title=runtime.mjs
75+
76+
var __webpack_modules__ = {};
77+
// The module cache
78+
var __webpack_module_cache__ = {};
79+
// The require function
80+
function __webpack_require__(moduleId) {
81+
// Check if module is in cache
82+
var cachedModule = __webpack_module_cache__[moduleId];
83+
if (cachedModule !== undefined) {
84+
return cachedModule.exports;
85+
}
86+
// Create a new module (and put it into the cache)
87+
var module = (__webpack_module_cache__[moduleId] = {
88+
exports: {}
89+
});
90+
// Execute the module function
91+
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
92+
93+
// Return the exports of the module
94+
return module.exports;
95+
}
96+
// expose the modules object (__webpack_modules__)
97+
__webpack_require__.m = __webpack_modules__;
98+
99+
// esm library register module runtime
100+
(() => {
101+
__webpack_require__.add = function registerModules(modules) { Object.assign(__webpack_require__.m, modules) }
102+
103+
})();
104+
// webpack/runtime/define_property_getters
105+
(() => {
106+
__webpack_require__.d = (exports, definition) => {
107+
for(var key in definition) {
108+
if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
109+
Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
110+
}
111+
}
112+
};
113+
})();
114+
// webpack/runtime/has_own_property
115+
(() => {
116+
__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
117+
})();
118+
119+
export { __webpack_require__ };
120+
121+
```
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export * from './lib'
2+
3+
it('should re-export esm correctly', async () => {
4+
const { lib, lib2, lib3 } = await import(/* webpackIgnore: true */ './main.mjs')
5+
expect(lib).toBe(42)
6+
expect(lib2).toBe(42)
7+
expect(lib3).toBe(42)
8+
})
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export * from './lib2'
2+
3+
export const lib = 42
4+
5+
eval('')

0 commit comments

Comments
 (0)