rspack_core/
context_module.rs

1use std::{borrow::Cow, hash::Hash, sync::Arc};
2
3use cow_utils::CowUtils;
4use derive_more::Debug;
5use indoc::formatdoc;
6use itertools::Itertools;
7use rspack_cacheable::{
8  cacheable, cacheable_dyn,
9  with::{AsOption, AsPreset, AsVec, Unsupported},
10};
11use rspack_collections::{Identifiable, Identifier};
12use rspack_error::{Result, impl_empty_diagnosable_trait};
13use rspack_hash::{RspackHash, RspackHashDigest};
14use rspack_macros::impl_source_map_config;
15use rspack_paths::{ArcPathSet, Utf8PathBuf};
16use rspack_regex::RspackRegex;
17use rspack_sources::{BoxSource, OriginalSource, RawStringSource, SourceExt};
18use rspack_util::{
19  fx_hash::FxIndexMap,
20  itoa, json_stringify,
21  source_map::{ModuleSourceMapConfig, SourceMapKind},
22};
23use rustc_hash::FxHashMap as HashMap;
24use swc_core::atoms::Atom;
25
26use crate::{
27  AsyncDependenciesBlock, AsyncDependenciesBlockIdentifier, BoxDependency, BuildContext, BuildInfo,
28  BuildMeta, BuildMetaDefaultObject, BuildMetaExportsType, BuildResult, ChunkGraph,
29  ChunkGroupOptions, CodeGenerationResult, Compilation, ConcatenationScope,
30  ContextElementDependency, DependenciesBlock, Dependency, DependencyCategory, DependencyId,
31  DependencyLocation, DynamicImportMode, ExportsType, FactoryMeta, FakeNamespaceObjectMode,
32  GroupOptions, ImportAttributes, LibIdentOptions, Module, ModuleGraph, ModuleId,
33  ModuleIdsArtifact, ModuleLayer, ModuleType, RealDependencyLocation, Resolve, RuntimeGlobals,
34  RuntimeSpec, SourceType, block_promise, contextify, get_exports_type_with_strict,
35  impl_module_meta_info, module_update_hash, returning_function, to_path,
36};
37
38static WEBPACK_CHUNK_NAME_INDEX_PLACEHOLDER: &str = "[index]";
39static WEBPACK_CHUNK_NAME_REQUEST_PLACEHOLDER: &str = "[request]";
40
41#[cacheable]
42#[derive(Debug, PartialEq, Eq, Clone, Hash)]
43pub enum ContextMode {
44  Sync,
45  Eager,
46  Weak,
47  AsyncWeak,
48  Lazy,
49  LazyOnce,
50}
51
52impl ContextMode {
53  pub fn as_str(&self) -> &str {
54    match self {
55      ContextMode::Sync => "sync",
56      ContextMode::Eager => "eager",
57      ContextMode::Weak => "weak",
58      ContextMode::Lazy => "lazy",
59      ContextMode::LazyOnce => "lazy-once",
60      ContextMode::AsyncWeak => "async-weak",
61    }
62  }
63}
64
65impl From<&str> for ContextMode {
66  fn from(value: &str) -> Self {
67    match try_convert_str_to_context_mode(value) {
68      Some(m) => m,
69      // TODO should give warning
70      _ => panic!("unknown context mode"),
71    }
72  }
73}
74
75impl From<DynamicImportMode> for ContextMode {
76  fn from(value: DynamicImportMode) -> Self {
77    match value {
78      DynamicImportMode::Lazy => Self::Lazy,
79      DynamicImportMode::Weak => Self::AsyncWeak,
80      DynamicImportMode::Eager => Self::Eager,
81      DynamicImportMode::LazyOnce => Self::LazyOnce,
82    }
83  }
84}
85
86pub fn try_convert_str_to_context_mode(s: &str) -> Option<ContextMode> {
87  match s {
88    "sync" => Some(ContextMode::Sync),
89    "eager" => Some(ContextMode::Eager),
90    "weak" => Some(ContextMode::Weak),
91    "lazy" => Some(ContextMode::Lazy),
92    "lazy-once" => Some(ContextMode::LazyOnce),
93    "async-weak" => Some(ContextMode::AsyncWeak),
94    // TODO should give warning
95    _ => None,
96  }
97}
98
99#[cacheable]
100#[derive(Debug, Clone, PartialEq, Eq, Hash)]
101pub enum ContextNameSpaceObject {
102  Bool(bool),
103  Strict,
104  Unset,
105}
106
107impl ContextNameSpaceObject {
108  pub fn is_false(&self) -> bool {
109    matches!(self, ContextNameSpaceObject::Unset)
110      || matches!(self, ContextNameSpaceObject::Bool(v) if !v)
111  }
112}
113
114#[cacheable]
115#[derive(Debug, Clone, Copy, Hash, PartialEq, PartialOrd, Ord, Eq)]
116pub enum ContextTypePrefix {
117  Import,
118  Normal,
119}
120
121#[cacheable]
122#[derive(Debug, Clone)]
123pub struct ContextOptions {
124  pub mode: ContextMode,
125  pub recursive: bool,
126  pub reg_exp: Option<RspackRegex>,
127  pub include: Option<RspackRegex>,
128  pub exclude: Option<RspackRegex>,
129  pub category: DependencyCategory,
130  pub request: String,
131  pub context: String,
132  pub namespace_object: ContextNameSpaceObject,
133  pub group_options: Option<GroupOptions>,
134  pub replaces: Vec<(String, u32, u32)>,
135  pub start: u32,
136  pub end: u32,
137  #[cacheable(with=AsOption<AsVec<AsVec<AsPreset>>>)]
138  pub referenced_exports: Option<Vec<Vec<Atom>>>,
139  pub attributes: Option<ImportAttributes>,
140}
141
142#[cacheable]
143#[derive(Debug, Clone)]
144pub struct ContextModuleOptions {
145  pub addon: String,
146  #[cacheable(with=AsPreset)]
147  pub resource: Utf8PathBuf,
148  pub resource_query: String,
149  pub resource_fragment: String,
150  pub context_options: ContextOptions,
151  pub layer: Option<ModuleLayer>,
152  pub resolve_options: Option<Arc<Resolve>>,
153  pub type_prefix: ContextTypePrefix,
154}
155
156#[derive(Debug)]
157pub enum FakeMapValue {
158  Bit(FakeNamespaceObjectMode),
159  Map(HashMap<String, FakeNamespaceObjectMode>),
160}
161
162pub type ResolveContextModuleDependencies =
163  Arc<dyn Fn(ContextModuleOptions) -> Result<Vec<ContextElementDependency>> + Send + Sync>;
164
165#[impl_source_map_config]
166#[cacheable]
167#[derive(Debug)]
168pub struct ContextModule {
169  dependencies: Vec<DependencyId>,
170  blocks: Vec<AsyncDependenciesBlockIdentifier>,
171  identifier: Identifier,
172  options: ContextModuleOptions,
173  factory_meta: Option<FactoryMeta>,
174  build_info: BuildInfo,
175  build_meta: BuildMeta,
176  #[debug(skip)]
177  #[cacheable(with=Unsupported)]
178  resolve_dependencies: ResolveContextModuleDependencies,
179}
180
181impl ContextModule {
182  pub fn new(
183    resolve_dependencies: ResolveContextModuleDependencies,
184    options: ContextModuleOptions,
185  ) -> Self {
186    Self {
187      dependencies: Vec::new(),
188      blocks: Vec::new(),
189      identifier: create_identifier(&options),
190      options,
191      factory_meta: None,
192      build_info: Default::default(),
193      build_meta: BuildMeta {
194        exports_type: BuildMetaExportsType::Default,
195        default_object: BuildMetaDefaultObject::RedirectWarn { ignore: false },
196        ..Default::default()
197      },
198      source_map_kind: SourceMapKind::empty(),
199      resolve_dependencies,
200    }
201  }
202
203  pub fn get_module_id<'a>(&self, module_ids: &'a ModuleIdsArtifact) -> &'a ModuleId {
204    ChunkGraph::get_module_id(module_ids, self.identifier).expect("module id not found")
205  }
206
207  pub fn get_context_options(&self) -> &ContextOptions {
208    &self.options.context_options
209  }
210
211  fn get_fake_map<'a>(
212    &self,
213    dependencies: impl IntoIterator<Item = &'a DependencyId>,
214    compilation: &Compilation,
215  ) -> FakeMapValue {
216    let dependencies = dependencies.into_iter();
217    if self.options.context_options.namespace_object.is_false() {
218      return FakeMapValue::Bit(FakeNamespaceObjectMode::NAMESPACE);
219    }
220    let mut has_type = 0;
221    let mut fake_map = HashMap::default();
222    let module_graph = compilation.get_module_graph();
223    let sorted_modules = dependencies
224      .filter_map(|dep_id| {
225        module_graph
226          .module_identifier_by_dependency_id(dep_id)
227          .map(|m| (m, dep_id))
228      })
229      .filter_map(|(m, dep)| {
230        ChunkGraph::get_module_id(&compilation.module_ids_artifact, *m)
231          .map(|id| (id.to_string(), dep))
232      })
233      .sorted_unstable_by_key(|(module_id, _)| module_id.to_string());
234    for (module_id, dep) in sorted_modules {
235      let exports_type = get_exports_type_with_strict(
236        &compilation.get_module_graph(),
237        &compilation.module_graph_cache_artifact,
238        dep,
239        matches!(
240          self.options.context_options.namespace_object,
241          ContextNameSpaceObject::Strict
242        ),
243      );
244      match exports_type {
245        ExportsType::Namespace => {
246          fake_map.insert(module_id, FakeNamespaceObjectMode::NAMESPACE);
247          has_type |= 1;
248        }
249        ExportsType::Dynamic => {
250          fake_map.insert(module_id, FakeNamespaceObjectMode::DYNAMIC);
251          has_type |= 2;
252        }
253        ExportsType::DefaultOnly => {
254          fake_map.insert(module_id, FakeNamespaceObjectMode::MODULE_ID);
255          has_type |= 4;
256        }
257        ExportsType::DefaultWithNamed => {
258          fake_map.insert(module_id, FakeNamespaceObjectMode::DEFAULT_WITH_NAMED);
259          has_type |= 8;
260        }
261      }
262    }
263
264    match has_type {
265      0 | 1 => FakeMapValue::Bit(FakeNamespaceObjectMode::NAMESPACE),
266      2 => FakeMapValue::Bit(FakeNamespaceObjectMode::DYNAMIC),
267      4 => FakeMapValue::Bit(FakeNamespaceObjectMode::MODULE_ID),
268      8 => FakeMapValue::Bit(FakeNamespaceObjectMode::DEFAULT_WITH_NAMED),
269      _ => FakeMapValue::Map(fake_map),
270    }
271  }
272
273  fn get_fake_map_init_statement(&self, fake_map: &FakeMapValue) -> String {
274    match fake_map {
275      FakeMapValue::Bit(_) => String::new(),
276      FakeMapValue::Map(map) => format!("var fakeMap = {}", json_stringify(map)),
277    }
278  }
279
280  fn get_return_module_object_source(
281    &self,
282    fake_map: &FakeMapValue,
283    async_module: bool,
284    fake_map_data_expr: &str,
285  ) -> String {
286    if let FakeMapValue::Bit(bit) = fake_map {
287      return self.get_return(bit, async_module);
288    }
289    format!(
290      "return {}(id, {}{});",
291      RuntimeGlobals::CREATE_FAKE_NAMESPACE_OBJECT,
292      fake_map_data_expr,
293      if async_module { " | 16" } else { "" },
294    )
295  }
296
297  fn get_return(&self, fake_map_bit: &FakeNamespaceObjectMode, async_module: bool) -> String {
298    if *fake_map_bit == FakeNamespaceObjectMode::NAMESPACE {
299      return format!("return {}(id);", RuntimeGlobals::REQUIRE);
300    }
301    format!(
302      "return {}(id, {}{});",
303      RuntimeGlobals::CREATE_FAKE_NAMESPACE_OBJECT,
304      fake_map_bit,
305      if async_module { " | 16" } else { "" },
306    )
307  }
308
309  fn get_user_request_map<'a>(
310    &self,
311    dependencies: impl IntoIterator<Item = &'a DependencyId>,
312    compilation: &Compilation,
313  ) -> FxIndexMap<String, Option<String>> {
314    let module_graph = compilation.get_module_graph();
315    let dependencies = dependencies.into_iter();
316    dependencies
317      .filter_map(|dep_id| {
318        let dep = module_graph.dependency_by_id(dep_id).and_then(|dep| {
319          if let Some(d) = dep.as_module_dependency() {
320            Some(d.user_request().to_string())
321          } else {
322            dep.as_context_dependency().map(|d| d.request().to_string())
323          }
324        });
325        let module_id = module_graph
326          .module_identifier_by_dependency_id(dep_id)
327          .and_then(|module| ChunkGraph::get_module_id(&compilation.module_ids_artifact, *module))
328          .map(|s| s.to_string());
329        // module_id could be None in weak mode
330        dep.map(|dep| (dep, module_id))
331      })
332      .sorted_by(|(a, _), (b, _)| a.cmp(b))
333      .collect()
334  }
335
336  fn get_source_for_empty_async_context(&self, compilation: &Compilation) -> String {
337    formatdoc! {r#"
338      function webpackEmptyAsyncContext(req) {{
339        // Here Promise.resolve().then() is used instead of new Promise() to prevent
340        // uncaught exception popping up in devtools
341        return Promise.resolve().then(function() {{
342          var e = new Error("Cannot find module '" + req + "'");
343          e.code = 'MODULE_NOT_FOUND';
344          throw e;
345        }});
346      }}
347      webpackEmptyAsyncContext.keys = {keys};
348      webpackEmptyAsyncContext.resolve = webpackEmptyAsyncContext;
349      webpackEmptyAsyncContext.id = {id};
350      module.exports = webpackEmptyAsyncContext;
351      "#,
352      keys = returning_function(&compilation.options.output.environment, "[]", ""),
353      id = json_stringify(self.get_module_id(&compilation.module_ids_artifact))
354    }
355  }
356
357  fn get_source_for_empty_context(&self, compilation: &Compilation) -> String {
358    formatdoc! {r#"
359      function webpackEmptyContext(req) {{
360        var e = new Error("Cannot find module '" + req + "'");
361        e.code = 'MODULE_NOT_FOUND';
362        throw e;
363      }}
364      webpackEmptyContext.keys = {keys};
365      webpackEmptyContext.resolve = webpackEmptyContext;
366      webpackEmptyContext.id = {id};
367      module.exports = webpackEmptyContext;
368      "#,
369      keys = returning_function(&compilation.options.output.environment, "[]", ""),
370      id = json_stringify(self.get_module_id(&compilation.module_ids_artifact))
371    }
372  }
373
374  #[inline]
375  fn get_source_string(
376    &self,
377    compilation: &Compilation,
378    code_gen_result: &mut CodeGenerationResult,
379  ) -> String {
380    match self.options.context_options.mode {
381      ContextMode::Lazy => {
382        if !self.get_blocks().is_empty() {
383          self.get_lazy_source(compilation)
384        } else {
385          self.get_source_for_empty_async_context(compilation)
386        }
387      }
388      ContextMode::Eager => {
389        if !self.get_dependencies().is_empty() {
390          self.get_eager_source(compilation)
391        } else {
392          self.get_source_for_empty_async_context(compilation)
393        }
394      }
395      ContextMode::LazyOnce => {
396        if let Some(block) = self.get_blocks().first() {
397          self.get_lazy_once_source(compilation, block, code_gen_result)
398        } else {
399          self.get_source_for_empty_async_context(compilation)
400        }
401      }
402      ContextMode::AsyncWeak => {
403        if !self.get_dependencies().is_empty() {
404          self.get_async_weak_source(compilation)
405        } else {
406          self.get_source_for_empty_async_context(compilation)
407        }
408      }
409      ContextMode::Weak => {
410        if !self.get_dependencies().is_empty() {
411          self.get_sync_weak_source(compilation)
412        } else {
413          self.get_source_for_empty_context(compilation)
414        }
415      }
416      ContextMode::Sync => {
417        if !self.get_dependencies().is_empty() {
418          self.get_sync_source(compilation)
419        } else {
420          self.get_source_for_empty_context(compilation)
421        }
422      }
423    }
424  }
425
426  fn get_lazy_source(&self, compilation: &Compilation) -> String {
427    let module_graph = compilation.get_module_graph();
428    let blocks = self
429      .get_blocks()
430      .iter()
431      .filter_map(|b| module_graph.block_by_id(b));
432    let block_and_first_dependency_list = blocks
433      .clone()
434      .filter_map(|b| b.get_dependencies().first().map(|d| (b, d)));
435    let first_dependencies = block_and_first_dependency_list.clone().map(|(_, d)| d);
436    let mut has_multiple_or_no_chunks = false;
437    let mut has_no_chunk = true;
438    let fake_map = self.get_fake_map(first_dependencies, compilation);
439    let has_fake_map = matches!(fake_map, FakeMapValue::Map(_));
440    let mut items = block_and_first_dependency_list
441      .filter_map(|(b, d)| {
442        let chunks = compilation
443          .chunk_graph
444          .get_block_chunk_group(&b.identifier(), &compilation.chunk_group_by_ukey)
445          .map(|chunk_group| {
446            let chunks = &chunk_group.chunks;
447            if !chunks.is_empty() {
448              has_no_chunk = false;
449            }
450            if chunks.len() != 1 {
451              has_multiple_or_no_chunks = true;
452            }
453            chunks
454          })
455          .or_else(|| {
456            has_multiple_or_no_chunks = true;
457            None
458          });
459        let user_request = compilation
460          .get_module_graph()
461          .dependency_by_id(d)
462          .and_then(|dep| {
463            dep
464              .as_module_dependency()
465              .map(|d| d.user_request().to_string())
466              .or_else(|| dep.as_context_dependency().map(|d| d.request().to_string()))
467          })?;
468        let module_id = module_graph
469          .module_identifier_by_dependency_id(d)
470          .and_then(|m| ChunkGraph::get_module_id(&compilation.module_ids_artifact, *m))?;
471        Some((chunks, user_request, module_id.to_string()))
472      })
473      .collect::<Vec<_>>();
474    let short_mode = has_no_chunk && !has_fake_map;
475    items.sort_unstable_by(|a, b| a.1.cmp(&b.1));
476    let map = items
477      .into_iter()
478      .map(|(chunks, user_request, module_id)| {
479        let value = if short_mode {
480          serde_json::Value::String(module_id)
481        } else {
482          let second = if let FakeMapValue::Map(fake_map) = &fake_map {
483            Some(fake_map[&module_id])
484          } else {
485            None
486          };
487          let mut array_start = vec![serde_json::json!(module_id)];
488          if let Some(second) = second {
489            array_start.push(serde_json::json!(second.bits()));
490          }
491          if let Some(chunks) = chunks {
492            array_start.extend(chunks.iter().map(|c| {
493              let chunk_id = compilation
494                .chunk_by_ukey
495                .expect_get(c)
496                .id(&compilation.chunk_ids_artifact)
497                .expect("should have chunk id in code generation");
498              serde_json::json!(chunk_id)
499            }))
500          }
501          serde_json::json!(array_start)
502        };
503        (user_request, value)
504      })
505      .collect::<HashMap<_, _>>();
506    let chunks_start_position = if has_fake_map { 2 } else { 1 };
507    let request_prefix = if has_no_chunk {
508      "Promise.resolve()".to_string()
509    } else if has_multiple_or_no_chunks {
510      format!(
511        "Promise.all(ids.slice({chunks_start_position}).map({}))",
512        RuntimeGlobals::ENSURE_CHUNK
513      )
514    } else {
515      let mut chunks_start_position_buffer = itoa::Buffer::new();
516      let chunks_start_position_str = chunks_start_position_buffer.format(chunks_start_position);
517      format!(
518        "{}(ids[{}])",
519        RuntimeGlobals::ENSURE_CHUNK,
520        chunks_start_position_str
521      )
522    };
523    let return_module_object = self.get_return_module_object_source(
524      &fake_map,
525      true,
526      if short_mode { "invalid" } else { "ids[1]" },
527    );
528    let webpack_async_context = if has_no_chunk {
529      formatdoc! {r#"
530        function webpackAsyncContext(req) {{
531          return Promise.resolve().then(function() {{
532            if(!{}(map, req)) {{
533              var e = new Error("Cannot find module '" + req + "'");
534              e.code = 'MODULE_NOT_FOUND';
535              throw e;
536            }}
537
538            {}
539            {return_module_object}
540          }});
541        }}
542        "#,
543        RuntimeGlobals::HAS_OWN_PROPERTY,
544        if short_mode {
545          "var id = map[req];"
546        } else {
547          "var ids = map[req], id = ids[0];"
548        }
549      }
550    } else {
551      formatdoc! {r#"
552        function webpackAsyncContext(req) {{
553          if(!{}(map, req)) {{
554            return Promise.resolve().then(function() {{
555              var e = new Error("Cannot find module '" + req + "'");
556              e.code = 'MODULE_NOT_FOUND';
557              throw e;
558            }});
559          }}
560
561          var ids = map[req], id = ids[0];
562          return {request_prefix}.then(function() {{
563            {return_module_object}
564          }});
565        }}
566        "#,
567        RuntimeGlobals::HAS_OWN_PROPERTY,
568      }
569    };
570    formatdoc! {r#"
571      var map = {map};
572      {webpack_async_context}
573      webpackAsyncContext.keys = {keys};
574      webpackAsyncContext.id = {id};
575      module.exports = webpackAsyncContext;
576      "#,
577      map = json_stringify(&map),
578      keys = returning_function(&compilation.options.output.environment, "Object.keys(map)", ""),
579      id = json_stringify(self.get_module_id(&compilation.module_ids_artifact))
580    }
581  }
582
583  fn get_lazy_once_source(
584    &self,
585    compilation: &Compilation,
586    block_id: &AsyncDependenciesBlockIdentifier,
587    code_gen_result: &mut CodeGenerationResult,
588  ) -> String {
589    let mg = compilation.get_module_graph();
590    let block = mg.block_by_id_expect(block_id);
591    let dependencies = block.get_dependencies();
592    let promise = block_promise(
593      Some(block_id),
594      &mut code_gen_result.runtime_requirements,
595      compilation,
596      "lazy-once context",
597    );
598    let map = self.get_user_request_map(dependencies, compilation);
599    let fake_map = self.get_fake_map(dependencies, compilation);
600    let then_function = if !matches!(
601      fake_map,
602      FakeMapValue::Bit(FakeNamespaceObjectMode::NAMESPACE)
603    ) {
604      formatdoc! {r#"
605        function(id) {{
606          {}
607        }}
608        "#,
609        self.get_return_module_object_source(&fake_map, true, "fakeMap[id]"),
610      }
611    } else {
612      RuntimeGlobals::REQUIRE.name().to_string()
613    };
614    formatdoc! {r#"
615      var map = {map};
616      {fake_map_init_statement}
617
618      function webpackAsyncContext(req) {{
619        return webpackAsyncContextResolve(req).then({then_function});
620      }}
621      function webpackAsyncContextResolve(req) {{
622        return {promise}.then(function() {{
623          if(!{has_own_property}(map, req)) {{
624            var e = new Error("Cannot find module '" + req + "'");
625            e.code = 'MODULE_NOT_FOUND';
626            throw e;
627          }}
628          return map[req];
629        }})
630      }}
631      webpackAsyncContext.keys = {keys};
632      webpackAsyncContext.resolve = webpackAsyncContextResolve;
633      webpackAsyncContext.id = {id};
634      module.exports = webpackAsyncContext;
635      "#,
636      map = json_stringify(&map),
637      fake_map_init_statement = self.get_fake_map_init_statement(&fake_map),
638      has_own_property = RuntimeGlobals::HAS_OWN_PROPERTY,
639      keys = returning_function(&compilation.options.output.environment, "Object.keys(map)", ""),
640      id = json_stringify(self.get_module_id(&compilation.module_ids_artifact))
641    }
642  }
643
644  fn get_async_weak_source(&self, compilation: &Compilation) -> String {
645    let dependencies = self.get_dependencies();
646    let map = self.get_user_request_map(dependencies, compilation);
647    let fake_map = self.get_fake_map(dependencies, compilation);
648    let return_module_object = self.get_return_module_object_source(&fake_map, true, "fakeMap[id]");
649    formatdoc! {r#"
650      var map = {map};
651      {fake_map_init_statement}
652
653      function webpackAsyncContext(req) {{
654        return webpackAsyncContextResolve(req).then(function(id) {{
655          if(!{module_factories}[id]) {{
656            var e = new Error("Module '" + req + "' ('" + id + "') is not available (weak dependency)");
657            e.code = 'MODULE_NOT_FOUND';
658            throw e;
659          }}
660          {return_module_object}
661        }});
662      }}
663      function webpackAsyncContextResolve(req) {{
664        // Here Promise.resolve().then() is used instead of new Promise() to prevent
665        // uncaught exception popping up in devtools
666        return Promise.resolve().then(function() {{
667          if(!{has_own_property}(map, req)) {{
668            var e = new Error("Cannot find module '" + req + "'");
669            e.code = 'MODULE_NOT_FOUND';
670            throw e;
671          }}
672          return map[req];
673        }})
674      }}
675      webpackAsyncContext.keys = {keys};
676      webpackAsyncContext.resolve = webpackAsyncContextResolve;
677      webpackAsyncContext.id = {id};
678      module.exports = webpackAsyncContext;
679      "#,
680      map = json_stringify(&map),
681      fake_map_init_statement = self.get_fake_map_init_statement(&fake_map),
682      module_factories = RuntimeGlobals::MODULE_FACTORIES,
683      has_own_property = RuntimeGlobals::HAS_OWN_PROPERTY,
684      keys = returning_function(&compilation.options.output.environment, "Object.keys(map)", ""),
685      id = json_stringify(self.get_module_id(&compilation.module_ids_artifact))
686    }
687  }
688
689  fn get_sync_weak_source(&self, compilation: &Compilation) -> String {
690    let dependencies = self.get_dependencies();
691    let map = self.get_user_request_map(dependencies, compilation);
692    let fake_map = self.get_fake_map(dependencies, compilation);
693    let return_module_object = self.get_return_module_object_source(&fake_map, true, "fakeMap[id]");
694    formatdoc! {r#"
695      var map = {map};
696      {fake_map_init_statement}
697
698      function webpackContext(req) {{
699        var id = webpackContextResolve(req);
700        if(!{module_factories}[id]) {{
701          var e = new Error("Module '" + req + "' ('" + id + "') is not available (weak dependency)");
702          e.code = 'MODULE_NOT_FOUND';
703          throw e;
704        }}
705        {return_module_object}
706      }}
707      function webpackContextResolve(req) {{
708        if(!{has_own_property}(map, req)) {{
709          var e = new Error("Cannot find module '" + req + "'");
710          e.code = 'MODULE_NOT_FOUND';
711          throw e;
712        }}
713        return map[req];
714      }}
715      webpackContext.keys = {keys};
716      webpackContext.resolve = webpackContextResolve;
717      webpackContext.id = {id};
718      module.exports = webpackContext;
719      "#,
720      map = json_stringify(&map),
721      fake_map_init_statement = self.get_fake_map_init_statement(&fake_map),
722      module_factories = RuntimeGlobals::MODULE_FACTORIES,
723      has_own_property = RuntimeGlobals::HAS_OWN_PROPERTY,
724      keys = returning_function(&compilation.options.output.environment, "Object.keys(map)", ""),
725      id = json_stringify(self.get_module_id(&compilation.module_ids_artifact))
726    }
727  }
728
729  fn get_eager_source(&self, compilation: &Compilation) -> String {
730    let dependencies = self.get_dependencies();
731    let map = self.get_user_request_map(dependencies, compilation);
732    let fake_map = self.get_fake_map(dependencies, compilation);
733    let then_function = if !matches!(
734      fake_map,
735      FakeMapValue::Bit(FakeNamespaceObjectMode::NAMESPACE)
736    ) {
737      formatdoc! {r#"
738        function(id) {{
739          {}
740        }}
741        "#,
742        self.get_return_module_object_source(&fake_map, true, "fakeMap[id]"),
743      }
744    } else {
745      RuntimeGlobals::REQUIRE.name().to_string()
746    };
747    formatdoc! {r#"
748      var map = {map};
749      {fake_map_init_statement}
750
751      function webpackAsyncContext(req) {{
752        return webpackAsyncContextResolve(req).then({then_function});
753      }}
754      function webpackAsyncContextResolve(req) {{
755        // Here Promise.resolve().then() is used instead of new Promise() to prevent
756        // uncaught exception popping up in devtools
757        return Promise.resolve().then(function() {{
758          if(!{has_own_property}(map, req)) {{
759            var e = new Error("Cannot find module '" + req + "'");
760            e.code = 'MODULE_NOT_FOUND';
761            throw e;
762          }}
763          return map[req];
764        }})
765      }}
766      webpackAsyncContext.keys = {keys};
767      webpackAsyncContext.resolve = webpackAsyncContextResolve;
768      webpackAsyncContext.id = {id};
769      module.exports = webpackAsyncContext;
770      "#,
771      map = json_stringify(&map),
772      fake_map_init_statement = self.get_fake_map_init_statement(&fake_map),
773      has_own_property = RuntimeGlobals::HAS_OWN_PROPERTY,
774      keys = returning_function(&compilation.options.output.environment, "Object.keys(map)", ""),
775      id = json_stringify(self.get_module_id(&compilation.module_ids_artifact))
776    }
777  }
778
779  fn get_sync_source(&self, compilation: &Compilation) -> String {
780    let dependencies = self.get_dependencies();
781    let map = self.get_user_request_map(dependencies, compilation);
782    let fake_map = self.get_fake_map(dependencies, compilation);
783    let return_module_object =
784      self.get_return_module_object_source(&fake_map, false, "fakeMap[id]");
785    formatdoc! {r#"
786      var map = {map};
787      {fake_map_init_statement}
788
789      function webpackContext(req) {{
790        var id = webpackContextResolve(req);
791        {return_module_object}
792      }}
793      function webpackContextResolve(req) {{
794        if(!{has_own_property}(map, req)) {{
795          var e = new Error("Cannot find module '" + req + "'");
796          e.code = 'MODULE_NOT_FOUND';
797          throw e;
798        }}
799        return map[req];
800      }}
801      webpackContext.keys = function webpackContextKeys() {{
802        return Object.keys(map);
803      }};
804      webpackContext.resolve = webpackContextResolve;
805      module.exports = webpackContext;
806      webpackContext.id = {id};
807      "#,
808      map = json_stringify(&map),
809      fake_map_init_statement = self.get_fake_map_init_statement(&fake_map),
810      has_own_property = RuntimeGlobals::HAS_OWN_PROPERTY,
811      id = json_stringify(self.get_module_id(&compilation.module_ids_artifact))
812    }
813  }
814
815  fn get_source(&self, source_string: String, compilation: &Compilation) -> BoxSource {
816    let source_map_kind = self.get_source_map_kind();
817    if source_map_kind.enabled() {
818      OriginalSource::new(
819        source_string,
820        format!(
821          "webpack://{}",
822          contextify(&compilation.options.context, self.identifier.as_str(),)
823        ),
824      )
825      .boxed()
826    } else {
827      RawStringSource::from(source_string).boxed()
828    }
829  }
830}
831
832impl DependenciesBlock for ContextModule {
833  fn add_block_id(&mut self, block: AsyncDependenciesBlockIdentifier) {
834    self.blocks.push(block)
835  }
836
837  fn get_blocks(&self) -> &[AsyncDependenciesBlockIdentifier] {
838    &self.blocks
839  }
840
841  fn add_dependency_id(&mut self, dependency: DependencyId) {
842    self.dependencies.push(dependency)
843  }
844
845  fn remove_dependency_id(&mut self, dependency: DependencyId) {
846    self.dependencies.retain(|d| d != &dependency)
847  }
848
849  fn get_dependencies(&self) -> &[DependencyId] {
850    &self.dependencies
851  }
852}
853
854#[cacheable_dyn]
855#[async_trait::async_trait]
856impl Module for ContextModule {
857  impl_module_meta_info!();
858
859  fn module_type(&self) -> &ModuleType {
860    &ModuleType::JsAuto
861  }
862
863  fn source_types(&self, _module_graph: &ModuleGraph) -> &[SourceType] {
864    &[SourceType::JavaScript]
865  }
866
867  fn source(&self) -> Option<&rspack_sources::BoxSource> {
868    None
869  }
870
871  fn readable_identifier(&self, _context: &crate::Context) -> std::borrow::Cow<'_, str> {
872    self.identifier.as_str().into()
873  }
874
875  fn size(
876    &self,
877    _source_type: Option<&crate::SourceType>,
878    _compilation: Option<&Compilation>,
879  ) -> f64 {
880    160.0
881  }
882
883  fn lib_ident(&self, options: LibIdentOptions) -> Option<Cow<'_, str>> {
884    let mut id = String::new();
885    if let Some(layer) = &self.options.layer {
886      id += "(";
887      id += layer;
888      id += ")/";
889    }
890    id += &contextify(options.context, self.options.resource.as_str());
891    id += " ";
892    id += self.options.context_options.mode.as_str();
893    if self.options.context_options.recursive {
894      id += " recursive";
895    }
896    if !self.options.addon.is_empty() {
897      id += " ";
898      id += &self.options.addon;
899    }
900    if let Some(regexp) = &self.options.context_options.reg_exp {
901      id += " ";
902      id += &regexp.to_pretty_string(true);
903    }
904    if let Some(include) = &self.options.context_options.include {
905      id += " include: ";
906      id += &include.to_pretty_string(true);
907    }
908    if let Some(exclude) = &self.options.context_options.exclude {
909      id += " exclude: ";
910      id += &exclude.to_pretty_string(true);
911    }
912    if let Some(exports) = &self.options.context_options.referenced_exports {
913      id += " referencedExports: ";
914      id += &exports.iter().map(|ids| ids.iter().join(".")).join(", ");
915    }
916    Some(Cow::Owned(id))
917  }
918
919  async fn build(
920    &mut self,
921    _build_context: BuildContext,
922    _: Option<&Compilation>,
923  ) -> Result<BuildResult> {
924    let resolve_dependencies = &self.resolve_dependencies;
925    let context_element_dependencies = resolve_dependencies(self.options.clone())?;
926
927    let mut dependencies: Vec<BoxDependency> = vec![];
928    let mut blocks = vec![];
929    if matches!(self.options.context_options.mode, ContextMode::LazyOnce)
930      && !context_element_dependencies.is_empty()
931    {
932      let loc = DependencyLocation::Real(RealDependencyLocation::new(
933        (
934          self.options.context_options.start,
935          self.options.context_options.end,
936        )
937          .into(),
938        None,
939      ));
940      let mut block = AsyncDependenciesBlock::new(
941        (*self.identifier).into(),
942        Some(loc),
943        None,
944        context_element_dependencies
945          .into_iter()
946          .map(|dep| Box::new(dep) as Box<dyn Dependency>)
947          .collect(),
948        None,
949      );
950      if let Some(group_options) = &self.options.context_options.group_options {
951        block.set_group_options(group_options.clone());
952      }
953      blocks.push(Box::new(block));
954    } else if matches!(self.options.context_options.mode, ContextMode::Lazy) {
955      let mut index = 0;
956      for context_element_dependency in context_element_dependencies {
957        let group_options = self
958          .options
959          .context_options
960          .group_options
961          .as_ref()
962          .and_then(|g| g.normal_options());
963        let name = group_options
964          .and_then(|group_options| group_options.name.as_ref())
965          .map(|name| {
966            let name = if !(name.contains(WEBPACK_CHUNK_NAME_INDEX_PLACEHOLDER)
967              || name.contains(WEBPACK_CHUNK_NAME_REQUEST_PLACEHOLDER))
968            {
969              Cow::Owned(format!("{name}{WEBPACK_CHUNK_NAME_INDEX_PLACEHOLDER}"))
970            } else {
971              Cow::Borrowed(name)
972            };
973
974            let name = name.cow_replace(WEBPACK_CHUNK_NAME_INDEX_PLACEHOLDER, &index.to_string());
975            let name = name.cow_replace(
976              WEBPACK_CHUNK_NAME_REQUEST_PLACEHOLDER,
977              &to_path(&context_element_dependency.user_request),
978            );
979
980            index += 1;
981            name.into_owned()
982          });
983        let preload_order = group_options.and_then(|o| o.preload_order);
984        let prefetch_order = group_options.and_then(|o| o.prefetch_order);
985        let fetch_priority = group_options.and_then(|o| o.fetch_priority);
986        let mut block = AsyncDependenciesBlock::new(
987          (*self.identifier).into(),
988          None,
989          Some(&context_element_dependency.user_request.clone()),
990          vec![Box::new(context_element_dependency)],
991          Some(self.options.context_options.request.clone()),
992        );
993        block.set_group_options(GroupOptions::ChunkGroup(ChunkGroupOptions::new(
994          name,
995          preload_order,
996          prefetch_order,
997          fetch_priority,
998        )));
999        blocks.push(Box::new(block));
1000      }
1001    } else {
1002      dependencies = context_element_dependencies
1003        .into_iter()
1004        .map(|d| Box::new(d) as BoxDependency)
1005        .collect();
1006    }
1007
1008    let mut context_dependencies: ArcPathSet = Default::default();
1009    context_dependencies.insert(self.options.resource.as_std_path().into());
1010
1011    self.build_info.context_dependencies = context_dependencies;
1012
1013    Ok(BuildResult {
1014      dependencies,
1015      blocks,
1016      optimization_bailouts: vec![],
1017    })
1018  }
1019
1020  // #[tracing::instrument("ContextModule::code_generation", skip_all, fields(identifier = ?self.identifier()))]
1021  async fn code_generation(
1022    &self,
1023    compilation: &Compilation,
1024    _runtime: Option<&RuntimeSpec>,
1025    _: Option<ConcatenationScope>,
1026  ) -> Result<CodeGenerationResult> {
1027    let mut code_generation_result = CodeGenerationResult::default();
1028    let source = self.get_source(
1029      self.get_source_string(compilation, &mut code_generation_result),
1030      compilation,
1031    );
1032    code_generation_result.add(SourceType::JavaScript, source);
1033    let mut all_deps = self.get_dependencies().to_vec();
1034    let module_graph = compilation.get_module_graph();
1035    for block in self.get_blocks() {
1036      let block = module_graph
1037        .block_by_id(block)
1038        .expect("should have block in ContextModule code_generation");
1039      all_deps.extend(block.get_dependencies());
1040    }
1041    code_generation_result
1042      .runtime_requirements
1043      .insert(RuntimeGlobals::MODULE);
1044    code_generation_result
1045      .runtime_requirements
1046      .insert(RuntimeGlobals::HAS_OWN_PROPERTY);
1047    if !all_deps.is_empty() {
1048      code_generation_result
1049        .runtime_requirements
1050        .insert(RuntimeGlobals::REQUIRE);
1051      match self.options.context_options.mode {
1052        ContextMode::Weak => {
1053          code_generation_result
1054            .runtime_requirements
1055            .insert(RuntimeGlobals::MODULE_FACTORIES);
1056        }
1057        ContextMode::AsyncWeak => {
1058          code_generation_result
1059            .runtime_requirements
1060            .insert(RuntimeGlobals::MODULE_FACTORIES);
1061          code_generation_result
1062            .runtime_requirements
1063            .insert(RuntimeGlobals::ENSURE_CHUNK);
1064        }
1065        ContextMode::Lazy | ContextMode::LazyOnce => {
1066          code_generation_result
1067            .runtime_requirements
1068            .insert(RuntimeGlobals::ENSURE_CHUNK);
1069        }
1070        _ => {}
1071      }
1072      let fake_map = self.get_fake_map(all_deps.iter(), compilation);
1073      if !matches!(fake_map, FakeMapValue::Bit(bit) if bit == FakeNamespaceObjectMode::NAMESPACE) {
1074        code_generation_result
1075          .runtime_requirements
1076          .insert(RuntimeGlobals::CREATE_FAKE_NAMESPACE_OBJECT);
1077      }
1078    }
1079    Ok(code_generation_result)
1080  }
1081
1082  async fn get_runtime_hash(
1083    &self,
1084    compilation: &Compilation,
1085    runtime: Option<&RuntimeSpec>,
1086  ) -> Result<RspackHashDigest> {
1087    let mut hasher = RspackHash::from(&compilation.options.output);
1088    module_update_hash(self, &mut hasher, compilation, runtime);
1089    Ok(hasher.digest(&compilation.options.output.hash_digest))
1090  }
1091}
1092
1093impl_empty_diagnosable_trait!(ContextModule);
1094
1095impl Identifiable for ContextModule {
1096  fn identifier(&self) -> Identifier {
1097    self.identifier
1098  }
1099}
1100
1101fn create_identifier(options: &ContextModuleOptions) -> Identifier {
1102  let mut id = options.resource.as_str().to_owned();
1103  if !options.resource_query.is_empty() {
1104    id += "|";
1105    id += &options.resource_query;
1106  }
1107  if !options.resource_fragment.is_empty() {
1108    id += "|";
1109    id += &options.resource_fragment;
1110  }
1111  id += "|";
1112  id += options.context_options.mode.as_str();
1113  if !options.context_options.recursive {
1114    id += "|nonrecursive";
1115  }
1116  if !options.addon.is_empty() {
1117    id += "|";
1118    id += &options.addon;
1119  }
1120  if let Some(regexp) = &options.context_options.reg_exp {
1121    id += "|";
1122    id += &regexp.to_pretty_string(false);
1123  }
1124  if let Some(include) = &options.context_options.include {
1125    id += "|include: ";
1126    id += &include.to_source_string();
1127  }
1128  if let Some(exclude) = &options.context_options.exclude {
1129    id += "|exclude: ";
1130    id += &exclude.to_source_string();
1131  }
1132  if let Some(exports) = &options.context_options.referenced_exports {
1133    id += "|referencedExports: ";
1134    id += &exports.iter().map(|ids| ids.iter().join(".")).join(", ");
1135  }
1136
1137  if let Some(GroupOptions::ChunkGroup(group)) = &options.context_options.group_options {
1138    if let Some(chunk_name) = &group.name {
1139      id += "|chunkName: ";
1140      id += chunk_name;
1141    }
1142    id += "|groupOptions: {";
1143    if let Some(o) = group.prefetch_order {
1144      id.push_str(&format!("prefetchOrder: {o},"));
1145    }
1146    if let Some(o) = group.preload_order {
1147      id.push_str(&format!("preloadOrder: {o},"));
1148    }
1149    if let Some(o) = group.fetch_priority {
1150      id.push_str(&format!("fetchPriority: {o},"));
1151    }
1152    id += "}";
1153  }
1154  id += match options.context_options.namespace_object {
1155    ContextNameSpaceObject::Strict => "|strict namespace object",
1156    ContextNameSpaceObject::Bool(true) => "|namespace object",
1157    _ => "",
1158  };
1159  if let Some(layer) = &options.layer {
1160    id += "|layer: ";
1161    id += layer;
1162  }
1163  id.into()
1164}