Настройка рендеринга 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 и какие методы реализовать
- Минимальный пример: простой консольный рендерер
- Использование vue-termui — быстрый старт
- События, ввод (stdin) и маршрутизация событий
- Типичные ошибки и отладка
- Хорошие практики и интеграция с Vite/SFC
- Источники
- Заключение
Что делает 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.
// 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.
Быстрый старт (по мотивам репозитория и гида):
- Установите пакет:
- npm i vue-termui
- Создайте простой App.vue с привычными шаблонами (vue-termui предоставляет компоненты и стилевые API).
- main.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:
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.
Источники
- https://vuejs.org/api/custom-renderer.html
- https://dev.to/jacobandrewsky/using-vues-custom-renderer-api-to-build-interfaces-beyond-the-dom-1jjg
- https://github.com/vue-terminal/vue-termui
- https://vue-termui.dev/guide/introduction
- https://github.com/shershen08/vue-terminal-ui
- https://lachlan-miller.me/articles/vue-3-pdf-customer-renderer
Заключение
Настроить нестандартный рендеринг во Vue.js можно через createRenderer: реализуйте набор низкоуровневых операций и смонтируйте приложение на корневой контейнер, который будет писать в консоль. Чтобы не писать весь рендерер вручную, используйте готовый пакет vue-termui: он оборачивает createRenderer, привязывает stdin/stdout и предоставляет компоненты для терминального UI.