Skip to content

Project structure ​

@sogody/experiment-framework scaffolds a Vite + Preact project for Adobe Target. Each variation builds to a self-contained IIFE bundle in dist/vN-index.jsx.

The generated project includes:

  • src/config.js for selectors and experiment content.
  • src/js/v1/index.jsx as the first variation entry point.
  • src/components/ExperimentButton/ for the scaffolded Preact button UI.
  • experiment.config.js for package-level tooling config.
  • vite.config.js for IIFE bundling, CSS Modules, aliases, and Sass setup.
  • biome.json for JavaScript, JSX, JSON, and formatting checks.
  • Optional e2e/ Playwright tests when E2E is enabled during scaffolding.

A generated project with two variations looks like this:

my-experiment/
│
├── src/
│   ├── components/
│   │   └── ExperimentButton/
│   │       ├── index.jsx              # Preact button component
│   │       └── styles.module.scss     # Scoped component styles
│   │
│   ├── js/
│   │   ├── control/
│   │   │   └── index.jsx              # Optional unchanged-control tracking entry
│   │   ├── v1/
│   │   │   ├── index.jsx              # Variation 1 entry point
│   │   │   └── styles.module.scss     # Mount-wrapper styles
│   │   └── v2/
│   │       ├── index.jsx              # Variation 2 entry point
│   │       └── styles.module.scss     # Mount-wrapper styles
│   │
│   ├── config.js                      # selectors and button text
│   └── helpers.js                     # shared tracking labels and page context
│
├── e2e/                               # Only present when E2E is enabled
│   ├── smoke.spec.js
│   ├── config.js                      # Market URLs
│   └── helpers.js
│
├── experiment.config.js               # target URL, runtime, and live-preview options
├── vite.config.js                     # IIFE lib mode, Preact plugin, CSS Modules, aliases
├── playwright.config.js               # Only present when E2E is enabled
├── biome.json                         # Biome linter + formatter
├── jsconfig.json                      # JSX support and aliases for editors
├── .nvmrc                             # Node 24
├── .editorconfig
├── .gitignore
├── CLAUDE.md                           # Points Claude Code to AGENTS.md
├── AGENTS.md                           # Tracked framework and project rules
└── package.json

The control/ folder is present only when the project was scaffolded with --control or someone added it later. A control generated by the flag tracks the existing page without mounting Preact.

The instruction files are part of the scaffold. AGENTS.md sends coding tools to the documentation installed with the current framework version, while CLAUDE.md imports the same rules. See AI Project Support for the discovery and refresh flow.

Key files ​

experiment.config.js ​

This root-level file configures the build and live-preview tools.

js
export default {
    targetUrl: 'https://www.samsung.com/uk/smartphones/all-smartphones/',
    runtime: {
        globalObject: 'sgd',
        includeEmergencyBrake: true,
    },
    live: {
        variation: 0,
        overlay: 'visible',
        profile: 'ephemeral',
    },
};

See Configuration for details.

src/config.js ​

This is where you keep experiment-specific selectors and values. Edit it first when setting up a project.

js
export const selectors = {
    primary: '.target-selector',
    fallbacks: ['.alternate-selector', 'body'],
};

export const buttonText = 'Click Me';

Runtime helpers ​

The experiment runtime is imported from @sogody/experiment-framework/framework. Every variation entry point can use:

  • runScript(fn) waits for the DOM before running the experiment.
  • mountExperiment(selector, fallback?, position?, options?) creates and inserts the mount div. Pass className: style.root from src/js/vN/styles.module.scss to style the wrapper.
  • trackAAEvent(evar, event, data) sends an Adobe Analytics event.
  • trackInView(element, options) sends a viewport impression and returns a cleanup handle.
  • waitFor(selectors, callback) polls until the elements are present.
  • watchFor(selector, callback, options?) waits with a MutationObserver.
  • setupTracking(container, options) attaches click tracking after rendering.
  • getPath(), getPathSegments(), and getMarket() read the current path and market.
  • log() and debug() provide development and opt-in diagnostic output.

See the Framework API for full documentation.

src/js/v1/index.jsx ​

Each variation has its own entry point. Mount the container, render the component, and then attach tracking:

jsx
import { render } from 'preact';
import { mountExperiment, runScript, setupTracking, trackInView } from '@sogody/experiment-framework/framework';
import ExperimentButton from '@components/ExperimentButton';
import { buttonText, selectors } from '../../config';
import { getTrackingLabel } from '../../helpers';
import style from './styles.module.scss';

runScript(async () => {
    const container = mountExperiment(selectors.primary, selectors.fallbacks, 'afterbegin', {
        className: style.root,
        dataset: { experiment: 'my-experiment' },
    });
    if (!container) return;

    render(<ExperimentButton text={buttonText} />, container);

    trackInView(container.querySelector('button'), {
        label: getTrackingLabel('v', 'scrolled into view'),
        onceKey: 'my-experiment:v1:button-impression',
    });

    // Attach tracking after render.
    setupTracking(container, {
        label: getTrackingLabel('v', 'cta clicked'),
        selector: 'button',
    });
});

Tracking must come after render

setupTracking queries the DOM for the element to attach to. Calling it before render() will silently fail because the element doesn't exist yet.

Import aliases ​

Vite resolves these aliases in generated projects:

AliasResolves to
@srcsrc/
@jssrc/js/
@componentssrc/components/
@servicessrc/services/
@helperssrc/helpers/

Every generated variation includes styles.module.scss for mount-wrapper styling. Keep component styles under src/components/*/styles.module.scss.

Internal tool - Samsung / Sogody experimentation team

Help improve the frameworkShare an idea or friction you encountered.Share framework feedback(opens in a new tab)