Commit25b7b30cRecorded31 Mar 2026Repositorysigil-web-repl

Use unbuffered xterm output and binary lib.bundle

Message

Replace ConsoleStdout.lineBuffered with custom unbuffered Fd so the REPL prompt (which doesn't end with newline) renders immediately.

Switch from per-file HTTP fetches to single lib.bundle binary file for loading compiled Sigil modules into the WASI virtual filesystem.

Changed
 assets/index.html | 68 ++++++++++++++++++++++++++++++++++++++++++++------------------------
 1 file changed, 44 insertions(+), 24 deletions(-)
Diff
assets/index.htmlmodified
@@ -1118,29 +1118,37 @@
1118
term.onData(handleInput);
1119
window.addEventListener('resize', () => { if (ready) fitAddon.fit(); });
1120
1121
// Build virtual filesystem from lib/ directory
+1121
// Load module bundle into virtual filesystem
+1122
// Bundle format: [uint32 count] then per file:
+1123
// [uint16 path-len] [path bytes] [uint32 data-len] [data bytes]
1124
async function buildFs() {
1125
const { File, Directory } = await import("https://cdn.jsdelivr.net/npm/@bjorn3/[email protected]/dist/index.js");
1126
const files = {};
1127
try {
1126
const resp = await fetch('lib-manifest.json');
+1128
const resp = await fetch('lib.bundle');
1129
if (!resp.ok) return { files, File, Directory };
1128
const manifest = await resp.json();
1129
await Promise.all(manifest.map(async (path) => {
1130
const fileResp = await fetch('lib/' + path);
1131
if (fileResp.ok) {
1132
const data = new Uint8Array(await fileResp.arrayBuffer());
1133
const parts = path.split('/');
1134
let dir = files;
1135
for (let i = 0; i < parts.length - 1; i++) {
1136
if (!dir[parts[i]]) dir[parts[i]] = {};
1137
dir = dir[parts[i]];
1138
}
1139
dir[parts[parts.length - 1]] = new File(data);
+1130
const buf = await resp.arrayBuffer();
+1131
const view = new DataView(buf);
+1132
const decoder = new TextDecoder();
+1133
let offset = 0;
+1134
+1135
const count = view.getUint32(offset, true); offset += 4;
+1136
for (let i = 0; i < count; i++) {
+1137
const pathLen = view.getUint16(offset, true); offset += 2;
+1138
const path = decoder.decode(new Uint8Array(buf, offset, pathLen)); offset += pathLen;
+1139
const dataLen = view.getUint32(offset, true); offset += 4;
+1140
const data = new Uint8Array(buf, offset, dataLen); offset += dataLen;
+1141
+1142
const parts = path.split('/');
+1143
let dir = files;
+1144
for (let j = 0; j < parts.length - 1; j++) {
+1145
if (!dir[parts[j]]) dir[parts[j]] = {};
+1146
dir = dir[parts[j]];
1147
}
1141
}));
+1148
dir[parts[parts.length - 1]] = new File(new Uint8Array(data));
+1149
}
1150
} catch (e) {
1143
console.warn('Module loading:', e.message);
+1151
console.warn('Bundle loading:', e.message);
1152
}
1153
return { files, File, Directory };
1154
}
@@ -1167,21 +1175,33 @@
1175
return result;
1176
}
1177
+1178
// Custom WASI Fd that writes to xterm immediately (unbuffered)
+1179
function makeTermWriter(termRef, wasiModule) {
+1180
const decoder = new TextDecoder();
+1181
const fd = new wasiModule.Fd();
+1182
fd.fd_write = function(data) {
+1183
const text = decoder.decode(data, { stream: true });
+1184
termRef.write(text.replace(/\n/g, '\r\n'));
+1185
return { ret: 0, nwritten: data.byteLength };
+1186
};
+1187
fd.fd_fdstat_get = function() {
+1188
const { Fdstat } = wasiModule.wasi;
+1189
return { ret: 0, fdstat: new Fdstat(2, 0) };
+1190
};
+1191
return fd;
+1192
}
+1193
1194
// Initialize WASI and load WASM
1195
async function init() {
1172
const { WASI, File: WasiFile, OpenFile, ConsoleStdout, PreopenDirectory } =
1173
await import("https://cdn.jsdelivr.net/npm/@bjorn3/[email protected]/dist/index.js");
+1196
const wasiModule = await import("https://cdn.jsdelivr.net/npm/@bjorn3/[email protected]/dist/index.js");
+1197
const { WASI, File: WasiFile, Fd, Fdstat, OpenFile, PreopenDirectory } = wasiModule;
1198
1199
const { files: libFiles, File: FileClass, Directory: DirectoryClass } = await buildFs();
1200
1201
const fds = [
1202
new OpenFile(new WasiFile([])), // stdin
1179
ConsoleStdout.lineBuffered(msg => {
1180
term.write(msg.replace(/\n/g, '\r\n') + '\r\n');
1181
}),
1182
ConsoleStdout.lineBuffered(msg => {
1183
term.write(msg.replace(/\n/g, '\r\n') + '\r\n');
1184
}),
+1203
makeTermWriter(term, wasiModule), // stdout
+1204
makeTermWriter(term, wasiModule), // stderr
1205
new PreopenDirectory("/", new Map([
1206
["lib", makeDir(libFiles, FileClass, DirectoryClass)]
1207
])),