diff --git a/Cargo.lock b/Cargo.lock index 691a8fbcb9..3243f384e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1621,12 +1621,12 @@ name = "farmfe_compiler" version = "0.0.13" dependencies = [ "farmfe_core", - "farmfe_plugin_bundle", "farmfe_plugin_css", "farmfe_plugin_define", "farmfe_plugin_html", "farmfe_plugin_json", "farmfe_plugin_lazy_compilation", + "farmfe_plugin_library", "farmfe_plugin_minify", "farmfe_plugin_partial_bundling", "farmfe_plugin_polyfill", @@ -1778,6 +1778,17 @@ dependencies = [ "farmfe_utils 0.1.6", ] +[[package]] +name = "farmfe_plugin_library" +version = "0.0.1" +dependencies = [ + "farmfe_core", + "farmfe_plugin_bundle", + "farmfe_testing_helpers", + "farmfe_toolkit", + "farmfe_utils 0.1.6", +] + [[package]] name = "farmfe_plugin_minify" version = "0.0.13" diff --git a/crates/compiler/Cargo.toml b/crates/compiler/Cargo.toml index eeab02e4f6..12339bb5e4 100644 --- a/crates/compiler/Cargo.toml +++ b/crates/compiler/Cargo.toml @@ -28,7 +28,8 @@ farmfe_plugin_json = { path = "../plugin_json", version = "0.0.13" } farmfe_plugin_polyfill = { path = "../plugin_polyfill", version = "0.0.13" } farmfe_plugin_progress = { path = "../plugin_progress", version = "0.0.13" } farmfe_plugin_define = { path = "../plugin_define", version = "0.0.13" } -farmfe_plugin_bundle = { path = "../plugin_bundle", version = "0.0.7" } +# farmfe_plugin_bundle = { path = "../plugin_bundle", version = "0.0.7" } +farmfe_plugin_library = { path = "../plugin_library", version = "0.0.1" } farmfe_testing = { path = "../macro_testing", version = "0.0.2" } [features] diff --git a/crates/compiler/src/build/mod.rs b/crates/compiler/src/build/mod.rs index c57d5a9065..00fc694cf7 100644 --- a/crates/compiler/src/build/mod.rs +++ b/crates/compiler/src/build/mod.rs @@ -480,20 +480,30 @@ impl Compiler { resolve_module_id_result, }) => { farm_profile_scope!(format!("new module {:?}", module.id)); + if resolve_module_id_result.resolve_result.external { // insert external module to the graph let module_id = module.id.clone(); - Self::add_module(module, &resolve_param.kind, &context); + Self::add_module(module, &context); Self::add_edge(&resolve_param, module_id, order, &context); return; } + // mark entry module first after resolving if let ResolveKind::Entry(ref name) = resolve_param.kind { context .module_graph .write() .entries .insert(module.id.clone(), name.to_string()); + module.is_entry = true; + } else if let ResolveKind::DynamicEntry { .. } = resolve_param.kind { + context + .module_graph + .write() + .dynamic_entries + .insert(module.id.clone(), resolve_param.source.clone()); + module.is_dynamic_entry = true; } match Self::build_module( @@ -525,7 +535,7 @@ impl Compiler { fn handle_dependencies(params: HandleDependenciesParams) { let HandleDependenciesParams { module, - resolve_param, + mut resolve_param, order, deps, thread_pool, @@ -534,9 +544,16 @@ impl Compiler { } = params; let module_id = module.id.clone(); + // dynamic entry modules may reset importer module, reset importer here + if let ResolveKind::DynamicEntry { no_importer, .. } = &resolve_param.kind { + if *no_importer { + resolve_param.importer = None + } + } + let immutable = module.immutable; // add module to the graph - Self::add_module(module, &resolve_param.kind, &context); + Self::add_module(module, &context); // add edge to the graph Self::add_edge(&resolve_param, module_id.clone(), order, &context); @@ -580,22 +597,9 @@ impl Compiler { } /// add a module to the module graph, if the module already exists, update it - pub(crate) fn add_module(mut module: Module, kind: &ResolveKind, context: &CompilationContext) { + pub(crate) fn add_module(module: Module, context: &CompilationContext) { let mut module_graph = context.module_graph.write(); - // mark entry module - if let ResolveKind::Entry(name) = kind { - module.is_entry = true; - module_graph - .entries - .insert(module.id.clone(), name.to_string()); - } else if let ResolveKind::DynamicEntry { name, .. } = kind { - module.is_dynamic_entry = true; - module_graph - .dynamic_entries - .insert(module.id.clone(), name.to_string()); - } - // check if the module already exists if module_graph.has_module(&module.id) { module_graph.replace_module(module); diff --git a/crates/compiler/src/generate/finalize_resources.rs b/crates/compiler/src/generate/finalize_resources.rs index 58df151bf3..9909b316d3 100644 --- a/crates/compiler/src/generate/finalize_resources.rs +++ b/crates/compiler/src/generate/finalize_resources.rs @@ -1,13 +1,19 @@ use std::sync::Arc; use farmfe_core::{ - config::Mode, context::CompilationContext, plugin::PluginFinalizeResourcesHookParams, + config::Mode, + context::CompilationContext, + plugin::{PluginFinalizeResourcesHookParams, PluginHandleEntryResourceHookParams}, + resource::{Resource, ResourceType}, + HashMap, }; pub fn finalize_resources(context: &Arc) -> farmfe_core::error::Result<()> { { let mut resources_map = context.resources_map.lock(); + handle_entry_resource(&mut resources_map, context)?; + let mut param = PluginFinalizeResourcesHookParams { resources_map: &mut resources_map, config: &context.config, @@ -45,3 +51,55 @@ pub fn finalize_resources(context: &Arc) -> farmfe_core::err Ok(()) } + +fn handle_entry_resource( + resources_map: &mut HashMap, + context: &Arc, +) -> farmfe_core::error::Result<()> { + let module_graph = context.module_graph.read(); + let module_group_graph = context.module_group_graph.read(); + let resource_pot_map = context.resource_pot_map.read(); + + for (entry_module_id, _) in &module_graph.entries { + 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(); + + let mut params = PluginHandleEntryResourceHookParams { + resource: Resource::default(), + resource_source_map: None, + module_graph: &module_graph, + module_group_graph: &module_group_graph, + entry_module_id, + }; + + for resource_name in resource_pot.resources() { + // get resource from resources_map + let resource = resources_map.get_mut(resource_name).unwrap(); + let resource = std::mem::replace(resource, Resource::default()); + + if let ResourceType::Js = &resource.resource_type { + params.resource = resource; + } else if let ResourceType::SourceMap(_) = &resource.resource_type { + params.resource_source_map = Some(resource); + } + } + + context + .plugin_driver + .handle_entry_resource(&mut params, context)?; + + // write entry resource back to resources_map + let resource = resources_map.get_mut(¶ms.resource.name).unwrap(); + *resource = params.resource; + + if let Some(resource) = params.resource_source_map { + let resource = resources_map.get_mut(&resource.name).unwrap(); + *resource = resource.clone(); + } + } + } + + Ok(()) +} diff --git a/crates/compiler/src/generate/partial_bundling.rs b/crates/compiler/src/generate/partial_bundling.rs index b66910e1e6..e537d72b5f 100644 --- a/crates/compiler/src/generate/partial_bundling.rs +++ b/crates/compiler/src/generate/partial_bundling.rs @@ -142,7 +142,7 @@ pub fn fill_necessary_fields_for_resource_pot( if module_graph.entries.contains_key(module_id) { if entry_module.is_some() { - panic!("a resource pot can only have one entry module, but both {:?} and {:?} are entry modules", entry_module.unwrap(), module_id); + panic!("a resource pot({}) can only have one entry module, but both {:?} and {:?} are entry modules", resource_pot.id, entry_module.unwrap(), module_id); } entry_module = Some(module_id.clone()); } @@ -192,6 +192,11 @@ fn generate_enforce_resource_pots( // generate enforce resource pots first for g in module_group_graph.module_groups() { + // skip dynamic entry module group + if matches!(g.module_group_type, ModuleGroupType::DynamicEntry) { + continue; + } + for module_id in g.modules() { // ignore external module if module_graph.module(module_id).unwrap().external { diff --git a/crates/compiler/src/generate/render_resource_pots.rs b/crates/compiler/src/generate/render_resource_pots.rs index a6e1b7455e..9966e8759a 100644 --- a/crates/compiler/src/generate/render_resource_pots.rs +++ b/crates/compiler/src/generate/render_resource_pots.rs @@ -26,6 +26,7 @@ pub fn render_resource_pots_and_generate_resources( let resources = Mutex::new(vec![]); let entries = context.module_graph.read().entries.clone(); + let dynamic_entries = context.module_graph.read().dynamic_entries.clone(); let mut resource_pots_need_render = vec![]; @@ -88,7 +89,11 @@ pub fn render_resource_pots_and_generate_resources( if r.should_transform_output_filename { let content_with_extra_content_hash = &[&r.bytes, augment_resource_hash_bytes].concat(); if let Some(name) = resource_pot.entry_module.as_ref() { - let entry_name = entries.get(name).unwrap(); + let entry_name = entries + .get(name) + .or_else(|| dynamic_entries.get(name)) + .unwrap(); + r.name = transform_output_entry_filename( context.config.output.entry_filename.clone(), resource_pot.id.to_string().as_str(), diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs index 2bd9b125c5..1ef0e3ab93 100644 --- a/crates/compiler/src/lib.rs +++ b/crates/compiler/src/lib.rs @@ -36,7 +36,7 @@ impl Compiler { /// The params are [farmfe_core::config::Config] and dynamic load rust plugins and js plugins [farmfe_core::plugin::Plugin] pub fn new(config: Config, mut plugin_adapters: Vec>) -> Result { let render_plugin: Arc = if config.output.target_env.is_library() { - Arc::new(farmfe_plugin_bundle::FarmPluginBundle::new()) as _ + Arc::new(farmfe_plugin_library::FarmPluginLibrary::new(&config)) as _ } else { Arc::new(farmfe_plugin_runtime::FarmPluginRuntime::new(&config)) as _ }; diff --git a/crates/compiler/src/update/diff_and_patch_module_graph/test_patch_module_graph.rs b/crates/compiler/src/update/diff_and_patch_module_graph/test_patch_module_graph.rs index 4844a42a5c..d0febad3cc 100644 --- a/crates/compiler/src/update/diff_and_patch_module_graph/test_patch_module_graph.rs +++ b/crates/compiler/src/update/diff_and_patch_module_graph/test_patch_module_graph.rs @@ -429,6 +429,7 @@ fn test_patch_module_graph_add_remove_dynamic_entry() { kind: ResolveKind::DynamicEntry { name: "AD".to_string(), output_filename: None, + no_importer: false, }, ..Default::default() }]), @@ -454,6 +455,7 @@ fn test_patch_module_graph_add_remove_dynamic_entry() { kind: ResolveKind::DynamicEntry { name: "BH".to_string(), output_filename: None, + no_importer: false, }, ..Default::default() }]), diff --git a/crates/compiler/src/update/patch_module_group_graph/test_dynamic_entries.rs b/crates/compiler/src/update/patch_module_group_graph/test_dynamic_entries.rs index 47afb1d906..77367d3fe8 100644 --- a/crates/compiler/src/update/patch_module_group_graph/test_dynamic_entries.rs +++ b/crates/compiler/src/update/patch_module_group_graph/test_dynamic_entries.rs @@ -27,6 +27,7 @@ fn test_patch_module_group_graph_dynamic_entry_complex() { kind: ResolveKind::DynamicEntry { name: "AD".to_string(), output_filename: None, + no_importer: false, }, ..Default::default() }]), @@ -43,6 +44,7 @@ fn test_patch_module_group_graph_dynamic_entry_complex() { kind: ResolveKind::DynamicEntry { name: "EI".to_string(), output_filename: None, + no_importer: false, }, ..Default::default() }]), @@ -170,6 +172,7 @@ fn test_patch_module_group_graph_dynamic_entry_update_dynamic_entry() { kind: ResolveKind::DynamicEntry { name: "AD".to_string(), output_filename: None, + no_importer: false, }, ..Default::default() }]), diff --git a/crates/compiler/src/update/regenerate_resources/generate_and_diff_resource_pots.rs b/crates/compiler/src/update/regenerate_resources/generate_and_diff_resource_pots.rs index e059f88d45..f3a7918f16 100644 --- a/crates/compiler/src/update/regenerate_resources/generate_and_diff_resource_pots.rs +++ b/crates/compiler/src/update/regenerate_resources/generate_and_diff_resource_pots.rs @@ -87,12 +87,20 @@ fn get_affected_modules( context: &Arc, ) -> Vec { let module_group_graph = context.module_group_graph.read(); - // let mut enforce_resource_pots = HashSet::new(); + module_groups .iter() .fold(HashSet::default(), |mut acc, module_group_id| { let module_group = module_group_graph.module_group(module_group_id).unwrap(); - acc.extend(module_group.modules().clone()); + + // ignore dynamic entry module group + if !matches!( + module_group.module_group_type, + ModuleGroupType::DynamicEntry + ) { + acc.extend(module_group.modules().clone()); + } + acc }) .into_iter() diff --git a/crates/compiler/src/update/regenerate_resources/generate_and_diff_resource_pots/test_generate_and_diff_resource_pots.rs b/crates/compiler/src/update/regenerate_resources/generate_and_diff_resource_pots/test_generate_and_diff_resource_pots.rs index c8c8cf0d02..8286154f1c 100644 --- a/crates/compiler/src/update/regenerate_resources/generate_and_diff_resource_pots/test_generate_and_diff_resource_pots.rs +++ b/crates/compiler/src/update/regenerate_resources/generate_and_diff_resource_pots/test_generate_and_diff_resource_pots.rs @@ -54,6 +54,7 @@ fn test_generate_and_diff_resource_pots() { kind: ResolveKind::DynamicEntry { name: "EI".to_string(), output_filename: None, + no_importer: false, }, ..Default::default() }]), diff --git a/crates/compiler/src/update/regenerate_resources/generate_and_diff_resource_pots/test_handle_dynamic_entry_resource_pots.rs b/crates/compiler/src/update/regenerate_resources/generate_and_diff_resource_pots/test_handle_dynamic_entry_resource_pots.rs index ad224478e0..082b5af85e 100644 --- a/crates/compiler/src/update/regenerate_resources/generate_and_diff_resource_pots/test_handle_dynamic_entry_resource_pots.rs +++ b/crates/compiler/src/update/regenerate_resources/generate_and_diff_resource_pots/test_handle_dynamic_entry_resource_pots.rs @@ -46,6 +46,7 @@ fn test_handle_dynamic_entry_resource_pots() { kind: farmfe_core::plugin::ResolveKind::DynamicEntry { name: "BH".to_string(), output_filename: None, + no_importer: false, }, ..Default::default() }]), diff --git a/crates/compiler/tests/common/mod.rs b/crates/compiler/tests/common/mod.rs index 81d687d4c0..a9689b1913 100644 --- a/crates/compiler/tests/common/mod.rs +++ b/crates/compiler/tests/common/mod.rs @@ -32,7 +32,6 @@ pub fn generate_runtime(crate_path: PathBuf) -> Box { .join("fixtures") .join("_internal") .join("runtime") - .join("index.js") .to_string_lossy() .to_string(); diff --git a/crates/compiler/tests/fixtures/_internal/runtime/index.js b/crates/compiler/tests/fixtures/_internal/runtime/index.js deleted file mode 100644 index b2ca3ebb0a..0000000000 --- a/crates/compiler/tests/fixtures/_internal/runtime/index.js +++ /dev/null @@ -1 +0,0 @@ -console.log('runtime/index.js') \ No newline at end of file diff --git a/crates/compiler/tests/fixtures/_internal/runtime/src/module_system.ts b/crates/compiler/tests/fixtures/_internal/runtime/src/module_system.ts new file mode 100644 index 0000000000..0dded9d9f2 --- /dev/null +++ b/crates/compiler/tests/fixtures/_internal/runtime/src/module_system.ts @@ -0,0 +1 @@ +console.log('module_system.ts'); \ No newline at end of file diff --git a/crates/core/src/module/meta_data/script.rs b/crates/core/src/module/meta_data/script.rs index 57d2df8a39..da83bcb3c4 100644 --- a/crates/core/src/module/meta_data/script.rs +++ b/crates/core/src/module/meta_data/script.rs @@ -221,4 +221,8 @@ impl ModuleSystem { ModuleSystem::Custom(_) => module_system, } } + + pub fn contains_commonjs(&self) -> bool { + matches!(self, ModuleSystem::CommonJs | ModuleSystem::Hybrid) + } } diff --git a/crates/core/src/module/meta_data/script/statement.rs b/crates/core/src/module/meta_data/script/statement.rs index 86e05c5e47..9cf8ca5dc5 100644 --- a/crates/core/src/module/meta_data/script/statement.rs +++ b/crates/core/src/module/meta_data/script/statement.rs @@ -184,11 +184,16 @@ impl SwcId { #[cache_item] #[serde(rename_all = "camelCase")] pub enum ImportSpecifierInfo { + /// import * as foo from 'foo'; Namespace(SwcId), + /// import { foo, bar as zoo } from 'foo'; Named { + /// foo or zoo in `import { foo, bar as zoo } from 'foo';` local: SwcId, + /// bar in `import { foo, bar as zoo } from 'foo';` imported: Option, }, + /// import foo from 'foo'; Default(SwcId), } diff --git a/crates/core/src/plugin/hooks/resolve.rs b/crates/core/src/plugin/hooks/resolve.rs index 25881526f4..1ba525171c 100644 --- a/crates/core/src/plugin/hooks/resolve.rs +++ b/crates/core/src/plugin/hooks/resolve.rs @@ -15,6 +15,9 @@ pub enum ResolveKind { name: String, /// the same as config.output.filename, default to config.output.filename output_filename: Option, + /// disable importer of the dynamic entry. for example if b.ts is a dynamic entry return by a.ts, then a.ts is the importer of b.ts by default, + /// if you want a isolate dynamic entry, you can set this field to true + no_importer: bool, }, /// static import, e.g. `import a from './a'` #[default] diff --git a/crates/core/src/plugin/mod.rs b/crates/core/src/plugin/mod.rs index 1a8af56658..196b3d2aff 100644 --- a/crates/core/src/plugin/mod.rs +++ b/crates/core/src/plugin/mod.rs @@ -499,7 +499,8 @@ pub struct PluginFinalizeResourcesHookParams<'a> { } pub struct PluginHandleEntryResourceHookParams<'a> { - pub resource: &'a mut Resource, + pub resource: Resource, + pub resource_source_map: Option, pub module_graph: &'a ModuleGraph, pub module_group_graph: &'a ModuleGroupGraph, pub entry_module_id: &'a ModuleId, diff --git a/crates/core/src/resource/mod.rs b/crates/core/src/resource/mod.rs index 4fd49bfa57..67b592c986 100644 --- a/crates/core/src/resource/mod.rs +++ b/crates/core/src/resource/mod.rs @@ -1,7 +1,6 @@ use heck::AsLowerCamelCase; use farmfe_macro_cache_item::cache_item; -use resource_pot::ResourcePotType; use crate::module::ModuleId; diff --git a/crates/node/src/plugin_adapters/rust_plugin_adapter/plugin_loader.rs b/crates/node/src/plugin_adapters/rust_plugin_adapter/plugin_loader.rs index 35cabd38bc..a768bf890d 100644 --- a/crates/node/src/plugin_adapters/rust_plugin_adapter/plugin_loader.rs +++ b/crates/node/src/plugin_adapters/rust_plugin_adapter/plugin_loader.rs @@ -15,10 +15,8 @@ pub unsafe fn load_rust_plugin + std::fmt::Display>( options: String, ) -> Result<(Arc, Library), Error> { type PluginCreate = unsafe fn(config: &Config, options: String) -> Arc; - println!("try load rust plugin {}", filename); let lib = Library::new(filename.as_ref()).unwrap(); - println!("lib created {}", filename); let core_version_fn: Symbol String> = lib.get(b"_core_version")?; let core_version = core_version_fn(); @@ -36,6 +34,6 @@ If you are plugin author, please build your plugin with rust toolchain `nightly- let constructor: Symbol = lib.get(b"_plugin_create")?; let plugin = constructor(config, options); - println!("plugin {} loaded", plugin.name()); + Ok((plugin, lib)) } diff --git a/crates/plugin_bundle/src/lib.rs b/crates/plugin_bundle/src/lib.rs index bfcbbdb38a..8c16905e49 100644 --- a/crates/plugin_bundle/src/lib.rs +++ b/crates/plugin_bundle/src/lib.rs @@ -27,310 +27,310 @@ use farmfe_core::{ HashMap, HashSet, }; use farmfe_toolkit::constant::RUNTIME_SUFFIX; -// use resource_pot_to_bundle::{ -// BundleGroup, GeneratorAstResult, ShareBundleOptions, SharedBundle, FARM_BUNDLE_POLYFILL_SLOT, -// FARM_BUNDLE_REFERENCE_SLOT_PREFIX, -// }; - -// pub mod resource_pot_to_bundle; - -#[derive(Default)] -pub struct FarmPluginBundle { - // runtime_code: Mutex>, - // bundle_map: Mutex>, - resource_pot_id_resource_map: Mutex>, -} - -impl FarmPluginBundle { - pub fn new() -> Self { - Self::default() - } -} - -impl FarmPluginBundle { - fn should_bundle(config: &Config) -> bool { - config.output.target_env.is_library() - } -} - -impl Plugin for FarmPluginBundle { - fn name(&self) -> &str { - "farm-plugin-bundle" - } - - // fn config(&self, config: &mut Config) -> farmfe_core::error::Result> { - // if Self::should_bundle(&config) { - // // push it last - // config - // .partial_bundling - // .enforce_resources - // .push(PartialBundlingEnforceResourceConfig { - // name: "farm_runtime".to_string(), - // test: vec![ConfigRegex::new(FARM_BUNDLE_POLYFILL_SLOT)], - // }); - // } - - // Ok(None) - // } - - // fn resolve( - // &self, - // param: &PluginResolveHookParam, - // _context: &Arc, - // _hook_context: &PluginHookContext, - // ) -> farmfe_core::error::Result> { - // if param.source.starts_with(FARM_BUNDLE_POLYFILL_SLOT) { - // return Ok(Some(PluginResolveHookResult { - // resolved_path: FARM_BUNDLE_POLYFILL_SLOT.to_string(), - // external: false, - // side_effects: true, - // query: vec![], - // meta: Default::default(), - // })); - // } - - // Ok(None) - // } - - // fn load( - // &self, - // param: &PluginLoadHookParam, - // _context: &Arc, - // _hook_context: &PluginHookContext, - // ) -> farmfe_core::error::Result> { - // if param.resolved_path.starts_with(FARM_BUNDLE_POLYFILL_SLOT) { - // return Ok(Some(PluginLoadHookResult { - // // TODO: disable tree-shaking it - // content: r#"export {}"#.to_string(), - // module_type: ModuleType::Js, - // source_map: None, - // })); - // } - - // Ok(None) - // } - - // fn analyze_deps( - // &self, - // param: &mut farmfe_core::plugin::PluginAnalyzeDepsHookParam, - // context: &Arc, - // ) -> farmfe_core::error::Result> { - // let module_graph = context.module_graph.read(); - - // if Self::should_bundle(&context.config) - // && module_graph.entries.contains_key(¶m.module.id) - // && param.module.module_type.is_script() - // && !param.module.id.to_string().ends_with(RUNTIME_SUFFIX) - // { - // param.deps.push(PluginAnalyzeDepsHookResultEntry { - // source: FARM_BUNDLE_POLYFILL_SLOT.to_string(), - // kind: ResolveKind::Import, - // }); - // } - - // Ok(None) - // } - - // fn process_resource_pots( - // &self, - // resource_pots: &mut Vec<&mut ResourcePot>, - // context: &Arc, - // ) -> farmfe_core::error::Result> { - // println!( - // "process_resource_pots {} {}", - // self.runtime_code.lock().is_some(), - // resource_pots.len() - // ); - // if self.runtime_code.lock().is_some() { - // return Ok(None); - // } - - // let module_graph = context.module_graph.read(); - - // resource_pots.sort_by_key(|item| item.id.clone()); - - // let r = resource_pots - // .iter() - // .filter(|item| { - // context.config.output.target_env.is_library() - // || matches!(item.resource_pot_type, ResourcePotType::Runtime) - // }) - // .map(|item| BundleGroup::from(&**item)) - // .collect::>(); - // let mut shared_bundle = SharedBundle::new( - // r, - // &module_graph, - // context, - // Some(ShareBundleOptions { - // format: context.config.output.format, - // ..Default::default() - // }), - // )?; - - // shared_bundle.render()?; - - // println!("process_resource_pots {}", resource_pots.len(),); - - // for resource_pot in resource_pots.iter() { - // println!( - // "bundle resource pot id {} {:?}", - // resource_pot.id, resource_pot.resource_pot_type - // ); - // if matches!(resource_pot.resource_pot_type, ResourcePotType::Runtime) - // || (context.config.output.target_env.is_library() - // && matches!(resource_pot.resource_pot_type, ResourcePotType::Js)) - // { - // println!("bundle resource pot id {}", resource_pot.id); - // let resource_pot_id = resource_pot.id.clone(); - - // let module = shared_bundle.codegen(&resource_pot_id)?; - - // if matches!(resource_pot.resource_pot_type, ResourcePotType::Runtime) { - // *self.runtime_code.lock() = Some(module); - // } else { - // self.bundle_map.lock().insert(resource_pot_id, module); - // } - // } - // } - - // Ok(None) - // } - - // fn render_resource_pot( - // &self, - // resource_pot: &ResourcePot, - // _context: &Arc, - // _hook_context: &PluginHookContext, - // ) -> farmfe_core::error::Result> { - // println!( - // "render_resource_pot id {} {}", - // resource_pot.id, - // self.runtime_code.lock().is_some() - // ); - // if matches!(resource_pot.resource_pot_type, ResourcePotType::Runtime) { - // if let Some(code) = self.runtime_code.lock().as_ref() { - // return Ok(Some(ResourcePotMetaData::Js(JsResourcePotMetaData { - // // ast: code.ast.clone(), - // // comments: code.comments.clone(), - // external_modules: Default::default(), - // rendered_modules: Default::default(), - // }))); - // } - - // return Ok(None); - // } else if let Some(bundle) = self.bundle_map.lock().remove(&resource_pot.id) { - // return Ok(Some(ResourcePotMetaData::Js(JsResourcePotMetaData { - // // ast: bundle.ast, - // // comments: bundle.comments, - // external_modules: Default::default(), - // rendered_modules: Default::default(), - // }))); - // } - - // Ok(None) - // } - - // fn process_generated_resources( - // &self, - // resources: &mut PluginGenerateResourcesHookResult, - // _context: &Arc, - // ) -> farmfe_core::error::Result> { - // if let ResourceOrigin::ResourcePot(ref resource_pot_id) = resources.resource.origin { - // self - // .resource_pot_id_resource_map - // .lock() - // .insert(resource_pot_id.to_string(), resources.resource.name.clone()); - // } - - // Ok(None) - // } - - // fn finalize_resources( - // &self, - // param: &mut PluginFinalizeResourcesHookParams, - // context: &Arc, - // ) -> farmfe_core::error::Result> { - // if !context.config.output.target_env.is_library() { - // return Ok(None); - // } - - // let mut map = HashMap::default(); - - // for (name, resource) in param.resources_map.iter() { - // if let ResourceOrigin::ResourcePot(id) = &resource.origin { - // map.insert(id.clone(), name.clone()); - // } - // } - - // for (name, resource) in param.resources_map.iter_mut() { - // if !matches!( - // resource.resource_type, - // ResourceType::Js | ResourceType::Runtime - // ) { - // continue; - // } - // let before = std::time::Instant::now(); - - // let r = format!("/{}", name); - // let relative_path = RelativePath::new(&r); - - // let mut content = String::from_utf8_lossy(&resource.bytes).to_string(); - - // let reg = - // Regex::new(format!("{}\\(\\(.+?\\)\\)", FARM_BUNDLE_REFERENCE_SLOT_PREFIX).as_str()) - // .unwrap(); - - // let items = reg - // .captures_iter(&content) - // .flat_map(|i| { - // i.iter() - // .flatten() - // .map(|i| i.as_str().to_string()) - // .collect::>() - // }) - // .map(|i| i.as_str().to_string()) - // .collect::>(); - - // if items.is_empty() { - // continue; - // } - - // for item in items { - // let resource_pot_id = item - // .trim_start_matches(FARM_BUNDLE_REFERENCE_SLOT_PREFIX) - // .trim_start_matches("((") - // .trim_end_matches("))"); - // let resource_name = map - // .get(resource_pot_id) - // .expect("cannot find bundle reference, please ensure your resource cornet"); - - // let r1 = format!("/{}", resource_name); - - // println!("resource pot id {} to {} ", resource_pot_id, r1); - - // let relative_resource_path = RelativePath::new(&r1); - // content = content.replace( - // &item, - // &format!( - // "./{}", - // relative_path - // .parent() - // .map(|i| i.relative(relative_resource_path).to_string()) - // .unwrap() - // .trim_start_matches("/") - // ), - // ); - // } - - // resource.bytes = content.into_bytes(); - - // println!( - // "resource_name {} time: {}", - // name, - // before.elapsed().as_secs_f32() - // ); - // } - - // Ok(None) - // } -} +use resource_pot_to_bundle::{ + BundleGroup, GeneratorAstResult, ShareBundleOptions, SharedBundle, FARM_BUNDLE_POLYFILL_SLOT, + FARM_BUNDLE_REFERENCE_SLOT_PREFIX, +}; + +pub mod resource_pot_to_bundle; + +// #[derive(Default)] +// pub struct FarmPluginBundle { +// // runtime_code: Mutex>, +// // bundle_map: Mutex>, +// resource_pot_id_resource_map: Mutex>, +// } + +// impl FarmPluginBundle { +// pub fn new() -> Self { +// Self::default() +// } +// } + +// impl FarmPluginBundle { +// fn should_bundle(config: &Config) -> bool { +// config.output.target_env.is_library() +// } +// } + +// impl Plugin for FarmPluginBundle { +// fn name(&self) -> &str { +// "farm-plugin-bundle" +// } + +// fn config(&self, config: &mut Config) -> farmfe_core::error::Result> { +// if Self::should_bundle(&config) { +// // push it last +// config +// .partial_bundling +// .enforce_resources +// .push(PartialBundlingEnforceResourceConfig { +// name: "farm_runtime".to_string(), +// test: vec![ConfigRegex::new(FARM_BUNDLE_POLYFILL_SLOT)], +// }); +// } + +// Ok(None) +// } + +// fn resolve( +// &self, +// param: &PluginResolveHookParam, +// _context: &Arc, +// _hook_context: &PluginHookContext, +// ) -> farmfe_core::error::Result> { +// if param.source.starts_with(FARM_BUNDLE_POLYFILL_SLOT) { +// return Ok(Some(PluginResolveHookResult { +// resolved_path: FARM_BUNDLE_POLYFILL_SLOT.to_string(), +// external: false, +// side_effects: true, +// query: vec![], +// meta: Default::default(), +// })); +// } + +// Ok(None) +// } + +// fn load( +// &self, +// param: &PluginLoadHookParam, +// _context: &Arc, +// _hook_context: &PluginHookContext, +// ) -> farmfe_core::error::Result> { +// if param.resolved_path.starts_with(FARM_BUNDLE_POLYFILL_SLOT) { +// return Ok(Some(PluginLoadHookResult { +// // TODO: disable tree-shaking it +// content: r#"export {}"#.to_string(), +// module_type: ModuleType::Js, +// source_map: None, +// })); +// } + +// Ok(None) +// } + +// fn analyze_deps( +// &self, +// param: &mut farmfe_core::plugin::PluginAnalyzeDepsHookParam, +// context: &Arc, +// ) -> farmfe_core::error::Result> { +// let module_graph = context.module_graph.read(); + +// if Self::should_bundle(&context.config) +// && module_graph.entries.contains_key(¶m.module.id) +// && param.module.module_type.is_script() +// && !param.module.id.to_string().ends_with(RUNTIME_SUFFIX) +// { +// param.deps.push(PluginAnalyzeDepsHookResultEntry { +// source: FARM_BUNDLE_POLYFILL_SLOT.to_string(), +// kind: ResolveKind::Import, +// }); +// } + +// Ok(None) +// } + +// fn process_resource_pots( +// &self, +// resource_pots: &mut Vec<&mut ResourcePot>, +// context: &Arc, +// ) -> farmfe_core::error::Result> { +// println!( +// "process_resource_pots {} {}", +// self.runtime_code.lock().is_some(), +// resource_pots.len() +// ); +// if self.runtime_code.lock().is_some() { +// return Ok(None); +// } + +// let module_graph = context.module_graph.read(); + +// resource_pots.sort_by_key(|item| item.id.clone()); + +// let r = resource_pots +// .iter() +// .filter(|item| { +// context.config.output.target_env.is_library() +// || matches!(item.resource_pot_type, ResourcePotType::Runtime) +// }) +// .map(|item| BundleGroup::from(&**item)) +// .collect::>(); +// let mut shared_bundle = SharedBundle::new( +// r, +// &module_graph, +// context, +// Some(ShareBundleOptions { +// format: context.config.output.format, +// ..Default::default() +// }), +// )?; + +// shared_bundle.render()?; + +// println!("process_resource_pots {}", resource_pots.len(),); + +// for resource_pot in resource_pots.iter() { +// println!( +// "bundle resource pot id {} {:?}", +// resource_pot.id, resource_pot.resource_pot_type +// ); +// if matches!(resource_pot.resource_pot_type, ResourcePotType::Runtime) +// || (context.config.output.target_env.is_library() +// && matches!(resource_pot.resource_pot_type, ResourcePotType::Js)) +// { +// println!("bundle resource pot id {}", resource_pot.id); +// let resource_pot_id = resource_pot.id.clone(); + +// let module = shared_bundle.codegen(&resource_pot_id)?; + +// if matches!(resource_pot.resource_pot_type, ResourcePotType::Runtime) { +// *self.runtime_code.lock() = Some(module); +// } else { +// self.bundle_map.lock().insert(resource_pot_id, module); +// } +// } +// } + +// Ok(None) +// } + +// fn render_resource_pot( +// &self, +// resource_pot: &ResourcePot, +// _context: &Arc, +// _hook_context: &PluginHookContext, +// ) -> farmfe_core::error::Result> { +// println!( +// "render_resource_pot id {} {}", +// resource_pot.id, +// self.runtime_code.lock().is_some() +// ); +// if matches!(resource_pot.resource_pot_type, ResourcePotType::Runtime) { +// if let Some(code) = self.runtime_code.lock().as_ref() { +// return Ok(Some(ResourcePotMetaData::Js(JsResourcePotMetaData { +// // ast: code.ast.clone(), +// // comments: code.comments.clone(), +// external_modules: Default::default(), +// rendered_modules: Default::default(), +// }))); +// } + +// return Ok(None); +// } else if let Some(bundle) = self.bundle_map.lock().remove(&resource_pot.id) { +// return Ok(Some(ResourcePotMetaData::Js(JsResourcePotMetaData { +// // ast: bundle.ast, +// // comments: bundle.comments, +// external_modules: Default::default(), +// rendered_modules: Default::default(), +// }))); +// } + +// Ok(None) +// } + +// fn process_generated_resources( +// &self, +// resources: &mut PluginGenerateResourcesHookResult, +// _context: &Arc, +// ) -> farmfe_core::error::Result> { +// if let ResourceOrigin::ResourcePot(ref resource_pot_id) = resources.resource.origin { +// self +// .resource_pot_id_resource_map +// .lock() +// .insert(resource_pot_id.to_string(), resources.resource.name.clone()); +// } + +// Ok(None) +// } + +// fn finalize_resources( +// &self, +// param: &mut PluginFinalizeResourcesHookParams, +// context: &Arc, +// ) -> farmfe_core::error::Result> { +// if !context.config.output.target_env.is_library() { +// return Ok(None); +// } + +// let mut map = HashMap::default(); + +// for (name, resource) in param.resources_map.iter() { +// if let ResourceOrigin::ResourcePot(id) = &resource.origin { +// map.insert(id.clone(), name.clone()); +// } +// } + +// for (name, resource) in param.resources_map.iter_mut() { +// if !matches!( +// resource.resource_type, +// ResourceType::Js | ResourceType::Runtime +// ) { +// continue; +// } +// let before = std::time::Instant::now(); + +// let r = format!("/{}", name); +// let relative_path = RelativePath::new(&r); + +// let mut content = String::from_utf8_lossy(&resource.bytes).to_string(); + +// let reg = +// Regex::new(format!("{}\\(\\(.+?\\)\\)", FARM_BUNDLE_REFERENCE_SLOT_PREFIX).as_str()) +// .unwrap(); + +// let items = reg +// .captures_iter(&content) +// .flat_map(|i| { +// i.iter() +// .flatten() +// .map(|i| i.as_str().to_string()) +// .collect::>() +// }) +// .map(|i| i.as_str().to_string()) +// .collect::>(); + +// if items.is_empty() { +// continue; +// } + +// for item in items { +// let resource_pot_id = item +// .trim_start_matches(FARM_BUNDLE_REFERENCE_SLOT_PREFIX) +// .trim_start_matches("((") +// .trim_end_matches("))"); +// let resource_name = map +// .get(resource_pot_id) +// .expect("cannot find bundle reference, please ensure your resource cornet"); + +// let r1 = format!("/{}", resource_name); + +// println!("resource pot id {} to {} ", resource_pot_id, r1); + +// let relative_resource_path = RelativePath::new(&r1); +// content = content.replace( +// &item, +// &format!( +// "./{}", +// relative_path +// .parent() +// .map(|i| i.relative(relative_resource_path).to_string()) +// .unwrap() +// .trim_start_matches("/") +// ), +// ); +// } + +// resource.bytes = content.into_bytes(); + +// println!( +// "resource_name {} time: {}", +// name, +// before.elapsed().as_secs_f32() +// ); +// } + +// Ok(None) +// } +// } diff --git a/crates/plugin_bundle/src/resource_pot_to_bundle/bundle/bundle_analyzer.rs b/crates/plugin_bundle/src/resource_pot_to_bundle/bundle/bundle_analyzer.rs index dcf85d8216..7e8d02fcfc 100644 --- a/crates/plugin_bundle/src/resource_pot_to_bundle/bundle/bundle_analyzer.rs +++ b/crates/plugin_bundle/src/resource_pot_to_bundle/bundle/bundle_analyzer.rs @@ -1527,7 +1527,6 @@ impl<'a> BundleAnalyzer<'a> { let cm = self .context .meta - .script .merge_modules_source_mpa(&self.ordered_modules, self.module_graph); let get_start_pos = |module_id: &ModuleId| { diff --git a/crates/plugin_bundle/src/resource_pot_to_bundle/bundle/mod.rs b/crates/plugin_bundle/src/resource_pot_to_bundle/bundle/mod.rs index ed023b48e0..14138bbed9 100644 --- a/crates/plugin_bundle/src/resource_pot_to_bundle/bundle/mod.rs +++ b/crates/plugin_bundle/src/resource_pot_to_bundle/bundle/mod.rs @@ -1113,6 +1113,7 @@ mod tests { top_level_idents: Default::default(), unresolved_idents: Default::default(), statements: vec![], + feature_flags: Default::default(), is_async: false, })); }) diff --git a/crates/plugin_bundle/src/resource_pot_to_bundle/mod.rs b/crates/plugin_bundle/src/resource_pot_to_bundle/mod.rs index 9fbe04cf23..b725616d3b 100644 --- a/crates/plugin_bundle/src/resource_pot_to_bundle/mod.rs +++ b/crates/plugin_bundle/src/resource_pot_to_bundle/mod.rs @@ -170,13 +170,13 @@ impl<'a> SharedBundle<'a> { (&bundle_group.modules) .into_par_iter() .try_for_each(|module_id| { - let is_dynamic = module_graph.is_dynamic(module_id); + let is_dynamic = module_graph.is_dynamic_import(module_id); let is_entry = bundle_group .entry_module .as_ref() .is_some_and(|item| item == *module_id); let module = module_graph.module(module_id).unwrap(); - let is_runtime = matches!(module.module_type, ModuleType::Runtime); + // let is_runtime = matches!(module.module_type, ModuleType::Runtime); // 1-2. analyze bundle module let module_analyzer = ModuleAnalyzer::new( @@ -185,7 +185,8 @@ impl<'a> SharedBundle<'a> { bundle_group.id.clone(), is_entry, is_dynamic, - is_runtime, + // is_runtime, + false, )?; module_analyzer_map diff --git a/crates/plugin_library/CHANGELOG.md b/crates/plugin_library/CHANGELOG.md new file mode 100644 index 0000000000..9ae953797b --- /dev/null +++ b/crates/plugin_library/CHANGELOG.md @@ -0,0 +1,6 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + diff --git a/crates/plugin_library/Cargo.toml b/crates/plugin_library/Cargo.toml new file mode 100644 index 0000000000..4e12c1e4ed --- /dev/null +++ b/crates/plugin_library/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "farmfe_plugin_library" +version = "0.0.1" +edition = "2021" +authors = ["shulandmimi", "brightwwu"] +license = "MIT" +description = "Support bundling ESM/CJS modules into a pure ESM/CJS module for Farm, similar to Rollup/Esbuild." +homepage = "https://farmfe.org" +repository = "https://github.com/farm-fe/farm" +documentation = "https://docs.rs/farmfe_plugin_library" + +[dependencies] +farmfe_core = { path = "../core", version = "0.7.1" } +farmfe_toolkit = { path = "../toolkit", version = "0.0.15" } +farmfe_testing_helpers = { path = "../testing_helpers", version = "0.0.15" } +farmfe_utils = { path = "../utils", version = "0.1.6" } +farmfe_plugin_bundle = { path = "../plugin_bundle", version = "0.0.7" } + +[features] +profile = ["farmfe_core/profile"] diff --git a/crates/plugin_library/src/lib.rs b/crates/plugin_library/src/lib.rs new file mode 100644 index 0000000000..c17ac0c0fd --- /dev/null +++ b/crates/plugin_library/src/lib.rs @@ -0,0 +1,16 @@ +use farmfe_core::{config::Config, plugin::Plugin}; + +#[derive(Default)] +pub struct FarmPluginLibrary {} + +impl FarmPluginLibrary { + pub fn new(_: &Config) -> Self { + Self::default() + } +} + +impl Plugin for FarmPluginLibrary { + fn name(&self) -> &str { + "FarmPluginLibrary" + } +} diff --git a/crates/plugin_partial_bundling/src/analyze_module_graph.rs b/crates/plugin_partial_bundling/src/analyze_module_graph.rs index 0b68f24534..7a6cded18b 100644 --- a/crates/plugin_partial_bundling/src/analyze_module_graph.rs +++ b/crates/plugin_partial_bundling/src/analyze_module_graph.rs @@ -388,6 +388,7 @@ mod tests { kind: ResolveKind::DynamicEntry { name: "AD".to_string(), output_filename: None, + no_importer: true, }, ..Default::default() }, diff --git a/crates/plugin_runtime/src/insert_runtime_modules.rs b/crates/plugin_runtime/src/insert_runtime_modules.rs index ded9f86f76..1d8c2e7371 100644 --- a/crates/plugin_runtime/src/insert_runtime_modules.rs +++ b/crates/plugin_runtime/src/insert_runtime_modules.rs @@ -53,6 +53,8 @@ fn insert_dynamic_input_import( }]), ) .unwrap(); + + module_graph.dynamic_entries.remove(&dynamic_entry); } /// Example: diff --git a/crates/plugin_runtime/src/insert_runtime_plugins.rs b/crates/plugin_runtime/src/insert_runtime_plugins.rs index d0880c5e17..500b057485 100644 --- a/crates/plugin_runtime/src/insert_runtime_plugins.rs +++ b/crates/plugin_runtime/src/insert_runtime_plugins.rs @@ -5,7 +5,7 @@ use farmfe_toolkit::html::get_farm_global_this; const PLUGIN_VAR_PREFIX: &str = "__farm_plugin__"; -pub fn insert_runtime_plugins(content: &str, context: &Arc) -> String { +pub fn insert_runtime_plugins(context: &Arc) -> String { let plugins = context .config .runtime @@ -26,6 +26,11 @@ pub fn insert_runtime_plugins(content: &str, context: &Arc) (ident, import_stmt) }) .collect::>(); + + if plugins.is_empty() { + return "".to_string(); + } + let idents = plugins .iter() .map(|(ident, _)| ident.as_str()) @@ -47,5 +52,5 @@ pub fn insert_runtime_plugins(content: &str, context: &Arc) idents.join(",") ); - format!("{}\n{}\n{}", imports.join("\n"), content, plugins_call) + format!("{}\n{}", imports.join("\n"), plugins_call) } diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index 3551074d1f..be4af87c7a 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -15,7 +15,7 @@ use farmfe_core::{ module::{meta_data::script::feature_flag::FeatureFlag, Module, ModuleId, ModuleType}, parking_lot::Mutex, plugin::{ - Plugin, PluginAnalyzeDepsHookResultEntry, PluginFinalizeResourcesHookParams, + GeneratedResource, Plugin, PluginAnalyzeDepsHookResultEntry, PluginFinalizeResourcesHookParams, PluginGenerateResourcesHookResult, PluginHookContext, PluginLoadHookParam, PluginLoadHookResult, PluginResolveHookParam, PluginResolveHookResult, PluginTransformHookResult, ResolveKind, @@ -30,6 +30,7 @@ use farmfe_core::{ swc_ecma_ast::{Expr, ExprStmt, Module as SwcModule, ModuleItem, Stmt}, HashMap, HashSet, }; +use farmfe_plugin_bundle::resource_pot_to_bundle::{BundleGroup, ShareBundleOptions, SharedBundle}; use farmfe_toolkit::{ fs::read_file_utf8, html::get_farm_global_this, @@ -59,7 +60,9 @@ pub const RUNTIME_PACKAGE: &str = "@farmfe/runtime"; /// * merge module's ast and render the script module using farm runtime's specification, for example, wrap the module to something like `function(module, exports, require) { xxx }`, see [Farm Runtime RFC](https://github.com/farm-fe/rfcs/pull/1) /// /// All runtime module (including the runtime core and its plugins) will be suffixed as `.farm-runtime` to distinguish with normal script modules. -pub struct FarmPluginRuntime {} +pub struct FarmPluginRuntime { + added_runtime_modules: Mutex>, +} impl Plugin for FarmPluginRuntime { fn name(&self) -> &str { @@ -67,13 +70,6 @@ impl Plugin for FarmPluginRuntime { } fn config(&self, config: &mut Config) -> farmfe_core::error::Result> { - // runtime package entry file - if !config.runtime.path.is_empty() { - config - .input - .insert(RUNTIME_INPUT_SCOPE.to_string(), RUNTIME_PACKAGE.to_string()); - } - if !config.runtime.swc_helpers_path.is_empty() { config.resolve.alias.push(AliasItem::Complex { find: StringOrRegex::String("@swc/helpers".to_string()), @@ -81,14 +77,6 @@ impl Plugin for FarmPluginRuntime { }); } - // config.partial_bundling.enforce_resources.insert( - // 0, - // PartialBundlingEnforceResourceConfig { - // name: RUNTIME_INPUT_SCOPE.to_string(), - // test: vec![ConfigRegex::new(&format!(".+{RUNTIME_INPUT_SCOPE}"))], - // }, - // ); - config.define.insert( "'<@__farm_global_this__@>'".to_string(), serde_json::Value::String(format!( @@ -112,6 +100,12 @@ impl Plugin for FarmPluginRuntime { ..Default::default() })); } else if param.source.starts_with(RUNTIME_PACKAGE) { + if context.config.runtime.path.is_empty() { + return Err(CompilationError::GenericError( + "config.runtime.path is not set, please set or remove config.runtime.path in farm.config.ts. normally you should not set config.runtime.path manually".to_string(), + )); + } + let rest_str = param.source.replace(RUNTIME_PACKAGE, ""); return Ok(Some(PluginResolveHookResult { @@ -132,7 +126,7 @@ impl Plugin for FarmPluginRuntime { // load farm runtime entry as a empty module, it will be filled later in freeze_module hook if param.resolved_path == RUNTIME_PACKAGE { return Ok(Some(PluginLoadHookResult { - content: insert_runtime_plugins("", context), + content: insert_runtime_plugins(context), module_type: ModuleType::Js, source_map: None, })); @@ -150,38 +144,57 @@ impl Plugin for FarmPluginRuntime { return Ok(None); } - let mut add_runtime_dynamic_input = |name: &str| { + let mut add_runtime_dynamic_input = |name: &str, dir: &str| { + // add runtime module to the dynamic input if it's not added + let mut added_runtime_modules = self.added_runtime_modules.lock(); + + if added_runtime_modules.contains(name) { + return; + } + + let suffix = if name == "index" { + "".to_string() + } else { + format!("/src/{dir}{name}") + }; + param.deps.push(PluginAnalyzeDepsHookResultEntry { - source: format!("@farmfe/runtime/src/modules/{name}"), + source: format!("{RUNTIME_PACKAGE}{suffix}"), kind: ResolveKind::DynamicEntry { name: format!("{RUNTIME_INPUT_SCOPE}_{}", name.replace("-", "_")), output_filename: None, + no_importer: true, }, }); + + added_runtime_modules.insert(name.to_string()); }; + // add runtime package entry file for the first entry module + add_runtime_dynamic_input("index", ""); + // The goal of rendering runtime code is to make sure the runtime is as small as possible. // So we need to collect all the runtime related information in finalize_module hook, - // for example, if a module uses dynamic import, we will append import '@farmfe/runtime/modules/dynamic-import' to the runtime entry module. + // for example, if a module uses dynamic import, we will append import '@farmfe/runtime/src/modules/dynamic-import' to the runtime entry module. let feature_flags = ¶m.module.meta.as_script().feature_flags; if feature_flags.contains(&FeatureFlag::DefaultImport) { - add_runtime_dynamic_input("dynamic-import"); + add_runtime_dynamic_input("dynamic-import", "modules/"); } if feature_flags.contains(&FeatureFlag::ModuleDecl) { - add_runtime_dynamic_input("module-system-helper"); + add_runtime_dynamic_input("module-system-helper", "modules/"); } // module system is always required - add_runtime_dynamic_input("module-system"); + add_runtime_dynamic_input("module-system", ""); if context.config.mode.is_dev() { - add_runtime_dynamic_input("module-helper"); + add_runtime_dynamic_input("module-helper", "modules/"); } if context.config.runtime.plugins.len() > 0 { - add_runtime_dynamic_input("plugin"); + add_runtime_dynamic_input("plugin", "modules/"); } Ok(Some(())) @@ -196,12 +209,45 @@ impl Plugin for FarmPluginRuntime { Ok(Some(())) } + fn process_resource_pots( + &self, + resource_pots: &mut Vec<&mut ResourcePot>, + _context: &Arc, + ) -> farmfe_core::error::Result> { + // find runtime resource pot and set the resource pot type to Runtime + for resource_pot in resource_pots { + if resource_pot.name.starts_with(RUNTIME_INPUT_SCOPE) { + resource_pot.resource_pot_type = ResourcePotType::Runtime; + } + } + + Ok(Some(())) + } + fn render_resource_pot( &self, resource_pot: &ResourcePot, context: &Arc, _hook_context: &PluginHookContext, ) -> farmfe_core::error::Result> { + // render runtime resource pot + if matches!(resource_pot.resource_pot_type, ResourcePotType::Runtime) { + let module_graph = context.module_graph.read(); + let bundle_group = BundleGroup::from(resource_pot); + let bundle_group_id = bundle_group.id.clone(); + // concatenate all runtime modules, all runtime modules should be esm only + let mut bundle = SharedBundle::new(vec![bundle_group], &module_graph, context, None)?; + bundle.render()?; + + let result = bundle.codegen(&bundle_group_id)?; + return Ok(Some(ResourcePotMetaData::Js(JsResourcePotMetaData { + ast: result.ast, + external_modules: Default::default(), + rendered_modules: result.rendered_modules, + comments: result.comments, + }))); + } + if resource_pot.resource_pot_type != ResourcePotType::Js { return Ok(None); } @@ -245,6 +291,15 @@ impl Plugin for FarmPluginRuntime { }))) } + // fn handle_entry_resource( + // &self, + // _resource: &mut farmfe_core::plugin::PluginHandleEntryResourceHookParams, + // _context: &Arc, + // ) -> farmfe_core::error::Result> { + // // TODO handle runtime resource for entry + // Ok(None) + // } + fn finalize_resources( &self, param: &mut PluginFinalizeResourcesHookParams, @@ -262,6 +317,8 @@ impl Plugin for FarmPluginRuntime { impl FarmPluginRuntime { pub fn new(_: &Config) -> Self { - Self {} + Self { + added_runtime_modules: Mutex::new(HashSet::default()), + } } } diff --git a/crates/plugin_runtime/src/render_resource_pot/mod.rs b/crates/plugin_runtime/src/render_resource_pot/mod.rs index 747b711260..82d141c8b7 100644 --- a/crates/plugin_runtime/src/render_resource_pot/mod.rs +++ b/crates/plugin_runtime/src/render_resource_pot/mod.rs @@ -70,20 +70,20 @@ pub fn render_resource_pot_modules( ) }); - // let mut hoisted_ast = if hoisted_group.hoisted_module_ids.len() > 1 { - // Some(hoisted_group.render(module_graph, context)?) - // } else { - // None - // }; + let mut hoisted_ast = if hoisted_group.hoisted_module_ids.len() > 1 { + Some(hoisted_group.concatenate_modules(module_graph, context)?) + } else { + None + }; - // let hoisted_modules = hoisted_ast - // .as_mut() - // .map(|item| item.rendered_modules.take()); + let hoisted_modules = hoisted_ast + .as_mut() + .map(|item| item.rendered_modules.take()); let render_module_result = render_module(RenderModuleOptions { module, module_graph, - // hoisted_ast, + hoisted_ast, context, })?; diff --git a/crates/plugin_runtime/src/render_resource_pot/render_module.rs b/crates/plugin_runtime/src/render_resource_pot/render_module.rs index acfa32aaef..ffa9d57489 100644 --- a/crates/plugin_runtime/src/render_resource_pot/render_module.rs +++ b/crates/plugin_runtime/src/render_resource_pot/render_module.rs @@ -12,6 +12,7 @@ use farmfe_core::{ }, swc_ecma_ast::{ArrowExpr, BlockStmtOrExpr, Expr, ExprStmt, FnExpr}, }; +use farmfe_plugin_bundle::resource_pot_to_bundle::GeneratorAstResult; // use farmfe_plugin_bundle::resource_pot_to_bundle::GeneratorAstResult; use farmfe_toolkit::{ minify::minify_js_module, @@ -44,7 +45,7 @@ use super::{ pub struct RenderModuleOptions<'a> { pub module: &'a Module, - // pub hoisted_ast: Option, + pub hoisted_ast: Option, pub module_graph: &'a ModuleGraph, pub context: &'a Arc, } @@ -54,24 +55,24 @@ pub fn render_module( ) -> farmfe_core::error::Result { let RenderModuleOptions { module, - // hoisted_ast, + hoisted_ast, module_graph, context, } = options; let is_async_module = module.meta.as_script().is_async; - // let is_use_hoisted = hoisted_ast.is_some(); + let is_use_hoisted = hoisted_ast.is_some(); - // let (mut cloned_module, comments) = - // if let Some(GeneratorAstResult { ast, comments, .. }) = hoisted_ast { - // (ast, SingleThreadedComments::from(comments)) - // } else { - // let script = module.meta.as_script(); - // (script.ast.clone(), script.comments.clone().into()) - // }; - let (mut cloned_module, comments): (SwcModule, SingleThreadedComments) = { - let script = module.meta.as_script(); - (script.ast.clone(), script.comments.clone().into()) - }; + let (mut cloned_module, comments) = + if let Some(GeneratorAstResult { ast, comments, .. }) = hoisted_ast { + (ast, SingleThreadedComments::from(comments)) + } else { + let script = module.meta.as_script(); + (script.ast.clone(), script.comments.clone().into()) + }; + // let (mut cloned_module, comments): (SwcModule, SingleThreadedComments) = { + // let script = module.meta.as_script(); + // (script.ast.clone(), script.comments.clone().into()) + // }; let (cm, _) = context .meta .create_swc_source_map(&module.id, module.content.clone()); @@ -131,8 +132,8 @@ pub fn render_module( module_id: module.id.clone(), mode: context.config.mode.clone(), target_env: context.config.output.target_env.clone(), - // is_strict_find_source: !is_use_hoisted, - is_strict_find_source: false, + is_strict_find_source: !is_use_hoisted, + // is_strict_find_source: false, }); cloned_module.visit_mut_with(&mut source_replacer); cloned_module.visit_mut_with(&mut hygiene_with_config(HygieneConfig { diff --git a/crates/plugin_runtime/src/render_resource_pot/scope_hoisting.rs b/crates/plugin_runtime/src/render_resource_pot/scope_hoisting.rs index edab93ffd3..8a0cdf4f96 100644 --- a/crates/plugin_runtime/src/render_resource_pot/scope_hoisting.rs +++ b/crates/plugin_runtime/src/render_resource_pot/scope_hoisting.rs @@ -9,6 +9,9 @@ use farmfe_core::{ swc_ecma_ast::Module, HashMap, HashSet, }; +use farmfe_plugin_bundle::resource_pot_to_bundle::{ + BundleGroup, GeneratorAstResult, ShareBundleOptions, SharedBundle, +}; // use farmfe_plugin_bundle::resource_pot_to_bundle::{ // BundleGroup, GeneratorAstResult, ShareBundleOptions, SharedBundle, @@ -46,49 +49,46 @@ impl ScopeHoistedModuleGroup { self.hoisted_module_ids.extend(hoisted_module_ids); } - // /// Render this [ScopeHoistedModuleGroup] to a Farm runtime module. For example: - // /// ```js - // /// function(module, exports, farmRequire, farmDynamicRequire) { - // /// const xxx = farmDynamicRequire('./xxx'); - // /// - // /// const module_D = 'D'; // hoisted code of module D - // /// const module_C = 'C'; // hoisted code of module C - // /// const module_B = 'B'; // hoisted code of module B - // /// console.log(module_D, module_C, module_B, xxx); // code of module A - // /// - // /// module.o(exports, 'b', module_B); - // /// } - // /// ``` - // pub fn render( - // &self, - // module_graph: &ModuleGraph, - // context: &Arc, - // ) -> farmfe_core::error::Result { - // let bundle_id = self.target_hoisted_module_id.to_string(); - - // let mut share_bundle = SharedBundle::new( - // vec![BundleGroup { - // id: bundle_id.clone(), - // modules: self.hoisted_module_ids.iter().collect(), - // entry_module: Some(self.target_hoisted_module_id.clone()), - // group_type: ResourcePotType::Js, - // }], - // module_graph, - // context, - // Some(ShareBundleOptions { - // reference_slot: false, - // ignore_external_polyfill: true, - // // should ignore - // format: ModuleFormat::EsModule, - // hash_path: true, - // concatenation_module: true, - // ..Default::default() - // }), - // )?; - - // share_bundle.render()?; - // share_bundle.codegen(&bundle_id) - // } + /// concatenate the [ScopeHoistedModuleGroup] into a single ast. For example: + /// ```js + /// const xxx = farmDynamicRequire('./xxx'); + /// const module_D = 'D'; // hoisted code of module D + /// const module_C = 'C'; // hoisted code of module C + /// const module_B = 'B'; // hoisted code of module B + /// console.log(module_D, module_C, module_B, xxx); // code of module A + /// + /// module.o(exports, 'b', module_B); + /// ``` + pub fn concatenate_modules( + &self, + module_graph: &ModuleGraph, + context: &Arc, + ) -> farmfe_core::error::Result { + let bundle_id = self.target_hoisted_module_id.to_string(); + + let mut share_bundle = SharedBundle::new( + vec![BundleGroup { + id: bundle_id.clone(), + modules: self.hoisted_module_ids.iter().collect(), + entry_module: Some(self.target_hoisted_module_id.clone()), + group_type: ResourcePotType::Js, + }], + module_graph, + context, + Some(ShareBundleOptions { + reference_slot: false, + ignore_external_polyfill: true, + // should ignore + format: ModuleFormat::EsModule, + hash_path: true, + concatenation_module: true, + ..Default::default() + }), + )?; + + share_bundle.render()?; + share_bundle.codegen(&bundle_id) + } } /// Handle the modules of a resource pot in topological order. diff --git a/crates/plugin_script/src/lib.rs b/crates/plugin_script/src/lib.rs index 35cb95f030..ae64e6547f 100644 --- a/crates/plugin_script/src/lib.rs +++ b/crates/plugin_script/src/lib.rs @@ -311,11 +311,9 @@ impl Plugin for FarmPluginScript { &self, context: &Arc, ) -> farmfe_core::error::Result> { - println!("generate start"); let async_modules = find_async_modules::find_async_modules(context); - println!("find async modules"); let mut module_graph = context.module_graph.write(); - println!("write module graph"); + for module_id in async_modules { let module = module_graph.module_mut(&module_id).unwrap(); module.meta.as_script_mut().is_async = true; @@ -341,7 +339,7 @@ impl Plugin for FarmPluginScript { _hook_context: &PluginHookContext, ) -> farmfe_core::error::Result> { if let ResourcePotMetaData::Js(JsResourcePotMetaData { - ast: wrapped_resource_pot_ast, + ast, comments: merged_comments, rendered_modules, .. @@ -356,7 +354,7 @@ impl Plugin for FarmPluginScript { let (code, map) = generate_code_and_sourcemap( resource_pot, &module_graph, - &wrapped_resource_pot_ast, + &ast, merged_sourcemap, merged_comments.into(), context, diff --git a/crates/toolkit/src/concatenate_modules/mod.rs b/crates/toolkit/src/concatenate_modules/mod.rs deleted file mode 100644 index b48b1d41a2..0000000000 --- a/crates/toolkit/src/concatenate_modules/mod.rs +++ /dev/null @@ -1,105 +0,0 @@ -use std::sync::Arc; - -use farmfe_core::{ - context::CompilationContext, - module::{meta_data::script::statement::ExportInfo, module_graph::ModuleGraph, ModuleId}, - plugin::ResolveKind, - swc_ecma_ast::Module as SwcModule, - HashSet, -}; - -pub struct ConcatenateModulesAstResult { - /// The concatenated AST of the modules - pub ast: SwcModule, - /// The module IDs of the modules that are concatenated in order - pub module_ids: Vec, - /// The external modules that are imported by the modules - pub external_modules: HashSet, -} - -/// Concatenate the ASTs of the modules in the module graph starting from the entry module by DFS -/// for example, if the input files are: -/// ```js -/// // a.js -/// import b from './b.js'; -/// console.log(b + 1); -/// -/// // b.js -/// export default 1; -/// ``` -/// -/// The output should be: -/// ```js -/// // b.js -/// var b = 1; -/// // a.js -/// console.log(b + 1); -/// ``` -pub fn concatenate_modules_ast( - entry_module_id: ModuleId, - module_ids: &HashSet, - module_graph: &ModuleGraph, - context: &Arc, -) -> ConcatenateModulesAstResult { - let mut visited = HashSet::default(); - - traverse_modules_dfs( - entry_module_id, - module_ids, - module_graph, - context, - &mut visited, - ) -} - -fn traverse_modules_dfs( - entry_module_id: ModuleId, - module_ids: &HashSet, - module_graph: &ModuleGraph, - context: &Arc, - visited: &mut HashSet, -) -> ConcatenateModulesAstResult { - let entry_module = module_graph.module(&entry_module_id).unwrap(); - let entry_module_meta = entry_module.meta.as_script(); - let mut concatenated_ast = entry_module_meta.ast.clone(); - let mut concatenated_module_ids = vec![]; - let mut external_modules: HashSet = HashSet::default(); - - visited.insert(entry_module_id.clone()); - - // traverse the module starting from the entry module by DFS - for statement in &entry_module_meta.statements { - // deal with import/export from statement - if let Some(import_info) = &statement.import_info { - let dep_module_id = module_graph.get_dep_by_source( - &entry_module_id, - &import_info.source, - Some(ResolveKind::Import), - ); - - if !visited.contains(&dep_module_id) { - let result = - traverse_modules_dfs(dep_module_id, module_ids, module_graph, context, visited); - - concatenated_ast - .body - .splice(statement.id..(statement.id + 1), result.ast.body); - concatenated_module_ids.extend(result.module_ids); - } - } else if let Some(ExportInfo { - source: Some(source), - specifiers, - .. - }) = &statement.export_info - { - } - } - - concatenated_module_ids.push(entry_module_id); - - ConcatenateModulesAstResult { - ast: concatenated_ast, - module_ids: concatenated_module_ids, - external_modules, - } -} diff --git a/crates/toolkit/src/lib.rs b/crates/toolkit/src/lib.rs index 5f9bbd84df..1a34b7719c 100644 --- a/crates/toolkit/src/lib.rs +++ b/crates/toolkit/src/lib.rs @@ -1,7 +1,6 @@ #![feature(box_patterns)] #![feature(let_chains)] -pub mod concatenate_modules; pub mod css; pub mod fs; pub mod hash; diff --git a/crates/toolkit/src/script/concatenate_modules/expand_exports.rs b/crates/toolkit/src/script/concatenate_modules/expand_exports.rs new file mode 100644 index 0000000000..9f41b5e60a --- /dev/null +++ b/crates/toolkit/src/script/concatenate_modules/expand_exports.rs @@ -0,0 +1,74 @@ +use farmfe_core::{module::module_graph::ModuleGraph, HashMap, HashSet}; + +/// expand the exports of the module graph +/// for example, if the input files are: +/// ```js +/// // a.js +/// export * from './b.js'; +/// export { bar } from './c.js'; +/// export { default as baz } from './d.js'; +/// export * as ns from './e.js'; +/// export * from './f.js'; +/// +/// // b.js +/// export * as a from './a.js'; +/// export * from './a.js'; +/// export var foo = 1; +/// +/// // c.js +/// export * as d from './d.js'; +/// export var bar = 2; +/// +/// // d.js +/// export * from './e.js'; +/// export default 3; +/// +/// // e.js +/// export var e = 4; +/// export default function() { console.log('e'); } +/// +/// // f.js +/// var f = 5; +/// var f_d = 'f_d'; +/// export { f, f_d as default }; +/// ``` +/// The output should be: +/// ```js +/// // exports of a.js +/// { foo: 1, bar: 2, baz: 3, ns: e_ns, f: 5 } +/// +/// // exports of b.js +/// { a: a_ns, foo: 1, bar: 2, baz: 3, ns: e_ns, f: 5, default: 'f_d' } +/// +/// // exports of c.js +/// { d: d_ns, bar: 2 } +/// +/// // exports of d.js +/// { e: 4, default: 3 } +/// +/// // exports of e.js +/// { e: 4, default: function() { console.log('e'); } } +/// +/// // exports of f.js +/// { f: 5, default: 'f_d' } +/// ``` +pub fn expand_exports(module_graph: &ModuleGraph) -> HashMap { + // 1. collect all exports by breadth-first search in the module graph(a common utility function that is used in concatenating modules and mangle exports) + // 1.1. the export type can be: namespace object, named export(including default), reexport + // 1.2. change the internal export of each module from the importer similar to the tree shaking + // 2. concatenate import/export statements based on the collected exports above + let mut reference_exports = HashMap::new(); + let mut visited = HashSet::new(); + + for module_id in module_graph.module_ids() { + let module = module_graph.module(&module_id).unwrap(); + if visited.contains(&module_id) { + continue; + } + + let reference_export = expand_exports_of_module(module_id, module_graph, &mut visited)?; + reference_exports.insert(module_id, reference_export); + } + + reference_exports +} diff --git a/crates/toolkit/src/script/concatenate_modules/ident_generator.rs b/crates/toolkit/src/script/concatenate_modules/ident_generator.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/toolkit/src/script/concatenate_modules/mod.rs b/crates/toolkit/src/script/concatenate_modules/mod.rs new file mode 100644 index 0000000000..d83c6cc61a --- /dev/null +++ b/crates/toolkit/src/script/concatenate_modules/mod.rs @@ -0,0 +1,108 @@ +use std::sync::Arc; + +use farmfe_core::{ + context::CompilationContext, + module::{ + meta_data::script::statement::ExportInfo, module_graph::ModuleGraph, ModuleId, ModuleSystem, + }, + plugin::ResolveKind, + swc_common::DUMMY_SP, + swc_ecma_ast::Module as SwcModule, + HashSet, +}; + +mod expand_exports; +mod ident_generator; +mod strip_module; + +pub struct ConcatenateModulesAstResult { + /// The concatenated AST of the modules + pub ast: SwcModule, + /// The module IDs of the modules that are concatenated in order + pub module_ids: Vec, + /// The external modules that are imported by the modules + pub external_modules: HashSet, +} + +/// Concatenate the ASTs of the modules in the module graph starting from the entry module +/// for example, if the input files are: +/// ```js +/// // a.js +/// import b from './b.js'; +/// console.log(b + 1); +/// +/// // b.js +/// export default 1; +/// ``` +/// +/// The output should be: +/// ```js +/// // b.js +/// var b = 1; +/// // a.js +/// console.log(b + 1); +/// +/// The concatenation algorithm is as follows: +/// 1. Sort the modules by execution order +/// 2. Check if the module is esm, error if it is not +/// 3. Visit the sorted modules in order, for each module: +/// - Visit the module's AST and rewrite the import/export statements to use the module's variable name +/// - collect external modules that are not in module_ids +/// - for dynamic import, if the dynamic imported is in module_ids, replace dynamic import with module_ns, otherwise, replace it with the other resource path +/// 4. Concatenate the ASTs of the modules in order and return the result +/// ``` +pub fn concatenate_modules_ast( + module_ids: &HashSet, + module_graph: &ModuleGraph, + context: &Arc, +) -> Result { + // 1. Sort the modules by execution order + let mut sorted_modules: Vec<_> = module_ids.iter().collect(); + sorted_modules.sort_by_key(|module_id| module_graph.module(module_id).unwrap().execution_order); + + // 2. Check if the module is esm, panic if it is not + for module_id in sorted_modules.iter() { + let module = module_graph.module(module_id).unwrap(); + // error if it is no script module + if !module.module_type.is_script() { + return Err("Only script modules are supported"); + } + + // error if it is not ESM + if module.meta.as_script().module_system != ModuleSystem::EsModule { + return Err("Only ESM modules are supported"); + } + } + + // 3. Visit sorted modules and process them + let mut concatenated_ast = SwcModule { + span: DUMMY_SP, + body: vec![], + shebang: None, + }; + + let mut external_modules = HashSet::default(); + + for module_id in sorted_modules { + let module = module_graph.module(module_id).unwrap(); + let script_meta = module.meta.as_script(); + + // Visit and rewrite import/export statements + let mut ast = script_meta.ast.clone(); + // rewrite_imports_exports(&mut ast, module_id); + + // // Collect external modules + // collect_external_modules(&ast, module_ids, &mut external_modules); + + // Add to concatenated AST + concatenated_ast.body.extend(ast.body); + } + + // 4. Return the concatenated result + Ok(ConcatenateModulesAstResult { + ast: concatenated_ast, + // module_ids: sorted_modules.into_iter().cloned().collect(), + module_ids: vec![], + external_modules, + }) +} diff --git a/crates/toolkit/src/script/concatenate_modules/strip_module.rs b/crates/toolkit/src/script/concatenate_modules/strip_module.rs new file mode 100644 index 0000000000..f9df2d24cd --- /dev/null +++ b/crates/toolkit/src/script/concatenate_modules/strip_module.rs @@ -0,0 +1,174 @@ +use farmfe_core::{ + farm_profile_function, farm_profile_scope, + module::{ + meta_data::script::statement::{ExportSpecifierInfo, ImportSpecifierInfo, StatementId}, + module_graph::ModuleGraph, + ModuleId, + }, + plugin::ResolveKind, + swc_ecma_ast::Module as SwcModule, + HashSet, +}; + +pub fn strip_module_decl(module_id: &ModuleId, module_graph: &ModuleGraph) -> SwcModule { + let module = module_graph.module(module_id).unwrap(); + let script_meta = module.meta.as_script(); + let mut ast = script_meta.ast.clone(); + + for statement in &script_meta.statements { + if let Some(import_info) = &statement.import_info { + let source_module_id = module_graph.get_dep_by_source(module_id, &import_info.source, None); + let source_module = module_graph.module(&source_module_id).unwrap(); + let source_module_script_meta = source_module.meta.as_script(); + + for specifier in &import_info.specifiers { + match specifier { + // import { foo, bar as baz } from './module'; + // => + // if module is esm: var foo = foo, baz = bar; + // if module is cjs: var module_cjs = module_default(), foo = module_cjs.foo, baz = module_cjs.bar; + ImportSpecifierInfo::Named { local, imported } => {} + + // import foo from './module'; + // => + // if module is esm: var foo = module_default; + // if module is cjs: var foo = _interopRequireDefault(module_default()).default; + ImportSpecifierInfo::Default(_) => {} + + // import * as ns from './module'; + // => + // if module is esm: var ns = module_ns; + // if module is cjs: var ns = module_default(); + ImportSpecifierInfo::Namespace(_) => { + // remove + } + } + } + } else if let Some(export_info) = &statement.export_info { + if let Some(source) = &export_info.source { + let source_module_id = module_graph.get_dep_by_source(module_id, source, None); + let source_module = module_graph.module(&source_module_id).unwrap(); + let source_module_script_meta = source_module.meta.as_script(); + + let mut stmts_to_remove = vec![]; + + for specifier in &export_info.specifiers { + match specifier { + // export * from './module'; + // => + // remove. cause the reexport is handled when expanding exports + ExportSpecifierInfo::All => { + stmts_to_remove.push(statement.id); + } + + // export default 'expression'; + // => + // var module_default = 'expression'; + // + // export default const foo = 1; + // => + // const foo = 1; var module_default = foo; + ExportSpecifierInfo::Default => {} + + // export { foo, bar as baz } from './module'; + // => + // if module is esm: var foo = foo, var baz = bar; + // if module is cjs: var module_cjs = module_default(), var foo = module_cjs.foo, var baz = module_cjs.bar; + // note that `baz` might be default + ExportSpecifierInfo::Named { .. } => { + stmts_to_remove.push(statement.id); + } + + // export * as ns from './module'; + // => + // if module is esm: var ns = module_ns; + // if module is cjs: var ns = module_default(); + ExportSpecifierInfo::Namespace(_) => { + stmts_to_remove.push(statement.id); + } + } + } + } else { + } + } + } + ast +} + +pub fn analyze_module_strip_action( + topo_sorted_module_ids: &Vec, + module_graph: &ModuleGraph, +) -> farmfe_core::error::Result<()> { + farm_profile_function!("strip module start"); + + for module_id in topo_sorted_module_ids { + farm_profile_scope!(format!("strip module: {}", module_id.to_string())); + + let module = module_graph.module(module_id).unwrap(); + let script_meta = module.meta.as_script(); + + let mut stmt_action = HashSet::default(); + + for statement in &script_meta.statements { + // import + if let Some(import) = statement.import_info.as_ref() { + if script_meta.module_system.contains_commonjs() { + let source_module_id = + module_graph.get_dep_by_source(module_id, &import.source, Some(ResolveKind::Import)); + stmt_action.insert(StmtAction::StripCjsImport( + statement.id, + if import.specifiers.is_empty() { + Some(source_module_id) + } else { + None + }, + )); + } else { + stmt_action.insert(StmtAction::RemoveImport(statement.id)); + } + } + + // export + if let Some(export) = statement.export_info.as_ref() { + if module_analyzer.is_commonjs() { + continue; + } + + if export.specifiers.is_empty() { + stmt_action.insert(StmtAction::StripExport(statement.id)); + continue; + } + + if export.source.is_some() { + stmt_action.insert(StmtAction::StripExport(statement.id)); + } else { + for specify in &export.specifiers { + match specify { + ExportSpecifierInfo::All(_) | ExportSpecifierInfo::Named { .. } => { + stmt_action.insert(StmtAction::StripExport(statement.id)); + } + + ExportSpecifierInfo::Default(default) => { + if self.bundle_variable.borrow().name(*default) == "default" { + stmt_action.insert(StmtAction::DeclDefaultExpr(statement.id, *default)); + } else { + stmt_action.insert(StmtAction::StripDefaultExport(statement.id, *default)); + } + } + + ExportSpecifierInfo::Namespace(_) => { + unreachable!("unsupported namespace have't source") + } + } + } + } + } + } + + if let Some(module_analyzer) = module_analyzer_manager.module_analyzer_mut(module_id) { + module_analyzer.statement_actions.extend(stmt_action); + } + } + + Ok(()) +} diff --git a/crates/toolkit/src/script/mod.rs b/crates/toolkit/src/script/mod.rs index 0cec94d1a8..a335a0e2b4 100644 --- a/crates/toolkit/src/script/mod.rs +++ b/crates/toolkit/src/script/mod.rs @@ -28,6 +28,7 @@ use crate::{minify::comments::minify_comments, source_map::create_swc_source_map pub use farmfe_toolkit_plugin_types::swc_ast::ParseScriptModuleResult; +pub mod concatenate_modules; pub mod constant; pub mod defined_idents_collector; pub mod module2cjs;