flagmint-vuejs-feature-flags
Version:
A Vue.js SDK for managing feature flags in Flagmint applications, supporting both Vue 2 and Vue 3.
398 lines (288 loc) • 11.5 kB
Markdown
# Flagmint Vue Feature Flags SDK
A lightweight and powerful feature flag SDK for Vue 2 and Vue 3 applications.
Supports:
✅ Client-side flag evaluation
✅ Segment targeting and rollout strategies
✅ WebSocket or HTTP long-polling
✅ Offline caching and preview mode
✅ Vue 2 and Vue 3 plugin, composables, and helpers
✅ Cross-Iframe & Multi-Tab Socket Optimization (Leader Election)
## 🔧 Installation
```bash
npm install flagmint-vuejs-feature-flags
````
## 🚀 Quick Start
### Vue 2
```ts
// main.js
import Vue from 'vue';
import { createFlagmintPlugin } from 'flagmint-vuejs-feature-flags';
Vue.use(createFlagmintPlugin({
apiKey: 'your-api-key',
context: { user_id: 'abc123', country: 'NG' },
transportMode: 'auto',
autoRefresh: true,
previewMode: false,
// Optional Cross-Iframe Optimization Parameters:
syncCrossIframes: true,
syncNamespace: 'wsd_app_prod'
}));
new Vue({ render: h => h(App) }).$mount('#app');
```
### Vue 3
```ts
// main.ts
import { createApp } from 'vue';
import App from './App.vue';
import { createFlagmintPlugin } from 'flagmint-vuejs-feature-flags';
const app = createApp(App);
app.use(createFlagmintPlugin({
apiKey: 'your-api-key',
context: { user_id: 'abc123' },
transportMode: 'auto',
previewMode: false,
// Optional Cross-Iframe Optimization Parameters:
syncCrossIframes: true,
syncNamespace: 'wsd_app_prod'
}));
app.mount('#app');
```
## ⚙️ API Overview
### `FlagClientOptions`
```ts
interface FlagClientOptions {
apiKey: string;
context?: Record<string, any>;
autoRefresh?: boolean;
refreshIntervalMs?: number;
persistContext?: boolean;
env?: string; // production | staging
enableOfflineCache?: boolean;
cacheTTL?: number;
transportMode?: 'auto' | 'websocket' | 'long-polling';
previewMode?: boolean;
onError?: (err: Error) => void;
// Framework Extension Parameters
deferInitialization?: boolean;
syncCrossIframes?: boolean; // Collapses multiple nested iframes/tabs down to 1 open socket
syncNamespace?: string; // Unique isolation scope descriptor to prevent account data bleed
}
```
## 🎯 Using Flags
### Cross-Iframe & Multi-Tab Connection Sharing
When embedding your Vue application multiple times on a single webpage using nested `iframe` configurations (or when a user leaves your app open across multiple simultaneous browser windows), initializing separate WebSockets or SSE streams per view causes significant connection overhead.
Setting syncCrossIframes: true leverages a reactive browser mesh network strategy via the BroadcastChannel API and Page Visibility API to solve this issue seamlessly.
```
🔒 Security Notice: Always match your syncNamespace parameter configuration with your underlying profile or customer session parameters (e.g. syncNamespace: currentSiteId). This isolates the messaging context and prevents separate client scopes from accidentally reading or blending flag variants in multi-tenant environments.
### ✅ Recommended (Reactive Flags)
Subscribe to flag updates so your component re-renders automatically on every WebSocket push:
```ts
// Vue 3 Composition API
import { useFlagmint } from 'flagmint-vuejs-feature-flags';
const { getFlag, isReady } = useFlagmint();
const darkMode = computed(() => getFlag('dark-mode', false));
```
```ts
// Vue 2 (via mixin)
export default {
mixins: [useFlagsMixin],
mounted() {
console.log(this.flags['dark-mode']);
console.log(this.getFlag('dark-mode', false));
}
}
```
### ⚠️ Non-reactive (snapshot only, won't update on push)
```ts
const enabled = this.$flagmint.getFlag('dark-mode', false);
```
Use this only when you genuinely need a one-time read (e.g. inside a non-reactive utility function). For anything rendered in a template, use the reactive pattern above.
## ⏳ Await Initialization
`$flagmintReady` is a reactive boolean ref, not a Promise — watch it rather than awaiting it:
```ts
// Vue 2
this.$watch('$flagmintReady', (ready) => {
if (ready) { /* client is ready */ }
});
```
```ts
// Vue 3
import { useFlagmintReady } from 'flagmint-vuejs-feature-flags';
const isReady = useFlagmintReady(); // Ref<boolean>
watch(isReady, (ready) => {
if (ready) { /* client is ready */ }
});
```
If you need a one-time async wait (e.g. before an `onMounted` block runs), call the injected `init()` function instead — it returns a Promise that resolves once the client is ready:
```ts
const client = await this.$flagmintInit(); // Vue 2
```
## 🔌 Vue 2 Helpers
### `$flagmint` access
```ts
export default {
async mounted() {
const client = await this.$flagmintInit();
const enabled = client.getFlag('dark-mode', false);
}
}
```
### ✅ Vue 2 Mixin
```ts
import { useFlagsMixin } from 'flagmint-vuejs-feature-flags/vue2/mixin/useFlagsMixin';
export default {
mixins: [useFlagsMixin],
mounted() {
console.log(this.flags['dark-mode']);
console.log(this.getFlag('dark-mode', false));
}
}
```
## 🔌 Vue 3 Helpers
### Option A: Composition API (Recommended)
```ts
import { useFlagmint } from 'flagmint-vuejs-feature-flags';
export default {
setup() {
const { getFlag, isReady } = useFlagmint();
const feature = computed(() => getFlag('chat-enabled', false));
return { feature };
}
}
```
### Option B: Injected
```ts
import { inject } from 'vue';
export default {
setup() {
const client = inject('__flagmint__');
const feature = client?.getFlag('chat-enabled');
return { feature };
}
}
```
## 💡 Debugging Broadcast Channels in DevTools
To verify that multiple frames are collapsing down to a single socket on your local device environment:
- Open Chrome/Edge DevTools (F12) and head to the Application tab.
- In the left panel section menu, find Background Services and click on Broadcast Channels.
- Reload your project page setup. You will see your namespace registration signature.
- Click on your project Network panel tab. Confirm that switching windows or updating your cloud variables updates all views concurrently while creating exactly one websocket initialization record track.
## 🧩 Feature Component
In Vue templates, use the `<BoolFeatureGate>` component:
<BoolFeatureGate> is for boolean flags only, and string/number/JSON flags should be read via getFlag() directly and branched on with v-if.
```vue
<template>
<BoolFeatureGate featureKeys="['dark-mode']">
<div>Dark mode is enabled!</div>
</BoolFeatureGate>
</template>
<script setup lang="ts">
import { BoolFeatureGate } from 'flagmint-vuejs-feature-flags/vue3/Feature';
</script>
```
`feature-keys` accepts a single string or an array of strings. If multiple keys are provided, **all** must be enabled.
> ⚠️ **Important:** Never call `client.destroy()` from within a component or composable. `destroy()` tears down the shared WebSocket connection for the entire application. If you're building a custom integration around `FlagClient`, only ever call `client.subscribe()` and its returned unsubscribe function for component-level cleanup.
## 🧪 Preview Mode (No Network)
Enable `previewMode: true` in `FlagClientOptions` to evaluate flags **locally only**:
* No API key needed
* Useful for SDK testing, Storybook, or static environments
```ts
createFlagmintPlugin({
previewMode: true,
context: { user_id: 'test' }
});
```
You can then load flags directly:
```ts
flagClient.setFlags([flag1, flag2], segmentsById);
```
⚠️ A console warning appears in development when `previewMode` is active.
## 🧠 Evaluation Logic
* Operators: `eq`, `neq`, `in`, `nin`, `gt`, `lt`, `exists`, `not_exists`
* Segment references and rule groups
* Rollout strategies:
* `percentage` — user hashes to percentile
* `variant` — weighted multi-variant assignment
## 🔁 Realtime Updates
Using `transportMode: 'websocket'` or `'auto'`, flags update live when changed.
Fallback to polling if WebSocket fails.
## 📦 Versioning & Releases
This project follows [Semantic Versioning](https://semver.org/). Releases are automated via GitHub Actions:
- Changes to `sdk/`, `package.json`, `rollup.config.js`, or `tsconfig.json` on the `main` branch trigger the release workflow
- The workflow extracts the version from `package.json`, builds the package, and publishes to npm
- Git tags and GitHub releases are automatically created
**To release a new version:**
1. Update the version in `package.json`
2. Update [CHANGELOG.md](https://github.com/jtad009/flagmint-vuejs-feature-flags-sdk/blob/main/CHANGELOG.md) with your changes
3. Push to `main` branch
4. The workflow handles the rest
See [CHANGELOG.md](https://github.com/jtad009/flagmint-vuejs-feature-flags-sdk/blob/main/CHANGELOG.md) for version history and release notes.
## 🤝 Contributing
Contributions are welcome! Here's how to get started:
1. Clone the repository
2. Install dependencies: `npm install`
3. Run tests: `npm test`
4. Build the project: `npm run build`
5. Create a feature branch: `git checkout -b feature/your-feature`
6. Make your changes and commit
7. Push to your fork and create a Pull Request
**Development Tips:**
- The SDK supports both Vue 2 and Vue 3 — test changes against both versions
- Keep bundle size in mind when adding dependencies
- Run tests before submitting PRs
- Never call `client.destroy()` from a component, mixin, or composable — only the plugin/app-level teardown should own that call
## 🗂 Roadmap
* [x] Segment evaluation
* [x] Rollout strategies
* [x] Preview/local-only mode
* [x] Composables and mixins
* [x] WebSocket + fallback
* [x] Feature component
* [ ] SSR / Nuxt support
* [ ] Variant analytics
* [ ] Remote override via devtools
## 🐛 Troubleshooting
### Flags not loading
- Ensure `apiKey` is valid and environment has network access
- Check browser console for errors
- Verify `context` is properly set with required attributes
- If using `previewMode: true`, ensure you've called `flagClient.setFlags()`
### WebSocket connection fails
- The SDK automatically falls back to long-polling
- Check network connectivity and CORS settings
- Verify the API server supports WebSocket connections
- Check browser console for connection errors
### Component re-renders not happening on flag updates
- Make sure you're using the reactive pattern (`useFlagmint()` composable or `useFlagsMixin`) rather than calling `client.getFlag()` directly in a `computed`. The client's internal flag state is not a Vue reactive object — only the `subscribe()`-backed wrappers re-render correctly
- If you unmounted and remounted a `<BoolFeatureGate>` and updates stopped working app-wide, confirm nothing in your codebase calls `client.destroy()` outside of app-level teardown
### Stale flags in offline cache
- Clear localStorage or set `enableOfflineCache: false`
- Adjust `cacheTTL` to control cache duration (in milliseconds)
- Use `previewMode: true` for testing without network
### Performance issues
- Limit the number of flags in targeting rules
- Use the `subscribe()`-backed reactive pattern (via composable or mixin) instead of polling `getFlag()` in a loop
- Consider lazy-loading flags for large applications
### TypeScript issues
- Ensure `tsconfig.json` includes `sdk/` in `include` paths
- Check that `node_modules` types are installed: `npm install`
## 📜 License
BSD 3-Clause License