rspack_plugin_dynamic_entry 0.7.10

rspack dynamic entry plugin
Documentation
use std::sync::Arc;

use atomic_refcell::AtomicRefCell;
use derive_more::Debug;
use futures::future::BoxFuture;
use rspack_core::{
  BoxDependency, Compilation, CompilationParams, CompilerCompilation, CompilerMake, Context,
  DependencyId, DependencyType, EntryDependency, EntryOptions, Plugin,
  incremental::IncrementalPasses, internal,
};
use rspack_error::Result;
use rspack_hook::{plugin, plugin_hook};
use rspack_util::fx_hash::FxHashMap;

pub struct EntryDynamicResult {
  pub import: Vec<String>,
  pub options: EntryOptions,
}

type EntryDynamic =
  Box<dyn for<'a> Fn() -> BoxFuture<'static, Result<Vec<EntryDynamicResult>>> + Sync + Send>;

pub struct DynamicEntryPluginOptions {
  pub context: Context,
  pub entry: EntryDynamic,
}

#[plugin]
#[derive(Debug)]
pub struct DynamicEntryPlugin {
  context: Context,
  #[debug(skip)]
  entry: EntryDynamic,
  // Need "cache" the dependency to tell incremental that this entry dependency is not changed
  // so it can be reused and skip the module make
  imported_dependencies: AtomicRefCell<FxHashMap<Arc<str>, FxHashMap<EntryOptions, DependencyId>>>,
}

impl DynamicEntryPlugin {
  pub fn new(options: DynamicEntryPluginOptions) -> Self {
    Self::new_inner(options.context, options.entry, Default::default())
  }
}

#[plugin_hook(CompilerCompilation for DynamicEntryPlugin)]
async fn compilation(
  &self,
  compilation: &mut Compilation,
  params: &mut CompilationParams,
) -> Result<()> {
  compilation.set_dependency_factory(DependencyType::Entry, params.normal_module_factory.clone());
  Ok(())
}

#[plugin_hook(CompilerMake for DynamicEntryPlugin)]
async fn make(&self, compilation: &mut Compilation) -> Result<()> {
  let entry_fn = &self.entry;
  let decs = entry_fn().await?;

  if compilation
    .incremental
    .mutations_readable(IncrementalPasses::MAKE)
  {
    let mut imported_dependencies = self.imported_dependencies.borrow_mut();
    let mut next_imported_dependencies: FxHashMap<Arc<str>, FxHashMap<EntryOptions, DependencyId>> =
      Default::default();

    for EntryDynamicResult { import, options } in decs {
      for entry in import {
        let module_graph = compilation.get_module_graph();

        let entry_dependency: BoxDependency = if let Some(map) =
          imported_dependencies.get(entry.as_str())
          && let Some(dependency_id) = map.get(&options)
          && let Some(dependency) = internal::try_dependency_by_id(module_graph, dependency_id)
        {
          next_imported_dependencies
            .entry(entry.into())
            .or_default()
            .insert(options.clone(), *dependency_id);
          dependency.clone()
        } else {
          let dependency: BoxDependency = Box::new(EntryDependency::new(
            entry.clone(),
            self.context.clone(),
            options.layer.clone(),
            false,
          ));
          next_imported_dependencies
            .entry(entry.into())
            .or_default()
            .insert(options.clone(), *dependency.id());
          dependency
        };
        compilation
          .add_entry(entry_dependency, options.clone())
          .await?;
      }
    }

    *imported_dependencies = next_imported_dependencies;
  } else {
    for EntryDynamicResult { import, options } in decs {
      for entry in import {
        let entry_dependency: BoxDependency = Box::new(EntryDependency::new(
          entry.clone(),
          self.context.clone(),
          options.layer.clone(),
          false,
        ));
        compilation
          .add_entry(entry_dependency, options.clone())
          .await?;
      }
    }
  }

  Ok(())
}

impl Plugin for DynamicEntryPlugin {
  fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
    ctx.compiler_hooks.compilation.tap(compilation::new(self));
    ctx.compiler_hooks.make.tap(make::new(self));
    Ok(())
  }
}