Vue Network Dashboard: трансформация ответов, брейкпоинты и OpenAPI-импорт

Vue Network Dashboard: десять новых фич
Продолжаю рассказывать про vue-network-dashboard — встраиваемый отладчик сети для Vue-приложений. В этом посте — всё, что накопилось за последние два коммита: от детектора N+1 до брейкпоинтов в стиле Charles Proxy.
Mock прямо из трафика


Раньше, чтобы замокать запрос, нужно было вручную открыть вкладку Mocks, угадать URL-паттерн и вписать тело ответа. Теперь в деталях любого запроса появилась кнопка Mock — она берёт URL, метод и тело ответа из захваченного лога и создаёт правило автоматически.
typescript
function createMockFromLog(log: UnifiedLogEntry) {
const group = mockGroups.value.find(g => g.name === 'default') ?? mockGroups.value[0]
group.rules.push({
pattern: new URL(log.url).pathname,
method: log.method,
status: log.response.status,
response: log.response.body,
enabled: true,
})
activeTab.value = 'mocks'
}
Кликнул — переключился на вкладку Mocks и увидел готовое правило. Можно сразу поправить статус или тело и включить.
Редактирование запроса перед Replay

Старый Replay просто повторял запрос один в один. Теперь перед отправкой открывается модальное окно:
┌─────────────────────────────────────┐
│ Edit & Replay ✕ │
├─────────────────────────────────────┤
│ [POST ▾] [https://api.example/gql] │
│ │
│ Headers │
│ Authorization Bearer eyJ... ✕ │
│ Content-Type application/json ✕ │
│ + Add header │
│ │
│ Body │
│ { "query": "{ user { id } }" } │
│ │
├─────────────────────────────────────┤
│ [Cancel] [↺ Send] │
└─────────────────────────────────────┘
Можно поменять URL, метод, добавить или удалить заголовки, отредактировать JSON-тело — и если тело невалидно, подсветится предупреждение. Технически это отдельный компонент ReplayModal.vue, который эмитит событие replay с готовым объектом запроса.
Throttling сети

Четыре пресета прямо в тулбаре вкладки Logs:
| Пресет | Задержка |
|---|---|
| No throttle | 0 мс |
| Fast 3G | 400 мс |
| Slow 3G | 2000 мс |
| Offline-ish | 5000 мс |
Хитрость реализации: интерцепторы инициализируются один раз при монтировании, и пересоздавать их ради смены задержки было бы дорого. Поэтому NetworkDashboard хранит значение в приватном поле, а наружу отдаёт геттер:
typescript
getThrottleDelay = () => this.throttleDelay
Интерцептор вызывает геттер прямо перед каждым запросом — всегда получает актуальное значение без какой-либо перерегистрации.
Детекция N+1 запросов

N+1 — классика: компонент рендерит список из 20 элементов, и каждый делает отдельный запрос за деталями. В тулбаре лога теперь появляется бейдж ×N оранжевого цвета, если в окне 5 секунд встретилось больше одного запроса с таким же методом и URL.
typescript
const duplicateCounts = computed(() => {
const counts: Record<string, number> = {}
const cutoff = Date.now() - 5000
for (const log of filteredLogs.value) {
if (log.startTime < cutoff) continue
const key = `${log.method}::${log.url}`
counts[key] = (counts[key] ?? 0) + 1
}
return counts
})
Никаких таймеров — чистый computed. Как только лог выходит за пределы окна, реактивность сама пересчитывает счётчики.
Определение GraphQL

Если POST-запрос содержит body.query: string, дашборд автоматически считает его GraphQL и показывает фиолетовый бейдж с именем операции:
[POST] /graphql [query GetUser] 200 142ms
В деталях запроса появляется отдельная секция с типом операции, именем и распарсенными Variables — рядом с исходным телом запроса.
typescript
const gqlInfo = computed(() => {
if (props.log.method !== 'POST') return null
const body = props.log.request.body
if (!body || typeof body.query !== 'string') return null
const opMatch = (body.query as string).trim()
.match(/^(query|mutation|subscription)\s+(\w+)/)
return {
operationType: opMatch?.[1] ?? 'query',
operationName: opMatch?.[2] ?? body.operationName ?? null,
variables: body.variables ?? null,
}
})
Работает без каких-либо сторонних GraphQL-парсеров — только регулярка.
Условия в моках

Раньше мок матчился только по URL-паттерну и методу. Теперь можно добавить условия:
typescript
interface MockRule {
// ...
conditions?: {
queryParams?: Record<string, string>
headers?: Record<string, string>
bodyFields?: Record<string, unknown>
}
}
Все условия — AND. Например, один и тот же POST /api/search можно замокать по-разному в зависимости от поля в теле:
typescript
// Вернёт 200 с результатами
{ pattern: '/api/search', method: 'POST',
conditions: { bodyFields: { type: 'user' } },
response: { items: [...] } }
// Вернёт пустой список
{ pattern: '/api/search', method: 'POST',
conditions: { bodyFields: { type: 'product' } },
response: { items: [] } }
Реорганизация шапки
Шапка панели была перегружена: восемь иконок в одну строку. Теперь в header-right остались только глобальные действия — тема, автоскрытие, полноэкранный режим, закрытие окна. Всё, что зависит от текущей вкладки, переехало в отдельный тулбар под вкладками:
┌────────────────────────────────────────────────┐
│ Network Dashboard 🌙 📌 ⛶ ✕ │
├──────────┬──────────┬───────────┐ │
│ Logs │ Mocks │ Compare │ │
├──────────┴──────────┴───────────┘ │
│ [Groups] [Diff] [↑] [↓] [Clear] ← Logs │
│ (только когда активна соответствующая вкладка) │
└────────────────────────────────────────────────┘
Тулбар появляется только на нужной вкладке — никаких серых неактивных кнопок.
Response Transform — модификация реального ответа

Моки полезны, но иногда нужно не заменить ответ целиком, а лишь подправить пару полей — например, добавить флаг isAdmin: true или убрать из тела чувствительное поле. Для этого в MockRule появился режим transform.
typescript
interface MockRule {
mode?: 'mock' | 'transform' // по умолчанию 'mock'
transform?: {
status?: number // переопределить HTTP-статус
headers?: Record<string, string> // добавить / перезаписать заголовки
bodyMerge?: Record<string, unknown> // влить поля в JSON-тело
bodyDelete?: string[] // удалить поля из JSON-тела
}
}
В режиме transform запрос уходит на реальный сервер как обычно. После получения ответа интерцептор применяет трансформации и возвращает в приложение уже изменённый Response. Реальный ответ при этом виден в логах с пометкой mocked.
Пример — добавить поле и сменить статус:
typescript
dashboard.addMock({
urlPattern: '/api/me',
method: 'GET',
mode: 'transform',
enabled: true,
response: { status: 200 }, // обязательное поле, не используется в transform
transform: {
bodyMerge: { isAdmin: true, beta: true },
bodyDelete: ['internalId'],
}
})
В UI режим переключается прямо в форме мока — кнопки Mock / Transform, и набор полей меняется в зависимости от выбора.
Breakpoints — заморозка запросов

Наверное, самая интересная фича с точки зрения реализации. Брейкпоинты работают как в Charles Proxy: запрос перехватывается до отправки, показывается в панели со всеми деталями, разработчик редактирует что нужно — и нажимает Release или Cancel.
Технически это Promise-канал между интерцептором и UI. Когда запрос совпадает с правилом, интерцептор создаёт промис и ждёт его резолва:
typescript
// В NetworkDashboard:
public checkBreakpoint = (url, method, body, headers) => {
const rule = this.breakpointRules.find(r => matches(r, url, method))
if (!rule) return null
return new Promise<BreakpointEdits | null>(resolve => {
this.pendingBreakpoints.set(id, { data, resolve })
this.notifyActiveBreakpoints() // → обновляет реактивный ref → рендерит карточку в UI
})
}
// В fetchInterceptor — запрос ждёт здесь:
const edits = await pauseRequest(url, method, body, headers)
if (edits === null) throw new DOMException('Cancelled', 'AbortError')
// иначе — пересобираем аргументы с отредактированными данными и делаем реальный fetch
В панели Breakpoints каждый паузированный запрос показывается как карточка с редактируемыми полями: URL, метод, заголовки (построчно), тело. Release отправляет запрос с изменёнными данными, Cancel бросает AbortError.
Правила добавляются через форму — паттерн URL + метод + опциональное имя. Бейдж с числом паузированных запросов появляется прямо на табе.
typescript
// Подключение через плагин:
const dashboard = useNetworkDashboard()
dashboard.addBreakpointRule({
urlPattern: '/api/checkout',
method: 'POST',
enabled: true,
name: 'Pause checkout'
})
// Управление из кода:
dashboard.releaseBreakpoint(id, { url, method, headers, body })
dashboard.cancelBreakpoint(id)
OpenAPI / Swagger import
Если у проекта есть спека — моки можно не создавать вручную. Кнопка OpenAPI в тулбаре вкладки Mocks принимает .json-файл (OpenAPI 3.x или Swagger 2.x) и генерирует правила для каждого path + method.
Парсер написан без зависимостей — около 120 строк TypeScript. Из схемы ответа он строит пример тела по типам полей:
typescript
function schemaToExample(schema, doc, depth = 0) {
if (schema.$ref) schema = resolveRef(schema.$ref, doc)
if (schema.example !== undefined) return schema.example
if (schema.type === 'object') {
return Object.fromEntries(
Object.entries(schema.properties ?? {})
.map(([k, v]) => [k, schemaToExample(v, doc, depth + 1)])
)
}
if (schema.type === 'array') return [schemaToExample(schema.items, doc, depth + 1)]
if (schema.type === 'string') return schema.enum?.[0] ?? schema.format ?? 'string'
if (schema.type === 'integer') return 0
if (schema.type === 'boolean') return false
// ...
}
Все сгенерированные моки попадают в новую группу с именем из info.title спеки. Оттуда их можно включать, редактировать и экспортировать как обычно.
Пример: загрузить спеку Petstore — получить 18 моков за один клик.
Группировка логов по маршруту Vue Router
Когда дашборд работает вместе с Vue Router, каждый лог-запись получает поле route — текущий $route.path в момент отправки запроса. Это позволяет фильтровать логи по странице и понимать, какой компонент что отправлял.
Подключается при инициализации плагина:
typescript
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({ history: createWebHistory(), routes })
app.use(NetworkDashboardPlugin, {
router, // передаём инстанс роутера
enrichWithRoute: true,
})
После этого в каждом логе появляется колонка маршрута, а в фильтрах — поле Route для поиска по пути. Никакой жёсткой зависимости от vue-router нет — плагин принимает любой объект с currentRoute.value и afterEach.
NPM: https://www.npmjs.com/package/vue-network-dashboard
GitHub: https://github.com/macrulezru/vue-network-dashboard