Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion contrib/mut-cjs-exports/src/local_export_strip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,6 @@ impl VisitMut for LocalExportStrip {
#[cfg(swc_ast_unknown)]
_ => panic!("unknown node"),
}))

}

/// ```javascript
Expand Down
63 changes: 63 additions & 0 deletions crates/swc_feature_flags/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,56 @@ use std::collections::HashMap;

use serde::Deserialize;

/// Transform mode for feature flags
#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum TransformMode {
/// Mark mode: marks flags with __SWC_FLAGS__ markers for later substitution
Mark,
/// Shake mode: directly substitutes flag values and performs DCE (dead code
/// elimination)
Shake,
}

impl Default for TransformMode {
fn default() -> Self {
Self::Mark
}
}

/// Unified configuration for feature flag transformation
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FeatureFlagsConfig {
/// Transform mode (default: "mark")
#[serde(default)]
pub mode: TransformMode,

/// Library configurations: library name -> config
/// Required in mark mode, not used in shake mode
#[serde(default)]
pub libraries: HashMap<String, LibraryConfig>,

/// Flags to exclude from processing
#[serde(default)]
pub exclude_flags: Vec<String>,

/// Global object name for markers (default: "__SWC_FLAGS__")
/// Only used in mark mode
#[serde(default = "default_marker_object")]
pub marker_object: String,

/// Flag values to apply (flag_name -> boolean)
/// Required in shake mode
#[serde(default)]
pub flag_values: HashMap<String, bool>,

/// Whether to collect statistics (default: true)
/// Only used in shake mode
#[serde(default = "default_true")]
pub collect_stats: bool,
}

/// Configuration for build-time feature flag transformation
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -76,3 +126,16 @@ impl Default for RuntimeConfig {
}
}
}

impl Default for FeatureFlagsConfig {
fn default() -> Self {
Self {
mode: TransformMode::default(),
libraries: HashMap::new(),
exclude_flags: Vec::new(),
marker_object: default_marker_object(),
flag_values: HashMap::new(),
collect_stats: true,
}
}
}
80 changes: 79 additions & 1 deletion crates/swc_feature_flags/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ pub mod stats;

// Re-exports for convenience
pub use build_time::BuildTimeTransform;
pub use config::{BuildTimeConfig, LibraryConfig, RuntimeConfig};
pub use config::{
BuildTimeConfig, FeatureFlagsConfig, LibraryConfig, RuntimeConfig, TransformMode,
};
pub use runtime::RuntimeTransform;
pub use stats::TransformStats;
use swc_ecma_ast::Pass;
Expand Down Expand Up @@ -111,3 +113,79 @@ pub fn build_time_pass(config: BuildTimeConfig) -> impl Pass {
pub fn runtime_pass(config: RuntimeConfig) -> impl Pass {
visit_mut_pass(RuntimeTransform::new(config))
}

/// Create a unified feature flags pass based on the configured mode
///
/// This pass supports two modes:
/// - **Mark mode** (default): Marks flags with `__SWC_FLAGS__` markers for
/// later substitution. This is the first pass in a two-phase transformation.
/// - **Shake mode**: Substitutes `__SWC_FLAGS__` markers with boolean values
/// and performs DCE (dead code elimination). This is the second pass that
/// processes output from mark mode.
///
/// # Two-Phase Workflow
///
/// 1. **Mark phase**: Use mark mode to convert flag variables to markers
/// 2. **Shake phase**: Use shake mode to substitute markers and eliminate dead
/// code
///
/// # Example
///
/// ```rust,ignore
/// use std::collections::HashMap;
/// use swc_feature_flags::{FeatureFlagsConfig, TransformMode, LibraryConfig};
///
/// // Phase 1: Mark mode (marker generation)
/// let mark_config = FeatureFlagsConfig {
/// mode: TransformMode::Mark,
/// libraries: HashMap::from([
/// ("@their/library".to_string(), LibraryConfig {
/// functions: vec!["useExperimentalFlags".to_string()],
/// }),
/// ]),
/// exclude_flags: vec![],
/// marker_object: "__SWC_FLAGS__".to_string(),
/// flag_values: HashMap::new(), // Not used in mark mode
/// collect_stats: false,
/// };
///
/// let program = program.apply(feature_flags_pass(mark_config));
///
/// // Phase 2: Shake mode (DCE)
/// let shake_config = FeatureFlagsConfig {
/// mode: TransformMode::Shake,
/// libraries: HashMap::new(), // Not used in shake mode
/// exclude_flags: vec![],
/// marker_object: "__SWC_FLAGS__".to_string(),
/// flag_values: HashMap::from([
/// ("featureA".to_string(), true),
/// ("featureB".to_string(), false),
/// ]),
/// collect_stats: true,
/// };
///
/// let program = program.apply(feature_flags_pass(shake_config));
/// ```
pub fn feature_flags_pass(config: FeatureFlagsConfig) -> Box<dyn Pass> {
match config.mode {
TransformMode::Mark => {
// Phase 1: Mark flags with __SWC_FLAGS__ markers
let build_config = BuildTimeConfig {
libraries: config.libraries,
exclude_flags: config.exclude_flags,
marker_object: config.marker_object,
};
Box::new(visit_mut_pass(BuildTimeTransform::new(build_config)))
}
TransformMode::Shake => {
// Phase 2: Substitute markers and perform DCE
let runtime_config = RuntimeConfig {
flag_values: config.flag_values,
remove_markers: true,
collect_stats: config.collect_stats,
marker_object: config.marker_object,
};
Box::new(visit_mut_pass(RuntimeTransform::new(runtime_config)))
}
}
}
Loading