diff --git a/.changeset/polite-waves-rest.md b/.changeset/polite-waves-rest.md new file mode 100644 index 000000000..7ab5c79bc --- /dev/null +++ b/.changeset/polite-waves-rest.md @@ -0,0 +1,5 @@ +--- +"@farmfe/core": patch +--- + +Support single bundle library diff --git a/crates/plugin_bundle/src/lib.rs b/crates/plugin_bundle/src/lib.rs index d50847d59..fae6456a7 100644 --- a/crates/plugin_bundle/src/lib.rs +++ b/crates/plugin_bundle/src/lib.rs @@ -3,6 +3,8 @@ use std::{collections::HashMap, sync::Arc}; use farmfe_core::{ + config::TargetEnv, + enhanced_magic_string::bundle::Bundle, parking_lot::Mutex, plugin::Plugin, resource::resource_pot::{ResourcePotMetaData, ResourcePotType}, @@ -20,6 +22,7 @@ const MODULE_NEED_POLYFILLS: [Polyfill; 3] = [ #[derive(Default)] pub struct FarmPluginBundle { runtime_code: Mutex>, + bundle_map: Mutex>, } impl FarmPluginBundle { @@ -45,29 +48,51 @@ impl Plugin for FarmPluginBundle { resource_pots.sort_by_key(|item| item.id.clone()); - for resource_pot in resource_pots { - if matches!(resource_pot.resource_pot_type, ResourcePotType::Runtime) { - let mut shared_bundle = SharedBundle::new(vec![&resource_pot], &module_graph, context)?; + let r = resource_pots.iter().map(|item| &**item).collect::>(); + let mut shared_bundle = SharedBundle::new(r, &module_graph, context)?; + + let runtime_resource_pot = resource_pots + .iter() + .find(|item| matches!(item.resource_pot_type, ResourcePotType::Runtime)) + .map(|i| i.id.clone()); - let polyfill = &mut shared_bundle - .bundle_map - .get_mut(&resource_pot.id) - .unwrap() - .polyfill; + if let Some(runtime_resource_pot_id) = runtime_resource_pot { + let polyfill = &mut shared_bundle + .bundle_map + .get_mut(&runtime_resource_pot_id) + .unwrap() + .polyfill; + + MODULE_NEED_POLYFILLS + .iter() + .for_each(|item| polyfill.add(item.clone())); + } - MODULE_NEED_POLYFILLS - .iter() - .for_each(|item| polyfill.add(item.clone())); - shared_bundle.render()?; + shared_bundle.render()?; + let mut defer_minify = vec![]; + for resource_pot in resource_pots.iter() { + if matches!(resource_pot.resource_pot_type, ResourcePotType::Runtime) + || (context.config.output.target_env == TargetEnv::Library + && resource_pot.resource_pot_type == ResourcePotType::Js) + { let resource_pot_id = resource_pot.id.clone(); let bundle = shared_bundle.codegen(&resource_pot_id)?; - resource_pot.defer_minify_as_resource_pot(); + defer_minify.push(resource_pot_id.clone()); - *self.runtime_code.lock() = Arc::new(bundle.to_string()); - break; + if matches!(resource_pot.resource_pot_type, ResourcePotType::Runtime) { + *self.runtime_code.lock() = Arc::new(bundle.to_string()); + } else { + self.bundle_map.lock().insert(resource_pot_id, bundle); + } + } + } + + for resource_pot in resource_pots { + if defer_minify.contains(&resource_pot.id) { + resource_pot.defer_minify_as_resource_pot(); } } @@ -88,6 +113,14 @@ impl Plugin for FarmPluginBundle { rendered_map_chain: vec![], custom_data: resource_pot.meta.custom_data.clone(), })); + } else if let Some(bundle) = self.bundle_map.lock().get(&resource_pot.id) { + return Ok(Some(ResourcePotMetaData { + // TODO + rendered_modules: HashMap::new(), + rendered_content: Arc::new(bundle.to_string()), + rendered_map_chain: vec![], + custom_data: resource_pot.meta.custom_data.clone(), + })); } Ok(None) diff --git a/crates/plugin_minify/src/imports_minifier.rs b/crates/plugin_minify/src/imports_minifier.rs index 3200c3d31..82b461e7e 100644 --- a/crates/plugin_minify/src/imports_minifier.rs +++ b/crates/plugin_minify/src/imports_minifier.rs @@ -5,9 +5,10 @@ use farmfe_core::{ plugin::ResolveKind, swc_common::{Mark, DUMMY_SP}, swc_ecma_ast::{ - CallExpr, Callee, ExportNamedSpecifier, ExportSpecifier, Expr, ExprOrSpread, ExprStmt, Id, - Ident, KeyValueProp, Lit, MemberExpr, MemberProp, ModuleDecl, ModuleExportName, ModuleItem, - NamedExport, ObjectLit, Prop, PropName, PropOrSpread, Stmt, Str, + BindingIdent, CallExpr, Callee, ExportNamedSpecifier, ExportSpecifier, Expr, ExprOrSpread, + ExprStmt, Id, Ident, KeyValuePatProp, KeyValueProp, Lit, MemberExpr, MemberProp, ModuleDecl, + ModuleExportName, ModuleItem, NamedExport, ObjectLit, Pat, Prop, PropName, PropOrSpread, Stmt, + Str, }, }; use farmfe_toolkit::swc_ecma_visit::{VisitMut, VisitMutWith}; @@ -510,6 +511,32 @@ impl<'a> VisitMut for IdentReplacer { } } } + + fn visit_mut_object_pat(&mut self, pat: &mut farmfe_core::swc_ecma_ast::ObjectPat) { + for n in &mut pat.props { + match n { + farmfe_core::swc_ecma_ast::ObjectPatProp::KeyValue(key) => key.value.visit_mut_with(self), + farmfe_core::swc_ecma_ast::ObjectPatProp::Assign(a) => { + if let Some(value) = &mut a.value { + value.visit_mut_with(self); + } else if let Some(replaced) = self.id_to_replace.get(&a.key.id.to_id()) { + *n = farmfe_core::swc_ecma_ast::ObjectPatProp::KeyValue(KeyValuePatProp { + key: PropName::Ident(a.key.id.clone()), + value: Box::new(Pat::Ident(BindingIdent { + id: Ident::new( + replaced.as_str().into(), + DUMMY_SP.apply_mark(a.key.id.span.ctxt().outer()), + ), + type_ann: None, + })), + }) + } + } + farmfe_core::swc_ecma_ast::ObjectPatProp::Rest(r) => r.visit_mut_with(self), + } + } + } + fn visit_mut_ident(&mut self, n: &mut farmfe_core::swc_ecma_ast::Ident) { if let Some(replaced) = self.id_to_replace.get(&n.to_id()) { n.sym = replaced.as_str().into(); diff --git a/crates/plugin_minify/src/lib.rs b/crates/plugin_minify/src/lib.rs index 6c08c34ec..f8c1f4358 100644 --- a/crates/plugin_minify/src/lib.rs +++ b/crates/plugin_minify/src/lib.rs @@ -284,10 +284,17 @@ impl Plugin for FarmPluginMinify { let meta = module.meta.as_script_mut(); let ast = &mut meta.ast; - if let Some(id_to_replace) = id_to_replace.lock().remove(&module.id) { - let mut ident_replacer = IdentReplacer::new(id_to_replace); - ast.visit_mut_with(&mut ident_replacer); - } + let (cm, _) = create_swc_source_map(Source { + path: PathBuf::from(module.id.to_string()), + content: module.content.clone(), + }); + try_with(cm, &context.meta.script.globals, || { + if let Some(id_to_replace) = id_to_replace.lock().remove(&module.id) { + let mut ident_replacer = IdentReplacer::new(id_to_replace); + ast.visit_mut_with(&mut ident_replacer); + } + }) + .unwrap(); }); // update used exports of the module diff --git a/crates/plugin_runtime/src/handle_entry_resources.rs b/crates/plugin_runtime/src/handle_entry_resources.rs index ac4295776..6cf71d4ec 100644 --- a/crates/plugin_runtime/src/handle_entry_resources.rs +++ b/crates/plugin_runtime/src/handle_entry_resources.rs @@ -351,11 +351,12 @@ pub fn handle_entry_resources( r#"{farm_global_this}.{FARM_MODULE_SYSTEM}.setDynamicModuleResourcesMap({dynamic_resources_code});"#, ); - let top_level_await_entry = if context.config.script.native_top_level_await && async_modules.contains(entry) { - "await " - } else { - "" - }; + let top_level_await_entry = + if context.config.script.native_top_level_await && async_modules.contains(entry) { + "await " + } else { + "" + }; // 5. append call entry let call_entry_code = format!( diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index 202f2ad06..25ab1fb85 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -59,6 +59,10 @@ impl Plugin for FarmPluginRuntime { } fn config(&self, config: &mut Config) -> farmfe_core::error::Result> { + if config.output.target_env == TargetEnv::Library { + return Ok(None); + } + // runtime package entry file if !config.runtime.path.is_empty() { config.input.insert( @@ -265,7 +269,9 @@ impl Plugin for FarmPluginRuntime { context: &Arc, _hook_context: &PluginHookContext, ) -> farmfe_core::error::Result> { - if matches!(resource_pot.resource_pot_type, ResourcePotType::Js) { + if context.config.output.target_env != TargetEnv::Library + && matches!(resource_pot.resource_pot_type, ResourcePotType::Js) + { let async_modules = self.get_async_modules(context); let async_modules = async_modules.downcast_ref::>().unwrap(); let module_graph = context.module_graph.read(); @@ -439,6 +445,10 @@ impl Plugin for FarmPluginRuntime { param: &mut PluginFinalizeResourcesHookParams, context: &Arc, ) -> farmfe_core::error::Result> { + if context.config.output.target_env == TargetEnv::Library { + return Ok(None); + } + let async_modules = self.get_async_modules(context); let async_modules = async_modules.downcast_ref::>().unwrap(); handle_entry_resources::handle_entry_resources(param.resources_map, context, async_modules); diff --git a/examples/react-antd/farm.config.ts b/examples/react-antd/farm.config.ts index abee6f2fa..e7213bbe4 100644 --- a/examples/react-antd/farm.config.ts +++ b/examples/react-antd/farm.config.ts @@ -11,7 +11,7 @@ export default defineConfig({ }, presetEnv: false, sourcemap: true, - persistentCache: true + persistentCache: false, }, server: { writeToDisk: false, diff --git a/examples/script-entry/farm.config.cjs.ts b/examples/script-entry/farm.config.cjs.ts index e5937b3f1..ac0e15ebd 100644 --- a/examples/script-entry/farm.config.cjs.ts +++ b/examples/script-entry/farm.config.cjs.ts @@ -10,7 +10,7 @@ const config: UserConfig = { output: { path: 'dist/cjs', entryFilename: '[entryName].cjs', - targetEnv: 'node-legacy', + targetEnv: 'library', format: 'cjs' }, lazyCompilation: false, diff --git a/examples/script-entry/farm.config.ts b/examples/script-entry/farm.config.ts index 970e7ad21..088f892b1 100644 --- a/examples/script-entry/farm.config.ts +++ b/examples/script-entry/farm.config.ts @@ -12,7 +12,7 @@ export default defineConfig({ output: { path: 'dist/esm', entryFilename: '[entryName].mjs', - targetEnv: 'node', + targetEnv: 'library', format: 'esm' }, presetEnv: false, @@ -24,10 +24,15 @@ export default defineConfig({ runtime: { isolate: true }, - minify: { - mangle: { - toplevel: true - } + minify: false, + mode: 'development', + partialBundling: { + enforceResources: [ + { + name: 'xxx', + test: ['.+'] + } + ] }, persistentCache: false, lazyCompilation: false diff --git a/js-plugins/dts/package.json b/js-plugins/dts/package.json index 08174926b..3ac0338fe 100644 --- a/js-plugins/dts/package.json +++ b/js-plugins/dts/package.json @@ -7,9 +7,9 @@ "type": "module", "exports": { ".": { - "default": "./build/index.cjs", "require": "./build/index.cjs", "import": "./dist/index.js", + "default": "./build/index.cjs", "types": "./dist/index.d.ts" } }, diff --git a/packages/core/src/config/normalize-config/normalize-output.ts b/packages/core/src/config/normalize-config/normalize-output.ts index 2b880e0b8..32afc6624 100644 --- a/packages/core/src/config/normalize-config/normalize-output.ts +++ b/packages/core/src/config/normalize-config/normalize-output.ts @@ -90,12 +90,8 @@ const targetsMap: TargetsMap = { cssTargets: es2017Browsers, scriptGenTarget: 'es2017' }, - 'browser-esnext': null -}; - -export const targetEnvMapPlatform: Record = { - 'lib-node': 'node', - 'lib-browser': 'browser' + 'browser-esnext': null, + library: null }; /** diff --git a/packages/core/src/config/schema.ts b/packages/core/src/config/schema.ts index 087bbf58a..6e47e2e99 100644 --- a/packages/core/src/config/schema.ts +++ b/packages/core/src/config/schema.ts @@ -44,7 +44,8 @@ const compilationConfigSchema = z 'browser-legacy', 'browser-esnext', 'browser-es2015', - 'browser-es2017' + 'browser-es2017', + 'library' ]) .optional(), format: z.enum(['cjs', 'esm']).optional() diff --git a/packages/core/src/types/binding.ts b/packages/core/src/types/binding.ts index 05b1f0ee2..998883abf 100644 --- a/packages/core/src/types/binding.ts +++ b/packages/core/src/types/binding.ts @@ -153,7 +153,8 @@ export interface OutputConfig { | 'browser-legacy' | 'browser-es2015' | 'browser-es2017' - | 'browser-esnext'; + | 'browser-esnext' + | 'library'; /** * output module format */ diff --git a/packages/core/src/utils/share.ts b/packages/core/src/utils/share.ts index f1f4800d1..a086b26c9 100644 --- a/packages/core/src/utils/share.ts +++ b/packages/core/src/utils/share.ts @@ -157,6 +157,8 @@ export function mapTargetEnvValue(config: Config['config']) { config.output.targetEnv = 'node'; } else if (FARM_TARGET_BROWSER_ENVS.includes(config.output.targetEnv)) { config.output.targetEnv = 'browser'; + } else { + config.output.targetEnv = 'library'; } }