diff --git a/packages/feature-flags/README.md b/packages/feature-flags/README.md index 7d343ad4e..f2031cfb7 100644 --- a/packages/feature-flags/README.md +++ b/packages/feature-flags/README.md @@ -43,7 +43,8 @@ Use mark mode to mark flags for later substitution: ### Shake Mode (Direct Optimization with DCE) -Use shake mode to directly substitute flag values and eliminate dead code: +Use shake mode to directly substitute flag values and eliminate dead code. +This mode operates on `__SWC_FLAGS__` markers and does not use `libraries`: ```json { @@ -52,11 +53,6 @@ Use shake mode to directly substitute flag values and eliminate dead code: "plugins": [ ["@swc/plugin-experimental-feature-flags", { "mode": "shake", - "libraries": { - "@their/library": { - "functions": ["useExperimentalFlags"] - } - }, "flagValues": { "featureA": true, "featureB": false @@ -117,11 +113,8 @@ function App() { ``` The plugin in shake mode: -1. Removes import statements from configured libraries -2. Detects destructuring patterns from configured functions -3. Directly substitutes flag identifiers with boolean literals -4. Performs dead code elimination (DCE) -5. Removes the hook call statements +1. Substitutes `__SWC_FLAGS__` markers with boolean literals +2. Performs dead code elimination (DCE) ## Configuration @@ -141,6 +134,7 @@ interface FeatureFlagsConfig { /** * Library configurations: library name -> config + * Required in mark mode, not used in shake mode * * @example * { @@ -154,13 +148,6 @@ interface FeatureFlagsConfig { */ libraries: Record; - /** - * Flags to exclude from transformation - * - * @default [] - */ - excludeFlags?: string[]; - /** * Global object name for markers * Only used in mark mode @@ -227,29 +214,6 @@ You can configure multiple libraries: } ``` -## Excluding Flags - -You can exclude specific flags from transformation: - -```json -{ - "jsc": { - "experimental": { - "plugins": [ - ["@swc/plugin-experimental-feature-flags", { - "libraries": { - "@their/library": { - "functions": ["useExperimentalFlags"] - } - }, - "excludeFlags": ["quickToggle", "tempDebugFlag"] - }] - ] - } - } -} -``` - ## Custom Marker Object You can customize the marker object name: diff --git a/packages/feature-flags/README.tmpl.md b/packages/feature-flags/README.tmpl.md index 4e5b8cc92..ef94230a3 100644 --- a/packages/feature-flags/README.tmpl.md +++ b/packages/feature-flags/README.tmpl.md @@ -43,7 +43,8 @@ Use mark mode to mark flags for later substitution: ### Shake Mode (Direct Optimization with DCE) -Use shake mode to directly substitute flag values and eliminate dead code: +Use shake mode to directly substitute flag values and eliminate dead code. +This mode operates on `__SWC_FLAGS__` markers and does not use `libraries`: ```json { @@ -52,11 +53,6 @@ Use shake mode to directly substitute flag values and eliminate dead code: "plugins": [ ["@swc/plugin-experimental-feature-flags", { "mode": "shake", - "libraries": { - "@their/library": { - "functions": ["useExperimentalFlags"] - } - }, "flagValues": { "featureA": true, "featureB": false @@ -117,11 +113,8 @@ function App() { ``` The plugin in shake mode: -1. Removes import statements from configured libraries -2. Detects destructuring patterns from configured functions -3. Directly substitutes flag identifiers with boolean literals -4. Performs dead code elimination (DCE) -5. Removes the hook call statements +1. Substitutes `__SWC_FLAGS__` markers with boolean literals +2. Performs dead code elimination (DCE) ## Configuration @@ -141,6 +134,7 @@ interface FeatureFlagsConfig { /** * Library configurations: library name -> config + * Required in mark mode, not used in shake mode * * @example * { @@ -154,13 +148,6 @@ interface FeatureFlagsConfig { */ libraries: Record; - /** - * Flags to exclude from transformation - * - * @default [] - */ - excludeFlags?: string[]; - /** * Global object name for markers * Only used in mark mode @@ -227,29 +214,6 @@ You can configure multiple libraries: } ``` -## Excluding Flags - -You can exclude specific flags from transformation: - -```json -{ - "jsc": { - "experimental": { - "plugins": [ - ["@swc/plugin-experimental-feature-flags", { - "libraries": { - "@their/library": { - "functions": ["useExperimentalFlags"] - } - }, - "excludeFlags": ["quickToggle", "tempDebugFlag"] - }] - ] - } - } -} -``` - ## Custom Marker Object You can customize the marker object name: diff --git a/packages/feature-flags/__tests__/wasm.test.ts b/packages/feature-flags/__tests__/wasm.test.ts index f7353265f..5bf365e81 100644 --- a/packages/feature-flags/__tests__/wasm.test.ts +++ b/packages/feature-flags/__tests__/wasm.test.ts @@ -173,6 +173,38 @@ function App() { }); }); +describe("Configuration validation", () => { + test("Should require libraries in mark mode", async () => { + const input = "const value = 1;"; + + await expect(transformCode(input, { mode: "mark" })).rejects.toThrow( + /failed to invoke plugin/i, + ); + }); + + test("Should require flagValues in shake mode", async () => { + const input = "const value = 1;"; + + await expect(transformCode(input, { mode: "shake" })).rejects.toThrow( + /failed to invoke plugin/i, + ); + }); + + test("Should require functions to be non-empty", async () => { + const input = "const value = 1;"; + await expect( + transformCode(input, { + mode: "mark", + libraries: { + "@their/library": { + functions: [], + }, + }, + }), + ).rejects.toThrow(/failed to invoke plugin/i); + }); +}); + describe("Multiple libraries", () => { test("Should handle multiple library sources in mark mode", async () => { const input = `import { useExperimentalFlags } from '@their/library'; diff --git a/packages/feature-flags/src/lib.rs b/packages/feature-flags/src/lib.rs index b881e6cd3..3ff6e5630 100644 --- a/packages/feature-flags/src/lib.rs +++ b/packages/feature-flags/src/lib.rs @@ -9,6 +9,45 @@ use swc_feature_flags::{ TransformMode, }; +fn validate_feature_flags_config(config: &FeatureFlagsConfig) { + match config.mode { + TransformMode::Mark => { + if config.libraries.is_empty() { + panic!("FeatureFlagsConfig: \"libraries\" is required in mark mode"); + } + } + TransformMode::Shake => { + if config.flag_values.is_empty() { + panic!("FeatureFlagsConfig: \"flagValues\" is required in shake mode"); + } + } + } + + for (library, library_config) in &config.libraries { + if library_config.functions.is_empty() { + panic!( + "FeatureFlagsConfig: \"functions\" must not be empty for library \"{}\"", + library + ); + } + } +} + +fn validate_build_time_config(config: &BuildTimeConfig) { + if config.libraries.is_empty() { + panic!("BuildTimeConfig: \"libraries\" is required"); + } + + for (library, library_config) in &config.libraries { + if library_config.functions.is_empty() { + panic!( + "BuildTimeConfig: \"functions\" must not be empty for library \"{}\"", + library + ); + } + } +} + /// SWC plugin entry point for feature flag transformation /// /// This plugin supports two modes: @@ -28,6 +67,7 @@ fn swc_plugin_feature_flags(mut program: Program, data: TransformPluginProgramMe // Try to parse as new unified config first if let Ok(config) = serde_json::from_str::(config_str) { + validate_feature_flags_config(&config); match config.mode { TransformMode::Mark => { // Phase 1: Mark flags with __SWC_FLAGS__ markers @@ -54,6 +94,7 @@ fn swc_plugin_feature_flags(mut program: Program, data: TransformPluginProgramMe // Fall back to old BuildTimeConfig for backward compatibility let config = serde_json::from_str::(config_str) .expect("invalid config: must be either FeatureFlagsConfig or BuildTimeConfig"); + validate_build_time_config(&config); let mut transform = BuildTimeTransform::new(config); program.visit_mut_with(&mut transform); diff --git a/packages/feature-flags/types.d.ts b/packages/feature-flags/types.d.ts index 98be06974..e9589fa0f 100644 --- a/packages/feature-flags/types.d.ts +++ b/packages/feature-flags/types.d.ts @@ -76,6 +76,7 @@ declare module "@swc/plugin-experimental-feature-flags" { /** * Library configurations: library name -> config * + * Required in mark mode, not used in shake mode. * The plugin will track imports from these libraries and * transform calls to the specified functions. * @@ -91,16 +92,6 @@ declare module "@swc/plugin-experimental-feature-flags" { */ libraries: Record; - /** - * Flags to exclude from transformation - * - * These flags will not be transformed and will remain as-is. - * Useful for flags that don't need optimization. - * - * @default [] - */ - excludeFlags?: string[]; - /** * Global object name for markers * @@ -163,16 +154,6 @@ declare module "@swc/plugin-experimental-feature-flags" { */ libraries: Record; - /** - * Flags to exclude from build-time marking - * - * These flags will not be transformed and will remain as-is. - * Useful for flags that don't need dead code elimination. - * - * @default [] - */ - excludeFlags?: string[]; - /** * Global object name for markers *