npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@amirafa/vuexp

v1.0.7

Published

Lightweight Vue 3 plugin for A/B testing and experiments with variant tracking.

Readme

Vuexp is a lightweight and flexible Vue 3 plugin for A/B testing.
It helps you define experiments, control variant traffic, track user behavior, and manage exposure — all directly within your Vue components.


📚 Table of Contents


📦 Installation

npm install @amirafa/vuexp
# or
yarn add @amirafa/vuexp

🧠 Fundamentals

Each A/B Test can include multiple Experiments.
Every Experiment decides which Variant (A, B, etc.) of your UI to show based on a statistical parameter called Traffic.

For example:

// Example traffic definition
traffic: {
  A: 0.6,
  B: 0.4,
}
  • The plugin will randomly assign a user to one of these variants based on the given probabilities.
  • Variant A appears ~60% of the time, and B ~40%.
  • If total traffic is less than 1.0 (e.g. { A: 0.6, B: 0.2 }), the remaining ratio (0.2) becomes the Holdout group, which sees your defined defaultVariant.

✅ Vuexp automatically normalizes the traffic and chooses holdouts for you.

Changing the version in the global config flushes all saved experiments, so users will get newly assigned variants the next time they appear.


⚙️ Setup

// main.ts or App.vue
import { createApp } from "vue";
import App from "./App.vue";
import { Vuexp } from "@amirafa/vuexp";

const app = createApp(App);

app.use(Vuexp, {
    version: "1.0.0", // Change to reset cached experiments
    debug: true, // Show logs in console (optional)

    // Optional remote config & tracking
    experimentsFetchApi: "https://example.com/config.php",
    experimentsTrackApi: "https://example.com/track.php",

    userId: undefined, // optional -better to set it after login (setUserContext({userId:<user_id>}))
    visitorId: "abc123", // optional - better to generate it based on browser

    // Global onTrack callback (fires after each track)
    onTrack(payload, response) {
        console.log("[onTrack()] Payload Sent", payload);
        console.log("[onTrack()] Response Received", response);
    },

    // Track mode options
    trackMode: {
        mode: "interval", // 'immediate' | 'interval'
        interval: 10000, // flush interval in ms (optional)
        batchSize: 2, // events per flush (optional)
    },

    // Default experiment fallback
    default: {
        variants: ["A", "B"],
        defaultVariant: "A",
        traffic: { A: 0.9, B: 0.6 },
    },
});

app.mount("#app");

🔺 Config Priority

Vuexp decides experiment settings in this order:

Remote Fetch → Component Props → Global Config → Default

🧩 Usage

Vuexp offers multiple ways to define experiments.


a) Component with Props

<Experiment
    experiment="experiment-button"
    :is="{ A: ButtonTypeA, B: ButtonTypeB }"
    :variants="['A', 'B']"
    :traffic="{ A: 0.6, B: 0.4 }"
/>

b) Component with Slots

<Experiment
    experiment="experiment-button"
    :variants="['A', 'B']"
    :traffic="{ A: 0.6, B: 0.4 }"
>
  <template #A>
    <ButtonTypeA />
  </template>

  <template #B>
    <ButtonTypeB />
  </template>
</Experiment>

c) Component with Slot Props

<Experiment
    experiment="experiment-button"
    :variants="['A', 'B']"
    :traffic="{ A: 0.6, B: 0.4 }"
    v-slot="{ variant, track }"
>
  <div
    @click="
      track('click', {
        attributes: {
          info: `[experiment-button] clicked at ${new Date().toISOString()}`,
        },
      })
    "
  >
    <ButtonTypeA v-if="variant === 'A'" />
    <ButtonTypeB v-else-if="variant === 'B'" />
    <span v-else>Holdout</span>
  </div>
</Experiment>

d) Composable: useExperiment

<template>
    <div>
        <ButtonTypeA v-if="variant === 'A'" />
        <ButtonTypeB v-if="variant === 'B'" />
    </div>
</template>

<script setup lang="ts">
import { useExperiment } from "@amirafa/vuexp";
import ButtonTypeA from "./ButtonTypeA.vue";
import ButtonTypeB from "./ButtonTypeB.vue";

const { variant, track } = useExperiment("btn-use", {
    variants: ["A", "B"],
    traffic: { A: 0.6, B: 0.4 },
});
</script>

🧾 Experiment Props

| Prop | Type | Required | Description | | ---------------- | --------------------------- | -------- | --------------------------------- | | experiment | string | ✅ | Unique experiment identifier | | variants | string[] | ❌ | Available variant names | | traffic | Record<string, number> | ❌ | Variant probabilities (0–1) | | defaultVariant | string | ❌ | Variant used for holdouts | | is | Record<string, Component> | ❌ | Map of variant keys to components |


🧩 Component Expose

Each <Experiment> component exposes a track() method:

track(
  event: "exposure" | "click" | "mouseenter" | "mouseleave" | string,
  options?: {
    attributes?: Record<string, any>;
    immediate?: boolean;
  }
)

Example:

<Experiment
    ref="experimentButtonRef"
    experiment="experiment-button"
    :is="{ A: ButtonTypeA, B: ButtonTypeB }"
    :variants="['A', 'B']"
    :traffic="{ A: 0.6, B: 0.4 }"
    @click="
        experimentButtonRef.track('click', {
            attributes: {
                info: `[btn-slot] clicked at ${new Date().toISOString()}`,
            },
        })
    "
/>

🎧 Component Emits

| Event | Payload | | ------------------------------------------- | ------------------ | | "click" / "mouseenter" / "mouseleave" | { track, event } |

Example:

<Experiment
    experiment="experiment-button"
    :is="{ A: ButtonTypeA, B: ButtonTypeB }"
    :variants="['A', 'B']"
    :traffic="{ A: 0.6, B: 0.4 }"
    @click="
        ({ track, event }) => {
            console.log('Event:', event);
            track('click', {
                attributes: {
                    info: `[experiment-button] clicked at ${new Date().toISOString()}`,
                },
            }).then((resp) => {
                console.log('[payload]', resp.payload);
                console.log('[response]', resp.response);
            });
        }
    "
/>

📡 Tracking System

You can track experiment data automatically or manually.

Global Tracking (onTrack)

onTrack(payload, response) {
  console.log("[onTrack()] Payload Sent", payload);
  console.log("[onTrack()] Response Received", response);
}

If experimentsTrackApi is defined, Vuexp sends the data to that endpoint and passes the response here.
Otherwise, only the payload is sent to onTrack (response = undefined).


🧩 Tracking Priority

1️⃣ Global onTrack()  →  2️⃣ Local track()

Local track() calls always run after the global callback.


⏱️ Track Modes

| Mode | Description | | ----------- | -------------------------------------------------------------------- | | immediate | Sends the event right after track() is called | | interval | Buffers events and sends them after a defined interval or batch size |

Even in interval mode, you can set { immediate: true } per event to override behavior.


📤 Track Data Structure

When you define a experimentsTrackApi endpoint in your plugin configuration,
Vuexp automatically sends a payload containing detailed tracking information for every experiment event.

Each record in the payload follows the type:

export type ExperimentTrackRequestType = {
    experiment: string; // Experiment name
    variant: string; // Assigned variant (A, B, etc.)
    version: string; // Event's version
    event: EventType; // 'exposure', 'click', 'mouseenter', etc.
    timestamp: number; // Unix timestamp of the event
    user_id?: string | number; // Optional user ID
    visitor_id?: string | number; // Optional visitor ID
    attributes?: Record<string, any>; // Custom attributes (optional)
};
  • experiment: The unique identifier of the experiment.
  • variant: The variant that was displayed to the user.
  • version: The version of the test in which the event was tracked.
  • event: The event type that triggered tracking (e.g., click, exposure).
  • timestamp: Captured automatically at the moment of tracking.
  • user_id / visitor_id: Identifiers that you can set dynamically using setUserContext().
  • attributes: Any custom metadata or context (e.g., button label, page, session data, etc.).

Example payload sent to your API:

[
    {
        "experiment": "experiment-button",
        "variant": "A",
        "version": "1.0.1"
        "event": "click",
        "timestamp": 1739923200000,
        "user_id": "1000",
        "visitor_id": "abc123",
        "attributes": {
            "info": "Clicked CTA in header"
        }
    }
]

💡 You can safely call setUserContext() at any time before or after plugin initialization — Vuexp automatically attaches the most recent user_id or visitor_id values to all future tracking events.


🎯 Events

Vuexp provides 4 main built-in event types:

"exposure" | "click" | "mouseenter" | "mouseleave"
  • exposure → fired automatically when an experiment first becomes visible
  • click, mouseenter, mouseleave → can be triggered manually via track() or emits
  • You can also use custom event names for advanced tracking.

Example:

<Experiment
    experiment="experiment-button"
    :is="{ A: ButtonTypeA, B: ButtonTypeB }"
    :variants="['A', 'B']"
    :traffic="{ A: 0.6, B: 0.4 }"
    @click="
        ({ track }) =>
            track('click', {
                attributes: {
                    info: `[experiment-button] clicked at ${new Date().toISOString()}`,
                },
            })
    "
/>

🌐 Remote Config Example

You can host your experiment configuration on a server:

GET https://example.com/config.php

{
    "experiments": {
        "experiment-button": {
            "variants": ["A", "B"],
            "traffic": { "A": 0.6, "B": 0.4 },
            "defaultVariant": "A"
        },
        "hero-banner": {
            "variants": ["Large", "Compact"],
            "traffic": { "Large": 0.5, "Compact": 0.5 }
        }
    },
    "default": {
        "variants": ["A", "B"],
        "traffic": { "A": 0.5, "B": 0.5 }
    }
}

Vuexp will automatically merge these configs and use them with highest priority.


🔄 Versioning & Cache Behavior

  • Each experiment’s variant is persisted per version.
  • Updating version in your Vuexp config flushes previous variant assignments.
  • This ensures that new configurations or traffic ratios are applied cleanly.

🧮 Holdout Group

When total traffic < 1.0, the remaining ratio is assigned to Holdout users —
they see your defaultVariant and are excluded from experiment analysis.

Example:

traffic: { A: 0.6, B: 0.2 } // 0.2 → Holdout

👤 Setting User Context

Vuexp allows you to associate experiment tracking data with a specific user or visitor.
This helps distinguish between logged-in users and anonymous sessions when sending tracking events.

You can set the userContext at GlobalConfig; But its better to leave it to the plugin to generate a unique id for visitor

app.use(Vuexp, {
  ...
  userId: undefined, //For logged-in users
  visitorId: 'abc123', // Optional unique ID for guests
  ...
});

You can update the userContext at any time — for example, right after user login or when visitor information becomes available.

import { setUserContext } from "@amirafa/vuexp";

// Example: set after authentication
setUserContext({
    userId: 1000, // For logged-in users
    visitorId: "abc123", // Optional unique ID for guests
});

Behind the scenes, Vuexp keeps an internal reactive state used for tracking:

state.value = {
  ...
  userId: string | undefined,    // Logged-in user identifier
  visitorId: string | undefined, // Anonymous visitor identifier
  ...
};
  • userId and visitorId are sent automatically with every tracked event once set.
  • visitorId is useful for anonymous visitors or guest sessions.
  • You can safely call setUserContext() whenever user data becomes available — it will instantly update the tracking context used for all future events.

Example integration in a login flow:

// After successful login
login().then((user) => {
    setUserContext({ userId: user.id });
});

🧠 Tip: Call setUserContext() once per session or whenever user identity changes (e.g., login, logout, or switch account).

🐞 Debug Mode

Set debug: true in your config to see internal logs:

[Vuexp] Experiment: experiment-button → Variant: B
[Vuexp] Track event: click { ...payload }

Useful for development and QA environments.


💡 Best Practices

  • Use consistent experiment naming (header-color, cta-button, etc.)
  • Bump version whenever changing traffic or variants
  • Keep traffic ratios between 0–1; Vuexp normalizes automatically
  • Use immediate: true for purchase or form events
  • Enable remote config for dynamic updates without redeploys
  • Use debug mode in staging to inspect assignments and payloads

📜 License

MIT © 2025 [Amirafa]

Simple. Reliable. Vue-native A/B Testing.


Changelog

[Version 1.0.7] - 2025-10-24

Feature

  • Update storing variants system

[Version 1.0.6] - 2025-10-21

Feature

  • Obfuscate dist

[Version 1.0.5] - 2025-10-21

Feature

  • Fix remote-config storage
  • Obfuscate the experiments key/value

[Version 1.0.4] - 2025-10-20

Feature

  • Add version to track payload.
  • Update README.md (Add vuexp logo)

[Version 1.0.3] - 2025-10-20

Feature

  • Add a feature that generates a unique visitor ID.
  • Update README.md

[Version 1.0.2] - 2025-10-19

Feature

  • Add Changelog to README.md

[Version 1.0.1] - 2025-10-19

Feature

  • Add README.md documentation
  • Add MIT Licence

[Version 1.0.0] - 2025-10-19

Feature

  • Release package