use farmfe_core::{
config::{partial_bundling::PartialBundlingConfig, Config},
module::{module_graph::ModuleGraph, Module, ModuleId},
HashMap, HashSet,
};
use crate::{generate_module_buckets::ResourceType, module_pot::ModulePot};
pub fn generate_module_pots(
modules: &HashSet<ModuleId>,
module_graph: &ModuleGraph,
config: &Config,
resource_type: ResourceType,
) -> Vec<ModulePot> {
let partial_bundling = &config.partial_bundling;
let mut module_pot_map = HashMap::<String, ModulePot>::default();
for module_id in modules {
let module = module_graph.module(module_id).unwrap();
let module_pot_meta = generate_module_pot_meta(module, partial_bundling, resource_type.clone());
let module_pot_id = ModulePot::gen_id(
&module_pot_meta.name,
module.module_type.clone(),
module.immutable,
);
let module_pot = module_pot_map.entry(module_pot_id).or_insert_with(|| {
ModulePot::new(
module_pot_meta.name,
module_pot_meta.source_type,
module.module_type.clone(),
module.immutable,
)
});
module_pot.add_module(module_id.clone(), module.size, module.execution_order);
}
let mut exceed_size_module_pot_ids = module_pot_map
.iter()
.filter(|(_, module_pot)| {
if matches!(module_pot.source_type, ModulePotSourceType::GroupsConfig) {
return false;
}
module_pot.size > partial_bundling.target_max_size
})
.map(|(module_pot_id, _)| module_pot_id.clone())
.collect::<Vec<_>>();
exceed_size_module_pot_ids.sort();
for exceed_size_module_pot_id in exceed_size_module_pot_ids {
let module_pot = module_pot_map.remove(&exceed_size_module_pot_id).unwrap();
let new_module_pot_numbers = (module_pot.size / partial_bundling.target_max_size) + 1;
let module_pot_name = module_pot.name.clone();
let immutable = module_pot.immutable;
let ty = module_pot.module_type.clone();
let mut modules = module_pot.take_modules().into_iter().collect::<Vec<_>>();
modules.sort_by_key(|m| m.to_string());
let page_size = modules.len() / new_module_pot_numbers;
for i in 0..new_module_pot_numbers {
let new_module_pot_name = format!("{module_pot_name}-{i}");
let new_module_pot_id = ModulePot::gen_id(&new_module_pot_name, ty.clone(), immutable);
let new_module_pot = module_pot_map.entry(new_module_pot_id).or_insert_with(|| {
ModulePot::new(
new_module_pot_name,
ModulePotSourceType::MergedModulePot,
ty.clone(),
immutable,
)
});
let start = i * page_size;
let end = if i == new_module_pot_numbers - 1 {
modules.len()
} else {
(i + 1) * page_size
};
for module_id in &modules[start..end] {
let module = module_graph.module(module_id).unwrap();
new_module_pot.add_module(module_id.clone(), module.size, module.execution_order);
}
}
}
let mut module_pots = module_pot_map.into_values().collect::<Vec<_>>();
module_pots.sort_by_key(|m| m.execution_order);
module_pots
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ModulePotSourceType {
MutableModule,
ImmutablePackage,
GroupsConfig,
MergedModulePot,
}
#[derive(Debug)]
pub struct ModulePotMeta {
pub(crate) name: String,
pub(crate) source_type: ModulePotSourceType,
}
fn generate_module_pot_meta(
module: &Module,
config: &PartialBundlingConfig,
resource_type: ResourceType,
) -> ModulePotMeta {
for group_config in &config.groups {
if group_config
.test
.iter()
.any(|c| c.is_match(&module.id.to_string()))
&& group_config.group_type.is_match(module.immutable)
&& (resource_type.is_match(group_config.resource_type.clone()))
{
return ModulePotMeta {
name: group_config.name.clone(),
source_type: ModulePotSourceType::GroupsConfig,
};
}
}
if module.immutable {
return ModulePotMeta {
name: format!("{}@{}", module.package_name, module.package_version),
source_type: ModulePotSourceType::ImmutablePackage,
};
}
ModulePotMeta {
name: module.id.to_string(),
source_type: ModulePotSourceType::MutableModule,
}
}
#[cfg(test)]
mod tests {
use farmfe_core::{
config::{
config_regex::ConfigRegex,
partial_bundling::{
PartialBundlingConfig, PartialBundlingGroupConfig, PartialBundlingGroupConfigGroupType,
PartialBundlingGroupConfigResourceType,
},
Config,
},
module::{module_graph::ModuleGraph, Module, ModuleType},
HashSet,
};
use farmfe_testing_helpers::{assert_debug_snapshot, fixture};
use std::mem;
use crate::{generate_module_buckets::ResourceType, generate_module_pots::generate_module_pots};
macro_rules! assert_module_pot_snapshot {
($module_pots:expr) => {
for module_pot in $module_pots.iter_mut() {
let mut modules = mem::take(&mut module_pot.modules)
.into_iter()
.collect::<Vec<_>>();
modules.sort();
assert_debug_snapshot!((&module_pot, &modules));
module_pot.modules = modules.into_iter().collect();
}
};
}
#[test]
fn test_generate_module_pots_package() {
fixture!(
"tests/fixtures/generate_module_pots/basic/index.ts",
|_, _cwd| {
let mut module_graph = ModuleGraph::new();
let mut module_index =
Module::new("tests/fixtures/generate_module_pots/basic/index.ts".into());
let mut module_utils =
Module::new("tests/fixtures/generate_module_pots/basic/utils.ts".into());
let mut module_a =
Module::new("tests/fixtures/generate_module_pots/basic1/index.ts".into());
module_index.module_type = ModuleType::Js;
module_index.immutable = true;
module_index.size = 10 * 1024;
module_index.execution_order = 2;
module_index.package_name = "test-package".to_string();
module_index.package_version = "1.0.0".to_string();
module_utils.module_type = ModuleType::Js;
module_utils.immutable = true;
module_utils.size = 5 * 1024;
module_utils.execution_order = 3;
module_utils.package_name = "test-package".to_string();
module_utils.package_version = "1.0.0".to_string();
module_a.module_type = ModuleType::Js;
module_a.execution_order = 1;
module_a.immutable = true;
module_a.size = 1 * 1024;
module_a.package_name = "test-package1".to_string();
module_a.package_version = "1.0.0".to_string();
module_graph.add_module(module_index);
module_graph.add_module(module_utils);
module_graph.add_module(module_a);
let modules = module_graph
.modules()
.iter()
.map(|m| m.id.clone())
.collect::<HashSet<_>>();
let mut module_pots = generate_module_pots(
&modules,
&module_graph,
&Default::default(),
ResourceType::Initial,
);
assert_eq!(module_pots.len(), 2);
assert_module_pot_snapshot!(module_pots);
}
);
}
#[test]
fn test_generate_module_pots_group() {
let mut module_graph = ModuleGraph::new();
let mut module_a = Module::new("src/a.ts".into());
let mut module_b = Module::new("src/b.ts".into());
let mut module_c = Module::new("utils/c.ts".into());
module_a.module_type = ModuleType::Js;
module_a.size = 10 * 1024;
module_a.execution_order = 1;
module_b.module_type = ModuleType::Js;
module_b.size = 5 * 1024;
module_b.execution_order = 2;
module_c.module_type = ModuleType::Js;
module_c.size = 1 * 1024;
module_c.execution_order = 3;
module_graph.add_module(module_a);
module_graph.add_module(module_b);
module_graph.add_module(module_c);
let modules = module_graph
.modules()
.iter()
.map(|m| m.id.clone())
.collect::<HashSet<_>>();
let config = Config {
partial_bundling: Box::new(PartialBundlingConfig {
groups: vec![PartialBundlingGroupConfig {
name: "test".into(),
test: vec![ConfigRegex::new("src/.*")],
..Default::default()
}],
..Default::default()
}),
..Default::default()
};
let mut module_pots =
generate_module_pots(&modules, &module_graph, &config, ResourceType::Initial);
assert_module_pot_snapshot!(module_pots);
let config = Config {
partial_bundling: Box::new(PartialBundlingConfig {
groups: vec![PartialBundlingGroupConfig {
name: "test".into(),
test: vec![ConfigRegex::new("src/.*")],
group_type: PartialBundlingGroupConfigGroupType::Immutable,
..Default::default()
}],
..Default::default()
}),
..Default::default()
};
let mut module_pots =
generate_module_pots(&modules, &module_graph, &config, ResourceType::Initial);
assert_module_pot_snapshot!(module_pots);
let config = Config {
partial_bundling: Box::new(PartialBundlingConfig {
groups: vec![PartialBundlingGroupConfig {
name: "test".into(),
test: vec![ConfigRegex::new("src/.*")],
resource_type: PartialBundlingGroupConfigResourceType::Async,
..Default::default()
}],
..Default::default()
}),
..Default::default()
};
let mut module_pots =
generate_module_pots(&modules, &module_graph, &config, ResourceType::Initial);
assert_module_pot_snapshot!(module_pots);
let mut module_pots =
generate_module_pots(&modules, &module_graph, &config, ResourceType::Async);
assert_module_pot_snapshot!(module_pots);
let config = Config {
partial_bundling: Box::new(PartialBundlingConfig {
groups: vec![PartialBundlingGroupConfig {
name: "test".into(),
test: vec![ConfigRegex::new("src/.*")],
resource_type: PartialBundlingGroupConfigResourceType::Initial,
..Default::default()
}],
..Default::default()
}),
..Default::default()
};
let mut module_pots =
generate_module_pots(&modules, &module_graph, &config, ResourceType::Initial);
assert_module_pot_snapshot!(module_pots);
}
}