Веб

Настройка рендеринга Vue.js в консоль с vue-termui

Как использовать createRenderer для нестандартного рендеринга Vue.js в терминале. Примеры с vue-termui, обработка stdin/stdout, события и типичные ошибки. Готовые решения для консольного UI.

Как настроить нестандартный рендеринг во Vue.js? Кто-нибудь работал с компонентом vue-termui? Не понимаю, как он работает и как заставить Vue.js рендерить не в браузер, а в консоль. В документации API есть информация о методе createRenderer, но неясно, как его использовать.

Чтобы рендерить Vue не в браузер, а в консоль, используйте API createRenderer: реализуете набор низкоуровневых функций (createElement, insert, patchProp, createText и т.д.), создаёте рендерер и монтируете приложение на произвольный «контейнер» (например, связанный с process.stdout). Если не хотите реализовывать всё сами, возьмите готовый пакет — vue-termui — он оборачивает createRenderer для терминала и даёт компоненты + привязку stdin/stdout, так что приложение запускается как обычный Vue‑app.

Содержание

Что делает createRenderer и какие методы реализовать

API createRenderer даёт вам возможность заменить DOM-«бэкэнд» Vue на любую платформу: терминал, Canvas, PDF и т.д. В документации перечислены минимальные операции, которые рендерер должен предоставить: Custom Renderer API.

Коротко, что нужно реализовать и зачем:

  • createElement(type): создать узел платформы (в терминале — объект, представляющий блок/строку).
  • createText(text): создать текстовый узел.
  • setElementText(el, text) / setText(node, text): записать текст внутрь узла.
  • insert(child, parent, anchor?): вставить дочерний узел в родителя (в корне — триггерить вывод в stdout).
  • remove(child): удалить узел.
  • patchProp(el, key, prev, next): обновить проп/событие/стиль узла.
  • (опционально) createComment, nextSibling и т.п. для более сложных сценариев.

Как это работает на практике? Vue при изменениях вызывает ваш patchProp / insert / remove, а вы переводите эти вызовы в конкретные действия терминала (перерисовать участок, изменить текст, зарегистрировать обработчик клавиш). Mount-точка (container) — произвольный объект: вы можете передать process.stdout или простой root-объект, который ваша реализация использует как «хранилище» для отрисовки.

Минимальный пример: простой консольный рендерер

Ниже — упрощённый скелет рендерера (демонстрация идеи — не production-решение). Основная идея: представлять узлы объектами и при вставке в корень сериализовать дерево в строку и писать в stdout.

js
// main.js
import { createRenderer } from '@vue/runtime-core'
import App from './App.vue' // SFC, компилится Vite/webpack

function renderToString(node) {
  if (!node) return ''
  if (node.isText) return node.text
  const children = (node.children || []).map(renderToString).join('')
  const prefix = node.type === 'box' ? '' : ''
  return prefix + children
}

const options = {
  createElement(type) { return { type, props: {}, children: [] } },
  createText(text) { return { isText: true, text } },
  insert(child, parent) {
    if (!parent._children) parent._children = []
    parent._children.push(child)
    // если parent — корневой контейнер, выводим в stdout
    if (parent.isRoot) {
      process.stdout.write(renderToString(child) + '\n')
    }
  },
  setElementText(el, text) { el.children = [{ isText: true, text }] },
  patchProp(el, key, prev, next) { el.props = el.props || {}; el.props[key] = next },
  remove(child) { /* удалить из parent._children */ }
}

const { createApp } = createRenderer(options)
const root = { isRoot: true } // контейнер-плейсхолдер
createApp(App).mount(root)

Пояснения:

  • Здесь мы полагаемся на компиляцию шаблонов (App.vue) вашим сборщиком (Vite/webpack) в render-функции. Если вы не используете SFC, придётся писать render() вручную с h(...).
  • В реальном терминале нужно управлять курсором, очищать экран и минимизировать мерцание; для этого добавляют буфер вывода и ANSI-последовательности.

Подробнее о создании кастомного рендерера — в практическом руководстве: Using Vue’s Custom Renderer API (tutorial).

Использование vue-termui — готовый рендерер для консоли

Если не хотите «изобретать велосипед», используйте проект vue-termui: он уже реализует рендерер для терминала, набор компонентов (flex, text и т.д.) и связи с stdin/stdout.

Быстрый старт (по мотивам репозитория и гида):

  1. Установите пакет:
    • npm i vue-termui
  2. Создайте простой App.vue с привычными шаблонами (vue-termui предоставляет компоненты и стилевые API).
  3. main.js:
js
import { createRenderer } from 'vue-termui'
import App from './App.vue'

const { createApp } = createRenderer({
  container: process.stdout, // вывод
  input: process.stdin       // ввод
})

createApp(App).mount()

Документация проекта: Введение в Vue TermUI и исходники: репозиторий vue-termui на GitHub. Vue-termui использует Flexbox-подобный API, поддержку HMR через Vite и привычный синтаксис компонентов — так вы пишете интерфейс почти как для браузера и запускаете в терминале.

События, ввод (stdin) и маршрутизация событий

Как обрабатывать нажатия клавиш и другие события?

  • В patchProp оборачивайте обработчики событий в свойства узла, например el._listeners = { keypress: fn }.
  • Настройте process.stdin:
js
process.stdin.setRawMode(true)
process.stdin.resume()
process.stdin.on('data', chunk => {
  const key = chunk.toString()
  // Найти фокусный/активный элемент и вызвать его слушатель:
  if (root._focused && root._focused._listeners && root._focused._listeners.keypress) {
    root._focused._listeners.keypress(key)
  }
})
  • Управление фокусом и навигацией — задача рендерера/библиотеки. vue-termui уже реализует базовую логику, поэтому при использовании пакета вручную эти детали скрыты.
  • Для сложных клавиатурных схем полезно парсить последовательности байтов (стрелки, Ctrl+C и т.д.) и применять process.stdin.setRawMode(true).

Типичные ошибки и отладка

  • Ничего не выводится:
    • Проверьте, что вы вызвали .mount(container) и что insert получает родителя, помеченный как корень (например, container.isRoot = true).
    • Убедитесь, что SFC шаблоны компилируются (Vite/webpack + @vue/compiler-sfc).
  • События не срабатывают:
    • Реализуйте обработку событий в patchProp и активируйте raw mode на stdin.
  • Мерцание/фликер:
    • Рисуйте в буфер и затем выводите одним write; используйте оптимальные diff-алгоритмы (Vue делает дифф, но ваша сериализация тоже должна быть эффективной).
  • Ошибки типа «template compilation is not available in the runtime-only build»:
    • Используйте сборщик, который компилирует SFC, либо экспортируйте компонент с render-функцией.
  • Хотите цвета/стили? Подключайте ANSI-библиотеки (chalk, ansi-escapes) или используйте возможности терминального рендерера (если он их поддерживает).

Если нужно посмотреть рабочие реализации и идеи — полезны примеры из репозиториев: vue-termui на GitHub и альтернативные проекты вроде vue-terminal-ui.

Хорошие практики и интеграция с Vite/SFC

  • Если вы пишете интерфейс в шаблонах (App.vue), используйте Vite/webpack, чтобы шаблон компилировался в render-функции.
  • Разделяйте логику рендерера и UI-компоненты: компоненты не должны знать о низкоуровневых деталях вывода.
  • Тестируйте рендерер в headless-режиме (mock container), чтобы не зависеть от терминала при юнит‑тестах.
  • Для сложных TUI-элементов (таблицы, графики) рассматривайте использование терминальных библиотек (blessed, ink) как основу или вдохновение.
  • Избегайте частых перерисовок: сгруппируйте изменения и делайте батчинг.

Дополнительный технический пример статического рендерера (полезно для понимания принципов) — разбор рендерера для PDF: PDF custom renderer для Vue 3.

Источники

Заключение

Настроить нестандартный рендеринг во Vue.js можно через createRenderer: реализуйте набор низкоуровневых операций и смонтируйте приложение на корневой контейнер, который будет писать в консоль. Чтобы не писать весь рендерер вручную, используйте готовый пакет vue-termui: он оборачивает createRenderer, привязывает stdin/stdout и предоставляет компоненты для терминального UI.

Авторы
Проверено модерацией
Модерация