Введение
deffector — инструменты разработчика для effector. Он показывает каждое действие пользователя как одну транзакцию — событие и всё, что оно вызвало, — позволяет перемещаться между ними, подставлять своё состояние в любой стор и записывать трейсы, по которым другой разработчик воспроизводит ваш баг, как в отладчике Elm.


Зачем
effector быстрый и декларативный, но из-за тех же свойств некоторые баги трудно увидеть:
- событие, вызванное из
setTimeoutили сокета безscopeBind, меняет глобальный граф, а приложение читает свой scope — на экране ничего не происходит; combineиsampleвыполняются в порядке объявления, а не в порядке графа, поэтому стор может пересчитаться дважды, аsample— прочитать значение, которое вот-вот изменится;filter, вернувшийfalse, не оставляет никаких следов;- исключение в
.mapостанавливает одну ветку, а остальные ветки уже применились; - баг, для которого нужны двадцать кликов и медленный ответ сервера, трудно передать коллеге.
deffector записывает то, что на самом деле сделало ядро, и объясняет это в панели: Log показывает шаги, Health превращает известные ловушки в предупреждения с исправлением, Traces делают баг воспроизводимым.
Как это устроено
Агент (@deffector/agent) работает внутри вашей страницы. Он должен быть первым импортом, чтобы видеть юниты в момент их создания. Агент подключается к инспектору ядра и инспектору графа effector — setInspector и setGraphInspector, тем самым функциям, на которых построен effector/inspect. Они отдают сырые узлы графа, точного родителя каждого шага и scope каждого вызова — сразу во всех scope:
- каждый юнит регистрируется с именем,
sid, файлом, фабрикой, доменом и моделью; - каждый шаг ядра попадает в свою транзакцию — с лейном, значением до и после и причиной остановки (filter, то же значение, исключение);
- изменения записываемых сторов хранятся по транзакциям, поэтому состояние любого момента в прошлом можно восстановить;
- команды из панели — переход, правка, вызов, подмена, воспроизведение — выполняются в странице.
Адаптер (DeffectorProvider для React и Solid, withDeffector / DeffectorScope для Vue) — прямая замена собственного Provider из effector. Он регистрирует scope под понятным именем и даёт агенту возможность отрисовать приложение с другим scope: форком прошлого для перемещения во времени или свежим scope для воспроизведения трейса.
Панель — один и тот же интерфейс на React, используется в двух местах:
- расширение браузера (Chrome, Edge, Firefox): агент общается с content script через
window.postMessage, content script — с фоновым скриптом, фоновый скрипт — с панелью DevTools. Content script открывает соединение, только когда на странице есть агент, поэтому обычные страницы ничего не теряют; - отдельное приложение (
npx @deffector/standalone) — для кода вне вкладки браузера: SSR-серверов, скриптов Node, тестов, React Native. Агенты подключаются по WebSocket к/agent, панели в любой вкладке браузера — к/panel.
В продакшен-сборке каждая точка входа @deffector/agent через условие экспорта production превращается в пустышку, так что импорт можно оставить в коде.
Термины
| Термин | Значение |
|---|---|
| Транзакция | Один корневой вызов (событие из UI, таймер, завершившийся эффект, правка из панели) и все шаги ядра, которые он синхронно вызвал. |
| Шаг | Один узел, выполненный ядром: обновление стора, оператор (sample, map, on…), вызов эффекта. |
| Лейн | Приоритет ядра, в котором выполнился шаг: pure, read, child, barrier, sampler, effect. |
| Scope | Результат fork(), отдельная копия состояния приложения. global — глобальный граф (без scope). |
| Ветка | Scope, созданный deffector: форк прошлого после перехода, scope с «тихими» правками или воспроизведение трейса. В списке scope помечается ⑂. |
| Трейс | JSON-файл (deffector-trace@1) с начальным состоянием, внешними вызовами и результатами эффектов одного scope — достаточно, чтобы воспроизвести ту же историю. |
Статус
deffector — версия 0.1, активно развивается. Изменения, которые нужны ему в самом effector, описаны в разделе PR в effector; пока они не приняты, deffector работает на обходных путях.