farmfe_toolkit 2.1.0

Toolkit for farm.
use farmfe_core::{
  config::Mode,
  module::{
    module_graph::ModuleGraph,
    module_group::{ModuleGroupGraph, ModuleGroupId, ModuleGroupType},
    ModuleId,
  },
  resource::{resource_pot_map::ResourcePotMap, Resource, ResourceType},
  HashMap,
};

/// Get all dynamic resources(except resources generated by initial module group)
pub fn get_dynamic_resources_map(
  module_group_graph: &ModuleGroupGraph,
  module_group_id: &ModuleGroupId,
  resource_pot_map: &ResourcePotMap,
  resources_map: &HashMap<String, Resource>,
  module_graph: &ModuleGraph,
) -> HashMap<ModuleId, Vec<(String, ResourceType)>> {
  let mut dep_module_groups = vec![];

  module_group_graph.bfs(&module_group_id, &mut |mg_id| {
    if mg_id != module_group_id {
      dep_module_groups.push(mg_id.clone());
    }
  });

  let mut dynamic_resources_map = HashMap::<ModuleId, Vec<(String, ResourceType)>>::default();

  for mg_id in dep_module_groups {
    let mg = module_group_graph.module_group(&mg_id).unwrap();

    for rp_id in &mg.sorted_resource_pots(module_graph, resource_pot_map) {
      let rp = resource_pot_map.resource_pot(rp_id).unwrap_or_else(|| {
        panic!(
          "Resource pot {} not found in resource pot map",
          rp_id.to_string()
        )
      });

      let resources = dynamic_resources_map
        .entry(mg.entry_module_id.clone())
        .or_default();

      for r in rp.resources() {
        let resource = resources_map
          .get(r)
          .unwrap_or_else(|| panic!("{r} not found"));

        if !is_resource_supported(resource) {
          continue;
        }

        resources.push((resource.name.clone(), resource.resource_type.clone()));
      }
    }
  }

  dynamic_resources_map
}

pub fn get_dynamic_resources_code(
  dynamic_resources_map: &HashMap<ModuleId, Vec<(String, ResourceType)>>,
  mode: Mode,
) -> (String, String) {
  let mut dynamic_resources_code_vec = vec![];
  let mut dynamic_resources = vec![];
  let mut visited_resources = HashMap::default();

  // inject dynamic resources
  let mut dynamic_resources_map_vec = dynamic_resources_map.iter().collect::<Vec<_>>();
  dynamic_resources_map_vec.sort_by_key(|(module_id, _)| module_id.to_string());
  for (module_id, resources) in dynamic_resources_map_vec {
    let mut dynamic_resources_index = vec![];

    for (resource_name, resource_type) in resources {
      let key = format!("{resource_name}{resource_type:?}");

      if let Some(index) = visited_resources.get(&key) {
        dynamic_resources_index.push(format!("{}", *index));
        continue;
      }

      match resource_type {
        ResourceType::Js => {
          dynamic_resources.push(format!(r#"{{ path: '{resource_name}', type: 0 }}"#));
        }
        ResourceType::Css => {
          dynamic_resources.push(format!(r#"{{ path: '{resource_name}', type: 1 }}"#));
        }
        _ => {
          panic!("unsupported type ({resource_type:?}) when injecting dynamic resources")
        }
      }

      dynamic_resources_index.push(format!("{}", dynamic_resources.len() - 1));
      visited_resources.insert(key, dynamic_resources.len() - 1);
    }

    let id = module_id.id(mode.clone()).replace(r"\", r"\\");
    dynamic_resources_code_vec.push((id, dynamic_resources_index.join(",")));
  }

  let mut dynamic_resources_code = dynamic_resources_code_vec
    .into_iter()
    .map(|(id, resources_code)| format!(r#"'{id}': [{resources_code}]"#))
    .collect::<Vec<_>>()
    .join(",");

  dynamic_resources_code = format!("{{ {dynamic_resources_code} }}");

  (
    format!("[{}]", dynamic_resources.join(",")),
    dynamic_resources_code,
  )
}

#[derive(Debug, Default)]
pub struct InitialResources {
  pub entry_resource_name: String,
  pub entry_resource_sourcemap_name: Option<String>,
  /// Initial resources name, including entry_resource_name
  pub initial_resources: Vec<(String, ResourceType)>,
}

pub fn get_initial_resources(
  entry_module_id: &ModuleId,
  module_graph: &ModuleGraph,
  module_group_graph: &ModuleGroupGraph,
  resource_pot_map: &ResourcePotMap,
  resources_map: &HashMap<String, Resource>,
) -> InitialResources {
  let mut result = InitialResources::default();
  let module = module_graph.module(entry_module_id).unwrap();

  for resource_pot in &module.resource_pots {
    let resource_pot = resource_pot_map.resource_pot(resource_pot).unwrap();
    // only resource pot generated by normal entry should be handled here
    if resource_pot.is_dynamic_entry {
      continue;
    }

    for resource_name in resource_pot.resources() {
      // get resource from resources_map
      let resource = resources_map.get(resource_name).unwrap();

      if let ResourceType::SourceMap(_) = &resource.resource_type {
        result.entry_resource_sourcemap_name = Some(resource_name.clone());
      } else {
        result.entry_resource_name = resource_name.clone();
      }
    }
  }

  let mut initial_resources = vec![];

  let module_group_id = ModuleGroupId::new(entry_module_id, &ModuleGroupType::Entry);
  let module_group = module_group_graph.module_group(&module_group_id).unwrap();
  let sorted_resource_pots = module_group.sorted_resource_pots(&module_graph, &resource_pot_map);

  for rp_id in &sorted_resource_pots {
    let rp = resource_pot_map.resource_pot(rp_id).unwrap_or_else(|| {
      panic!(
        "Resource pot {} not found in resource pot map",
        rp_id.to_string()
      )
    });

    let filtered_resources = rp
      .resources()
      .into_iter()
      .filter_map(|r| {
        resources_map
          .get(r)
          .filter(|resource| is_resource_supported(resource))
      })
      .map(|resource| (resource.name.to_string(), resource.resource_type.clone()));

    initial_resources.extend(filtered_resources);
  }

  result.initial_resources = initial_resources;

  result
}

/// Currently only support js and css
fn is_resource_supported(resource: &Resource) -> bool {
  matches!(resource.resource_type, ResourceType::Js | ResourceType::Css)
}