rspack_core/
init_fragment.rs

1use std::{
2  collections::{BTreeMap, BTreeSet},
3  fmt::Debug,
4  hash::{BuildHasherDefault, Hash},
5  sync::atomic::AtomicU32,
6};
7
8use dyn_clone::{DynClone, clone_trait_object};
9use hashlink::LinkedHashSet;
10use indexmap::IndexMap;
11use rspack_error::Result;
12use rspack_sources::{BoxSource, ConcatSource, RawStringSource, SourceExt};
13use rspack_util::ext::{DynHash, IntoAny};
14use rustc_hash::FxHasher;
15use swc_core::ecma::atoms::Atom;
16
17use crate::{
18  ExportsArgument, GenerateContext, RuntimeCondition, RuntimeGlobals, merge_runtime, property_name,
19  runtime_condition_expression,
20};
21
22static NEXT_INIT_FRAGMENT_KEY_UNIQUE_ID: AtomicU32 = AtomicU32::new(0);
23
24pub struct InitFragmentContents {
25  pub start: String,
26  pub end: Option<String>,
27}
28
29#[derive(Debug, Clone, Hash, PartialEq, Eq)]
30pub enum InitFragmentKey {
31  Unique(u32),
32  ESMImport(String),
33  ESMExportStar(String), // TODO: align with webpack and remove this
34  ESMExports,
35  CommonJsExports(String),
36  ModuleExternal(String),
37  ExternalModule(String),
38  AwaitDependencies,
39  ESMCompatibility,
40  ModuleDecorator(String /* module_id */),
41  ESMFakeNamespaceObjectFragment(String),
42  ESMDeferImportNamespaceObjectFragment(String),
43  Const(String),
44}
45
46impl InitFragmentKey {
47  pub fn unique() -> Self {
48    Self::Unique(
49      NEXT_INIT_FRAGMENT_KEY_UNIQUE_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
50    )
51  }
52}
53
54impl InitFragmentKey {
55  pub fn merge_fragments<C: InitFragmentRenderContext>(
56    &self,
57    fragments: Vec<Box<dyn InitFragment<C>>>,
58  ) -> Box<dyn InitFragment<C>> {
59    match self {
60      InitFragmentKey::ESMImport(_) => {
61        let mut iter = fragments.into_iter();
62        let first = iter
63          .next()
64          .expect("keyed_fragments should at least have one value");
65        let first = first
66          .into_any()
67          .downcast::<ConditionalInitFragment>()
68          .expect("fragment of InitFragmentKey::ESMImport should be a ConditionalInitFragment");
69
70        if matches!(first.runtime_condition, RuntimeCondition::Boolean(true)) {
71          return first;
72        }
73
74        let mut res = first;
75        for fragment in iter {
76          let fragment = fragment
77            .into_any()
78            .downcast::<ConditionalInitFragment>()
79            .expect("fragment of InitFragmentKey::ESMImport should be a ConditionalInitFragment");
80          res = ConditionalInitFragment::merge(res, fragment);
81          if matches!(res.runtime_condition, RuntimeCondition::Boolean(true)) {
82            return res;
83          }
84        }
85        res
86      }
87      InitFragmentKey::ESMExports => {
88        let mut export_map: Vec<(Atom, Atom)> = vec![];
89        let mut iter = fragments.into_iter();
90        let first = iter
91          .next()
92          .expect("keyed_fragments should at least have one value");
93        let first = first
94          .into_any()
95          .downcast::<ESMExportInitFragment>()
96          .expect("fragment of InitFragmentKey::ESMExports should be a ESMExportInitFragment");
97        let export_argument = first.exports_argument;
98        export_map.extend(first.export_map);
99        for fragment in iter {
100          let fragment = fragment
101            .into_any()
102            .downcast::<ESMExportInitFragment>()
103            .expect("fragment of InitFragmentKey::ESMExports should be a ESMExportInitFragment");
104          export_map.extend(fragment.export_map);
105        }
106        ESMExportInitFragment::new(export_argument, export_map).boxed()
107      }
108      InitFragmentKey::AwaitDependencies => {
109        let promises = fragments.into_iter().map(|f| f.into_any().downcast::<AwaitDependenciesInitFragment>().expect("fragment of InitFragmentKey::AwaitDependencies should be a AwaitDependenciesInitFragment")).flat_map(|f| f.promises).collect();
110        AwaitDependenciesInitFragment::new(promises).boxed()
111      }
112      InitFragmentKey::ExternalModule(_) => {
113        let mut iter = fragments.into_iter();
114        let first = iter
115          .next()
116          .expect("keyed_fragments should at least have one value");
117
118        let first = first
119          .into_any()
120          .downcast::<ExternalModuleInitFragment>()
121          .expect(
122            "fragment of InitFragmentKey::ExternalModule should be a ExternalModuleInitFragment",
123          );
124
125        let mut res = first;
126        for fragment in iter {
127          let fragment = fragment
128            .into_any()
129            .downcast::<ExternalModuleInitFragment>()
130            .expect(
131              "fragment of InitFragmentKey::ExternalModule should be a ExternalModuleInitFragment",
132            );
133          res = ExternalModuleInitFragment::merge(*res, *fragment);
134        }
135        res
136      }
137      InitFragmentKey::ESMFakeNamespaceObjectFragment(_)
138      | InitFragmentKey::ESMDeferImportNamespaceObjectFragment(_)
139      | InitFragmentKey::ESMExportStar(_)
140      | InitFragmentKey::ModuleExternal(_)
141      | InitFragmentKey::ModuleDecorator(_)
142      | InitFragmentKey::CommonJsExports(_)
143      | InitFragmentKey::ESMCompatibility
144      | InitFragmentKey::Const(_) => first(fragments),
145      InitFragmentKey::Unique(_) => {
146        debug_assert!(fragments.len() == 1, "fragment = {self:?}");
147        first(fragments)
148      }
149    }
150  }
151}
152
153fn first<C>(fragments: Vec<Box<dyn InitFragment<C>>>) -> Box<dyn InitFragment<C>> {
154  fragments
155    .into_iter()
156    .next()
157    .expect("should at least have one fragment")
158}
159
160pub trait InitFragmentRenderContext {
161  fn add_runtime_requirements(&mut self, requirement: RuntimeGlobals);
162  fn runtime_condition_expression(&mut self, runtime_condition: &RuntimeCondition) -> String;
163  fn returning_function(&self, return_value: &str, args: &str) -> String;
164}
165
166pub trait InitFragment<C>: IntoAny + DynHash + DynClone + Debug + Sync + Send {
167  /// getContent + getEndContent
168  fn contents(self: Box<Self>, context: &mut C) -> Result<InitFragmentContents>;
169
170  fn stage(&self) -> InitFragmentStage;
171
172  fn position(&self) -> i32;
173
174  fn key(&self) -> &InitFragmentKey;
175}
176
177clone_trait_object!(InitFragment<GenerateContext<'_>>);
178clone_trait_object!(InitFragment<ChunkRenderContext>);
179
180impl<C> Hash for dyn InitFragment<C> + '_ {
181  fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
182    self.dyn_hash(state)
183  }
184}
185
186pub trait InitFragmentExt<C> {
187  fn boxed(self) -> Box<dyn InitFragment<C>>;
188}
189
190impl<C, T: InitFragment<C> + 'static> InitFragmentExt<C> for T {
191  fn boxed(self) -> Box<dyn InitFragment<C>> {
192    Box::new(self)
193  }
194}
195
196#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
197pub enum InitFragmentStage {
198  StageConstants,
199  StageAsyncBoundary,
200  StageESMExports,
201  StageESMImports,
202  StageProvides,
203  StageAsyncDependencies,
204  StageAsyncESMImports,
205}
206
207/// InitFragment.addToSource
208pub fn render_init_fragments<C: InitFragmentRenderContext>(
209  source: BoxSource,
210  mut fragments: Vec<Box<dyn InitFragment<C>>>,
211  context: &mut C,
212) -> Result<BoxSource> {
213  // here use sort_by_key because need keep order equal stage fragments
214  fragments.sort_by(|a, b| {
215    let stage = a.stage().cmp(&b.stage());
216    if !stage.is_eq() {
217      return stage;
218    }
219    a.position().cmp(&b.position())
220  });
221
222  let mut keyed_fragments: IndexMap<
223    InitFragmentKey,
224    Vec<Box<dyn InitFragment<C>>>,
225    BuildHasherDefault<FxHasher>,
226  > = IndexMap::default();
227  for fragment in fragments {
228    let key = fragment.key();
229    if let Some(value) = keyed_fragments.get_mut(key) {
230      value.push(fragment);
231    } else {
232      keyed_fragments.insert(key.clone(), vec![fragment]);
233    }
234  }
235
236  let mut end_contents = vec![];
237  let mut concat_source = ConcatSource::default();
238
239  for (key, fragments) in keyed_fragments {
240    let f = key.merge_fragments(fragments);
241    let contents = f.contents(context)?;
242    concat_source.add(RawStringSource::from(contents.start));
243    if let Some(end_content) = contents.end {
244      end_contents.push(RawStringSource::from(end_content))
245    }
246  }
247
248  concat_source.add(source);
249
250  for content in end_contents.into_iter().rev() {
251    concat_source.add(content);
252  }
253
254  Ok(concat_source.boxed())
255}
256
257pub type BoxInitFragment<C> = Box<dyn InitFragment<C>>;
258pub type BoxModuleInitFragment<'a> = BoxInitFragment<GenerateContext<'a>>;
259pub type BoxChunkInitFragment = BoxInitFragment<ChunkRenderContext>;
260pub type ModuleInitFragments<'a> = Vec<BoxModuleInitFragment<'a>>;
261pub type ChunkInitFragments = Vec<BoxChunkInitFragment>;
262
263impl InitFragmentRenderContext for GenerateContext<'_> {
264  fn add_runtime_requirements(&mut self, requirement: RuntimeGlobals) {
265    self.runtime_requirements.insert(requirement);
266  }
267
268  fn runtime_condition_expression(&mut self, runtime_condition: &RuntimeCondition) -> String {
269    runtime_condition_expression(
270      &self.compilation.chunk_graph,
271      Some(runtime_condition),
272      self.runtime,
273      self.runtime_requirements,
274    )
275  }
276
277  fn returning_function(&self, return_value: &str, args: &str) -> String {
278    self
279      .compilation
280      .runtime_template
281      .returning_function(return_value, args)
282  }
283}
284
285pub struct ChunkRenderContext;
286
287impl InitFragmentRenderContext for ChunkRenderContext {
288  fn add_runtime_requirements(&mut self, _requirement: RuntimeGlobals) {
289    unreachable!("should not add runtime requirements in chunk render context")
290  }
291
292  fn runtime_condition_expression(&mut self, _runtime_condition: &RuntimeCondition) -> String {
293    unreachable!("should not call runtime condition expression in chunk render context")
294  }
295
296  fn returning_function(&self, _return_value: &str, _args: &str) -> String {
297    unreachable!("should not call returning function in chunk render context")
298  }
299}
300
301#[derive(Debug, Clone, Hash)]
302pub struct NormalInitFragment {
303  content: String,
304  stage: InitFragmentStage,
305  position: i32,
306  key: InitFragmentKey,
307  end_content: Option<String>,
308}
309
310impl NormalInitFragment {
311  pub fn new(
312    content: String,
313    stage: InitFragmentStage,
314    position: i32,
315    key: InitFragmentKey,
316    end_content: Option<String>,
317  ) -> Self {
318    NormalInitFragment {
319      content,
320      stage,
321      position,
322      key,
323      end_content,
324    }
325  }
326}
327
328impl<C> InitFragment<C> for NormalInitFragment {
329  fn contents(self: Box<Self>, _context: &mut C) -> Result<InitFragmentContents> {
330    Ok(InitFragmentContents {
331      start: self.content,
332      end: self.end_content,
333    })
334  }
335
336  fn stage(&self) -> InitFragmentStage {
337    self.stage
338  }
339
340  fn position(&self) -> i32 {
341    self.position
342  }
343
344  fn key(&self) -> &InitFragmentKey {
345    &self.key
346  }
347}
348
349#[derive(Debug, Clone, Hash)]
350pub struct ESMExportInitFragment {
351  exports_argument: ExportsArgument,
352  // TODO: should be a map
353  export_map: Vec<(Atom, Atom)>,
354}
355
356impl ESMExportInitFragment {
357  pub fn new(exports_argument: ExportsArgument, export_map: Vec<(Atom, Atom)>) -> Self {
358    Self {
359      exports_argument,
360      export_map,
361    }
362  }
363}
364
365impl<C: InitFragmentRenderContext> InitFragment<C> for ESMExportInitFragment {
366  fn contents(mut self: Box<Self>, context: &mut C) -> Result<InitFragmentContents> {
367    context.add_runtime_requirements(RuntimeGlobals::EXPORTS);
368    context.add_runtime_requirements(RuntimeGlobals::DEFINE_PROPERTY_GETTERS);
369    self.export_map.sort_by(|a, b| a.0.cmp(&b.0));
370    let exports = format!(
371      "{{\n  {}\n}}",
372      self
373        .export_map
374        .iter()
375        .map(|s| {
376          let prop = property_name(&s.0)?;
377          Ok(format!(
378            "{}: {}",
379            prop,
380            context.returning_function(&s.1, "")
381          ))
382        })
383        .collect::<Result<Vec<_>>>()?
384        .join(",\n  ")
385    );
386
387    Ok(InitFragmentContents {
388      start: format!(
389        "{}({}, {});\n",
390        RuntimeGlobals::DEFINE_PROPERTY_GETTERS,
391        self.exports_argument,
392        exports
393      ),
394      end: None,
395    })
396  }
397
398  fn stage(&self) -> InitFragmentStage {
399    InitFragmentStage::StageESMExports
400  }
401
402  fn position(&self) -> i32 {
403    1
404  }
405
406  fn key(&self) -> &InitFragmentKey {
407    &InitFragmentKey::ESMExports
408  }
409}
410
411#[derive(Debug, Clone, Hash)]
412pub struct AwaitDependenciesInitFragment {
413  promises: LinkedHashSet<String, BuildHasherDefault<FxHasher>>,
414}
415
416impl AwaitDependenciesInitFragment {
417  pub fn new(promises: LinkedHashSet<String, BuildHasherDefault<FxHasher>>) -> Self {
418    Self { promises }
419  }
420
421  pub fn new_single(promise: String) -> Self {
422    let mut promises = LinkedHashSet::default();
423    promises.insert(promise);
424    Self { promises }
425  }
426}
427
428impl<C: InitFragmentRenderContext> InitFragment<C> for AwaitDependenciesInitFragment {
429  fn contents(self: Box<Self>, context: &mut C) -> Result<InitFragmentContents> {
430    context.add_runtime_requirements(RuntimeGlobals::MODULE);
431    if self.promises.is_empty() {
432      Ok(InitFragmentContents {
433        start: String::new(),
434        end: None,
435      })
436    } else if self.promises.len() == 1 {
437      let sep = self.promises.front().expect("at least have one");
438      Ok(InitFragmentContents {
439        start: format!(
440          "var __webpack_async_dependencies__ = __webpack_handle_async_dependencies__([{sep}]);\n{sep} = (__webpack_async_dependencies__.then ? (await __webpack_async_dependencies__)() : __webpack_async_dependencies__)[0];"
441        ),
442        end: None,
443      })
444    } else {
445      let sep = Vec::from_iter(self.promises).join(", ");
446      Ok(InitFragmentContents {
447        start: format!(
448          "var __webpack_async_dependencies__ = __webpack_handle_async_dependencies__([{sep}]);\n([{sep}] = __webpack_async_dependencies__.then ? (await __webpack_async_dependencies__)() : __webpack_async_dependencies__);"
449        ),
450        end: None,
451      })
452    }
453  }
454
455  fn stage(&self) -> InitFragmentStage {
456    InitFragmentStage::StageAsyncDependencies
457  }
458
459  fn position(&self) -> i32 {
460    0
461  }
462
463  fn key(&self) -> &InitFragmentKey {
464    &InitFragmentKey::AwaitDependencies
465  }
466}
467
468#[derive(Debug, Clone, Hash)]
469pub struct ConditionalInitFragment {
470  content: String,
471  stage: InitFragmentStage,
472  position: i32,
473  key: InitFragmentKey,
474  end_content: Option<String>,
475  runtime_condition: RuntimeCondition,
476}
477
478impl ConditionalInitFragment {
479  pub fn new(
480    content: String,
481    stage: InitFragmentStage,
482    position: i32,
483    key: InitFragmentKey,
484    end_content: Option<String>,
485    runtime_condition: RuntimeCondition,
486  ) -> Self {
487    ConditionalInitFragment {
488      content,
489      stage,
490      position,
491      key,
492      end_content,
493      runtime_condition,
494    }
495  }
496
497  pub fn merge(
498    one: Box<ConditionalInitFragment>,
499    other: Box<ConditionalInitFragment>,
500  ) -> Box<ConditionalInitFragment> {
501    if matches!(one.runtime_condition, RuntimeCondition::Boolean(true)) {
502      return one;
503    }
504    if matches!(other.runtime_condition, RuntimeCondition::Boolean(true)) {
505      return other;
506    }
507    if matches!(one.runtime_condition, RuntimeCondition::Boolean(false)) {
508      return other;
509    }
510    if matches!(other.runtime_condition, RuntimeCondition::Boolean(false)) {
511      return one;
512    }
513    Box::new(Self {
514      content: one.content,
515      stage: one.stage,
516      position: one.position,
517      key: one.key,
518      end_content: one.end_content,
519      runtime_condition: RuntimeCondition::Spec(merge_runtime(
520        one.runtime_condition.as_spec().expect("should be spec"),
521        other.runtime_condition.as_spec().expect("should be spec"),
522      )),
523    })
524  }
525}
526
527impl<C: InitFragmentRenderContext> InitFragment<C> for ConditionalInitFragment {
528  fn contents(self: Box<Self>, context: &mut C) -> Result<InitFragmentContents> {
529    Ok(
530      if matches!(self.runtime_condition, RuntimeCondition::Boolean(false))
531        || self.content.is_empty()
532      {
533        InitFragmentContents {
534          start: String::new(),
535          end: Some(String::new()),
536        }
537      } else if matches!(self.runtime_condition, RuntimeCondition::Boolean(true)) {
538        InitFragmentContents {
539          start: self.content,
540          end: self.end_content,
541        }
542      } else {
543        let condition = context.runtime_condition_expression(&self.runtime_condition);
544        if condition == "true" {
545          InitFragmentContents {
546            start: self.content,
547            end: self.end_content,
548          }
549        } else {
550          InitFragmentContents {
551            start: wrap_in_condition(&condition, &self.content),
552            end: self.end_content.map(|c| wrap_in_condition(&condition, &c)),
553          }
554        }
555      },
556    )
557  }
558
559  fn stage(&self) -> InitFragmentStage {
560    self.stage
561  }
562
563  fn position(&self) -> i32 {
564    self.position
565  }
566
567  fn key(&self) -> &InitFragmentKey {
568    &self.key
569  }
570}
571
572fn wrap_in_condition(condition: &str, source: &str) -> String {
573  format!(
574    r#"if ({condition}) {{
575  {source}
576}}"#
577  )
578}
579
580#[derive(Debug, Clone, Hash)]
581pub struct ExternalModuleInitFragment {
582  imported_module: String,
583  // webpack also supports `ImportSpecifiers` but not ever used.
584  import_specifiers: BTreeMap<String, BTreeSet<String>>,
585  default_import: Option<String>,
586  stage: InitFragmentStage,
587  position: i32,
588  key: InitFragmentKey,
589}
590
591impl ExternalModuleInitFragment {
592  pub fn new(
593    imported_module: String,
594    import_specifiers: Vec<(String, String)>,
595    default_import: Option<String>,
596    stage: InitFragmentStage,
597    position: i32,
598  ) -> Self {
599    let mut self_import_specifiers: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
600
601    for (name, value) in import_specifiers {
602      if let Some(set) = self_import_specifiers.get_mut(&name) {
603        set.insert(value);
604      } else {
605        let mut set = BTreeSet::new();
606        set.insert(value);
607        self_import_specifiers.insert(name, set);
608      }
609    }
610
611    let key = InitFragmentKey::ExternalModule(format!(
612      "external module imports|{}|{}",
613      imported_module,
614      default_import.clone().unwrap_or_else(|| "null".to_string()),
615    ));
616
617    Self {
618      imported_module,
619      import_specifiers: self_import_specifiers,
620      default_import,
621      stage,
622      position,
623      key,
624    }
625  }
626
627  pub fn merge(
628    one: ExternalModuleInitFragment,
629    other: ExternalModuleInitFragment,
630  ) -> Box<ExternalModuleInitFragment> {
631    let mut import_specifiers = one.import_specifiers.clone();
632    for (name, value) in other.import_specifiers {
633      if let Some(set) = import_specifiers.get_mut(&name) {
634        set.extend(value);
635      } else {
636        import_specifiers.insert(name, value);
637      }
638    }
639
640    Box::new(Self {
641      imported_module: one.imported_module,
642      import_specifiers,
643      default_import: one.default_import,
644      stage: one.stage,
645      position: one.position,
646      key: one.key,
647    })
648  }
649}
650
651impl<C: InitFragmentRenderContext> InitFragment<C> for ExternalModuleInitFragment {
652  fn contents(self: Box<Self>, _context: &mut C) -> Result<InitFragmentContents> {
653    let mut named_imports = vec![];
654
655    for (name, specifiers) in self.import_specifiers {
656      for spec in specifiers {
657        if name == spec {
658          named_imports.push(spec);
659        } else {
660          named_imports.push(format!("{name} as {spec}"));
661        }
662      }
663    }
664
665    let mut imports_string: String;
666    imports_string = if named_imports.is_empty() {
667      String::new()
668    } else {
669      format!("{{{}}}", named_imports.join(", "))
670    };
671
672    if let Some(default_import) = self.default_import {
673      imports_string = format!(
674        "{}{}",
675        default_import,
676        if imports_string.is_empty() {
677          String::new()
678        } else {
679          format!(", {imports_string}")
680        }
681      );
682    }
683
684    let start = format!(
685      "import {} from {};\n",
686      imports_string,
687      serde_json::to_string(&self.imported_module).expect("invalid json tostring")
688    );
689
690    Ok(InitFragmentContents { start, end: None })
691  }
692
693  fn stage(&self) -> InitFragmentStage {
694    self.stage
695  }
696
697  fn position(&self) -> i32 {
698    self.position
699  }
700
701  fn key(&self) -> &InitFragmentKey {
702    &self.key
703  }
704}