From fcd4dc3fe3b7fad382c3952e2c36209bcd17817f Mon Sep 17 00:00:00 2001 From: brightwu <1521488775@qq.com> Date: Tue, 23 Jul 2024 22:16:42 +0800 Subject: [PATCH] feat: support targetEnv library library-browser/node (#1656) --- .changeset/tricky-jobs-sneeze.md | 9 ++ crates/core/src/config/mod.rs | 53 +++++++++++ crates/node/src/lib.rs | 88 ++++++++++++++++--- crates/plugin_bundle/src/lib.rs | 2 +- crates/plugin_resolve/src/resolver.rs | 6 +- crates/plugin_resolve/src/resolver/exports.rs | 2 +- crates/plugin_runtime/src/lib.rs | 7 +- crates/plugin_static_assets/src/lib.rs | 2 +- examples/css-modules/farm.config.ts | 8 +- examples/lib-for-browser/farm.config.ts | 8 +- examples/script-entry/farm.config.cjs.ts | 2 +- examples/script-entry/farm.config.ts | 2 +- packages/cli/package.json | 2 +- packages/core/binding/binding.d.ts | 16 +++- packages/core/package.json | 2 +- .../normalize-config/normalize-output.ts | 4 +- packages/core/src/config/schema.ts | 4 +- packages/core/src/index.ts | 19 ++-- packages/core/src/types/binding.ts | 4 +- packages/core/src/utils/trace-dependencies.ts | 2 +- packages/core/src/watcher/index.ts | 6 +- pnpm-lock.yaml | 38 ++++++-- 22 files changed, 228 insertions(+), 58 deletions(-) create mode 100644 .changeset/tricky-jobs-sneeze.md diff --git a/.changeset/tricky-jobs-sneeze.md b/.changeset/tricky-jobs-sneeze.md new file mode 100644 index 000000000..2387c1ee6 --- /dev/null +++ b/.changeset/tricky-jobs-sneeze.md @@ -0,0 +1,9 @@ +--- +"@farmfe/core": patch +"@farmfe/cli": patch +--- + +- Mark farm compatible with node 16 +- Support targetEnv `library-node` and `library-browser` +- fix watcher does not watch file change beyond the project root +- remove script bundle port conflict log when lazy compile is disabled \ No newline at end of file diff --git a/crates/core/src/config/mod.rs b/crates/core/src/config/mod.rs index 359e432e4..d659b59d3 100644 --- a/crates/core/src/config/mod.rs +++ b/crates/core/src/config/mod.rs @@ -115,11 +115,30 @@ pub enum TargetEnv { Browser, #[serde(rename = "node")] Node, + /// [TargetEnv::Library] is alias of [TargetEnv::Custom("library-browser")] #[serde(rename = "library")] Library, + #[serde(untagged)] Custom(String), } +impl TargetEnv { + pub fn is_browser(&self) -> bool { + matches!(self, TargetEnv::Browser | TargetEnv::Library) + || matches!(self, TargetEnv::Custom(custom) if custom == "library-browser") + } + + pub fn is_node(&self) -> bool { + matches!(self, TargetEnv::Node) + || matches!(self, TargetEnv::Custom(custom) if custom == "library-node") + } + + pub fn is_library(&self) -> bool { + matches!(self, TargetEnv::Library) + || matches!(self, TargetEnv::Custom(custom) if custom == "library-browser" || custom == "library-node") + } +} + #[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq, PartialEq, Hash, Default)] pub enum ModuleFormat { #[serde(rename = "esm")] @@ -355,4 +374,38 @@ mod tests { assert!(matches!(config, SourcemapConfig::All)); } + + #[test] + fn target_env() { + use super::TargetEnv; + let env = TargetEnv::Browser; + assert!(env.is_browser()); + assert!(!env.is_node()); + assert!(!env.is_library()); + + let env = TargetEnv::Node; + assert!(env.is_node()); + assert!(!env.is_browser()); + assert!(!env.is_library()); + + let env = TargetEnv::Library; + assert!(env.is_library()); + assert!(!env.is_node()); + assert!(env.is_browser()); + + let env = TargetEnv::Custom("library-browser".to_string()); + assert!(env.is_library()); + assert!(!env.is_node()); + assert!(env.is_browser()); + + let env = TargetEnv::Custom("library-node".to_string()); + assert!(env.is_library()); + assert!(env.is_node()); + assert!(!env.is_browser()); + + let env: TargetEnv = serde_json::from_str("\"library-browser\"").expect("failed to parse"); + assert!(env.is_library()); + assert!(!env.is_node()); + assert!(env.is_browser()); + } } diff --git a/crates/node/src/lib.rs b/crates/node/src/lib.rs index 48f125ab4..4f404fc47 100644 --- a/crates/node/src/lib.rs +++ b/crates/node/src/lib.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use std::{collections::HashMap, path::Path, sync::Arc}; -use farmfe_compiler::Compiler; +use farmfe_compiler::{trace_module_graph::TracedModuleGraph, Compiler}; pub mod plugin_adapters; pub mod plugin_toolkit; @@ -48,6 +48,42 @@ pub struct WatchDiffResult { pub remove: Vec, } +#[napi(object)] +pub struct JsTracedModule { + pub id: String, + pub content_hash: String, + pub package_name: String, + pub package_version: String, +} + +#[napi(object)] +pub struct JsTracedModuleGraph { + pub root: String, + pub modules: Vec, + pub edges: HashMap>, + pub reverse_edges: HashMap>, +} + +impl From for JsTracedModuleGraph { + fn from(t: TracedModuleGraph) -> Self { + Self { + root: t.root, + modules: t + .modules + .into_iter() + .map(|m| JsTracedModule { + id: m.id, + content_hash: m.content_hash, + package_name: m.package_name, + package_version: m.package_version, + }) + .collect(), + edges: t.edges, + reverse_edges: t.reverse_edges, + } + } +} + #[napi(object)] pub struct JsUpdateResult { pub added: Vec, @@ -206,21 +242,49 @@ impl JsCompiler { } #[napi] - pub fn trace_dependencies(&self) -> napi::Result> { - self - .compiler - .trace_dependencies() - .map_err(|e| napi::Error::new(Status::GenericFailure, format!("{}", e))) + pub fn trace_dependencies(&self, e: Env) -> napi::Result { + let (promise, result) = + e.create_deferred::, Box napi::Result>>>()?; + + let compiler = self.compiler.clone(); + self.compiler.thread_pool.spawn(move || { + match compiler + .trace_dependencies() + .map_err(|e| napi::Error::new(Status::GenericFailure, format!("{}", e))) + { + Ok(deps) => { + promise.resolve(Box::new(|_| Ok(deps))); + } + Err(err) => { + promise.reject(err); + } + } + }); + + Ok(result) } #[napi] - pub fn trace_module_graph(&self, e: Env) -> napi::Result { - let graph = self - .compiler - .trace_module_graph() - .map_err(|e| napi::Error::new(Status::GenericFailure, format!("{}", e)))?; + pub fn trace_module_graph(&self, e: Env) -> napi::Result { + let (promise, result) = + e.create_deferred:: napi::Result>>()?; + + let compiler = self.compiler.clone(); + self.compiler.thread_pool.spawn(move || { + match compiler + .trace_module_graph() + .map_err(|e| napi::Error::new(Status::GenericFailure, format!("{}", e))) + { + Ok(graph) => { + promise.resolve(Box::new(|_| Ok(graph.into()))); + } + Err(err) => { + promise.reject(err); + } + } + }); - e.to_js_value(&graph) + Ok(result) } /// async compile, return promise diff --git a/crates/plugin_bundle/src/lib.rs b/crates/plugin_bundle/src/lib.rs index fae6456a7..7410c9f28 100644 --- a/crates/plugin_bundle/src/lib.rs +++ b/crates/plugin_bundle/src/lib.rs @@ -73,7 +73,7 @@ impl Plugin for FarmPluginBundle { 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 + || (context.config.output.target_env.is_library() && resource_pot.resource_pot_type == ResourcePotType::Js) { let resource_pot_id = resource_pot.id.clone(); diff --git a/crates/plugin_resolve/src/resolver.rs b/crates/plugin_resolve/src/resolver.rs index d9feee0a5..a48289ac2 100644 --- a/crates/plugin_resolve/src/resolver.rs +++ b/crates/plugin_resolve/src/resolver.rs @@ -151,7 +151,7 @@ impl Resolver { context: &Arc, ) -> Option { farm_profile_function!("try_browser".to_string()); - if context.config.output.target_env != TargetEnv::Browser { + if !context.config.output.target_env.is_browser() { return None; } @@ -650,7 +650,7 @@ impl Resolver { resolve_exports_or_imports(&package_json_info, subpath, "exports", kind, context) .map(|resolve_exports_path| resolve_exports_path.get(0).unwrap().to_string()) .or_else(|| { - if context.config.output.target_env == TargetEnv::Browser { + if context.config.output.target_env.is_browser() { try_browser_map( &package_json_info, BrowserMapType::Source(subpath.to_string()), @@ -716,7 +716,7 @@ impl Resolver { .main_fields .iter() .find_map(|main_field| { - if main_field == "browser" && context.config.output.target_env == TargetEnv::Node { + if main_field == "browser" && !context.config.output.target_env.is_browser() { return None; } raw_package_json_info diff --git a/crates/plugin_resolve/src/resolver/exports.rs b/crates/plugin_resolve/src/resolver/exports.rs index 85759db7e..e7cd1b7f7 100644 --- a/crates/plugin_resolve/src/resolver/exports.rs +++ b/crates/plugin_resolve/src/resolver/exports.rs @@ -93,7 +93,7 @@ pub fn resolve_exports_or_imports( } // resolve exports field - let is_browser = context.config.output.target_env == TargetEnv::Browser; + let is_browser = context.config.output.target_env.is_browser(); let is_require = match kind { ResolveKind::Require => true, _ => false, diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index 25ab1fb85..91ff7bf4e 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -59,7 +59,8 @@ impl Plugin for FarmPluginRuntime { } fn config(&self, config: &mut Config) -> farmfe_core::error::Result> { - if config.output.target_env == TargetEnv::Library { + // library bundle does not contain runtime + if config.output.target_env.is_library() { return Ok(None); } @@ -269,7 +270,7 @@ impl Plugin for FarmPluginRuntime { context: &Arc, _hook_context: &PluginHookContext, ) -> farmfe_core::error::Result> { - if context.config.output.target_env != TargetEnv::Library + if !context.config.output.target_env.is_library() && matches!(resource_pot.resource_pot_type, ResourcePotType::Js) { let async_modules = self.get_async_modules(context); @@ -445,7 +446,7 @@ impl Plugin for FarmPluginRuntime { param: &mut PluginFinalizeResourcesHookParams, context: &Arc, ) -> farmfe_core::error::Result> { - if context.config.output.target_env == TargetEnv::Library { + if context.config.output.target_env.is_library() { return Ok(None); } diff --git a/crates/plugin_static_assets/src/lib.rs b/crates/plugin_static_assets/src/lib.rs index 75325829d..e3d256083 100644 --- a/crates/plugin_static_assets/src/lib.rs +++ b/crates/plugin_static_assets/src/lib.rs @@ -228,7 +228,7 @@ impl Plugin for FarmPluginStaticAssets { format!("/{}", resource_name) }; - let content = if matches!(context.config.output.target_env, TargetEnv::Node) { + let content = if context.config.output.target_env.is_node() { format!( "export default new URL(/* {} */{:?}, import.meta.url)", FARM_IGNORE_ACTION_COMMENT, assets_path diff --git a/examples/css-modules/farm.config.ts b/examples/css-modules/farm.config.ts index 99abd225e..8b4accfcd 100644 --- a/examples/css-modules/farm.config.ts +++ b/examples/css-modules/farm.config.ts @@ -8,10 +8,10 @@ function defineConfig(config: UserConfig) { export default defineConfig({ compilation: { input: { - index: './index.html', + index: './index.html' }, output: { - path: './build', + path: './build' }, record: true, sourcemap: true @@ -19,7 +19,7 @@ export default defineConfig({ // minify: true, }, server: { - open: true, + open: true }, - plugins: ['@farmfe/plugin-react', '@farmfe/plugin-sass', postcss()], + plugins: ['@farmfe/plugin-react', '@farmfe/plugin-sass', postcss()] }); diff --git a/examples/lib-for-browser/farm.config.ts b/examples/lib-for-browser/farm.config.ts index 88f4885e2..3d8c894b5 100644 --- a/examples/lib-for-browser/farm.config.ts +++ b/examples/lib-for-browser/farm.config.ts @@ -11,7 +11,7 @@ export default defineConfig({ format: 'esm', path: 'dist', entryFilename: '[entryName].js', - filename: '[name].jsx', + filename: '[name].jsx' }, minify: false, presetEnv: false, @@ -24,7 +24,5 @@ export default defineConfig({ ] } }, - plugins: [ - farmJsPluginDts({}) - ] -}) \ No newline at end of file + plugins: [farmJsPluginDts({})] +}); diff --git a/examples/script-entry/farm.config.cjs.ts b/examples/script-entry/farm.config.cjs.ts index ac0e15ebd..96212b752 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: 'library', + targetEnv: 'library-node', format: 'cjs' }, lazyCompilation: false, diff --git a/examples/script-entry/farm.config.ts b/examples/script-entry/farm.config.ts index 088f892b1..0b99ee831 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: 'library', + targetEnv: 'library-node', format: 'esm' }, presetEnv: false, diff --git a/packages/cli/package.json b/packages/cli/package.json index 3675718e2..7300c30b2 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -43,7 +43,7 @@ "dependencies": { "cac": "^6.7.14", "cross-spawn": "^7.0.3", - "inquirer": "^9.1.4", + "inquirer": "9.2.12", "walkdir": "^0.4.1" }, "devDependencies": { diff --git a/packages/core/binding/binding.d.ts b/packages/core/binding/binding.d.ts index 9b3cf2ee1..7e176dc9a 100644 --- a/packages/core/binding/binding.d.ts +++ b/packages/core/binding/binding.d.ts @@ -32,6 +32,18 @@ export interface WatchDiffResult { add: Array remove: Array } +export interface JsTracedModule { + id: string + contentHash: string + packageName: string + packageVersion: string +} +export interface JsTracedModuleGraph { + root: string + modules: Array + edges: Record> + reverseEdges: Record> +} export interface JsUpdateResult { added: Array changed: Array @@ -107,8 +119,8 @@ export interface ResourcePotRecord { export type JsCompiler = Compiler export declare class Compiler { constructor(config: object) - traceDependencies(): Array - traceModuleGraph(): unknown + traceDependencies(): object + traceModuleGraph(): object /** async compile, return promise */ compile(): object /** sync compile */ diff --git a/packages/core/package.json b/packages/core/package.json index 27bc7e67e..035c9bc55 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -109,7 +109,7 @@ "slashes": "^3.0.12", "ws": "^8.12.0", "zod": "^3.23.8", - "zod-validation-error": "^3.3.0" + "zod-validation-error": "^1.3.0" }, "engines": { "node": ">=16.15.1" diff --git a/packages/core/src/config/normalize-config/normalize-output.ts b/packages/core/src/config/normalize-config/normalize-output.ts index 32afc6624..7a40f5e55 100644 --- a/packages/core/src/config/normalize-config/normalize-output.ts +++ b/packages/core/src/config/normalize-config/normalize-output.ts @@ -91,7 +91,9 @@ const targetsMap: TargetsMap = { scriptGenTarget: 'es2017' }, 'browser-esnext': null, - library: null + library: null, + 'library-browser': null, + 'library-node': null }; /** diff --git a/packages/core/src/config/schema.ts b/packages/core/src/config/schema.ts index 6e47e2e99..34a917909 100644 --- a/packages/core/src/config/schema.ts +++ b/packages/core/src/config/schema.ts @@ -45,7 +45,9 @@ const compilationConfigSchema = z 'browser-esnext', 'browser-es2015', 'browser-es2017', - 'library' + 'library', + 'library-browser', + 'library-node' ]) .optional(), format: z.enum(['cjs', 'esm']).optional() diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 3fac0c1c2..3dab2fc21 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -167,13 +167,17 @@ export async function watch( true ); - const hostname = await resolveHostname(resolvedUserConfig.server.host); - resolvedUserConfig.compilation.define = { - ...(resolvedUserConfig.compilation.define ?? {}), - FARM_NODE_LAZY_COMPILE_SERVER_URL: `http://${ - hostname.host || 'localhost' - }:${resolvedUserConfig.server.port}` - }; + const lazyEnabled = resolvedUserConfig.compilation?.lazyCompilation; + + if (lazyEnabled) { + const hostname = await resolveHostname(resolvedUserConfig.server.host); + resolvedUserConfig.compilation.define = { + ...(resolvedUserConfig.compilation.define ?? {}), + FARM_NODE_LAZY_COMPILE_SERVER_URL: `http://${ + hostname.host || 'localhost' + }:${resolvedUserConfig.server.port}` + }; + } const compilerFileWatcher = await createBundleHandler( resolvedUserConfig, @@ -181,7 +185,6 @@ export async function watch( true ); - const lazyEnabled = resolvedUserConfig.compilation?.lazyCompilation; let devServer: Server | undefined; // create dev server for lazy compilation if (lazyEnabled) { diff --git a/packages/core/src/types/binding.ts b/packages/core/src/types/binding.ts index 998883abf..5844dca94 100644 --- a/packages/core/src/types/binding.ts +++ b/packages/core/src/types/binding.ts @@ -154,7 +154,9 @@ export interface OutputConfig { | 'browser-es2015' | 'browser-es2017' | 'browser-esnext' - | 'library'; + | 'library' + | 'library-browser' + | 'library-node'; /** * output module format */ diff --git a/packages/core/src/utils/trace-dependencies.ts b/packages/core/src/utils/trace-dependencies.ts index f21a83594..181b5cf80 100644 --- a/packages/core/src/utils/trace-dependencies.ts +++ b/packages/core/src/utils/trace-dependencies.ts @@ -24,7 +24,7 @@ export async function traceDependencies( } const compiler = createTraceDepCompiler(configFilePath, logger); - const files = await compiler.traceDependencies(); + const files = (await compiler.traceDependencies()) as string[]; return files; } catch (error) { const errorMessage = convertErrorMessage(error); diff --git a/packages/core/src/watcher/index.ts b/packages/core/src/watcher/index.ts index 649ae6f6b..f27728942 100644 --- a/packages/core/src/watcher/index.ts +++ b/packages/core/src/watcher/index.ts @@ -34,9 +34,11 @@ export class FileWatcher implements ImplFileWatcher { } filterWatchFile(file: string, root: string): boolean { + const suffix = process.platform === 'win32' ? '\\' : '/'; + return ( - !file.startsWith(root) && - !file.includes('node_modules/') && + !file.startsWith(`${root}${suffix}`) && + !file.includes(`node_modules${suffix}`) && !file.includes('\0') && existsSync(file) ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5bdb804df..9291862cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2234,8 +2234,8 @@ importers: specifier: ^7.0.3 version: 7.0.3 inquirer: - specifier: ^9.1.4 - version: 9.2.11 + specifier: 9.2.12 + version: 9.2.12 walkdir: specifier: ^0.4.1 version: 0.4.1 @@ -2340,8 +2340,8 @@ importers: specifier: ^3.23.8 version: 3.23.8 zod-validation-error: - specifier: ^3.3.0 - version: 3.3.0(zod@3.23.8) + specifier: ^1.3.0 + version: 1.5.0(zod@3.23.8) devDependencies: '@napi-rs/cli': specifier: ^2.18.4 @@ -8735,6 +8735,10 @@ packages: resolution: {integrity: sha512-B2LafrnnhbRzCWfAdOXisUzL89Kg8cVJlYmhqoi3flSiV/TveO+nsXwgKr9h9PIo+J1hz7nBSk6gegRIMBBf7g==} engines: {node: '>=14.18.0'} + inquirer@9.2.12: + resolution: {integrity: sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q==} + engines: {node: '>=14.18.0'} + inquirer@9.2.15: resolution: {integrity: sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==} engines: {node: '>=18'} @@ -13300,9 +13304,9 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} - zod-validation-error@3.3.0: - resolution: {integrity: sha512-Syib9oumw1NTqEv4LT0e6U83Td9aVRk9iTXPUQr1otyV1PuXQKOvOwhMNqZIq5hluzHP2pMgnOmHEo7kPdI2mw==} - engines: {node: '>=18.0.0'} + zod-validation-error@1.5.0: + resolution: {integrity: sha512-/7eFkAI4qV0tcxMBB/3+d2c1P6jzzZYdYSlBuAklzMuCrJu5bzJfHS0yVAS87dRHVlhftd6RFJDIvv03JgkSbw==} + engines: {node: '>=16.0.0'} peerDependencies: zod: ^3.18.0 @@ -21806,6 +21810,24 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 6.2.0 + inquirer@9.2.12: + dependencies: + '@ljharb/through': 2.3.13 + ansi-escapes: 4.3.2 + chalk: 5.3.0 + cli-cursor: 3.1.0 + cli-width: 4.1.0 + external-editor: 3.1.0 + figures: 5.0.0 + lodash: 4.17.21 + mute-stream: 1.0.0 + ora: 5.4.1 + run-async: 3.0.0 + rxjs: 7.8.1 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + inquirer@9.2.15: dependencies: '@ljharb/through': 2.3.13 @@ -27193,7 +27215,7 @@ snapshots: yocto-queue@1.0.0: {} - zod-validation-error@3.3.0(zod@3.23.8): + zod-validation-error@1.5.0(zod@3.23.8): dependencies: zod: 3.23.8