Skip to content

Commit 7026ed2

Browse files
authored
feat(flags): Validate config and align docs (#584)
## Summary - remove excludeFlags docs/types and clarify shake mode usage - add config validation for required mark/shake options and non-empty functions - add validation coverage in feature-flags tests ## Testing - pnpm --filter @swc/plugin-experimental-feature-flags test
1 parent 207e42e commit 7026ed2

5 files changed

Lines changed: 84 additions & 102 deletions

File tree

packages/feature-flags/README.md

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ Use mark mode to mark flags for later substitution:
4343

4444
### Shake Mode (Direct Optimization with DCE)
4545

46-
Use shake mode to directly substitute flag values and eliminate dead code:
46+
Use shake mode to directly substitute flag values and eliminate dead code.
47+
This mode operates on `__SWC_FLAGS__` markers and does not use `libraries`:
4748

4849
```json
4950
{
@@ -52,11 +53,6 @@ Use shake mode to directly substitute flag values and eliminate dead code:
5253
"plugins": [
5354
["@swc/plugin-experimental-feature-flags", {
5455
"mode": "shake",
55-
"libraries": {
56-
"@their/library": {
57-
"functions": ["useExperimentalFlags"]
58-
}
59-
},
6056
"flagValues": {
6157
"featureA": true,
6258
"featureB": false
@@ -117,11 +113,8 @@ function App() {
117113
```
118114

119115
The plugin in shake mode:
120-
1. Removes import statements from configured libraries
121-
2. Detects destructuring patterns from configured functions
122-
3. Directly substitutes flag identifiers with boolean literals
123-
4. Performs dead code elimination (DCE)
124-
5. Removes the hook call statements
116+
1. Substitutes `__SWC_FLAGS__` markers with boolean literals
117+
2. Performs dead code elimination (DCE)
125118

126119
## Configuration
127120

@@ -141,6 +134,7 @@ interface FeatureFlagsConfig {
141134

142135
/**
143136
* Library configurations: library name -> config
137+
* Required in mark mode, not used in shake mode
144138
*
145139
* @example
146140
* {
@@ -154,13 +148,6 @@ interface FeatureFlagsConfig {
154148
*/
155149
libraries: Record<string, LibraryConfig>;
156150

157-
/**
158-
* Flags to exclude from transformation
159-
*
160-
* @default []
161-
*/
162-
excludeFlags?: string[];
163-
164151
/**
165152
* Global object name for markers
166153
* Only used in mark mode
@@ -227,29 +214,6 @@ You can configure multiple libraries:
227214
}
228215
```
229216

230-
## Excluding Flags
231-
232-
You can exclude specific flags from transformation:
233-
234-
```json
235-
{
236-
"jsc": {
237-
"experimental": {
238-
"plugins": [
239-
["@swc/plugin-experimental-feature-flags", {
240-
"libraries": {
241-
"@their/library": {
242-
"functions": ["useExperimentalFlags"]
243-
}
244-
},
245-
"excludeFlags": ["quickToggle", "tempDebugFlag"]
246-
}]
247-
]
248-
}
249-
}
250-
}
251-
```
252-
253217
## Custom Marker Object
254218

255219
You can customize the marker object name:

packages/feature-flags/README.tmpl.md

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ Use mark mode to mark flags for later substitution:
4343

4444
### Shake Mode (Direct Optimization with DCE)
4545

46-
Use shake mode to directly substitute flag values and eliminate dead code:
46+
Use shake mode to directly substitute flag values and eliminate dead code.
47+
This mode operates on `__SWC_FLAGS__` markers and does not use `libraries`:
4748

4849
```json
4950
{
@@ -52,11 +53,6 @@ Use shake mode to directly substitute flag values and eliminate dead code:
5253
"plugins": [
5354
["@swc/plugin-experimental-feature-flags", {
5455
"mode": "shake",
55-
"libraries": {
56-
"@their/library": {
57-
"functions": ["useExperimentalFlags"]
58-
}
59-
},
6056
"flagValues": {
6157
"featureA": true,
6258
"featureB": false
@@ -117,11 +113,8 @@ function App() {
117113
```
118114

119115
The plugin in shake mode:
120-
1. Removes import statements from configured libraries
121-
2. Detects destructuring patterns from configured functions
122-
3. Directly substitutes flag identifiers with boolean literals
123-
4. Performs dead code elimination (DCE)
124-
5. Removes the hook call statements
116+
1. Substitutes `__SWC_FLAGS__` markers with boolean literals
117+
2. Performs dead code elimination (DCE)
125118

126119
## Configuration
127120

@@ -141,6 +134,7 @@ interface FeatureFlagsConfig {
141134

142135
/**
143136
* Library configurations: library name -> config
137+
* Required in mark mode, not used in shake mode
144138
*
145139
* @example
146140
* {
@@ -154,13 +148,6 @@ interface FeatureFlagsConfig {
154148
*/
155149
libraries: Record<string, LibraryConfig>;
156150

157-
/**
158-
* Flags to exclude from transformation
159-
*
160-
* @default []
161-
*/
162-
excludeFlags?: string[];
163-
164151
/**
165152
* Global object name for markers
166153
* Only used in mark mode
@@ -227,29 +214,6 @@ You can configure multiple libraries:
227214
}
228215
```
229216

230-
## Excluding Flags
231-
232-
You can exclude specific flags from transformation:
233-
234-
```json
235-
{
236-
"jsc": {
237-
"experimental": {
238-
"plugins": [
239-
["@swc/plugin-experimental-feature-flags", {
240-
"libraries": {
241-
"@their/library": {
242-
"functions": ["useExperimentalFlags"]
243-
}
244-
},
245-
"excludeFlags": ["quickToggle", "tempDebugFlag"]
246-
}]
247-
]
248-
}
249-
}
250-
}
251-
```
252-
253217
## Custom Marker Object
254218

255219
You can customize the marker object name:

packages/feature-flags/__tests__/wasm.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,38 @@ function App() {
173173
});
174174
});
175175

176+
describe("Configuration validation", () => {
177+
test("Should require libraries in mark mode", async () => {
178+
const input = "const value = 1;";
179+
180+
await expect(transformCode(input, { mode: "mark" })).rejects.toThrow(
181+
/failed to invoke plugin/i,
182+
);
183+
});
184+
185+
test("Should require flagValues in shake mode", async () => {
186+
const input = "const value = 1;";
187+
188+
await expect(transformCode(input, { mode: "shake" })).rejects.toThrow(
189+
/failed to invoke plugin/i,
190+
);
191+
});
192+
193+
test("Should require functions to be non-empty", async () => {
194+
const input = "const value = 1;";
195+
await expect(
196+
transformCode(input, {
197+
mode: "mark",
198+
libraries: {
199+
"@their/library": {
200+
functions: [],
201+
},
202+
},
203+
}),
204+
).rejects.toThrow(/failed to invoke plugin/i);
205+
});
206+
});
207+
176208
describe("Multiple libraries", () => {
177209
test("Should handle multiple library sources in mark mode", async () => {
178210
const input = `import { useExperimentalFlags } from '@their/library';

packages/feature-flags/src/lib.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,45 @@ use swc_feature_flags::{
99
TransformMode,
1010
};
1111

12+
fn validate_feature_flags_config(config: &FeatureFlagsConfig) {
13+
match config.mode {
14+
TransformMode::Mark => {
15+
if config.libraries.is_empty() {
16+
panic!("FeatureFlagsConfig: \"libraries\" is required in mark mode");
17+
}
18+
}
19+
TransformMode::Shake => {
20+
if config.flag_values.is_empty() {
21+
panic!("FeatureFlagsConfig: \"flagValues\" is required in shake mode");
22+
}
23+
}
24+
}
25+
26+
for (library, library_config) in &config.libraries {
27+
if library_config.functions.is_empty() {
28+
panic!(
29+
"FeatureFlagsConfig: \"functions\" must not be empty for library \"{}\"",
30+
library
31+
);
32+
}
33+
}
34+
}
35+
36+
fn validate_build_time_config(config: &BuildTimeConfig) {
37+
if config.libraries.is_empty() {
38+
panic!("BuildTimeConfig: \"libraries\" is required");
39+
}
40+
41+
for (library, library_config) in &config.libraries {
42+
if library_config.functions.is_empty() {
43+
panic!(
44+
"BuildTimeConfig: \"functions\" must not be empty for library \"{}\"",
45+
library
46+
);
47+
}
48+
}
49+
}
50+
1251
/// SWC plugin entry point for feature flag transformation
1352
///
1453
/// This plugin supports two modes:
@@ -28,6 +67,7 @@ fn swc_plugin_feature_flags(mut program: Program, data: TransformPluginProgramMe
2867

2968
// Try to parse as new unified config first
3069
if let Ok(config) = serde_json::from_str::<FeatureFlagsConfig>(config_str) {
70+
validate_feature_flags_config(&config);
3171
match config.mode {
3272
TransformMode::Mark => {
3373
// Phase 1: Mark flags with __SWC_FLAGS__ markers
@@ -54,6 +94,7 @@ fn swc_plugin_feature_flags(mut program: Program, data: TransformPluginProgramMe
5494
// Fall back to old BuildTimeConfig for backward compatibility
5595
let config = serde_json::from_str::<BuildTimeConfig>(config_str)
5696
.expect("invalid config: must be either FeatureFlagsConfig or BuildTimeConfig");
97+
validate_build_time_config(&config);
5798

5899
let mut transform = BuildTimeTransform::new(config);
59100
program.visit_mut_with(&mut transform);

packages/feature-flags/types.d.ts

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ declare module "@swc/plugin-experimental-feature-flags" {
7676
/**
7777
* Library configurations: library name -> config
7878
*
79+
* Required in mark mode, not used in shake mode.
7980
* The plugin will track imports from these libraries and
8081
* transform calls to the specified functions.
8182
*
@@ -91,16 +92,6 @@ declare module "@swc/plugin-experimental-feature-flags" {
9192
*/
9293
libraries: Record<string, LibraryConfig>;
9394

94-
/**
95-
* Flags to exclude from transformation
96-
*
97-
* These flags will not be transformed and will remain as-is.
98-
* Useful for flags that don't need optimization.
99-
*
100-
* @default []
101-
*/
102-
excludeFlags?: string[];
103-
10495
/**
10596
* Global object name for markers
10697
*
@@ -163,16 +154,6 @@ declare module "@swc/plugin-experimental-feature-flags" {
163154
*/
164155
libraries: Record<string, LibraryConfig>;
165156

166-
/**
167-
* Flags to exclude from build-time marking
168-
*
169-
* These flags will not be transformed and will remain as-is.
170-
* Useful for flags that don't need dead code elimination.
171-
*
172-
* @default []
173-
*/
174-
excludeFlags?: string[];
175-
176157
/**
177158
* Global object name for markers
178159
*

0 commit comments

Comments
 (0)