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
46 changes: 5 additions & 41 deletions packages/feature-flags/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -141,6 +134,7 @@ interface FeatureFlagsConfig {

/**
* Library configurations: library name -> config
* Required in mark mode, not used in shake mode
*
* @example
* {
Expand All @@ -154,13 +148,6 @@ interface FeatureFlagsConfig {
*/
libraries: Record<string, LibraryConfig>;

/**
* Flags to exclude from transformation
*
* @default []
*/
excludeFlags?: string[];

/**
* Global object name for markers
* Only used in mark mode
Expand Down Expand Up @@ -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:
Expand Down
46 changes: 5 additions & 41 deletions packages/feature-flags/README.tmpl.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -141,6 +134,7 @@ interface FeatureFlagsConfig {

/**
* Library configurations: library name -> config
* Required in mark mode, not used in shake mode
*
* @example
* {
Expand All @@ -154,13 +148,6 @@ interface FeatureFlagsConfig {
*/
libraries: Record<string, LibraryConfig>;

/**
* Flags to exclude from transformation
*
* @default []
*/
excludeFlags?: string[];

/**
* Global object name for markers
* Only used in mark mode
Expand Down Expand Up @@ -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:
Expand Down
32 changes: 32 additions & 0 deletions packages/feature-flags/__tests__/wasm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
41 changes: 41 additions & 0 deletions packages/feature-flags/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}
}
Comment on lines +26 to +33
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validation for non-empty functions in libraries should only apply in mark mode, not shake mode. According to the updated documentation, shake mode does not use libraries. This validation block should be moved inside the mark mode branch (after line 17) to prevent incorrect validation errors when a user provides a library config with empty functions in shake mode.

Copilot uses AI. Check for mistakes.
}

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:
Expand All @@ -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::<FeatureFlagsConfig>(config_str) {
validate_feature_flags_config(&config);
match config.mode {
TransformMode::Mark => {
// Phase 1: Mark flags with __SWC_FLAGS__ markers
Expand All @@ -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::<BuildTimeConfig>(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);
Expand Down
21 changes: 1 addition & 20 deletions packages/feature-flags/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -91,16 +92,6 @@ declare module "@swc/plugin-experimental-feature-flags" {
*/
libraries: Record<string, LibraryConfig>;

/**
* 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
*
Expand Down Expand Up @@ -163,16 +154,6 @@ declare module "@swc/plugin-experimental-feature-flags" {
*/
libraries: Record<string, LibraryConfig>;

/**
* 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
*
Expand Down
Loading