diff --git a/docs/terminal-implementation.md b/docs/terminal-implementation.md new file mode 100644 index 0000000000..ffd8b3ebdd --- /dev/null +++ b/docs/terminal-implementation.md @@ -0,0 +1,1093 @@ +# Terminal Implementation Guide for Electron Applications + + + +基于 VS Code 终端功能分析及简化实现方案 + +## 概述 + +本文档分析了 VS Code 的终端实现原理,并提供了两种实现方案: +1. **纯 JavaScript 方案**(推荐)- 无需本地编译环境,基于 child_process +2. **node-pty 方案** - 功能更完整但需要本地编译环境 + +## VS Code Terminal 技术栈分析 + +### 核心组件 + +1. **xterm.js** - 前端终端模拟器(TypeScript编写) +2. **node-pty** - 后端伪终端处理 +3. **ConPTY/winpty** - Windows系统兼容层 + +### 架构设计 + +``` +┌─────────────────┐ ┌──────────────┐ ┌─────────────┐ +│ Renderer │ │ Main │ │ System │ +│ (xterm.js) │◄──►│ (node-pty) │◄──►│ Shell │ +└─────────────────┘ └──────────────┘ └─────────────┘ +``` + +VS Code 的终端功能主要特点: + +- **跨平台兼容性**: Windows 10+ 使用 ConPTY API,旧版本回退到 winpty +- **高性能**: xterm.js 经过高度优化,能够处理大量输出 +- **丰富功能**: 支持主题、字体定制、搜索、链接识别等 + +## 方案一:纯 JavaScript 实现(推荐) + +### 环境要求 + +- Node.js 12+ 或 Electron 8+ +- **无需任何本地编译环境** + +### 1. 依赖安装 + +```bash +npm install xterm @xterm/addon-fit +# 不需要 node-pty! +``` + +### 2. 主进程实现 (main.js) - 纯 JavaScript 版本 + +```javascript +const { app, BrowserWindow, ipcMain } = require('electron'); +const { spawn } = require('child_process'); +const path = require('path'); +const os = require('os'); + +let mainWindow; +let shellProcess; + +function createWindow() { + mainWindow = new BrowserWindow({ + width: 1200, + height: 800, + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + preload: path.join(__dirname, 'preload.js') + } + }); + + mainWindow.loadFile('index.html'); +} + +// 获取系统默认 shell +function getDefaultShell() { + if (process.platform === 'win32') { + return process.env.COMSPEC || 'cmd.exe'; + } else { + return process.env.SHELL || '/bin/bash'; + } +} + +// 创建 shell 进程 +ipcMain.handle('terminal-create', () => { + try { + const shell = getDefaultShell(); + const args = process.platform === 'win32' ? [] : ['-i']; // interactive mode for unix + + shellProcess = spawn(shell, args, { + cwd: os.homedir(), + env: { ...process.env, TERM: 'xterm-256color' }, + stdio: 'pipe' + }); + + // 监听标准输出 + shellProcess.stdout.on('data', (data) => { + mainWindow.webContents.send('terminal-data', data.toString()); + }); + + // 监听错误输出 + shellProcess.stderr.on('data', (data) => { + mainWindow.webContents.send('terminal-data', data.toString()); + }); + + // 监听进程退出 + shellProcess.on('exit', (code) => { + mainWindow.webContents.send('terminal-exit', code); + shellProcess = null; + }); + + // 监听进程错误 + shellProcess.on('error', (error) => { + mainWindow.webContents.send('terminal-error', error.message); + }); + + return { success: true }; + } catch (error) { + return { success: false, error: error.message }; + } +}); + +// 向 shell 写入命令 +ipcMain.handle('terminal-write', (event, data) => { + if (shellProcess && shellProcess.stdin) { + shellProcess.stdin.write(data); + } +}); + +// 终端清屏(发送 clear 命令) +ipcMain.handle('terminal-clear', () => { + if (shellProcess && shellProcess.stdin) { + const clearCommand = process.platform === 'win32' ? 'cls\r\n' : 'clear\r\n'; + shellProcess.stdin.write(clearCommand); + } +}); + +// 终止进程 +ipcMain.handle('terminal-kill', () => { + if (shellProcess) { + if (process.platform === 'win32') { + // Windows 使用 taskkill 强制终止 + spawn('taskkill', ['/pid', shellProcess.pid, '/f', '/t']); + } else { + // Unix-like 系统发送 SIGTERM + shellProcess.kill('SIGTERM'); + } + shellProcess = null; + } +}); + +// 发送 Ctrl+C 信号 +ipcMain.handle('terminal-interrupt', () => { + if (shellProcess) { + if (process.platform === 'win32') { + // Windows 发送 Ctrl+C + shellProcess.stdin.write('\x03'); + } else { + // Unix-like 系统发送 SIGINT + shellProcess.kill('SIGINT'); + } + } +}); + +app.whenReady().then(createWindow); + +app.on('before-quit', () => { + if (shellProcess) { + shellProcess.kill(); + } +}); +``` + +### 3. 预加载脚本 (preload.js) - 纯 JavaScript 版本 + +```javascript +const { contextBridge, ipcRenderer } = require('electron'); + +contextBridge.exposeInMainWorld('electronAPI', { + terminal: { + create: () => ipcRenderer.invoke('terminal-create'), + write: (data) => ipcRenderer.invoke('terminal-write', data), + clear: () => ipcRenderer.invoke('terminal-clear'), + kill: () => ipcRenderer.invoke('terminal-kill'), + interrupt: () => ipcRenderer.invoke('terminal-interrupt'), + onData: (callback) => ipcRenderer.on('terminal-data', callback), + onExit: (callback) => ipcRenderer.on('terminal-exit', callback), + onError: (callback) => ipcRenderer.on('terminal-error', callback) + } +}); +``` + +### 4. 渲染进程实现 (renderer.js) - 纯 JavaScript 版本 + +```javascript +import { Terminal } from 'xterm'; +import { FitAddon } from '@xterm/addon-fit'; +import { WebLinksAddon } from '@xterm/addon-web-links'; + +class PureJSTerminal { + constructor(containerId) { + this.terminal = new Terminal({ + theme: { + background: '#1a1a1a', + foreground: '#ffffff', + cursor: '#ffffff', + cursorAccent: '#000000', + selection: 'rgba(255, 255, 255, 0.3)', + black: '#000000', + red: '#e74c3c', + green: '#2ecc71', + yellow: '#f39c12', + blue: '#3498db', + magenta: '#9b59b6', + cyan: '#1abc9c', + white: '#ecf0f1', + brightBlack: '#34495e', + brightRed: '#c0392b', + brightGreen: '#27ae60', + brightYellow: '#e67e22', + brightBlue: '#2980b9', + brightMagenta: '#8e44ad', + brightCyan: '#16a085', + brightWhite: '#bdc3c7' + }, + fontSize: 14, + fontFamily: '"Cascadia Code", "Fira Code", Monaco, Menlo, "Ubuntu Mono", monospace', + fontWeight: 'normal', + fontWeightBold: 'bold', + cursorBlink: true, + cursorStyle: 'block', + scrollback: 1000, + tabStopWidth: 4 + }); + + this.fitAddon = new FitAddon(); + this.webLinksAddon = new WebLinksAddon(); + + this.terminal.loadAddon(this.fitAddon); + this.terminal.loadAddon(this.webLinksAddon); + + this.container = document.getElementById(containerId); + this.isConnected = false; + this.currentCommand = ''; + + this.init(); + } + + async init() { + try { + this.terminal.open(this.container); + this.fitAddon.fit(); + + // 显示欢迎信息 + this.terminal.writeln('\x1b[33m🖥️ Pure JavaScript Terminal\x1b[0m'); + this.terminal.writeln('\x1b[90mConnecting to shell...\x1b[0m'); + + const result = await window.electronAPI.terminal.create(); + if (result.success) { + this.isConnected = true; + this.setupEventListeners(); + this.terminal.writeln('\x1b[32m✓ Connected!\x1b[0m'); + this.terminal.focus(); + } else { + this.terminal.writeln(`\x1b[31m✗ Failed to connect: ${result.error}\x1b[0m`); + } + } catch (error) { + this.terminal.writeln(`\x1b[31m✗ Error: ${error.message}\x1b[0m`); + } + } + + setupEventListeners() { + // 用户输入处理 + this.terminal.onData(data => { + if (!this.isConnected) return; + + // 处理特殊按键 + if (data === '\r') { // Enter + this.currentCommand = ''; + window.electronAPI.terminal.write('\r\n'); + } else if (data === '\u007F') { // Backspace + window.electronAPI.terminal.write('\b \b'); + } else if (data === '\u0003') { // Ctrl+C + window.electronAPI.terminal.interrupt(); + } else { + this.currentCommand += data; + window.electronAPI.terminal.write(data); + } + }); + + // 接收输出数据 + window.electronAPI.terminal.onData((event, data) => { + // 处理 ANSI 转义序列和特殊字符 + this.terminal.write(data); + }); + + // 处理进程退出 + window.electronAPI.terminal.onExit((event, code) => { + this.isConnected = false; + this.terminal.writeln(`\r\n\x1b[33mProcess exited with code ${code}\x1b[0m`); + this.terminal.writeln('\x1b[90mPress any key to restart...\x1b[0m'); + + // 允许重新启动 + this.terminal.onData(() => { + this.init(); + }); + }); + + // 处理错误 + window.electronAPI.terminal.onError((event, error) => { + this.terminal.writeln(`\r\n\x1b[31mError: ${error}\x1b[0m`); + }); + + // 窗口大小调整 + const resizeObserver = new ResizeObserver(() => { + this.fitAddon.fit(); + }); + resizeObserver.observe(this.container); + + // 键盘快捷键 + this.terminal.attachCustomKeyEventHandler((event) => { + // Ctrl+C - 中断当前命令 + if (event.ctrlKey && event.code === 'KeyC' && event.type === 'keydown') { + if (!this.terminal.hasSelection()) { + window.electronAPI.terminal.interrupt(); + return false; + } + // 如果有选中文本,则复制 + document.execCommand('copy'); + return false; + } + + // Ctrl+V - 粘贴 + if (event.ctrlKey && event.code === 'KeyV' && event.type === 'keydown') { + navigator.clipboard.readText().then(text => { + if (this.isConnected) { + window.electronAPI.terminal.write(text); + } + }); + return false; + } + + // Ctrl+L - 清屏 + if (event.ctrlKey && event.code === 'KeyL' && event.type === 'keydown') { + this.clear(); + return false; + } + + return true; + }); + } + + clear() { + if (this.isConnected) { + window.electronAPI.terminal.clear(); + } + } + + focus() { + this.terminal.focus(); + } + + write(text) { + this.terminal.write(text); + } + + dispose() { + if (this.isConnected) { + window.electronAPI.terminal.kill(); + } + this.terminal.dispose(); + } +} + +// 导出给全局使用 +window.PureJSTerminal = PureJSTerminal; + +document.addEventListener('DOMContentLoaded', () => { + window.terminal = new PureJSTerminal('terminal-container'); +}); +``` + +### 5. 构建配置 (package.json) - 纯 JavaScript 版本 + +```json +{ + "name": "pure-js-terminal", + "version": "1.0.0", + "description": "Pure JavaScript terminal implementation with xterm.js", + "main": "main.js", + "scripts": { + "start": "electron .", + "build": "electron-builder", + "dev": "electron . --development" + }, + "dependencies": { + "electron": "^22.0.0", + "xterm": "^5.1.0", + "@xterm/addon-fit": "^0.7.0", + "@xterm/addon-web-links": "^0.8.0" + }, + "devDependencies": { + "electron-builder": "^24.0.0" + }, + "build": { + "appId": "com.example.pure-js-terminal", + "productName": "Pure JS Terminal", + "directories": { + "output": "dist" + } + } +} +``` + +--- + +## 方案二:node-pty 实现(需要编译环境) + +### 环境要求 + +- Node.js 12+ 或 Electron 8+ +- Python 和 C++ 编译器(用于 node-pty 编译) + +### 1. 依赖安装 + +```bash +npm install xterm @xterm/addon-fit node-pty +``` + +### 2. 主进程实现 (main.js) + +```javascript +const { app, BrowserWindow, ipcMain } = require('electron'); +const pty = require('node-pty'); +const path = require('path'); + +let mainWindow; +let ptyProcess; + +function createWindow() { + mainWindow = new BrowserWindow({ + width: 1200, + height: 800, + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + preload: path.join(__dirname, 'preload.js') + } + }); + + mainWindow.loadFile('index.html'); +} + +// 创建终端进程 +ipcMain.handle('terminal-create', () => { + const shell = process.platform === 'win32' ? 'powershell.exe' : 'bash'; + + ptyProcess = pty.spawn(shell, [], { + name: 'xterm-color', + cols: 80, + rows: 24, + cwd: process.env.HOME, + env: process.env + }); + + // 监听终端数据输出 + ptyProcess.on('data', (data) => { + mainWindow.webContents.send('terminal-data', data); + }); + + // 监听进程退出 + ptyProcess.on('exit', (code) => { + mainWindow.webContents.send('terminal-exit', code); + }); + + return { success: true }; +}); + +// 向终端写入数据 +ipcMain.handle('terminal-write', (event, data) => { + if (ptyProcess) { + ptyProcess.write(data); + } +}); + +// 调整终端大小 +ipcMain.handle('terminal-resize', (event, cols, rows) => { + if (ptyProcess) { + ptyProcess.resize(cols, rows); + } +}); + +// 终端清屏 +ipcMain.handle('terminal-clear', () => { + if (ptyProcess) { + ptyProcess.write('\x1bc'); // 发送清屏命令 + } +}); + +// 关闭终端 +ipcMain.handle('terminal-kill', () => { + if (ptyProcess) { + ptyProcess.kill(); + ptyProcess = null; + } +}); + +app.whenReady().then(createWindow); + +app.on('before-quit', () => { + if (ptyProcess) { + ptyProcess.kill(); + } +}); +``` + +### 3. 预加载脚本 (preload.js) + +```javascript +const { contextBridge, ipcRenderer } = require('electron'); + +contextBridge.exposeInMainWorld('electronAPI', { + terminal: { + create: () => ipcRenderer.invoke('terminal-create'), + write: (data) => ipcRenderer.invoke('terminal-write', data), + resize: (cols, rows) => ipcRenderer.invoke('terminal-resize', cols, rows), + clear: () => ipcRenderer.invoke('terminal-clear'), + kill: () => ipcRenderer.invoke('terminal-kill'), + onData: (callback) => ipcRenderer.on('terminal-data', callback), + onExit: (callback) => ipcRenderer.on('terminal-exit', callback) + } +}); +``` + +### 4. 渲染进程实现 (renderer.js) + +```javascript +import { Terminal } from 'xterm'; +import { FitAddon } from '@xterm/addon-fit'; +import { WebLinksAddon } from '@xterm/addon-web-links'; +import { SearchAddon } from '@xterm/addon-search'; + +class SimpleTerminal { + constructor(containerId) { + this.terminal = new Terminal({ + theme: { + background: '#1e1e1e', + foreground: '#cccccc', + cursor: '#ffffff', + selection: 'rgba(255, 255, 255, 0.3)', + black: '#000000', + red: '#cd3131', + green: '#0dbc79', + yellow: '#e5e510', + blue: '#2472c8', + magenta: '#bc3fbc', + cyan: '#11a8cd', + white: '#e5e5e5', + brightBlack: '#666666', + brightRed: '#f14c4c', + brightGreen: '#23d18b', + brightYellow: '#f5f543', + brightBlue: '#3b8eea', + brightMagenta: '#d670d6', + brightCyan: '#29b8db', + brightWhite: '#ffffff', + }, + fontSize: 14, + fontFamily: 'Monaco, Menlo, "Ubuntu Mono", Consolas, "Courier New", monospace', + fontWeight: 'normal', + fontWeightBold: 'bold', + lineHeight: 1.2, + cursorBlink: true, + cursorStyle: 'block', + scrollback: 1000, + tabStopWidth: 4, + bellStyle: 'none' + }); + + // 加载插件 + this.fitAddon = new FitAddon(); + this.webLinksAddon = new WebLinksAddon(); + this.searchAddon = new SearchAddon(); + + this.terminal.loadAddon(this.fitAddon); + this.terminal.loadAddon(this.webLinksAddon); + this.terminal.loadAddon(this.searchAddon); + + this.container = document.getElementById(containerId); + this.isConnected = false; + + this.init(); + } + + async init() { + try { + // 挂载终端到 DOM + this.terminal.open(this.container); + this.fitAddon.fit(); + + // 创建后端终端进程 + const result = await window.electronAPI.terminal.create(); + if (result.success) { + this.isConnected = true; + this.setupEventListeners(); + this.terminal.focus(); + } + } catch (error) { + console.error('Terminal initialization failed:', error); + this.terminal.write('\r\n\x1b[31mError: Failed to initialize terminal\x1b[0m\r\n'); + } + } + + setupEventListeners() { + // 监听用户输入 + this.terminal.onData(data => { + if (this.isConnected) { + window.electronAPI.terminal.write(data); + } + }); + + // 监听后端数据输出 + window.electronAPI.terminal.onData((event, data) => { + this.terminal.write(data); + }); + + // 监听进程退出 + window.electronAPI.terminal.onExit((event, code) => { + this.isConnected = false; + this.terminal.write(`\r\n\x1b[33mProcess exited with code ${code}\x1b[0m\r\n`); + }); + + // 监听窗口大小变化 + const resizeObserver = new ResizeObserver(() => { + this.fitAddon.fit(); + if (this.isConnected) { + const { cols, rows } = this.terminal; + window.electronAPI.terminal.resize(cols, rows); + } + }); + + resizeObserver.observe(this.container); + + // 监听键盘快捷键 + this.terminal.attachCustomKeyEventHandler((event) => { + // Ctrl+C 复制选中内容 + if (event.ctrlKey && event.code === 'KeyC' && event.type === 'keydown') { + const selection = this.terminal.getSelection(); + if (selection) { + navigator.clipboard.writeText(selection); + return false; + } + } + + // Ctrl+V 粘贴 + if (event.ctrlKey && event.code === 'KeyV' && event.type === 'keydown') { + navigator.clipboard.readText().then(text => { + if (this.isConnected) { + window.electronAPI.terminal.write(text); + } + }); + return false; + } + + // Ctrl+L 清屏 + if (event.ctrlKey && event.code === 'KeyL' && event.type === 'keydown') { + this.clear(); + return false; + } + + return true; + }); + + // 鼠标滚轮缩放 + this.terminal.attachCustomWheelEventHandler((event) => { + if (event.ctrlKey) { + event.preventDefault(); + const delta = event.deltaY < 0 ? 1 : -1; + const newSize = Math.max(8, Math.min(24, this.terminal.options.fontSize + delta)); + this.terminal.options.fontSize = newSize; + this.fitAddon.fit(); + return false; + } + return true; + }); + } + + // 清屏 + clear() { + if (this.isConnected) { + window.electronAPI.terminal.clear(); + } + } + + // 聚焦终端 + focus() { + this.terminal.focus(); + } + + // 搜索文本 + search(text, options = {}) { + return this.searchAddon.findNext(text, options); + } + + // 获取选中文本 + getSelection() { + return this.terminal.getSelection(); + } + + // 写入文本到终端 + write(text) { + this.terminal.write(text); + } + + // 销毁终端 + dispose() { + if (this.isConnected) { + window.electronAPI.terminal.kill(); + } + this.terminal.dispose(); + } +} + +// 使用示例 +document.addEventListener('DOMContentLoaded', () => { + const terminal = new SimpleTerminal('terminal-container'); + + // 全局引用,便于调试 + window.terminal = terminal; +}); +``` + +### 5. HTML 界面 (index.html) + +```html + + + + + + Simple Terminal + + + + +
+

🖥️ Simple Terminal

+
+ + + +
+
+ +
+
+
+ + + + + +``` + +### 6. 构建配置 (package.json) + +```json +{ + "name": "simple-terminal", + "version": "1.0.0", + "description": "Simple terminal implementation with xterm.js and node-pty", + "main": "main.js", + "scripts": { + "start": "electron .", + "build": "electron-builder", + "rebuild": "electron-rebuild" + }, + "dependencies": { + "electron": "^22.0.0", + "xterm": "^5.1.0", + "@xterm/addon-fit": "^0.7.0", + "@xterm/addon-web-links": "^0.8.0", + "@xterm/addon-search": "^0.12.0", + "node-pty": "^0.10.1" + }, + "devDependencies": { + "electron-builder": "^24.0.0", + "electron-rebuild": "^3.2.9" + }, + "build": { + "appId": "com.example.simple-terminal", + "productName": "Simple Terminal", + "directories": { + "output": "dist" + }, + "files": [ + "**/*", + "!node_modules/node-pty/build/Release/obj/**/*" + ] + } +} +``` + +## 增强功能 + +### 主题支持 + +```javascript +const themes = { + dark: { + background: '#1e1e1e', + foreground: '#cccccc', + // ... 其他颜色 + }, + light: { + background: '#ffffff', + foreground: '#000000', + // ... 其他颜色 + }, + solarized: { + background: '#002b36', + foreground: '#839496', + // ... 其他颜色 + } +}; + +// 切换主题 +terminal.options.theme = themes.light; +``` + +### 多标签页支持 + +```javascript +class TerminalManager { + constructor() { + this.terminals = new Map(); + this.activeTab = null; + } + + createTab(id) { + const terminal = new SimpleTerminal(`terminal-${id}`); + this.terminals.set(id, terminal); + return terminal; + } + + switchTab(id) { + if (this.activeTab) { + this.activeTab.container.style.display = 'none'; + } + + const terminal = this.terminals.get(id); + if (terminal) { + terminal.container.style.display = 'block'; + terminal.focus(); + this.activeTab = terminal; + } + } +} +``` + +### 历史命令支持 + +```javascript +class CommandHistory { + constructor() { + this.history = []; + this.currentIndex = -1; + } + + add(command) { + this.history.push(command); + this.currentIndex = this.history.length; + } + + previous() { + if (this.currentIndex > 0) { + this.currentIndex--; + return this.history[this.currentIndex]; + } + return null; + } + + next() { + if (this.currentIndex < this.history.length - 1) { + this.currentIndex++; + return this.history[this.currentIndex]; + } + return null; + } +} +``` + +## 部署注意事项 + +### Windows 环境 + +1. 安装 Visual Studio Build Tools 或完整版本 +2. 或使用 `npm install --global windows-build-tools` +3. node-pty 需要本地编译 + +### macOS/Linux 环境 + +1. 确保安装了 Python 和基本开发工具 +2. macOS 需要 Xcode Command Line Tools +3. Linux 需要 build-essential 包 + +### Electron 打包 + +```bash +# 重建原生模块 +npm run rebuild + +# 构建应用 +npm run build +``` + +## 性能优化 + +1. **限制滚动缓冲区**: 设置合理的 `scrollback` 值 +2. **按需渲染**: 使用 `renderer.renderRows()` 控制渲染范围 +3. **内存管理**: 及时清理不使用的终端实例 +4. **数据节流**: 对高频输出进行节流处理 + +## 安全考虑 + +1. **命令验证**: 对用户输入进行基本验证 +2. **环境隔离**: 限制终端进程的环境变量 +3. **权限控制**: 以最小权限运行终端进程 +4. **输出过滤**: 过滤潜在的恶意输出序列 + +## 两种方案对比 + +| 特性 | 纯 JavaScript 方案 | node-pty 方案 | +| -------------- | ------------------ | ----------------- | +| **环境要求** | ✅ 无需编译环境 | ❌ 需要 Python/C++ | +| **安装简易度** | ✅ 简单快速 | ❌ 复杂,可能出错 | +| **打包体积** | ✅ 更小 | ❌ 更大 | +| **跨平台兼容** | ✅ 良好 | ✅ 很好 | +| **功能完整度** | ⚠️ 基本功能 | ✅ 完整功能 | +| **终端特性** | ⚠️ 部分支持 | ✅ 完全支持 | +| **性能** | ✅ 良好 | ✅ 很好 | + +## 功能限制说明 + +### 纯 JavaScript 方案限制 + +1. **无法处理原生终端特性**: + - 不支持真正的 PTY(伪终端) + - 无法正确处理一些交互式程序(如 `vim`、`nano`) + - 终端大小调整可能不完整 + +2. **输入输出处理较简单**: + - 可能无法完美处理所有 ANSI 转义序列 + - 某些特殊按键组合可能不工作 + - 命令历史记录需要自己实现 + +3. **适用场景**: + - 简单命令执行 + - 日志输出显示 + - 开发工具集成 + - 快速原型开发 + +### node-pty 方案优势 + +1. **完整终端体验**: + - 真正的 PTY 支持 + - 完整的 ANSI 转义序列处理 + - 支持所有交互式程序 + +2. **更好的兼容性**: + - 与系统终端行为一致 + - 支持复杂的终端应用 + - 更好的字符编码处理 + +## 推荐使用场景 + +### 选择纯 JavaScript 方案当: +- ✅ 只需要基本的命令执行功能 +- ✅ 希望简化部署和分发 +- ✅ 避免编译环境依赖 +- ✅ 应用体积敏感 +- ✅ 快速开发原型 + +### 选择 node-pty 方案当: +- ✅ 需要完整的终端体验 +- ✅ 要支持复杂的交互式程序 +- ✅ 对终端功能要求很高 +- ✅ 不在乎部署复杂度 +- ✅ 有稳定的构建环境 + +## 总结 + +**纯 JavaScript 方案**提供了: +- ✅ 零依赖编译环境 +- ✅ 简单快速的部署 +- ✅ 基本但实用的终端功能 +- ✅ 良好的跨平台兼容性 +- ✅ 更小的应用体积 + +**node-pty 方案**提供了: +- ✅ 完整的终端体验 +- ✅ 更好的程序兼容性 +- ✅ 专业级的功能支持 +- ❌ 复杂的环境要求 +- ❌ 更大的应用体积 + +对于大多数应用场景,**推荐使用纯 JavaScript 方案**,它能够满足 80% 的终端使用需求,同时避免了环境依赖的复杂性。