@variantlab/core 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1209 -39
- package/docs/API.md +692 -0
- package/docs/ARCHITECTURE.md +430 -0
- package/docs/CONTRIBUTING.md +264 -0
- package/docs/ROADMAP.md +292 -0
- package/docs/SECURITY.md +323 -0
- package/docs/design/api-philosophy.md +347 -0
- package/docs/design/config-format.md +442 -0
- package/docs/design/design-principles.md +212 -0
- package/docs/design/targeting-dsl.md +433 -0
- package/docs/features/codegen.md +351 -0
- package/docs/features/crash-rollback.md +399 -0
- package/docs/features/debug-overlay.md +328 -0
- package/docs/features/hmac-signing.md +330 -0
- package/docs/features/killer-features.md +308 -0
- package/docs/features/multivariate.md +339 -0
- package/docs/features/qr-sharing.md +372 -0
- package/docs/features/targeting.md +481 -0
- package/docs/features/time-travel.md +306 -0
- package/docs/features/value-experiments.md +487 -0
- package/docs/phases/phase-2-expansion.md +307 -0
- package/docs/phases/phase-3-ecosystem.md +289 -0
- package/docs/phases/phase-4-advanced.md +306 -0
- package/docs/phases/phase-5-v1-stable.md +350 -0
- package/docs/research/bundle-size-analysis.md +279 -0
- package/docs/research/competitors.md +327 -0
- package/docs/research/framework-ssr-quirks.md +394 -0
- package/docs/research/naming-rationale.md +238 -0
- package/docs/research/origin-story.md +179 -0
- package/docs/research/security-threats.md +312 -0
- package/package.json +2 -1
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# Bundle size analysis
|
|
2
|
+
|
|
3
|
+
How we plan to hit **< 3 KB gzipped for `@variantlab/core`** and the other budgets in [`ARCHITECTURE.md`](../../ARCHITECTURE.md).
|
|
4
|
+
|
|
5
|
+
## Table of contents
|
|
6
|
+
|
|
7
|
+
- [Why bundle size matters](#why-bundle-size-matters)
|
|
8
|
+
- [The budgets](#the-budgets)
|
|
9
|
+
- [Techniques we use](#techniques-we-use)
|
|
10
|
+
- [Techniques we avoid](#techniques-we-avoid)
|
|
11
|
+
- [Measurement methodology](#measurement-methodology)
|
|
12
|
+
- [Per-feature cost estimates](#per-feature-cost-estimates)
|
|
13
|
+
- [What happens when we exceed the budget](#what-happens-when-we-exceed-the-budget)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Why bundle size matters
|
|
18
|
+
|
|
19
|
+
1. **First-contentful-paint on mobile web.** Every KB in your JS bundle delays FCP by ~10-20 ms on median 4G connections. A 30 KB A/B testing SDK costs half a second before your users see anything.
|
|
20
|
+
2. **React Native cold-start time.** Hermes parses JS on every cold launch. Larger bundles = slower launches. Users notice 100 ms delays.
|
|
21
|
+
3. **Edge runtime limits.** Cloudflare Workers have a 1 MB unzipped bundle limit. Every KB matters.
|
|
22
|
+
4. **Dependency hell avoidance.** Small bundles can be copy-pasted as a file. Large bundles force you to dependency-manage.
|
|
23
|
+
5. **Honesty.** Most A/B testing SDKs bloat over time. Committing to a hard budget at the start keeps us honest.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## The budgets
|
|
28
|
+
|
|
29
|
+
| Package | Budget (gzipped) | Rationale |
|
|
30
|
+
|---|---:|---|
|
|
31
|
+
| `@variantlab/core` | **3 KB** | The entire engine. This is the number people will benchmark us on. |
|
|
32
|
+
| `@variantlab/react` | **1.5 KB** | Just hooks + context + render-prop components. |
|
|
33
|
+
| `@variantlab/react-native` | **4 KB** | Includes debug overlay; tree-shaken in prod. |
|
|
34
|
+
| `@variantlab/next` | **2 KB** | Server + middleware + client re-exports. |
|
|
35
|
+
| `@variantlab/remix` | **1.5 KB** | Loader helpers + cookie stickiness. |
|
|
36
|
+
| `@variantlab/vue` | **1.5 KB** | Composables + components. |
|
|
37
|
+
| `@variantlab/svelte` | **1 KB** | Svelte compiles most of the runtime away. |
|
|
38
|
+
| `@variantlab/solid` | **1 KB** | Same reason. |
|
|
39
|
+
| `@variantlab/vanilla` | **0.5 KB** | Just a hook over the engine. |
|
|
40
|
+
| `@variantlab/astro` | **1 KB** | Astro integration is very thin. |
|
|
41
|
+
| `@variantlab/nuxt` | **1.5 KB** | Nuxt module wrapping Vue adapter. |
|
|
42
|
+
|
|
43
|
+
All budgets are **enforced in CI** by `size-limit`. A PR that exceeds any budget is blocked.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Techniques we use
|
|
48
|
+
|
|
49
|
+
### 1. Zero runtime dependencies
|
|
50
|
+
|
|
51
|
+
Every runtime dependency adds at minimum a few hundred bytes of import overhead and often much more due to transitive bloat. We ship zero.
|
|
52
|
+
|
|
53
|
+
**What we would pull in if we were careless**:
|
|
54
|
+
|
|
55
|
+
| Dep | Purpose | Cost (gzipped) |
|
|
56
|
+
|---|---|---:|
|
|
57
|
+
| `zod` | Schema validation | ~12 KB |
|
|
58
|
+
| `valibot` | Schema validation | ~2 KB |
|
|
59
|
+
| `lodash.merge` | Deep merge | ~1 KB |
|
|
60
|
+
| `nanoid` | ID generation | ~130 B |
|
|
61
|
+
| `ms` | Duration parsing | ~250 B |
|
|
62
|
+
| `semver` | Version range matching | ~6 KB |
|
|
63
|
+
| `glob` | Pattern matching | ~4 KB |
|
|
64
|
+
|
|
65
|
+
Total if we used all of these: ~25 KB.
|
|
66
|
+
|
|
67
|
+
**What we do instead**: Write hand-rolled replacements. Each is 100-400 bytes and purpose-built for our use case. Total cost: ~1.5 KB for all replacements combined.
|
|
68
|
+
|
|
69
|
+
### 2. Pure ESM with tree-shaking
|
|
70
|
+
|
|
71
|
+
- Every module exports only what it needs
|
|
72
|
+
- No barrel files that re-export everything
|
|
73
|
+
- No side effects marked at package.json level: `"sideEffects": false`
|
|
74
|
+
- Bundlers (esbuild, Rollup, Vite, Webpack 5+) automatically drop unused code
|
|
75
|
+
|
|
76
|
+
### 3. No class inheritance hierarchies
|
|
77
|
+
|
|
78
|
+
Classes with inheritance force bundlers to include the whole chain. We use a single `VariantEngine` class with composition via injected interfaces (`Storage`, `Fetcher`, `Telemetry`).
|
|
79
|
+
|
|
80
|
+
### 4. No generics at runtime
|
|
81
|
+
|
|
82
|
+
TypeScript generics are compile-time only. We never use reflection, `Object.keys`, or runtime type checks beyond simple `typeof` guards.
|
|
83
|
+
|
|
84
|
+
### 5. Hand-rolled schema validator
|
|
85
|
+
|
|
86
|
+
The schema validator is ~400 bytes. It's a switch statement over primitive types plus an allow-list check for object keys. That's it. No recursive descent parsers, no grammar, no regex. It rejects anything it doesn't understand.
|
|
87
|
+
|
|
88
|
+
### 6. Hand-rolled semver matcher
|
|
89
|
+
|
|
90
|
+
We support only the operators that matter for targeting: `>=`, `<`, `<=`, `>`, `=`, `^`, `~`, range dashes. Total implementation: ~250 bytes. Full `semver` package is 6 KB. We lose nothing practical.
|
|
91
|
+
|
|
92
|
+
### 7. Hand-rolled glob matcher
|
|
93
|
+
|
|
94
|
+
We support `/foo`, `/foo/*`, `/foo/**`, and `/foo/:param`. No character classes, no negation, no brace expansion. Total implementation: ~150 bytes. The `glob` or `minimatch` packages are 4+ KB.
|
|
95
|
+
|
|
96
|
+
### 8. Hand-rolled hash function
|
|
97
|
+
|
|
98
|
+
For sticky assignment we need a stable hash of `(userId, experimentId)`. We use a simplified 32-bit FNV-1a implementation: ~80 bytes. `murmurhash` is 500 bytes.
|
|
99
|
+
|
|
100
|
+
### 9. Web Crypto API for HMAC
|
|
101
|
+
|
|
102
|
+
`crypto.subtle.verify` is available in every modern runtime — browsers, Node 18+, Deno, Bun, Cloudflare Workers, React Native Hermes (via `react-native-quick-crypto` peer dep). We use the platform, not a polyfill. Zero bytes for HMAC.
|
|
103
|
+
|
|
104
|
+
### 10. Dead code elimination via `__DEV__`
|
|
105
|
+
|
|
106
|
+
Debug overlay code and extended error messages are gated behind `typeof __DEV__ !== "undefined"` or `process.env.NODE_ENV !== "production"`. Bundlers replace these at build time with constants and drop unreachable branches.
|
|
107
|
+
|
|
108
|
+
Example:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
function validateExperiment(exp) {
|
|
112
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
113
|
+
if (!exp.id) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`Experiment missing ID. Experiments need a unique string ID to track assignments. Example: { id: "my-experiment", ... }`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
if (!exp.id) throw new Error("no id");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
In production, the verbose error message is eliminated entirely.
|
|
125
|
+
|
|
126
|
+
### 11. Minification-friendly APIs
|
|
127
|
+
|
|
128
|
+
- Short internal names
|
|
129
|
+
- Exported names are the only ones that survive minification
|
|
130
|
+
- No computed property names in hot paths
|
|
131
|
+
- No `arguments` object usage
|
|
132
|
+
|
|
133
|
+
### 12. Separate entry points
|
|
134
|
+
|
|
135
|
+
Instead of one big `index.ts`, each feature is a separate entry point:
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"exports": {
|
|
140
|
+
".": "./dist/index.js",
|
|
141
|
+
"./debug": "./dist/debug.js",
|
|
142
|
+
"./crypto": "./dist/crypto.js",
|
|
143
|
+
"./storage/memory": "./dist/storage-memory.js"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Users import only what they need. `@variantlab/core/debug` is separate from `@variantlab/core` so debug code never ships to production.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Techniques we avoid
|
|
153
|
+
|
|
154
|
+
### Class transforms and polyfills
|
|
155
|
+
|
|
156
|
+
No `Object.entries` polyfills (required since ES2017). No `class` polyfills (required since ES2015). Target ES2020 baseline — Hermes, Node 18, every modern browser.
|
|
157
|
+
|
|
158
|
+
### JSON Schema libraries
|
|
159
|
+
|
|
160
|
+
JSON Schema validators are 20-100+ KB. We use our hand-rolled validator for runtime + publish the `experiments.schema.json` only for IDE tooling.
|
|
161
|
+
|
|
162
|
+
### Runtime TypeScript features
|
|
163
|
+
|
|
164
|
+
- No `enum` (produces runtime object)
|
|
165
|
+
- No `namespace` (produces runtime object)
|
|
166
|
+
- No decorators
|
|
167
|
+
- No reflect-metadata
|
|
168
|
+
|
|
169
|
+
### Prototype chains
|
|
170
|
+
|
|
171
|
+
A single class. No inheritance. No mixins.
|
|
172
|
+
|
|
173
|
+
### Eager initialization
|
|
174
|
+
|
|
175
|
+
Storage is lazy. Fetcher polling is lazy. Crash-rollback counters allocate on first crash. Nothing runs until you call into the engine.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Measurement methodology
|
|
180
|
+
|
|
181
|
+
### Tooling
|
|
182
|
+
|
|
183
|
+
- **`size-limit`** with the `@size-limit/esbuild` plugin — matches what users actually ship
|
|
184
|
+
- **Bundlephobia** referenced for competitive comparison
|
|
185
|
+
- **Webpack stats-analyzer** for identifying unexpected bloat
|
|
186
|
+
|
|
187
|
+
### Automation
|
|
188
|
+
|
|
189
|
+
- Every PR runs `size-limit` as a required check
|
|
190
|
+
- Size changes are reported as a PR comment
|
|
191
|
+
- A release cannot ship if any package exceeds budget
|
|
192
|
+
|
|
193
|
+
### Real-world measurement
|
|
194
|
+
|
|
195
|
+
We test bundle impact in real apps:
|
|
196
|
+
|
|
197
|
+
1. **Drishtikon Mobile** (React Native, Expo)
|
|
198
|
+
2. **Next.js App Router example**
|
|
199
|
+
3. **SvelteKit example**
|
|
200
|
+
4. **Vite + React example**
|
|
201
|
+
|
|
202
|
+
Each example publishes its production bundle size to a tracking doc so we see *actual user impact*, not just synthetic benchmarks.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Per-feature cost estimates
|
|
207
|
+
|
|
208
|
+
Rough estimates based on our hand-rolled implementations:
|
|
209
|
+
|
|
210
|
+
| Feature | Estimated cost (gzipped) |
|
|
211
|
+
|---|---:|
|
|
212
|
+
| `VariantEngine` class + constructor | ~500 B |
|
|
213
|
+
| Schema validator | ~400 B |
|
|
214
|
+
| Targeting matcher | ~300 B |
|
|
215
|
+
| Semver matcher | ~250 B |
|
|
216
|
+
| Glob route matcher | ~150 B |
|
|
217
|
+
| Sticky hash function | ~80 B |
|
|
218
|
+
| Assignment strategies | ~200 B |
|
|
219
|
+
| Storage interface + memory impl | ~150 B |
|
|
220
|
+
| Fetcher interface | ~80 B |
|
|
221
|
+
| Telemetry interface | ~50 B |
|
|
222
|
+
| Crash rollback logic | ~200 B |
|
|
223
|
+
| Time-travel recording | ~150 B (off by default) |
|
|
224
|
+
| HMAC verification (WebCrypto wrapper) | ~150 B |
|
|
225
|
+
| Error classes | ~150 B |
|
|
226
|
+
| Type exports (zero runtime) | 0 B |
|
|
227
|
+
| Boilerplate / glue | ~200 B |
|
|
228
|
+
| **Total core** | **~3000 B** |
|
|
229
|
+
|
|
230
|
+
Fits within the 3 KB budget with ~100 B to spare. Any new feature must either stay under that headroom or displace an existing feature.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## What happens when we exceed the budget
|
|
235
|
+
|
|
236
|
+
A PR that exceeds the budget is blocked by CI. The author must either:
|
|
237
|
+
|
|
238
|
+
1. **Optimize** the change to fit
|
|
239
|
+
2. **Remove** an existing feature to make room
|
|
240
|
+
3. **Move** the change to a separate entry point (e.g., `@variantlab/core/extra`)
|
|
241
|
+
4. **Request a budget increase** via a GitHub discussion, requiring 2 maintainer approvals
|
|
242
|
+
|
|
243
|
+
We expect to reject the majority of budget-increase requests. The budget is the contract.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Comparison: what we avoid
|
|
248
|
+
|
|
249
|
+
If we used common conveniences, our core would be:
|
|
250
|
+
|
|
251
|
+
| Luxury | Cost | Running total |
|
|
252
|
+
|---|---:|---:|
|
|
253
|
+
| Starting point (hand-rolled) | 3.0 KB | 3.0 KB |
|
|
254
|
+
| Add `zod` for schema | +12 KB | 15 KB |
|
|
255
|
+
| Add `semver` | +6 KB | 21 KB |
|
|
256
|
+
| Add `minimatch` | +4 KB | 25 KB |
|
|
257
|
+
| Add `lodash.merge` | +1 KB | 26 KB |
|
|
258
|
+
| Add `eventemitter3` | +1 KB | 27 KB |
|
|
259
|
+
| Add `nanoid` | +0.5 KB | 27.5 KB |
|
|
260
|
+
|
|
261
|
+
**We would be nearly 10x our budget** before we wrote a line of actual logic. This is why zero-dependency is the founding principle, not an optimization we add later.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Future work
|
|
266
|
+
|
|
267
|
+
- **Brotli-compressed size as a secondary metric.** Browsers support Brotli; npm does not measure it. We should publish both numbers.
|
|
268
|
+
- **ESBuild minification tuning.** Terser may squeeze out a few more bytes in corner cases.
|
|
269
|
+
- **Constant folding for literal configs.** If a user passes a literal JSON config, we can inline it at build time via a plugin.
|
|
270
|
+
- **Subsetting for fixed-feature builds.** If someone uses only value experiments and never render experiments, we could ship a smaller build.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## See also
|
|
275
|
+
|
|
276
|
+
- [`ARCHITECTURE.md`](../../ARCHITECTURE.md#size-budgets) — the budget table
|
|
277
|
+
- [`docs/design/api-philosophy.md`](../design/api-philosophy.md) — how API choices affect bundle size
|
|
278
|
+
- Bundlephobia: https://bundlephobia.com (for competitor measurements)
|
|
279
|
+
- size-limit: https://github.com/ai/size-limit
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# Competitor analysis
|
|
2
|
+
|
|
3
|
+
A detailed comparison of variantlab to every existing A/B testing and feature-flagging solution we evaluated. The goal of this document is to be honest: where competitors do something better, we say so, and we plan to match or exceed them.
|
|
4
|
+
|
|
5
|
+
## Table of contents
|
|
6
|
+
|
|
7
|
+
- [Methodology](#methodology)
|
|
8
|
+
- [Summary matrix](#summary-matrix)
|
|
9
|
+
- [Firebase Remote Config](#firebase-remote-config)
|
|
10
|
+
- [GrowthBook](#growthbook)
|
|
11
|
+
- [Statsig](#statsig)
|
|
12
|
+
- [LaunchDarkly](#launchdarkly)
|
|
13
|
+
- [Amplitude Experiment](#amplitude-experiment)
|
|
14
|
+
- [PostHog Feature Flags](#posthog-feature-flags)
|
|
15
|
+
- [Flagsmith](#flagsmith)
|
|
16
|
+
- [Unleash](#unleash)
|
|
17
|
+
- [ConfigCat](#configcat)
|
|
18
|
+
- [react-native-ab](#react-native-ab)
|
|
19
|
+
- [Why a new package](#why-a-new-package)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Methodology
|
|
24
|
+
|
|
25
|
+
For each competitor we evaluated:
|
|
26
|
+
|
|
27
|
+
1. **Pricing** — free tier, paid tier, what you give up for free
|
|
28
|
+
2. **Framework support** — which frameworks have official SDKs, which are community-maintained
|
|
29
|
+
3. **SSR support** — does it work in Next.js App Router, Remix, SvelteKit without hydration mismatches
|
|
30
|
+
4. **Bundle size** — minified + gzipped for the primary SDK
|
|
31
|
+
5. **Runtime dependencies** — transitive supply chain exposure
|
|
32
|
+
6. **Self-hosting** — can you run the whole thing on your own infra
|
|
33
|
+
7. **Debug tooling** — what do you get for local development
|
|
34
|
+
8. **Security model** — HMAC, encryption, CSP compatibility
|
|
35
|
+
9. **Privacy** — what telemetry does the SDK send
|
|
36
|
+
10. **Type safety** — are experiment IDs typed
|
|
37
|
+
|
|
38
|
+
Sources: official documentation, npm package analysis, GitHub repos, pricing pages, community forums (as of early 2026).
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Summary matrix
|
|
43
|
+
|
|
44
|
+
| Tool | Free forever | Self-host | Bundle (gz) | Deps | Multi-framework | SSR | Type safety | Privacy |
|
|
45
|
+
|---|:-:|:-:|---:|---:|:-:|:-:|:-:|:-:|
|
|
46
|
+
| **variantlab** | ✅ | ✅ | ~3 KB | 0 | 10+ | ✅ | ✅ codegen | ✅ zero telemetry |
|
|
47
|
+
| Firebase Remote Config | Limited | ❌ | ~18 KB | many | 3 | Partial | ❌ | ❌ Google telemetry |
|
|
48
|
+
| GrowthBook | ✅ OSS | ✅ | ~7 KB | few | 5 | Partial | Partial | Partial |
|
|
49
|
+
| Statsig | Limited free | ❌ | ~14 KB | some | 4 | Partial | Partial | ❌ |
|
|
50
|
+
| LaunchDarkly | ❌ $$$ | ❌ | ~25 KB | some | 8 | ✅ | Partial | ❌ |
|
|
51
|
+
| Amplitude Experiment | Enterprise | ❌ | ~20 KB | many | 3 | Partial | ❌ | ❌ |
|
|
52
|
+
| PostHog Feature Flags | ✅ OSS | ✅ | ~30 KB | many | 4 | Partial | ❌ | Partial |
|
|
53
|
+
| Flagsmith | ✅ OSS | ✅ | ~8 KB | few | 4 | Partial | ❌ | Partial |
|
|
54
|
+
| Unleash | ✅ OSS | ✅ | ~10 KB | some | 5 | Partial | ❌ | Partial |
|
|
55
|
+
| ConfigCat | Limited | ❌ | ~12 KB | few | 5 | Partial | ❌ | ❌ |
|
|
56
|
+
| react-native-ab | ✅ OSS | N/A | ~2 KB | 0 | 1 | N/A | ❌ | ✅ |
|
|
57
|
+
|
|
58
|
+
Numbers are approximate and reflect the primary SDK (`firebase/remote-config`, `@growthbook/growthbook-react`, etc.) at the time of research.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Firebase Remote Config
|
|
63
|
+
|
|
64
|
+
**Pricing**: Free under the Spark plan with limits; paid under Blaze.
|
|
65
|
+
|
|
66
|
+
**Strengths**:
|
|
67
|
+
- Mature, battle-tested, used by billions of users
|
|
68
|
+
- Tight integration with other Firebase products (Analytics, Crashlytics)
|
|
69
|
+
- A/B Testing Console has a decent UI
|
|
70
|
+
- Works offline with local caching
|
|
71
|
+
|
|
72
|
+
**Weaknesses for us to beat**:
|
|
73
|
+
- **Lock-in** — tied to Google Cloud. You can't migrate away without rewriting.
|
|
74
|
+
- **Bundle size** — the Web SDK pulls in 18 KB+ of Firebase app initialization code even for a single `getValue()` call.
|
|
75
|
+
- **Telemetry-by-default** — sends analytics events on every config fetch.
|
|
76
|
+
- **No true SSR support** — Firebase Web SDK assumes a browser global.
|
|
77
|
+
- **No screen-size targeting** — you have to implement it yourself.
|
|
78
|
+
- **No type safety** — values are stringly-typed; you cast on read.
|
|
79
|
+
- **No debug picker UI** — you build your own.
|
|
80
|
+
- **No self-host option** — you cannot run Remote Config without Google Cloud.
|
|
81
|
+
- **Config signing is proprietary** — verified by Google's infrastructure, not cryptographically auditable by the client.
|
|
82
|
+
|
|
83
|
+
**Verdict**: Great if you're already all-in on Firebase. Wrong choice for anyone who wants portability, small bundles, or strong privacy.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## GrowthBook
|
|
88
|
+
|
|
89
|
+
**Pricing**: Free open-source + paid SaaS.
|
|
90
|
+
|
|
91
|
+
**Strengths**:
|
|
92
|
+
- Open source core, self-hostable
|
|
93
|
+
- Well-designed A/B test result analysis (Bayesian + frequentist)
|
|
94
|
+
- React, Vue, and Node SDKs
|
|
95
|
+
- Visual editor (paid)
|
|
96
|
+
- Feature flag concept is well-modeled
|
|
97
|
+
|
|
98
|
+
**Weaknesses for us to beat**:
|
|
99
|
+
- **Web-first, React Native is an afterthought** — the RN SDK is a wrapper around the JS SDK with known issues around offline mode.
|
|
100
|
+
- **No Svelte, Solid, or Astro support.**
|
|
101
|
+
- **Bundle size is ~7 KB gzipped** for the React SDK, which is better than Firebase but still 2x our target.
|
|
102
|
+
- **No crash-triggered rollback.**
|
|
103
|
+
- **No debug overlay out of the box** — you get a "debug panel" in the dashboard, not on-device.
|
|
104
|
+
- **No deep link override.**
|
|
105
|
+
- **SSR works but is manual** — you must hydrate context yourself.
|
|
106
|
+
- **Type safety is partial** — you can type feature keys in TS but it's not auto-generated from config.
|
|
107
|
+
|
|
108
|
+
**Verdict**: The best existing OSS option, and a worthy benchmark. variantlab's differentiation is (a) wider framework support, (b) smaller bundle, (c) better RN story, (d) on-device debug tooling, (e) HMAC signing.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Statsig
|
|
113
|
+
|
|
114
|
+
**Pricing**: Free tier up to a modest event volume, then paid.
|
|
115
|
+
|
|
116
|
+
**Strengths**:
|
|
117
|
+
- Fastest-growing in the space
|
|
118
|
+
- Excellent real-time experiment dashboards
|
|
119
|
+
- Native SDKs for iOS and Android (not just React Native)
|
|
120
|
+
- Pulse analysis for automated decisioning
|
|
121
|
+
|
|
122
|
+
**Weaknesses for us to beat**:
|
|
123
|
+
- **Telemetry is mandatory** — Statsig's core value prop is sending events back to their dashboard
|
|
124
|
+
- **No self-host** in the free tier
|
|
125
|
+
- **Bundle size** ~14 KB
|
|
126
|
+
- **Framework support** is limited to React, React Native, Node, and browsers
|
|
127
|
+
- **Target audience is teams with data scientists** — overkill for a solo developer
|
|
128
|
+
|
|
129
|
+
**Verdict**: Excellent product for a funded startup. Overkill and underfit for our audience of developers who want a free, lightweight, self-contained toolkit.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## LaunchDarkly
|
|
134
|
+
|
|
135
|
+
**Pricing**: Starts at ~$8.33/seat/month, enterprise features much higher.
|
|
136
|
+
|
|
137
|
+
**Strengths**:
|
|
138
|
+
- Industry standard for feature flags in regulated enterprises
|
|
139
|
+
- Best-in-class approval workflows and audit logs
|
|
140
|
+
- Wide framework support (8+ SDKs)
|
|
141
|
+
- Excellent SSR support including streaming
|
|
142
|
+
- Crash-triggered rollback (in enterprise tier)
|
|
143
|
+
|
|
144
|
+
**Weaknesses for us to beat**:
|
|
145
|
+
- **Not free.** At all. For anyone.
|
|
146
|
+
- **Bundle size ~25 KB** gzipped for the browser SDK
|
|
147
|
+
- **Telemetry mandatory** — core value prop is shipping events to the dashboard
|
|
148
|
+
- **Vendor lock-in** — their proprietary targeting DSL does not map cleanly to anything else
|
|
149
|
+
- **Adoption cost is high** — requires org-wide buy-in
|
|
150
|
+
|
|
151
|
+
**Verdict**: The feature set we want to match for free. LaunchDarkly's crash-rollback, approval workflows, and SSR streaming are the north star.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Amplitude Experiment
|
|
156
|
+
|
|
157
|
+
**Pricing**: Enterprise tier of Amplitude Analytics. Not sold standalone.
|
|
158
|
+
|
|
159
|
+
**Strengths**:
|
|
160
|
+
- Tight integration with Amplitude's behavioral analytics
|
|
161
|
+
- Sophisticated cohort-based targeting
|
|
162
|
+
|
|
163
|
+
**Weaknesses for us to beat**:
|
|
164
|
+
- **Enterprise-only**
|
|
165
|
+
- **Bundle size ~20 KB** plus Amplitude's core SDK
|
|
166
|
+
- **Telemetry-heavy**
|
|
167
|
+
- **React Native support is limited**
|
|
168
|
+
|
|
169
|
+
**Verdict**: Not relevant to our audience. Listed for completeness.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## PostHog Feature Flags
|
|
174
|
+
|
|
175
|
+
**Pricing**: Free tier generous; paid for high volume.
|
|
176
|
+
|
|
177
|
+
**Strengths**:
|
|
178
|
+
- Open source and self-hostable
|
|
179
|
+
- Part of the broader PostHog analytics platform
|
|
180
|
+
- Good React and RN SDKs
|
|
181
|
+
|
|
182
|
+
**Weaknesses for us to beat**:
|
|
183
|
+
- **Bundle size ~30 KB** for the primary SDK because it's coupled to PostHog's analytics
|
|
184
|
+
- **Telemetry coupling** — feature flags require PostHog's event pipeline
|
|
185
|
+
- **No type safety from config**
|
|
186
|
+
- **No on-device debug picker**
|
|
187
|
+
- **No SSR story for Next.js App Router**
|
|
188
|
+
|
|
189
|
+
**Verdict**: Good choice if you already use PostHog for analytics. Heavy if you don't.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Flagsmith
|
|
194
|
+
|
|
195
|
+
**Pricing**: Free OSS + paid SaaS.
|
|
196
|
+
|
|
197
|
+
**Strengths**:
|
|
198
|
+
- Open source and self-hostable
|
|
199
|
+
- Multi-environment support built in
|
|
200
|
+
- Segment-based targeting
|
|
201
|
+
|
|
202
|
+
**Weaknesses for us to beat**:
|
|
203
|
+
- **Bundle size ~8 KB**
|
|
204
|
+
- **SSR requires manual hydration**
|
|
205
|
+
- **React Native SDK has network-request-per-flag antipattern**
|
|
206
|
+
- **No type safety**
|
|
207
|
+
- **No on-device debug tooling**
|
|
208
|
+
|
|
209
|
+
**Verdict**: Solid for server-side feature flags. Weak on mobile.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Unleash
|
|
214
|
+
|
|
215
|
+
**Pricing**: Free OSS + paid SaaS.
|
|
216
|
+
|
|
217
|
+
**Strengths**:
|
|
218
|
+
- Open source and self-hostable
|
|
219
|
+
- Focus on enterprise feature flags (as opposed to A/B testing)
|
|
220
|
+
- Well-designed API
|
|
221
|
+
|
|
222
|
+
**Weaknesses for us to beat**:
|
|
223
|
+
- **Weak A/B testing story** — designed as a flagging tool, not an experiment tool
|
|
224
|
+
- **No React Native SDK** (as of early 2026)
|
|
225
|
+
- **No debug overlay**
|
|
226
|
+
- **No type safety**
|
|
227
|
+
|
|
228
|
+
**Verdict**: Enterprise-flag focused. Different problem space.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## ConfigCat
|
|
233
|
+
|
|
234
|
+
**Pricing**: Free tier up to 10 feature flags.
|
|
235
|
+
|
|
236
|
+
**Strengths**:
|
|
237
|
+
- Simple, focused on feature flags
|
|
238
|
+
- Wide SDK coverage
|
|
239
|
+
|
|
240
|
+
**Weaknesses for us to beat**:
|
|
241
|
+
- **10-flag limit on free tier**
|
|
242
|
+
- **Bundle size ~12 KB**
|
|
243
|
+
- **No A/B test analysis**
|
|
244
|
+
- **No debug picker**
|
|
245
|
+
- **Telemetry by default**
|
|
246
|
+
|
|
247
|
+
**Verdict**: Too limited for our audience.
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## react-native-ab
|
|
252
|
+
|
|
253
|
+
**Pricing**: Free OSS.
|
|
254
|
+
|
|
255
|
+
**Strengths**:
|
|
256
|
+
- Tiny (~2 KB)
|
|
257
|
+
- Zero dependencies
|
|
258
|
+
- Simple API
|
|
259
|
+
|
|
260
|
+
**Weaknesses for us to beat**:
|
|
261
|
+
- **Unmaintained** — last release 2+ years ago
|
|
262
|
+
- **React Native only**
|
|
263
|
+
- **No targeting beyond random split**
|
|
264
|
+
- **No persistence**
|
|
265
|
+
- **No debug picker**
|
|
266
|
+
- **No SSR**
|
|
267
|
+
- **No type safety**
|
|
268
|
+
|
|
269
|
+
**Verdict**: The closest thing to what we want, and the direct inspiration for variantlab. But it hasn't grown up — variantlab is what `react-native-ab` could have been.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Why a new package
|
|
274
|
+
|
|
275
|
+
Looking at the matrix, every existing option fails at least one of the following:
|
|
276
|
+
|
|
277
|
+
1. **Free forever with no usage caps**
|
|
278
|
+
2. **Zero runtime dependencies**
|
|
279
|
+
3. **Smaller than 5 KB gzipped**
|
|
280
|
+
4. **Full multi-framework support**
|
|
281
|
+
5. **Strong SSR correctness**
|
|
282
|
+
6. **On-device debug tooling**
|
|
283
|
+
7. **Type safety from codegen**
|
|
284
|
+
8. **Zero telemetry by default**
|
|
285
|
+
|
|
286
|
+
variantlab's thesis: it is possible to do all of these at once, and no one has. That's the opportunity.
|
|
287
|
+
|
|
288
|
+
### What we should copy
|
|
289
|
+
|
|
290
|
+
- **LaunchDarkly's crash rollback** — best-in-class, bring it to free
|
|
291
|
+
- **GrowthBook's Bayesian analysis story** — reuse for post-v1.0 analytics integrations
|
|
292
|
+
- **Statsig's real-time targeting evaluation UI** — bring that to our debug overlay
|
|
293
|
+
- **Firebase's offline caching** — our Storage adapters already do this
|
|
294
|
+
- **PostHog's self-host-first ethos** — we do the same, but lighter
|
|
295
|
+
|
|
296
|
+
### What we should avoid
|
|
297
|
+
|
|
298
|
+
- **Firebase's bundle bloat** from an over-engineered SDK initialization story
|
|
299
|
+
- **Statsig's telemetry coupling** — we separate engine from analytics
|
|
300
|
+
- **LaunchDarkly's proprietary DSL** — we use plain JSON
|
|
301
|
+
- **GrowthBook's manual SSR hydration** — we automate it
|
|
302
|
+
- **Everyone's lack of on-device debug pickers** — this is our wedge
|
|
303
|
+
|
|
304
|
+
### Where we will lose (at first)
|
|
305
|
+
|
|
306
|
+
- **Dashboard UX** — we don't ship a hosted dashboard in v0.1. GrowthBook, Statsig, LaunchDarkly all do. We bet that the CLI + JSON config workflow is enough for early adopters.
|
|
307
|
+
- **Analytics integration depth** — PostHog ships with full event tracking. We only call `onExposure` hooks; integrators wire the rest.
|
|
308
|
+
- **Marketing** — we're a 1-person OSS project vs funded startups.
|
|
309
|
+
|
|
310
|
+
These are acceptable losses for v0.1. Post-v1.0 we can address the first two.
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Sources
|
|
315
|
+
|
|
316
|
+
- Firebase Remote Config docs: https://firebase.google.com/docs/remote-config
|
|
317
|
+
- GrowthBook docs: https://docs.growthbook.io
|
|
318
|
+
- Statsig docs: https://docs.statsig.com
|
|
319
|
+
- LaunchDarkly docs: https://docs.launchdarkly.com
|
|
320
|
+
- Amplitude Experiment docs: https://www.docs.developers.amplitude.com/experiment/
|
|
321
|
+
- PostHog docs: https://posthog.com/docs/feature-flags
|
|
322
|
+
- Flagsmith docs: https://docs.flagsmith.com
|
|
323
|
+
- Unleash docs: https://docs.getunleash.io
|
|
324
|
+
- ConfigCat docs: https://configcat.com/docs
|
|
325
|
+
- react-native-ab on npm: https://www.npmjs.com/package/react-native-ab
|
|
326
|
+
|
|
327
|
+
Bundle sizes measured via Bundlephobia and direct npm tarball inspection.
|