Migrating from Mojito
Move a Mojito JS Delivery wave to a Fabric Experiments YAML — automatically, with one command.
Fabric Experiments ships a one-shot importer plus runtime support for the
features Mojito users rely on most: domOps, divertTo, manualExposure,
holdback, and a Trigger DSL.
What's automatic
Run the importer:
fx import-mojito ./waves --out ./experimentsIt walks every wave directory and emits one Fabric YAML per wave. 1:1 conversion:
| Mojito | Fabric |
|---|---|
id | id |
name | name |
sampleRate | sampleRate |
divertTo | divertTo |
manualExposure | manualExposure |
recipes[<key>].name | variants[<key>].name |
recipes[<key>].sampleRate | variants[<key>].weight (× 10000) |
recipes[<key>].js (file) | variants[<key>].js (inlined) |
recipes[<key>].css (file) | variants[<key>].css (inlined) |
What needs review
trigger.js runs arbitrary code in Mojito. The importer preserves the
original code as a YAML comment header and emits trigger: { kind: 'auto' }
so the import is unblocked — but you must translate the trigger to one of:
-
The declarative Trigger DSL:
trigger: kind: urlMatch pattern: /cart # or trigger: kind: waitForSelector selector: 'button.add-to-cart' timeoutMs: 3000 # or trigger: kind: event name: 'app:ready' -
A host-supplied trigger override at SDK init time:
createClient(manifest, { subjectId, manifestUrl, triggers: { 'w12-checkout': () => location.pathname.startsWith('/checkout'), }, });
Side-by-side example
Mojito wave (waves/w12-hero/config.yml):
state: live
sampleRate: 0.5
id: w12-hero
name: W12 Hero
recipes:
"0":
name: Control
"1":
name: Treatment
js: 1.js
css: 1.css
trigger: trigger.jsAfter fx import-mojito waves → experiments/w12-hero.yaml:
# Imported from Mojito wave
# Source: waves/w12-hero
# Original state: live
#
# Original trigger.js (NOT auto-applied):
# function trigger(test) {
# if (location.pathname === '/') test.activate();
# }
id: w12-hero
name: W12 Hero
sampleRate: 0.5
variants:
- key: "0"
name: Control
- key: "1"
name: Treatment
js: |
// contents of 1.js
css: |
/* contents of 1.css */
trigger:
kind: autoEdit the trigger to match the original intent, then:
fx validate experiments
fx plan experiments
fx apply experimentsRecipe → variant authoring tips
Prefer domOps over js for new variants — declarative DOM ops are
CSP-friendlier than new Function(variant.js) and survive content security
policy hardening:
variants:
- key: control
name: Control
- key: treatment
name: Treatment
domOps:
- op: replaceText
selector: 'h1.hero'
value: 'Ship faster'
- op: setStyle
selector: 'a.cta'
name: background
value: '#16a34a'The full op list is in Reference / YAML.
Runtime parity table
| Mojito feature | Fabric equivalent |
|---|---|
?mojito_<id>=<recipe> URL preview | ?fxpreview=<expId>:<variantKey>&fxtoken=<jwt> (signed; not exposure-tracked) |
| Sticky cookies | Cookie fx.<expId> + localStorage fallback |
manualExposure: true + test.trackExposureEvent() | manualExposure: true + client.expose(id) |
divertTo | divertTo |
holdback (per-recipe sampleRate < 1) | holdback: 0.1 (compiles to audience.sampleRate = 0.9) |
| Snowplow tracker | @fabricorg/experiments-web-adapters → snowplowAdapter() |
| GA Optimize | gaAdapter({ eventName: 'experiment_exposure' }) |
What's not portable
gaExperimentId(Google Optimize is sunset). UsegaAdapterto send a custom GA4 event instead.- Wave-level
state: inactive— Fabric uses the lifecycle (draft → review → approved → running → paused → killed). The importer dropsstateand leaves you indraft; promote withfx apply. - The
mojito_*URL preview pattern. Usefx preview <expId> --variant <key>to mint signed Fabric preview links.