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/
│   │   ├── 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

├── 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                           # Optional local AI instructions; created by pnpm init-claude
├── AGENTS.md                           # Optional local AI instructions; created by pnpm init-agents
├── .claude/skills/<skill-name>/        # Optional Claude Code project skill; same generated content
├── .cursor/skills/<skill-name>/        # Optional Cursor project skill; same generated content
└── package.json

The instruction files and skill directories are opt-in and are not present immediately after scaffolding. The generated .gitignore excludes CLAUDE.md, AGENTS.md, .agents, .claude, and .cursor, so project-specific AI support remains local by default. See AI Project Support for the complete skill tree.

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.
  • 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 } from '@sogody/experiment-framework/framework';
import ExperimentButton from '@components/ExperimentButton';
import { buttonText, selectors } from '../../config';
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);

    // Attach tracking after render.
    setupTracking(container, {
        label: 'my-experiment: v1 button 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)