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), ESMExports,
35 CommonJsExports(String),
36 ModuleExternal(String),
37 ExternalModule(String),
38 AwaitDependencies,
39 ESMCompatibility,
40 ModuleDecorator(String ),
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 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
207pub 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 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 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 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}