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.jsfor selectors and experiment content.src/js/v1/index.jsxas the first variation entry point.src/components/ExperimentButton/for the scaffolded Preact button UI.experiment.config.jsfor package-level tooling config.vite.config.jsfor IIFE bundling, CSS Modules, aliases, and Sass setup.biome.jsonfor 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.jsonThe 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.
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.
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 mountdiv. PassclassName: style.rootfromsrc/js/vN/styles.module.scssto 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 aMutationObserver.setupTracking(container, options)attaches click tracking after rendering.getPath(),getPathSegments(), andgetMarket()read the current path and market.log()anddebug()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:
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:
| Alias | Resolves to |
|---|---|
@src | src/ |
@js | src/js/ |
@components | src/components/ |
@services | src/services/ |
@helpers | src/helpers/ |
Every generated variation includes styles.module.scss for mount-wrapper styling. Keep component styles under src/components/*/styles.module.scss.