Skip to content

Commit 59cb82f

Browse files
authored
feat: make wasmPath optional with smart auto-detection (#19)
- Make Ghostty.load() wasmPath parameter optional with intelligent fallback logic - Try multiple default paths: import.meta.url, relative, and root paths - Update Terminal class to remove hardcoded wasmPath default - Simplify test code to use auto-detection - Fix build-wasm.sh to reference coder/ghostty repository - Improve error messages with helpful guidance
1 parent 20c9d29 commit 59cb82f

5 files changed

Lines changed: 58 additions & 33 deletions

File tree

lib/ghostty.ts

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -55,40 +55,65 @@ export class Ghostty {
5555

5656
/**
5757
* Load Ghostty WASM from URL or file path
58+
* If no path is provided, attempts to load from common default locations
5859
*/
59-
static async load(wasmPath: string): Promise<Ghostty> {
60-
let wasmBytes: ArrayBuffer;
60+
static async load(wasmPath?: string): Promise<Ghostty> {
61+
// Default WASM paths to try (in order)
62+
const defaultPaths = [
63+
// When published as npm package
64+
new URL('../ghostty-vt.wasm', import.meta.url).href,
65+
// When used from CDN or local dev
66+
'./ghostty-vt.wasm',
67+
'/ghostty-vt.wasm',
68+
];
69+
70+
const pathsToTry = wasmPath ? [wasmPath] : defaultPaths;
71+
let lastError: Error | null = null;
72+
73+
for (const path of pathsToTry) {
74+
try {
75+
let wasmBytes: ArrayBuffer;
76+
77+
// Try loading as file first (for Node/Bun environments)
78+
try {
79+
const fs = await import('fs/promises');
80+
const buffer = await fs.readFile(path);
81+
wasmBytes = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
82+
} catch (e) {
83+
// Fall back to fetch (for browser environments)
84+
const response = await fetch(path);
85+
if (!response.ok) {
86+
throw new Error(`Failed to fetch WASM: ${response.status} ${response.statusText}`);
87+
}
88+
wasmBytes = await response.arrayBuffer();
89+
if (wasmBytes.byteLength === 0) {
90+
throw new Error(`WASM file is empty (0 bytes). Check path: ${path}`);
91+
}
92+
}
6193

62-
// Try loading as file first (for Node/Bun environments)
63-
try {
64-
const fs = await import('fs/promises');
65-
const buffer = await fs.readFile(wasmPath);
66-
wasmBytes = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
67-
} catch (e) {
68-
// Fall back to fetch (for browser environments)
69-
const response = await fetch(wasmPath);
70-
if (!response.ok) {
71-
throw new Error(`Failed to fetch WASM: ${response.status} ${response.statusText}`);
72-
}
73-
wasmBytes = await response.arrayBuffer();
74-
if (wasmBytes.byteLength === 0) {
75-
throw new Error(`WASM file is empty (0 bytes). Check path: ${wasmPath}`);
94+
// Successfully loaded, instantiate and return
95+
const wasmModule = await WebAssembly.instantiate(wasmBytes, {
96+
env: {
97+
// Stub out C runtime functions (not used by libghostty-vt)
98+
},
99+
});
100+
101+
return new Ghostty(wasmModule.instance);
102+
} catch (e) {
103+
lastError = e instanceof Error ? e : new Error(String(e));
104+
// Try next path
105+
continue;
76106
}
77107
}
78108

79-
const wasmModule = await WebAssembly.instantiate(wasmBytes, {
80-
env: {
81-
log: (ptr: number, len: number) => {
82-
const instance = (wasmModule as any).instance;
83-
const bytes = new Uint8Array(instance.exports.memory.buffer, ptr, len);
84-
const text = new TextDecoder().decode(bytes);
85-
console.log('[ghostty-wasm]', text);
86-
},
87-
},
88-
});
89-
90-
return new Ghostty(wasmModule.instance);
109+
// All paths failed
110+
throw new Error(
111+
`Failed to load ghostty-vt.wasm. Tried paths: ${pathsToTry.join(', ')}. ` +
112+
`Last error: ${lastError?.message}. ` +
113+
`You can specify a custom path with: new Terminal({ wasmPath: './path/to/ghostty-vt.wasm' })`
114+
);
91115
}
116+
92117
}
93118

94119
/**

lib/input-handler.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,8 @@ describe('InputHandler', () => {
129129

130130
beforeAll(async () => {
131131
// Load WASM once for all tests (expensive operation)
132-
const wasmPath = new URL('../ghostty-vt.wasm', import.meta.url).href;
133-
ghostty = await Ghostty.load(wasmPath);
132+
// wasmPath is now optional - auto-detected
133+
ghostty = await Ghostty.load();
134134
});
135135

136136
beforeEach(() => {

lib/interfaces.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export interface ITerminalOptions {
1212
fontSize?: number; // Default: 15
1313
fontFamily?: string; // Default: 'monospace'
1414
allowTransparency?: boolean;
15-
wasmPath?: string; // Default: '../ghostty-vt.wasm' (relative to examples/)
15+
wasmPath?: string; // Optional: custom WASM path (auto-detected by default)
1616
}
1717

1818
export interface ITheme {

lib/terminal.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export class Terminal implements ITerminalCore {
7979
fontSize: options.fontSize ?? 15,
8080
fontFamily: options.fontFamily ?? 'monospace',
8181
allowTransparency: options.allowTransparency ?? false,
82-
wasmPath: options.wasmPath ?? '../ghostty-vt.wasm',
82+
wasmPath: options.wasmPath, // Optional - Ghostty.load() handles defaults
8383
};
8484

8585
this.cols = this.options.cols;

scripts/build-wasm.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ echo "✓ Found Zig $ZIG_VERSION"
2121
GHOSTTY_DIR="/tmp/ghostty-for-wasm"
2222
if [ ! -d "$GHOSTTY_DIR" ]; then
2323
echo "📦 Cloning Ghostty..."
24-
git clone --depth=1 https://github.com/ghostty-org/ghostty.git "$GHOSTTY_DIR"
24+
git clone --depth=1 https://github.com/coder/ghostty.git "$GHOSTTY_DIR"
2525
else
2626
echo "📦 Updating Ghostty..."
2727
cd "$GHOSTTY_DIR"

0 commit comments

Comments
 (0)