Skip to main content

rspack_plugin_javascript/dependency/esm/
esm_import_dependency.rs

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
19// TODO: find a better way to implement this for performance
20// Align with https://github.com/webpack/webpack/blob/51f0f0aeac072f989f8d40247f6c23a1995c5c37/lib/dependencies/HarmonyImportDependency.js#L361-L365
21// This map is used to save the runtime conditions of modules and used by ESMAcceptDependency in hot module replacement.
22// It can not be saved in TemplateContext because only dependencies of rebuild modules will be templated again.
23pub 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// ESMImportDependency is merged ESMImportSideEffectDependency.
58#[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  // Only available when module factorization is successful.
125  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  // Bailout only if the module does exist and not active.
142  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    // lazy
149    if target_module.is_none() {
150      return;
151    }
152    // weak
153    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  // https://github.com/webpack/webpack/blob/ac7e531436b0d47cd88451f497cdfd0dad41535d/lib/dependencies/HarmonyImportDependency.js#L282-L285
204  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  // The import emitted map is consumed by ESMAcceptDependency which enabled by HotModuleReplacementPlugin
215  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      // For type re-export cases:
352      //   1. export { T } from "./types";
353      //   2. import { T } from "./types"; export { T };
354      // Check if the export is really a type export, if it is, then skip the error.
355      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      // ref: https://github.com/evanw/esbuild/blob/f4159a7b823cd5fe2217da2c30e8873d2f319667/internal/linker/linker.go#L3129-L3131
362      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        // Ignore the JSON named exports warning: this doesn't follow the standards
475        // but it's widely used by the community, other bundlers also ignore the warning.
476        && 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  // bailout the check of this export chain if there is a module that not transpiled from
506  // typescript, we only support that the export chain is all transpiled typescript, if not
507  // the check will be very slow especially when big javascript npm package exists.
508  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}