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/
│ │ ├── 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.jsonThe 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.
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.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 } 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:
| 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.