rspack_plugin_javascript 0.100.0-rc.2

rspack javascript plugin
Documentation
use bitflags::bitflags;
use rspack_cacheable::{
  cacheable, cacheable_dyn,
  with::{AsOption, AsPreset},
};
use rspack_core::{
  AffectType, AsContextDependency, AsModuleDependency, Dependency, DependencyCategory,
  DependencyCodeGeneration, DependencyId, DependencyRange, DependencyTemplate,
  DependencyTemplateType, DependencyType, ExportsArgument, ModuleArgument, ModuleCodeTemplate,
  RuntimeGlobals, TemplateContext, TemplateReplaceSource,
};
use rspack_util::{atom::Atom, json_stringify_str};

use super::local_module::LocalModule;

bitflags! {
  #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
  struct Branch: u8 {
    const L = 1 << 0;
    const A = 1 << 1;
    const O = 1 << 2;
    const F = 1 << 3;
  }
}

impl Branch {
  pub fn get_definition(&self, local_module_var: &Option<String>) -> String {
    let name = match local_module_var {
      Some(name) => name,
      None => "XXX",
    };
    match *self {
      f if f == Branch::F => "var __rspack_amd_exports;".to_string(),
      o if o == Branch::O => String::new(),
      o_f if o_f == (Branch::O | Branch::F) => {
        "var __rspack_amd_factory, __rspack_amd_exports;".to_string()
      }
      a_f if a_f == (Branch::A | Branch::F) => {
        "var __rspack_amd_deps, __rspack_amd_exports;".to_string()
      }
      a_o if a_o == (Branch::A | Branch::O) => String::new(),
      a_o_f if a_o_f == (Branch::A | Branch::O | Branch::F) => {
        "var __rspack_amd_factory, __rspack_amd_deps, __rspack_amd_exports;".to_string()
      }
      l_f if l_f == (Branch::L | Branch::F) => {
        format!("var {name}, {name}module;")
      }
      l_o if l_o == (Branch::L | Branch::O) => {
        format!("var {name};")
      }
      l_o_f if l_o_f == (Branch::L | Branch::O | Branch::F) => {
        format!("var {name}, {name}factory, {name}module;")
      }
      l_a_f if l_a_f == (Branch::L | Branch::A | Branch::F) => {
        format!("var __rspack_amd_deps, {name}, {name}exports;")
      }
      l_a_o if l_a_o == (Branch::L | Branch::A | Branch::O) => {
        format!("var {name};")
      }
      l_a_o_f if l_a_o_f == (Branch::L | Branch::A | Branch::O | Branch::F) => {
        format!("var {name}array, {name}factory, {name}exports, {name};")
      }
      _ => String::new(),
    }
  }

  pub fn get_content(
    &self,
    local_module_var: &Option<String>,
    named_module: &Option<Atom>,
    runtime_template: &mut ModuleCodeTemplate,
  ) -> String {
    let local_module_var = match local_module_var {
      Some(name) => name,
      None => "XXX",
    };
    let named_module = match named_module {
      Some(name) => name,
      None => "YYY",
    };
    match *self {
      f if f == Branch::F => {
        format!(
          "!(__rspack_amd_exports = (#).call({exports}, {require}, {exports}, {module}),
		__rspack_amd_exports !== undefined && ({module}.exports = __rspack_amd_exports))",
          require = runtime_template.render_runtime_globals(&RuntimeGlobals::REQUIRE),
          exports = runtime_template.render_exports_argument(ExportsArgument::Exports),
          module = runtime_template.render_module_argument(ModuleArgument::Module),
        )
      }
      o if o == Branch::O => format!("!({}.exports = #)", runtime_template.render_module_argument(ModuleArgument::Module)),
      o_f if o_f == (Branch::O | Branch::F) => {
        format!(
          "!(__rspack_amd_factory = (#),
		__rspack_amd_exports = (typeof __rspack_amd_factory === 'function' ?
		(__rspack_amd_factory.call({exports}, {require}, {exports}, {module})) :
		__rspack_amd_factory),
		__rspack_amd_exports !== undefined && ({module}.exports = __rspack_amd_exports))",
          require = runtime_template.render_runtime_globals(&RuntimeGlobals::REQUIRE),
          exports = runtime_template.render_exports_argument(ExportsArgument::Exports),
          module = runtime_template.render_module_argument(ModuleArgument::Module),
        )
      }
      a_f if a_f == (Branch::A | Branch::F) => format!("!(__rspack_amd_deps = #, __rspack_amd_exports = (#).apply({exports}, __rspack_amd_deps),
		__rspack_amd_exports !== undefined && ({module}.exports = __rspack_amd_exports))", 
        exports = runtime_template.render_exports_argument(ExportsArgument::Exports),
        module = runtime_template.render_module_argument(ModuleArgument::Module),
      ),
      a_o if a_o == (Branch::A | Branch::O) => format!("!(#, {}.exports = #)", runtime_template.render_module_argument(ModuleArgument::Module)),
      a_o_f if a_o_f == (Branch::A | Branch::O | Branch::F) => {
        format!("!(__rspack_amd_deps = #, __rspack_amd_factory = (#),
		__rspack_amd_exports = (typeof __rspack_amd_factory === 'function' ?
		(__rspack_amd_factory.apply({exports}, __rspack_amd_deps)) : __rspack_amd_factory),
		__rspack_amd_exports !== undefined && ({module}.exports = __rspack_amd_exports))",
          exports = runtime_template.render_exports_argument(ExportsArgument::Exports),
          module = runtime_template.render_module_argument(ModuleArgument::Module),
        )
      }
      l_f if l_f == (Branch::L | Branch::F) => {
        format!(
          "!({var_name}module = {{ id: {module_id}, exports: {{}}, loaded: false }}, {var_name} = (#).call({var_name}module.exports, {require}, {var_name}module.exports, {var_name}module), {var_name}module.loaded = true, {var_name} === undefined && ({var_name} = {var_name}module.exports))",
          var_name = local_module_var,
          module_id = json_stringify_str(named_module),
          require = runtime_template.render_runtime_globals(&RuntimeGlobals::REQUIRE),
        )
      }
      l_o if l_o == (Branch::L | Branch::O) => format!("!({local_module_var} = #)"),
      l_o_f if l_o_f == (Branch::L | Branch::O | Branch::F) => {
        format!(
          "!({var_name}factory = (#), (typeof {var_name}factory === 'function' ? (({var_name}module = {{ id: {module_id}, exports: {{}}, loaded: false }}), ({var_name} = {var_name}factory.call({var_name}module.exports, {require}, {var_name}module.exports, {var_name}module)), ({var_name}module.loaded = true), {var_name} === undefined && ({var_name} = {var_name}module.exports)) : {var_name} = {var_name}factory))",
          var_name = local_module_var,
          module_id = json_stringify_str(named_module),
          require = runtime_template.render_runtime_globals(&RuntimeGlobals::REQUIRE),
        )
      }
      l_a_f if l_a_f == (Branch::L | Branch::A | Branch::F) => format!("!(__rspack_amd_deps = #, {local_module_var} = (#).apply({local_module_var}exports = {{}}, __rspack_amd_deps), {local_module_var} === undefined && ({local_module_var} = {local_module_var}exports))"),
      l_a_o if l_a_o == (Branch::L | Branch::A | Branch::O) => format!("!(#, {local_module_var} = #)"),
      l_a_o_f if l_a_o_f == (Branch::L | Branch::A | Branch::O | Branch::F) => format!(
        "!({local_module_var}array = #, {local_module_var}factory = (#),
		(typeof {local_module_var}factory === 'function' ?
			(({local_module_var} = {local_module_var}factory.apply({local_module_var}exports = {{}}, {local_module_var}array)), {local_module_var} === undefined && ({local_module_var} = {local_module_var}exports)) :
			({local_module_var} = {local_module_var}factory)
		))",
      ),
      _ => String::new(),
    }
  }
}

#[cacheable]
#[derive(Debug, Clone)]
pub struct AMDDefineDependency {
  id: DependencyId,
  range: DependencyRange,
  array_range: Option<DependencyRange>,
  function_range: Option<DependencyRange>,
  object_range: Option<DependencyRange>,
  #[cacheable(with=AsOption<AsPreset>)]
  named_module: Option<Atom>,
  local_module: Option<LocalModule>,
}

impl AMDDefineDependency {
  pub fn new(
    range: DependencyRange,
    array_range: Option<DependencyRange>,
    function_range: Option<DependencyRange>,
    object_range: Option<DependencyRange>,
    named_module: Option<Atom>,
  ) -> Self {
    Self {
      id: DependencyId::new(),
      range,
      array_range,
      function_range,
      object_range,
      named_module,
      local_module: None,
    }
  }

  pub fn set_local_module(&mut self, local_module: LocalModule) {
    self.local_module = Some(local_module);
  }
}

#[cacheable_dyn]
impl Dependency for AMDDefineDependency {
  fn id(&self) -> &DependencyId {
    &self.id
  }

  fn range(&self) -> Option<DependencyRange> {
    Some(self.range)
  }

  fn category(&self) -> &DependencyCategory {
    &DependencyCategory::Amd
  }

  fn dependency_type(&self) -> &DependencyType {
    &DependencyType::AmdDefine
  }

  fn could_affect_referencing_module(&self) -> AffectType {
    AffectType::False
  }
}

impl AMDDefineDependency {
  fn local_module_var(&self) -> Option<String> {
    self.local_module.as_ref().and_then(|m| {
      if m.is_used() {
        Some(m.variable_name())
      } else {
        None
      }
    })
  }

  fn branch(&self) -> Branch {
    let mut ret = Branch::empty();
    if self.local_module.as_ref().is_some_and(|m| m.is_used()) {
      ret |= Branch::L;
    }
    if self.array_range.is_some() {
      ret |= Branch::A;
    }
    if self.object_range.is_some() {
      ret |= Branch::O;
    }
    if self.function_range.is_some() {
      ret |= Branch::F;
    }
    ret
  }
}

impl AsModuleDependency for AMDDefineDependency {}

impl AsContextDependency for AMDDefineDependency {}

#[cacheable_dyn]
impl DependencyCodeGeneration for AMDDefineDependency {
  fn dependency_template(&self) -> Option<DependencyTemplateType> {
    Some(AMDDefineDependencyTemplate::template_type())
  }
}

#[cacheable]
#[derive(Debug, Clone, Default)]
pub struct AMDDefineDependencyTemplate;

impl AMDDefineDependencyTemplate {
  pub fn template_type() -> DependencyTemplateType {
    DependencyTemplateType::Dependency(DependencyType::AmdDefine)
  }
}

impl DependencyTemplate for AMDDefineDependencyTemplate {
  fn render(
    &self,
    dep: &dyn DependencyCodeGeneration,
    source: &mut TemplateReplaceSource,
    code_generatable_context: &mut TemplateContext,
  ) {
    let dep = dep
      .as_any()
      .downcast_ref::<AMDDefineDependency>()
      .expect("AMDDefineDependencyTemplate should only be used for AMDDefineDependency");

    let branch = dep.branch();

    let local_module_var = dep.local_module_var();

    let text = branch.get_content(
      &local_module_var,
      &dep.named_module,
      code_generatable_context.runtime_template,
    );
    let definition = branch.get_definition(&local_module_var);

    let mut texts = text.split('#');

    if !definition.is_empty() {
      source.insert(0, definition, None);
    }

    let mut current = dep.range.start;
    if let Some(array_range) = &dep.array_range {
      source.replace(
        current,
        array_range.start,
        texts.next().unwrap_or("").to_string(),
        None,
      );
      current = array_range.end;
    }

    if let Some(object_range) = &dep.object_range {
      source.replace(
        current,
        object_range.start,
        texts.next().unwrap_or("").to_string(),
        None,
      );
      current = object_range.end;
    } else if let Some(function_range) = &dep.function_range {
      source.replace(
        current,
        function_range.start,
        texts.next().unwrap_or("").to_string(),
        None,
      );
      current = function_range.end;
    }

    source.replace(
      current,
      dep.range.end,
      texts.next().unwrap_or("").to_string(),
      None,
    );

    if texts.next().is_some() {
      panic!("Implementation error");
    }
  }
}