Skip to main content

rspack_plugin_dynamic_entry/
lib.rs

1use std::sync::Arc;
2
3use atomic_refcell::AtomicRefCell;
4use derive_more::Debug;
5use futures::future::BoxFuture;
6use rspack_core::{
7  BoxDependency, Compilation, CompilationParams, CompilerCompilation, CompilerMake, Context,
8  DependencyId, DependencyType, EntryDependency, EntryOptions, Plugin,
9  incremental::IncrementalPasses, internal,
10};
11use rspack_error::Result;
12use rspack_hook::{plugin, plugin_hook};
13use rspack_util::fx_hash::FxHashMap;
14
15pub struct EntryDynamicResult {
16  pub import: Vec<String>,
17  pub options: EntryOptions,
18}
19
20type EntryDynamic =
21  Box<dyn for<'a> Fn() -> BoxFuture<'static, Result<Vec<EntryDynamicResult>>> + Sync + Send>;
22
23pub struct DynamicEntryPluginOptions {
24  pub context: Context,
25  pub entry: EntryDynamic,
26}
27
28#[plugin]
29#[derive(Debug)]
30pub struct DynamicEntryPlugin {
31  context: Context,
32  #[debug(skip)]
33  entry: EntryDynamic,
34  // Need "cache" the dependency to tell incremental that this entry dependency is not changed
35  // so it can be reused and skip the module make
36  imported_dependencies: AtomicRefCell<FxHashMap<Arc<str>, FxHashMap<EntryOptions, DependencyId>>>,
37}
38
39impl DynamicEntryPlugin {
40  pub fn new(options: DynamicEntryPluginOptions) -> Self {
41    Self::new_inner(options.context, options.entry, Default::default())
42  }
43}
44
45#[plugin_hook(CompilerCompilation for DynamicEntryPlugin)]
46async fn compilation(
47  &self,
48  compilation: &mut Compilation,
49  params: &mut CompilationParams,
50) -> Result<()> {
51  compilation.set_dependency_factory(DependencyType::Entry, params.normal_module_factory.clone());
52  Ok(())
53}
54
55#[plugin_hook(CompilerMake for DynamicEntryPlugin)]
56async fn make(&self, compilation: &mut Compilation) -> Result<()> {
57  let entry_fn = &self.entry;
58  let decs = entry_fn().await?;
59
60  if compilation
61    .incremental
62    .mutations_readable(IncrementalPasses::BUILD_MODULE_GRAPH)
63  {
64    let mut imported_dependencies = self.imported_dependencies.borrow_mut();
65    let mut next_imported_dependencies: FxHashMap<Arc<str>, FxHashMap<EntryOptions, DependencyId>> =
66      Default::default();
67
68    for EntryDynamicResult { import, options } in decs {
69      for entry in import {
70        let module_graph = compilation.get_module_graph();
71
72        let entry_dependency: BoxDependency = if let Some(map) =
73          imported_dependencies.get(entry.as_str())
74          && let Some(dependency_id) = map.get(&options)
75          && let Some(dependency) = internal::try_dependency_by_id(module_graph, dependency_id)
76        {
77          next_imported_dependencies
78            .entry(entry.into())
79            .or_default()
80            .insert(options.clone(), *dependency_id);
81          dependency.clone()
82        } else {
83          let dependency: BoxDependency = Box::new(EntryDependency::new(
84            entry.clone(),
85            self.context.clone(),
86            options.layer.clone(),
87            false,
88          ));
89          next_imported_dependencies
90            .entry(entry.into())
91            .or_default()
92            .insert(options.clone(), *dependency.id());
93          dependency
94        };
95        compilation
96          .add_entry(entry_dependency, options.clone())
97          .await?;
98      }
99    }
100
101    *imported_dependencies = next_imported_dependencies;
102  } else {
103    // Cold branch must still seed `imported_dependencies` — otherwise the
104    // next Hot rebuild reallocates dep ids and breaks every downstream
105    // lookup that relies on dep id continuity across compiles.
106    let mut imported_dependencies = self.imported_dependencies.borrow_mut();
107    let mut next_imported_dependencies: FxHashMap<Arc<str>, FxHashMap<EntryOptions, DependencyId>> =
108      Default::default();
109    for EntryDynamicResult { import, options } in decs {
110      for entry in import {
111        let entry_dependency: BoxDependency = Box::new(EntryDependency::new(
112          entry.clone(),
113          self.context.clone(),
114          options.layer.clone(),
115          false,
116        ));
117        next_imported_dependencies
118          .entry(entry.clone().into())
119          .or_default()
120          .insert(options.clone(), *entry_dependency.id());
121        compilation
122          .add_entry(entry_dependency, options.clone())
123          .await?;
124      }
125    }
126    *imported_dependencies = next_imported_dependencies;
127  }
128
129  Ok(())
130}
131
132impl Plugin for DynamicEntryPlugin {
133  fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
134    ctx.compiler_hooks.compilation.tap(compilation::new(self));
135    ctx.compiler_hooks.make.tap(make::new(self));
136    Ok(())
137  }
138}