Перейти к содержимому
Star

Traces ​

Трейс — JSON-файл со всем, что нужно, чтобы воспроизвести происходившее в одном scope: состояние до первого шага, каждый внешний вызов и каждый результат эффекта. Вы записываете баг, отправляете файл, а коллега воспроизводит его в своей сборке — эффекты отвечают из записи, двадцать кликов не нужны, — как в отладчике Elm.

Вкладка Traces: экспорт трейса с JSON, импорт и воспроизведение с полем «Открыть на шаге», тест из трейса со сгенерированным кодом для vitestВкладка Traces: экспорт трейса с JSON, импорт и воспроизведение с полем «Открыть на шаге», тест из трейса со сгенерированным кодом для vitest

Экспорт ​

Экспорт трейса → Сформировать JSON (или Ctrl+S где угодно, или кнопка экспорта в панели инструментов). Затем Скачать .json или Копировать. Строка под кнопками подводит итог: 7 шагов · 4 результатов эффектов · 5.6 КБ · 2 замаскировано.

Трейс берётся из живого scope и содержит:

ЧастьСодержимое
formatdeffector-trace@1
metaимя приложения, URL, build, версии effector и агента, время, scope, user agent; focus — шаг, который откроется после воспроизведения
unitsвсе юниты, на которые ссылается трейс: имя, тип, sid, файл
initialзначения записываемых сторов до первого шага
stepsвнешние вызовы — события из UI, таймеры и т. п. — с их params; правки из панели тоже шаги, поэтому они тоже воспроизводятся
effectResultsчто вернул или выбросил каждый вызов эффекта, по порядку вызовов, с длительностью
notesваши заметки
redactedпути, которые были замаскированы
lossyзначения, которые нельзя восстановить точно (функции, DOM-узлы, экземпляры классов)

Секреты маскируются с сохранением длины. Значения под ключами, похожими на секреты, — password, passwd, secret, token, authorization, cookie, api_key / apiKey, cvv, card (без учёта регистра, в том числе как часть более длинного ключа), — и строковые значения сторов с такими именами заменяются на • той же длины. Валидация, которая проверяет длину, воспроизводится так же.

Если выбрана транзакция, флажок открыть на #N после воспроизведения записывает её в meta.focus.

Импорт и воспроизведение ​

Вставьте трейс в текстовое поле или нажмите Открыть файл…, затем Проверить совместимость. deffector сопоставляет юниты трейса с юнитами запущенного приложения:

  • сначала по sid;
  • если sid сменился (код изменили, другая сборка) — по имени, типу и файлу, с предупреждением;
  • ненайденные юниты — ошибки, шаги, которые их вызывают, будут пропущены.

Отчёт также сообщает, если трейс записан на другой сборке (опция build) или другой версии effector, что было замаскировано, сколько значений с потерями и сколько заметок оставил автор.

Дальше:

  • Воспроизвести в новом scope — deffector создаёт свежий scope из начального состояния трейса, подменяет хендлеры эффектов записанными результатами и выполняет шаги по одному, так что видно, как они появляются в логе. Баннер пишет «Воспроизведение …: шаг 3 из 7 — результаты эффектов берутся из записи, сеть не трогаем». По окончании новый scope становится живым.
  • Применить сразу — то же без пауз; баннер затем пишет «Трейс … применён».

В глобальном режиме (без fork()) воспроизведение записывает начальное состояние на месте и на время воспроизведения подменяет хендлеры эффектов глобально.

Открыть на шаге ​

Поле Открыть на шаге принимает #step=142 (или просто число). После воспроизведения deffector переходит к транзакции, которая соответствует шагу 142 записи. #step=N, вставленный после JSON в поле импорта, работает так же, а трейс с meta.focus открывается на нём сам. Если такого шага в воспроизведении нет, появится сообщение.

Тест из трейса ​

Скопировать как тест выдаёт тест для vitest/jest, который воспроизводит трейс без панели:

ts
import { fork, allSettled } from 'effector';
import { suggestFx, incremented, quickFill } from './/src/index';
import { email_changed } from './/src/field';

// Recorded by deffector: deffector demo · React, 2026-10-17T11:23:34.545Z
test('replays deffector demo · React trace', async () => {
  const replies = {
    suggestFx: [
      { status: 'done', value: ['ann@gmail.com'] },
      { status: 'done', value: [] },
    ],
  };
  const reply = (list: { status: string; value: unknown }[]) => () => {
    const r = list.shift()!;
    return r.status === 'done' ? Promise.resolve(r.value) : Promise.reject(r.value);
  };
  const scope = fork({
    handlers: [[suggestFx, reply(replies.suggestFx)]],
  });
  await allSettled(incremented, { scope });
  await allSettled(email_changed, { scope, params: 'ann@gm' });
  // здесь неправильно проверяется пароль
  await allSettled(quickFill, { scope });
  // expect(scope.getState($store)).toEqual(...)
});

Это заготовка (выше в сокращении): импорты выводятся из файлов юнитов, а имена юнитов превращаются в идентификаторы (email.changed → email_changed), поэтому подправьте их под свои экспорты; непустые начальные значения попадают в fork({ values }), записанные результаты эффектов — в fork({ handlers }), каждый шаг становится allSettled, а ваши заметки — комментариями. Добавьте expect, который зафиксирует баг.

Что переносится, а что нет ​

Трейс воспроизводит входы, а не выходы: шаги заново выполняет ядро принимающей сборки, поэтому исправленный баг действительно не воспроизводится, а изменённая модель показывает новое поведение. Эффекты, которые вызывались в записи, получают записанные результаты по порядку; эффект, который запись ни разу не вызывала (потому что код изменился), выполняет свой настоящий хендлер. Что трейс перенести не может: значения из lossy и вызовы юнитов, которых нет в принимающей сборке.

Трейсы из демо взаимозаменяемы: модель собрана babel-плагином, sid везде одинаковые, и трейс, записанный в React, воспроизводится во Vue, Solid или Next.js.