High-performance terminal panel using Ghostty WebAssembly rendering engine
npm install @industry-theme/ghostty-terminal-panelThis guide outlines how to integrate the experimental Ghostty WebAssembly (Wasm) engine into the industry-themed-panel-starter framework. This allows you to render a high-performance terminal interface within a React panel while offloading the shell processing to node-pty in the Electron main process.
Since Ghostty is a native Zig application, we cannot import it directly into a standard JavaScript bundle. We rely on the Wasm build to run the rendering engine in the browser context (Electron Renderer).
- View Layer (Panel): GhosttyWeb (Wasm) handles rendering pixels and capturing input.
- Transport Layer: The Panel uses actions and events props to communicate via Electron IPC.
- Logic Layer (Host): node-pty runs the actual shell (bash/zsh/powershell).
Since the official library is not yet public, use the experimental community wrapper or your own local Wasm build.
``bashOption A: Experimental package
npm install @coder/ghostty-web
$3
Create a new file:
src/panels/GhosttyTerminal.tsx.This component initializes the Wasm engine and bridges the gap between the Ghostty IO and the Panel Framework's event system.
`tsx
import React, { useEffect, useRef } from 'react';
import { GhosttyWeb } from '@coder/ghostty-web'; // or local import
import type { PanelComponentProps } from '../types';export const GhosttyTerminal: React.FC = ({
context,
actions,
events,
}) => {
const terminalRef = useRef(null);
const engineRef = useRef(null);
useEffect(() => {
if (!terminalRef.current) return;
const init = async () => {
// 1. Initialize Wasm Engine
const ghostty = await GhosttyWeb.init({
element: terminalRef.current,
// Ensure this path points to where you deployed the .wasm file
wasmUrl: '/assets/ghostty-vt.wasm',
fontSize: 14,
fontFamily: 'JetBrains Mono, monospace',
});
engineRef.current = ghostty;
// 2. Outgoing: User types in Ghostty -> Send to Host
ghostty.onData((input: string) => {
// Use the generic action handler to send data to Electron
if (actions.sendTerminalData) {
actions.sendTerminalData(input);
} else {
// Fallback for custom implementations
console.warn('Action "sendTerminalData" not available on host.');
}
});
// 3. Handle Resize
const resizeObserver = new ResizeObserver(() => {
ghostty.fit();
});
resizeObserver.observe(terminalRef.current);
};
init();
// Cleanup
return () => {
// engineRef.current?.dispose();
};
}, [actions]);
// 4. Incoming: Host sends PTY data -> Write to Ghostty
useEffect(() => {
const handleOutput = (data: any) => {
// Ensure data is string or Uint8Array
if (engineRef.current && data.payload) {
engineRef.current.write(data.payload);
}
};
// Listen to specific event ID defined by your host
events.on('terminal:output', handleOutput);
return () => {
events.off('terminal:output', handleOutput);
};
}, [events]);
return (
ref={terminalRef}
className="w-full h-full bg-black overflow-hidden"
/>
);
};
`$3
Update
src/index.tsx to export your new panel configuration.`tsx
import { GhosttyTerminal } from './panels/GhosttyTerminal';export const panels = [
{
id: 'com.your-org.ghostty-term',
name: 'Ghostty Terminal',
icon: 'Terminal',
description: 'Experimental Wasm-based Ghostty renderer',
component: GhosttyTerminal,
},
// ... other panels
];
`3. Host Side Integration (Electron Main)
The panel is just a view. The Host application (Electron Main process) must handle the actual PTY creation and IPC routing.
In your Electron Main Process:
`typescript
import * as pty from 'node-pty';// 1. Setup PTY
const ptyProcess = pty.spawn('bash', [], {
name: 'xterm-256color',
cols: 80,
rows: 30,
cwd: process.env.HOME,
env: process.env
});
// 2. Incoming from Panel (via IPC/Actions)
ipcMain.on('panel-action', (event, { action, payload }) => {
if (action === 'sendTerminalData') {
ptyProcess.write(payload);
}
});
// 3. Outgoing to Panel (via IPC/Events)
ptyProcess.onData((data) => {
mainWindow.webContents.send('panel-event', {
event: 'terminal:output',
payload: data
});
});
`4. Known Constraints
- Asset Loading: The Wasm build requires access to font files. You may need to configure your build tool (Vite/Webpack) to copy the .wasm and font assets to the final build output directory.
- Clipboard: Native clipboard integration might be limited in the Wasm environment; you may need to implement a bridge using
navigator.clipboard.
- WebGL: Ghostty Wasm uses WebGL. Ensure hardware acceleration is enabled in your Electron window configuration (webPreferences: { webgl: true }`).