1use rspack_cacheable::{cacheable, cacheable_dyn, with::AsPreset};
2use rspack_collections::{IdentifierMap, IdentifierSet};
3use rspack_core::{
4 AsContextDependency, AwaitDependenciesInitFragment, BuildMetaDefaultObject, ChunkGraph,
5 ConditionalInitFragment, ConnectionState, Dependency, DependencyCategory,
6 DependencyCodeGeneration, DependencyCondition, DependencyConditionFn, DependencyId,
7 DependencyLocation, DependencyRange, DependencyTemplate, DependencyTemplateType, DependencyType,
8 ExportProvided, ExportsInfoArtifact, ExportsType, ExtendedReferencedExport, FactorizeInfo,
9 ForwardId, ImportAttributes, ImportPhase, InitFragmentExt, InitFragmentKey, InitFragmentStage,
10 LazyUntil, ModuleDependency, ModuleGraph, ModuleGraphCacheArtifact, ModuleIdentifier,
11 ProvidedExports, ResourceIdentifier, RuntimeCondition, RuntimeSpec, SideEffectsStateArtifact,
12 SourceType, TemplateContext, TemplateReplaceSource, TypeReexportPresenceMode, filter_runtime,
13};
14use rspack_error::{Diagnostic, Error, Severity};
15use swc_atoms::Atom;
16
17use super::create_resource_identifier_for_esm_dependency;
18
19pub mod import_emitted_runtime {
24 use once_cell::sync::OnceCell;
25 use rspack_collections::{IdentifierDashMap, IdentifierMap};
26 use rspack_core::{ModuleIdentifier, RuntimeCondition};
27 #[cfg(allocative)]
28 use rspack_util::allocative;
29
30 #[cfg_attr(allocative, allocative::root)]
31 static IMPORT_EMITTED_MAP: OnceCell<IdentifierDashMap<IdentifierMap<RuntimeCondition>>> =
32 OnceCell::new();
33
34 pub fn init_map() {
35 IMPORT_EMITTED_MAP.get_or_init(Default::default);
36 }
37
38 pub fn get_map() -> Option<&'static IdentifierDashMap<IdentifierMap<RuntimeCondition>>> {
39 IMPORT_EMITTED_MAP.get()
40 }
41
42 pub fn get_runtime(
43 module: &ModuleIdentifier,
44 referenced_module: &ModuleIdentifier,
45 ) -> RuntimeCondition {
46 let map = get_map().expect("must call import_emitted_runtime::init_map() before");
47 let Some(condition_map) = map.get(module) else {
48 return RuntimeCondition::Boolean(false);
49 };
50 match condition_map.get(referenced_module) {
51 Some(r) => r.to_owned(),
52 None => RuntimeCondition::Boolean(false),
53 }
54 }
55}
56
57#[cacheable]
59#[derive(Debug, Clone)]
60pub struct ESMImportSideEffectDependency {
61 #[cacheable(with=AsPreset)]
62 request: Atom,
63 source_order: i32,
64 id: DependencyId,
65 range: DependencyRange,
66 dependency_type: DependencyType,
67 phase: ImportPhase,
68 attributes: Option<ImportAttributes>,
69 resource_identifier: ResourceIdentifier,
70 loc: Option<DependencyLocation>,
71 factorize_info: FactorizeInfo,
72 lazy_make: bool,
73 star_export: bool,
74}
75
76impl ESMImportSideEffectDependency {
77 #[allow(clippy::too_many_arguments)]
78 pub fn new(
79 request: Atom,
80 source_order: i32,
81 range: DependencyRange,
82 dependency_type: DependencyType,
83 phase: ImportPhase,
84 attributes: Option<ImportAttributes>,
85 loc: Option<DependencyLocation>,
86 star_export: bool,
87 ) -> Self {
88 let resource_identifier =
89 create_resource_identifier_for_esm_dependency(&request, phase, attributes.as_ref());
90 Self {
91 id: DependencyId::new(),
92 source_order,
93 request,
94 range,
95 dependency_type,
96 phase,
97 attributes,
98 resource_identifier,
99 loc,
100 factorize_info: Default::default(),
101 lazy_make: false,
102 star_export,
103 }
104 }
105
106 fn missing_module_active(&self) -> bool {
107 !self.lazy_make
108 }
109}
110
111pub fn esm_import_dependency_apply<T: ModuleDependency>(
112 module_dependency: &T,
113 source_order: i32,
114 phase: ImportPhase,
115 code_generatable_context: &mut TemplateContext,
116) {
117 let TemplateContext {
118 compilation,
119 module,
120 runtime,
121 runtime_template,
122 ..
123 } = code_generatable_context;
124 let module_graph = compilation.get_module_graph();
126 let module_graph_cache = &compilation.module_graph_cache_artifact;
127 let connection = module_graph.connection_by_dependency_id(module_dependency.id());
128 let is_target_active = if let Some(con) = connection {
129 con.is_target_active(
130 module_graph,
131 *runtime,
132 module_graph_cache,
133 &compilation
134 .build_module_graph_artifact
135 .side_effects_state_artifact,
136 &compilation.exports_info_artifact,
137 )
138 } else {
139 true
140 };
141 if !is_target_active {
143 return;
144 }
145
146 let target_module = module_graph.get_module_by_dependency_id(module_dependency.id());
147 if module_dependency.weak() {
148 if target_module.is_none() {
150 return;
151 }
152 if let Some(target_module) = target_module
154 && ChunkGraph::get_module_id(&compilation.module_ids_artifact, target_module.identifier())
155 .is_none()
156 {
157 return;
158 }
159 }
160
161 let runtime_condition = if module_dependency.weak() {
162 RuntimeCondition::Boolean(false)
163 } else if let Some(connection) = module_graph.connection_by_dependency_id(module_dependency.id())
164 {
165 filter_runtime(*runtime, |r| {
166 connection.is_target_active(
167 module_graph,
168 r,
169 module_graph_cache,
170 &compilation
171 .build_module_graph_artifact
172 .side_effects_state_artifact,
173 &compilation.exports_info_artifact,
174 )
175 })
176 } else {
177 RuntimeCondition::Boolean(true)
178 };
179
180 let import_var = compilation.get_import_var(
181 module.identifier(),
182 target_module,
183 module_dependency.user_request(),
184 phase,
185 *runtime,
186 );
187 let content: (String, String) = runtime_template.import_statement(
188 *module,
189 compilation,
190 module_dependency.id(),
191 &import_var,
192 module_dependency.request(),
193 phase,
194 false,
195 );
196 let TemplateContext {
197 init_fragments,
198 compilation,
199 module,
200 ..
201 } = code_generatable_context;
202
203 let module_key = target_module.map_or(module_dependency.request(), |m| m.identifier().as_str());
205 let key = format!(
206 "{}ESM import {module_key}",
207 match phase {
208 ImportPhase::Evaluation => "",
209 ImportPhase::Source => "",
210 ImportPhase::Defer => "deferred ",
211 }
212 );
213
214 if let Some(import_emitted_map) = import_emitted_runtime::get_map()
216 && let Some(target_module) = target_module
217 {
218 let target_module = target_module.identifier();
219 let mut emitted_modules = import_emitted_map.entry(module.identifier()).or_default();
220
221 let old_runtime_condition = match emitted_modules.get(&target_module) {
222 Some(v) => v.to_owned(),
223 None => RuntimeCondition::Boolean(false),
224 };
225
226 let mut merged_runtime_condition = runtime_condition.clone();
227 if !matches!(old_runtime_condition, RuntimeCondition::Boolean(false))
228 && !matches!(merged_runtime_condition, RuntimeCondition::Boolean(true))
229 {
230 if matches!(merged_runtime_condition, RuntimeCondition::Boolean(false))
231 || matches!(old_runtime_condition, RuntimeCondition::Boolean(true))
232 {
233 merged_runtime_condition = old_runtime_condition;
234 } else {
235 merged_runtime_condition
236 .as_spec_mut()
237 .expect("should be spec")
238 .extend(old_runtime_condition.as_spec().expect("should be spec"));
239 }
240 }
241 emitted_modules.insert(target_module, merged_runtime_condition);
242 }
243
244 let is_async_module = matches!(target_module, Some(target_module) if ModuleGraph::is_async(&compilation.async_modules_artifact, &target_module.identifier()));
245 if is_async_module {
246 init_fragments.push(Box::new(ConditionalInitFragment::new(
247 content.0,
248 InitFragmentStage::StageESMImports,
249 source_order,
250 InitFragmentKey::ESMImport(key.clone()),
251 None,
252 runtime_condition.clone(),
253 )));
254 init_fragments.push(AwaitDependenciesInitFragment::new_single(import_var).boxed());
255 init_fragments.push(Box::new(ConditionalInitFragment::new(
256 content.1,
257 InitFragmentStage::StageAsyncESMImports,
258 source_order,
259 InitFragmentKey::ESMImport(format!("{key} compat")),
260 None,
261 runtime_condition,
262 )));
263 } else {
264 init_fragments.push(Box::new(ConditionalInitFragment::new(
265 format!("{}{}", content.0, content.1),
266 InitFragmentStage::StageESMImports,
267 source_order,
268 InitFragmentKey::ESMImport(key),
269 None,
270 runtime_condition,
271 )));
272 }
273}
274
275#[allow(clippy::too_many_arguments)]
276pub fn esm_import_dependency_get_linking_error<T: ModuleDependency>(
277 module_dependency: &T,
278 ids: &[Atom],
279 module_graph: &ModuleGraph,
280 module_graph_cache: &ModuleGraphCacheArtifact,
281 exports_info_artifact: &ExportsInfoArtifact,
282 name: &Atom,
283 is_reexport: bool,
284 should_error: bool,
285) -> Option<Diagnostic> {
286 let imported_module = module_graph.get_module_by_dependency_id(module_dependency.id())?;
287 if imported_module.first_error().is_some() {
288 return None;
289 }
290 let parent_module_identifier = module_graph
291 .get_parent_module(module_dependency.id())
292 .expect("should have parent module for dependency");
293 let parent_module = module_graph
294 .module_by_identifier(parent_module_identifier)
295 .expect("should have module");
296 let exports_type = imported_module.get_exports_type(
297 module_graph,
298 module_graph_cache,
299 exports_info_artifact,
300 parent_module.build_meta().strict_esm_module(),
301 );
302 let additional_msg = || {
303 if is_reexport {
304 format!("(reexported as '{name}')")
305 } else {
306 format!("(imported as '{name}')")
307 }
308 };
309 let create_error = |message: String| {
310 let (severity, title) = if should_error {
311 (Severity::Error, "ESModulesLinkingError")
312 } else {
313 (Severity::Warning, "ESModulesLinkingWarning")
314 };
315 let mut error = if let Some(span) = module_dependency.range()
316 && let Some(source) = parent_module.source()
317 {
318 Error::from_string(
319 Some(source.source().into_string_lossy().into_owned()),
320 span.start as usize,
321 span.end as usize,
322 title.to_string(),
323 message,
324 )
325 } else {
326 let mut error = rspack_error::error!(message);
327 error.code = Some(title.into());
328 error
329 };
330 error.severity = severity;
331 error.hide_stack = Some(true);
332 let mut diagnostic = Diagnostic::from(error);
333 diagnostic.module_identifier = Some(*parent_module_identifier);
334 diagnostic
335 };
336 if matches!(
337 exports_type,
338 ExportsType::Namespace | ExportsType::DefaultWithNamed
339 ) {
340 if ids.is_empty() {
341 return None;
342 }
343 let imported_module_identifier = imported_module.identifier();
344 let exports_info = exports_info_artifact.get_exports_info_data(&imported_module_identifier);
345 if (!matches!(exports_type, ExportsType::DefaultWithNamed) || ids[0] != "default")
346 && matches!(
347 exports_info.is_export_provided(exports_info_artifact, ids),
348 Some(ExportProvided::NotProvided)
349 )
350 {
351 let type_reexports_presence = parent_module
356 .as_normal_module()
357 .and_then(|m| m.get_parser_options())
358 .and_then(|o| o.get_javascript())
359 .and_then(|o| o.type_reexports_presence)
360 .unwrap_or_default();
361 if !matches!(
363 type_reexports_presence,
364 TypeReexportPresenceMode::NoTolerant
365 ) && parent_module
366 .build_info()
367 .collected_typescript_info
368 .is_some()
369 && ids.len() == 1
370 && matches!(
371 exports_info_artifact
372 .get_exports_info_data(parent_module_identifier)
373 .is_export_provided(exports_info_artifact, std::slice::from_ref(name)),
374 Some(ExportProvided::Provided)
375 )
376 {
377 if matches!(
378 type_reexports_presence,
379 TypeReexportPresenceMode::TolerantNoCheck
380 ) {
381 return None;
382 }
383 if find_type_exports_from_outgoings(
384 module_graph,
385 &imported_module_identifier,
386 &ids[0],
387 &mut IdentifierSet::default(),
388 ) {
389 return None;
390 }
391 }
392 let mut pos = 0;
393 let mut maybe_exports_info =
394 Some(exports_info_artifact.get_exports_info_data(&imported_module_identifier));
395 while pos < ids.len()
396 && let Some(exports_info) = &maybe_exports_info
397 {
398 let id = &ids[pos];
399 pos += 1;
400 let export_info = exports_info.get_read_only_export_info(id);
401 if matches!(export_info.provided(), Some(ExportProvided::NotProvided)) {
402 let provided_exports = exports_info.get_provided_exports();
403 let more_info = if let ProvidedExports::ProvidedNames(exports) = &provided_exports {
404 if exports.is_empty() {
405 " (module has no exports)".to_string()
406 } else {
407 format!(
408 " (possible exports: {})",
409 exports
410 .iter()
411 .map(|e| e.as_str())
412 .collect::<Vec<_>>()
413 .join(", ")
414 )
415 }
416 } else {
417 " (possible exports unknown)".to_string()
418 };
419 let msg = format!(
420 "export {} {} was not found in '{}'{more_info}",
421 ids
422 .iter()
423 .take(pos)
424 .map(|id| format!("'{id}'"))
425 .collect::<Vec<_>>()
426 .join("."),
427 additional_msg(),
428 module_dependency.user_request(),
429 );
430 return Some(create_error(msg));
431 }
432 let Some(nested_exports_info) = export_info.exports_info() else {
433 maybe_exports_info = None;
434 continue;
435 };
436 maybe_exports_info = Some(nested_exports_info.as_data(exports_info_artifact));
437 }
438 let msg = format!(
439 "export {} {} was not found in '{}'",
440 ids
441 .iter()
442 .map(|id| format!("'{id}'"))
443 .collect::<Vec<_>>()
444 .join("."),
445 additional_msg(),
446 module_dependency.user_request()
447 );
448 return Some(create_error(msg));
449 }
450 }
451 #[allow(clippy::collapsible_match)]
452 match exports_type {
453 ExportsType::DefaultOnly => {
454 if !ids.is_empty() && ids[0] != "default" {
455 let msg = format!(
456 "Can't import the named export {} {} from default-exporting module (only default export is available)",
457 ids
458 .iter()
459 .map(|id| format!("'{id}'"))
460 .collect::<Vec<_>>()
461 .join("."),
462 additional_msg(),
463 );
464 return Some(create_error(msg));
465 }
466 }
467 ExportsType::DefaultWithNamed => {
468 if !ids.is_empty()
469 && ids[0] != "default"
470 && matches!(
471 imported_module.build_meta().default_object(),
472 BuildMetaDefaultObject::RedirectWarn
473 )
474 && imported_module.build_info().json_data.is_none()
477 {
478 let msg = format!(
479 "Should not import the named export {} {} from default-exporting module (only default export is available soon)",
480 ids
481 .iter()
482 .map(|id| format!("'{id}'"))
483 .collect::<Vec<_>>()
484 .join("."),
485 additional_msg(),
486 );
487 return Some(create_error(msg));
488 }
489 }
490 _ => {}
491 }
492 None
493}
494
495fn find_type_exports_from_outgoings(
496 mg: &ModuleGraph,
497 module_identifier: &ModuleIdentifier,
498 export_name: &Atom,
499 visited: &mut IdentifierSet,
500) -> bool {
501 visited.insert(*module_identifier);
502 let module = mg
503 .module_by_identifier(module_identifier)
504 .expect("should have module");
505 let Some(info) = &module.build_info().collected_typescript_info else {
509 return false;
510 };
511 if info.type_exports.contains(export_name) {
512 return true;
513 }
514 for connection in mg.get_outgoing_connections(module_identifier) {
515 if visited.contains(connection.module_identifier()) {
516 continue;
517 }
518 let dependency = mg.dependency_by_id(&connection.dependency_id);
519 if !matches!(
520 dependency.dependency_type(),
521 DependencyType::EsmImport | DependencyType::EsmExportImport
522 ) {
523 continue;
524 }
525 if find_type_exports_from_outgoings(mg, connection.module_identifier(), export_name, visited) {
526 return true;
527 }
528 }
529 false
530}
531
532#[cacheable_dyn]
533impl Dependency for ESMImportSideEffectDependency {
534 fn id(&self) -> &DependencyId {
535 &self.id
536 }
537
538 fn loc(&self) -> Option<DependencyLocation> {
539 self.loc.clone()
540 }
541
542 fn range(&self) -> Option<DependencyRange> {
543 Some(self.range)
544 }
545
546 fn source_order(&self) -> Option<i32> {
547 Some(self.source_order)
548 }
549
550 fn category(&self) -> &DependencyCategory {
551 &DependencyCategory::Esm
552 }
553
554 fn dependency_type(&self) -> &DependencyType {
555 &self.dependency_type
556 }
557
558 fn get_phase(&self) -> ImportPhase {
559 self.phase
560 }
561
562 fn get_attributes(&self) -> Option<&ImportAttributes> {
563 self.attributes.as_ref()
564 }
565
566 fn get_module_evaluation_side_effects_state(
567 &self,
568 module_graph: &ModuleGraph,
569 module_graph_cache: &ModuleGraphCacheArtifact,
570 side_effects_state_artifact: &SideEffectsStateArtifact,
571 module_chain: &mut IdentifierSet,
572 connection_state_cache: &mut IdentifierMap<ConnectionState>,
573 ) -> ConnectionState {
574 if let Some(module) = module_graph
575 .module_identifier_by_dependency_id(&self.id)
576 .and_then(|module_identifier| module_graph.module_by_identifier(module_identifier))
577 {
578 module.get_side_effects_connection_state(
579 module_graph,
580 module_graph_cache,
581 side_effects_state_artifact,
582 module_chain,
583 connection_state_cache,
584 )
585 } else {
586 ConnectionState::Active(self.missing_module_active())
587 }
588 }
589
590 fn resource_identifier(&self) -> Option<&str> {
591 Some(&self.resource_identifier)
592 }
593
594 fn get_referenced_exports(
595 &self,
596 _module_graph: &ModuleGraph,
597 _module_graph_cache: &ModuleGraphCacheArtifact,
598 _exports_info_artifact: &ExportsInfoArtifact,
599 _runtime: Option<&RuntimeSpec>,
600 ) -> Vec<ExtendedReferencedExport> {
601 vec![]
602 }
603
604 fn could_affect_referencing_module(&self) -> rspack_core::AffectType {
605 rspack_core::AffectType::True
606 }
607
608 fn forward_id(&self) -> ForwardId {
609 ForwardId::Empty
610 }
611
612 fn lazy(&self) -> Option<LazyUntil> {
613 self.lazy_make.then(|| {
614 if self.star_export {
615 LazyUntil::Fallback
616 } else {
617 LazyUntil::NoUntil
618 }
619 })
620 }
621
622 fn set_lazy(&mut self) {
623 self.lazy_make = true;
624 }
625
626 fn unset_lazy(&mut self) -> bool {
627 let changed = self.lazy_make;
628 self.lazy_make = false;
629 changed
630 }
631}
632
633#[cacheable_dyn]
634impl ModuleDependency for ESMImportSideEffectDependency {
635 fn request(&self) -> &str {
636 &self.request
637 }
638
639 fn user_request(&self) -> &str {
640 &self.request
641 }
642
643 fn get_condition(&self) -> Option<DependencyCondition> {
644 Some(DependencyCondition::new(
645 ESMImportSideEffectDependencyCondition,
646 ))
647 }
648
649 fn factorize_info(&self) -> &FactorizeInfo {
650 &self.factorize_info
651 }
652
653 fn factorize_info_mut(&mut self) -> &mut FactorizeInfo {
654 &mut self.factorize_info
655 }
656}
657
658impl AsContextDependency for ESMImportSideEffectDependency {}
659
660struct ESMImportSideEffectDependencyCondition;
661
662impl DependencyConditionFn for ESMImportSideEffectDependencyCondition {
663 fn get_connection_state(
664 &self,
665 conn: &rspack_core::ModuleGraphConnection,
666 _runtime: Option<&RuntimeSpec>,
667 module_graph: &ModuleGraph,
668 module_graph_cache: &ModuleGraphCacheArtifact,
669 side_effects_state_artifact: &SideEffectsStateArtifact,
670 _exports_info_artifact: &ExportsInfoArtifact,
671 ) -> ConnectionState {
672 let id = *conn.module_identifier();
673 if let Some(state) = side_effects_state_artifact.module_evaluation_side_effects_state(&id) {
674 return state;
675 }
676 if let Some(module) = module_graph.module_by_identifier(&id) {
677 module.get_side_effects_connection_state(
678 module_graph,
679 module_graph_cache,
680 side_effects_state_artifact,
681 &mut IdentifierSet::default(),
682 &mut IdentifierMap::default(),
683 )
684 } else {
685 let dependency = module_graph.dependency_by_id(&conn.dependency_id);
686 let dependency = dependency
687 .downcast_ref::<ESMImportSideEffectDependency>()
688 .expect("should be ESMImportSideEffectDependency");
689 ConnectionState::Active(dependency.missing_module_active())
690 }
691 }
692}
693
694#[cacheable_dyn]
695impl DependencyCodeGeneration for ESMImportSideEffectDependency {
696 fn dependency_template(&self) -> Option<DependencyTemplateType> {
697 Some(ESMImportSideEffectDependencyTemplate::template_type())
698 }
699}
700
701#[cacheable]
702#[derive(Debug, Clone, Default)]
703pub struct ESMImportSideEffectDependencyTemplate;
704
705impl ESMImportSideEffectDependencyTemplate {
706 pub fn template_type() -> DependencyTemplateType {
707 DependencyTemplateType::Dependency(DependencyType::EsmImport)
708 }
709}
710
711impl DependencyTemplate for ESMImportSideEffectDependencyTemplate {
712 fn render(
713 &self,
714 dep: &dyn DependencyCodeGeneration,
715 _source: &mut TemplateReplaceSource,
716 code_generatable_context: &mut TemplateContext,
717 ) {
718 let dep = dep
719 .as_any()
720 .downcast_ref::<ESMImportSideEffectDependency>()
721 .expect("ESMImportSideEffectDependencyTemplate should only be used for ESMImportSideEffectDependency");
722 let TemplateContext {
723 compilation,
724 concatenation_scope,
725 ..
726 } = code_generatable_context;
727 let module_graph = compilation.get_module_graph();
728
729 let module = module_graph.get_module_by_dependency_id(&dep.id);
730
731 if module.is_none() && !dep.missing_module_active() {
732 return;
733 }
734
735 if let Some(module) = module {
736 let source_types = module.source_types(module_graph);
737 if source_types
738 .iter()
739 .all(|source_type| matches!(source_type, SourceType::Css))
740 {
741 return;
742 }
743 }
744
745 if let Some(scope) = concatenation_scope
746 && module.is_some_and(|m| scope.is_module_in_scope(&m.identifier()))
747 {
748 return;
749 }
750 esm_import_dependency_apply(dep, dep.source_order, dep.phase, code_generatable_context);
751 }
752}