Создание терминальных интерфейсов с помощью Node.js
Ваш CLI-инструмент работает, но выглядит так, будто он из 1985 года. Пользователи ожидают большего, чем просто текстовые подсказки — им нужны интерактивные панели управления, обновления в реальном времени и навигация с помощью клавиатуры. Именно для этого существуют терминальные пользовательские интерфейсы (TUI), и Node.js 22 LTS предоставляет всё необходимое для их создания.
Это руководство охватывает основные примитивы для разработки терминальных UI на Node.js, обзор современной экосистемы TUI и показывает, как фреймворки вроде Ink и neo-blessed вписываются в продакшн CLI-инструменты.
Ключевые выводы
- TUI поддерживают постоянные интерактивные отображения, в отличие от простых CLI, которые принимают аргументы, выполняются и завершаются
- Node.js 22 предоставляет основные примитивы, такие как raw mode, события изменения размера и обработка потоков для создания терминальных интерфейсов
- Ink привносит компонентную модель React в терминалы, что делает его идеальным для разработчиков, знакомых с JSX
- neo-blessed продолжает наследие blessed для традиционных виджетных макетов с поддержкой мыши
- Комбинируйте CLI-фреймворки вроде oclif с TUI-библиотеками для создания организованных, многофункциональных инструментов командной строки
Что отличает TUI от простых CLI
CLI принимает аргументы, выполняется и завершается. TUI поддерживает постоянное интерактивное отображение. Сравните htop и ls.
TUI имеют смысл, когда вам нужно:
- Визуализация данных в реальном времени (панели мониторинга, отслеживание прогресса)
- Сложная навигация (многопанельные макеты, прокручиваемые списки)
- Постоянное состояние во время взаимодействия с пользователем
- Богатая обратная связь помимо последовательного текстового вывода
Для разработки TUI на Node.js 22 понимание базовых примитивов помогает выбрать правильный уровень абстракции.
Основные терминальные примитивы в Node.js 22
stdin, stdout и Raw Mode
Node.js предоставляет process.stdin и process.stdout как потоки. Для TUI обычно включают raw mode на stdin:
import * as readline from 'node:readline'
process.stdin.setRawMode(true)
readline.emitKeypressEvents(process.stdin)
Raw mode отправляет каждое нажатие клавиши немедленно, а не ждёт Enter. Это обеспечивает обработку ввода с клавиатуры в реальном времени — необходимое условие для любого интерактивного интерфейса.
ANSI Escape-последовательности
Терминалы интерпретируют специальные последовательности символов для стилизации и управления курсором. Перемещение курсора, очистка строк и применение цветов — всё использует ANSI-коды. Библиотеки абстрагируют это, но знание об их существовании помогает при отладке.
События изменения размера
Терминалы изменяют размер. Ваш TUI должен реагировать:
process.stdout.on('resize', () => {
const { columns, rows } = process.stdout
// Перерисовать интерфейс
})
Поддержка Unicode и цветов
Современные терминалы хорошо обрабатывают Unicode, но SSH-сессии и старые эмуляторы различаются. Проверяйте process.stdout.isTTY перед предположением о поддержке цветов и рассмотрите запасные варианты для окружений, где TERM указывает на ограниченные возможности.
Современная экосистема TUI
Ink: React для терминалов
Ink доминирует в разработке терминальных интерфейсов сегодня. Он привносит компонентную модель React в терминал — вы пишете JSX, а Ink занимается рендерингом.
import React from 'react'
import { render, Text, Box } from 'ink'
const App = () => (
<Box flexDirection="column">
<Text color="green">Статус: Работает</Text>
</Box>
)
render(<App />)
Окружающий инструментарий усиливает позицию Ink:
- @inkjs/ui предоставляет готовые компоненты (спиннеры, поля выбора, прогресс-бары)
- create-ink-app создаёт каркас новых проектов
- Pastel предлагает фреймворк-слой для больших Ink-приложений
Если вы знакомы с React, Ink покажется сразу понятным.
Discover how at OpenReplay.com.
Семейство Blessed: панели управления neo-blessed
Оригинальная библиотека blessed стала пионером богатых терминальных UI на Node.js с виджетами, макетами и поддержкой мыши. Сейчас она практически не поддерживается.
neo-blessed и reblessed продолжают разработку. Эти форки получают периодические обновления и исправляют проблемы совместимости с современными версиями Node.
С панелями управления neo-blessed вы получаете:
- Макеты Box, списки, таблицы и формы
- Поддержку мыши
- Прокрутку и управление фокусом
- Виджеты blessed-contrib (графики, датчики, карты)
Выбирайте библиотеки семейства blessed, когда вам нужны традиционные виджетные макеты, а не декларативная модель React.
Сочетание TUI-слоёв с CLI-фреймворками
Создание Node.js CLI с oclif даёт вам парсинг аргументов, организацию команд и архитектуру плагинов. Но oclif обрабатывает CLI-слой — он не рендерит интерфейсы.
Паттерн: используйте oclif для структуры команд, затем рендерите TUI-компоненты внутри конкретных команд:
import { Command } from '@oclif/core'
import { render } from 'ink'
import Dashboard from './components/Dashboard.js'
export default class Monitor extends Command {
async run() {
render(<Dashboard />)
}
}
Это разделение поддерживает организованность вашего многокомандного инструмента, одновременно обеспечивая богатые интерфейсы там, где это необходимо.
Выбор подхода
| Потребность | Решение |
|---|---|
| Знакомство с React, переиспользование компонентов | Ink |
| Традиционные виджеты, сложные макеты | neo-blessed |
| Структура многокомандного CLI | oclif + TUI-слой |
| Только простые подсказки | Inquirer или чистый readline |
Заключение
Начните с примитивов — разберитесь с raw mode и обработкой изменения размера. Затем выберите абстракцию, которая соответствует вашей ментальной модели: Ink для React-разработчиков, neo-blessed для виджетного мышления.
Терминал — это не ограничение. С современными API Node.js 22 и этими фреймворками вы можете создавать интерфейсы, которые соперничают с графическими инструментами, сохраняя при этом эффективность командной строки.
Часто задаваемые вопросы
Да, Ink имеет полную поддержку TypeScript. Библиотека поставляется с определениями типов, а create-ink-app может создавать каркас TypeScript-проектов напрямую. Большинство пакетов экосистемы Ink, таких как @inkjs/ui, также включают TypeScript-типы из коробки.
Слушайте сигналы SIGINT и SIGTERM на объекте process. В Ink вызовите функцию unmount, возвращаемую render(), перед выходом. Для neo-blessed вызовите screen.destroy(). Всегда восстанавливайте состояние терминала, отключая raw mode и очищая альтернативный буфер экрана.
В целом да, но с оговорками. SSH-сессии могут иметь ограниченную поддержку цветов или другие размеры терминала. Всегда проверяйте process.stdout.isTTY и переменную окружения TERM. Тестируйте с распространёнными SSH-клиентами и рассмотрите возможность предоставления упрощённого запасного режима для ограниченных окружений.
Хотя технически это возможно, это не рекомендуется. Обе библиотеки управляют состоянием терминала по-разному и могут конфликтовать при рендеринге. Выбирайте один подход для каждой команды или интерфейса. Если вам нужны возможности обеих библиотек, рассмотрите использование oclif для разделения команд, использующих разные TUI-библиотеки.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.