diff --git a/.changeset/weak-dogs-lie.md b/.changeset/weak-dogs-lie.md new file mode 100644 index 000000000..a1422d84b --- /dev/null +++ b/.changeset/weak-dogs-lie.md @@ -0,0 +1,5 @@ +--- +"@farmfe/core": patch +--- + +support css module name coversion diff --git a/crates/core/src/config/css.rs b/crates/core/src/config/css.rs new file mode 100644 index 000000000..e6381ef5f --- /dev/null +++ b/crates/core/src/config/css.rs @@ -0,0 +1,46 @@ +use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub enum NameConversion { + /// + /// to keep the original name + /// + #[default] + #[serde(rename = "asIs")] + AsIs, + /// + /// ```md + /// "It is we who built these palaces and cities." + /// // => + /// "itIsWeWhoBuiltThesePalacesAndCities" + /// ``` + #[serde(rename = "lowerCamel")] + LowerCamel, + /// ```md + /// "We are not in the least afraid of ruins." + /// // => + /// "WeAreNotInTheLeastAfraidOfRuins" + /// ``` + #[serde(rename = "upperCamel")] + UpperCamel, + + /// ```md + /// "We carry a new world here, in our hearts." + /// // => + /// "we_carry_a_new_world_here_in_our_hearts" + /// ``` + #[serde(rename = "snake")] + Snake, +} + +impl NameConversion { + pub fn transform(&self, name: &str) -> String { + match self { + NameConversion::LowerCamel => name.to_lower_camel_case(), + NameConversion::UpperCamel => name.to_upper_camel_case(), + NameConversion::Snake => name.to_snake_case(), + NameConversion::AsIs => name.to_string(), + } + } +} diff --git a/crates/core/src/config/custom.rs b/crates/core/src/config/custom.rs index ed9e2800b..c1407a974 100644 --- a/crates/core/src/config/custom.rs +++ b/crates/core/src/config/custom.rs @@ -4,6 +4,7 @@ use crate::context::CompilationContext; use super::{ config_regex::ConfigRegex, + css::NameConversion, external::{ExternalConfig, ExternalObject}, Config, }; @@ -11,6 +12,7 @@ use super::{ const CUSTOM_CONFIG_RUNTIME_ISOLATE: &str = "runtime.isolate"; pub const CUSTOM_CONFIG_EXTERNAL_RECORD: &str = "external.record"; pub const CUSTOM_CONFIG_RESOLVE_DEDUPE: &str = "resolve.dedupe"; +pub const CUSTOM_CONFIG_CSS_MODULES_LOCAL_CONVERSION: &str = "css.modules.locals_conversion"; pub fn get_config_runtime_isolate(context: &Arc) -> bool { if let Some(val) = context.config.custom.get(CUSTOM_CONFIG_RUNTIME_ISOLATE) { @@ -54,3 +56,14 @@ pub fn get_config_resolve_dedupe(config: &Config) -> Vec { vec![] } } + +pub fn get_config_css_modules_local_conversion(config: &Config) -> NameConversion { + if let Some(val) = config + .custom + .get(CUSTOM_CONFIG_CSS_MODULES_LOCAL_CONVERSION) + { + serde_json::from_str(val).unwrap_or_default() + } else { + Default::default() + } +} diff --git a/crates/core/src/config/mod.rs b/crates/core/src/config/mod.rs index a51b16707..f6ba19c12 100644 --- a/crates/core/src/config/mod.rs +++ b/crates/core/src/config/mod.rs @@ -21,6 +21,7 @@ pub const FARM_MODULE_EXPORT: &str = "exports"; pub mod bool_or_obj; pub mod comments; pub mod config_regex; +pub mod css; pub mod custom; pub mod external; pub mod html; diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 91f64a0cb..2178213ff 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -42,6 +42,7 @@ pub use swc_ecma_ast; pub use swc_ecma_parser; pub use swc_html_ast; pub use wax; +pub use heck; #[macro_export] macro_rules! farm_profile_scope { diff --git a/crates/plugin_css/src/lib.rs b/crates/plugin_css/src/lib.rs index d047e96a7..2890943b1 100644 --- a/crates/plugin_css/src/lib.rs +++ b/crates/plugin_css/src/lib.rs @@ -4,6 +4,8 @@ use std::collections::HashMap; use std::{path::PathBuf, sync::Arc}; use dep_analyzer::DepAnalyzer; +use farmfe_core::config::css::NameConversion; +use farmfe_core::config::custom::get_config_css_modules_local_conversion; use farmfe_core::config::minify::MinifyOptions; use farmfe_core::module::CommentsMetaData; use farmfe_core::{ @@ -171,6 +173,7 @@ pub struct FarmPluginCss { ast_map: Mutex>, content_map: Mutex>, sourcemap_map: Mutex>, + locals_conversion: NameConversion, } fn prefixer(stylesheet: &mut Stylesheet, css_prefixer_config: &CssPrefixerConfig) { @@ -184,6 +187,7 @@ impl Plugin for FarmPluginCss { fn name(&self) -> &str { "FarmPluginCss" } + /// This plugin should be executed at last fn priority(&self) -> i32 { -99 @@ -336,10 +340,14 @@ impl Plugin for FarmPluginCss { } } } - export_names.push((name, after_transform_classes)); + + export_names.push(( + self.locals_conversion.transform(&name), + after_transform_classes, + )); } - export_names.sort_by_key(|e| e.0); + export_names.sort_by_key(|e| e.0.to_string()); let code = format!( r#" @@ -764,6 +772,7 @@ impl FarmPluginCss { ast_map: Mutex::new(Default::default()), content_map: Mutex::new(Default::default()), sourcemap_map: Mutex::new(Default::default()), + locals_conversion: get_config_css_modules_local_conversion(config), } } diff --git a/cspell.json b/cspell.json index b26cefdb9..27303d2a4 100644 --- a/cspell.json +++ b/cspell.json @@ -41,6 +41,7 @@ "concurrentify", "Consolas", "consts", + "Convertion", "cpus", "csspart", "ctxt", diff --git a/packages/core/src/config/constants.ts b/packages/core/src/config/constants.ts index 29d12daae..34ca7d11b 100644 --- a/packages/core/src/config/constants.ts +++ b/packages/core/src/config/constants.ts @@ -12,7 +12,8 @@ export const FARM_DEFAULT_NAMESPACE = 'FARM_DEFAULT_NAMESPACE'; export const CUSTOM_KEYS = { external_record: 'external.record', runtime_isolate: 'runtime.isolate', - resolve_dedupe: 'resolve.dedupe' + resolve_dedupe: 'resolve.dedupe', + css_locals_conversion: 'css.modules.locals_conversion' }; export const FARM_RUST_PLUGIN_FUNCTION_ENTRY = 'func.js'; diff --git a/packages/core/src/config/index.ts b/packages/core/src/config/index.ts index c5638c170..dc99b5124 100644 --- a/packages/core/src/config/index.ts +++ b/packages/core/src/config/index.ts @@ -56,6 +56,7 @@ import { FARM_DEFAULT_NAMESPACE } from './constants.js'; import { mergeConfig, mergeFarmCliConfig } from './mergeConfig.js'; +import { normalizeCss } from './normalize-config/normalize-css.js'; import { normalizeExternal } from './normalize-config/normalize-external.js'; import { normalizeResolve } from './normalize-config/normalize-resolve.js'; import type { @@ -558,6 +559,7 @@ export async function normalizeUserCompilationConfig( ); normalizeResolve(userConfig, resolvedCompilation); + normalizeCss(userConfig, resolvedCompilation); return resolvedCompilation; } diff --git a/packages/core/src/config/normalize-config/normalize-css.ts b/packages/core/src/config/normalize-config/normalize-css.ts new file mode 100644 index 000000000..5b28827bb --- /dev/null +++ b/packages/core/src/config/normalize-config/normalize-css.ts @@ -0,0 +1,25 @@ +import { CUSTOM_KEYS } from '../constants.js'; +import { ResolvedCompilation, UserConfig } from '../types.js'; + +export function normalizeCss( + config: UserConfig, + resolvedCompilation: ResolvedCompilation +) { + if (config.compilation?.css?.modules) { + normalizeCssModules(config, resolvedCompilation); + } +} + +function normalizeCssModules( + config: UserConfig, + resolvedCompilation: ResolvedCompilation +) { + if (config.compilation.css.modules.localsConversion) { + const localsConvention = config.compilation.css.modules.localsConversion; + delete resolvedCompilation.css.modules.localsConversion; + if (typeof localsConvention === 'string') { + resolvedCompilation.custom[CUSTOM_KEYS.css_locals_conversion] = + JSON.stringify(localsConvention); + } + } +} diff --git a/packages/core/src/config/schema.ts b/packages/core/src/config/schema.ts index 9981357c0..0dbd4f02f 100644 --- a/packages/core/src/config/schema.ts +++ b/packages/core/src/config/schema.ts @@ -245,7 +245,9 @@ const compilationConfigSchema = z .union([ z.null(), z.object({ - indentName: z.string().optional() + indentName: z.string().optional(), + localsConversion: z.string().optional(), + paths: z.array(z.string()).optional() }) ]) diff --git a/packages/core/src/config/types.ts b/packages/core/src/config/types.ts index 69dc0c650..02fbf8189 100644 --- a/packages/core/src/config/types.ts +++ b/packages/core/src/config/types.ts @@ -8,7 +8,7 @@ import type { Options } from 'http-proxy-middleware'; import { Middleware } from 'koa'; import type { RustPlugin } from '../plugin/rust/index.js'; import type { JsPlugin } from '../plugin/type.js'; -import type { Config } from '../types/binding.js'; +import type { Config, CssConfig } from '../types/binding.js'; import type { Logger } from '../utils/index.js'; export interface ConfigEnv { @@ -108,6 +108,12 @@ export interface UserConfig { /** Files under this dir will always be treated as static assets. serve it in dev, and copy it to output.path when build */ } +interface ResolvedCss extends CssConfig { + modules?: CssConfig['modules'] & { + localsConversion?: never; + }; +} + // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ResolvedCompilation extends Exclude { @@ -115,6 +121,7 @@ export interface ResolvedCompilation resolve?: { dedupe?: never; } & Config['config']['resolve']; + css?: ResolvedCss; } export interface ResolvedUserConfig extends UserConfig { diff --git a/packages/core/src/types/binding.ts b/packages/core/src/types/binding.ts index f8ec6bab5..b5d853122 100644 --- a/packages/core/src/types/binding.ts +++ b/packages/core/src/types/binding.ts @@ -291,6 +291,16 @@ export interface CssConfig { paths?: string[]; // configure the generated css class name, the default is `[name]-[hash]` indentName?: string; + /** + * + * - `asIs` - Do not convert the local variable name + * - `lowerCamel` - Convert the local variable name to lower camel case e.g: `fooBar` + * - `upperCamel` - Convert the local variable name to upper camel case e.g: `FooBar` + * - `snake` - Convert the local variable name to snake case e.g: `foo_bar` + * + * @default 'asIs' + */ + localsConversion?: 'asIs' | 'lowerCamel' | 'upperCamel' | 'snake'; } | null; /** * Configure CSS compatibility prefixes, such as -webkit-.