[GH-ISSUE #807] Markdown rendering fails when compile with bun and distribute via brew #989

Open
opened 2026-03-14 09:12:25 +03:00 by kerem · 0 comments
Owner

Originally created by @sergeyzwezdin on GitHub (Mar 12, 2026).
Original GitHub issue: https://github.com/anomalyco/opentui/issues/807

I'm experiencing a problem with my opentui app. It works perfectly when I run it locally, with markdown rendering functioning as expected. However, when I use bun to compile it and then publish it through homebrew, it fails without any error messages:

Image

I've mentioned this issue in a related post here, but I wanted to bring it to your attention as well.

I spent some time with Claude and it finds the problem in how tree-sitter loads. Check below.


MarkdownRenderable (tree-sitter) broken in Bun compiled binaries — Worker entry point not bundled.

MarkdownRenderable (and any tree-sitter-based syntax highlighting) renders as plain text when the application is distributed as a Bun compiled binary (bun build --compile). It works correctly in dev mode (bun run dev) and appears to work when running the compiled binary from the build directory, but breaks when the binary is installed to a different location (e.g., via Homebrew).

Root Cause

TreeSitterClient.startWorker() spawns a Web Worker using:

worker_path = new URL("./parser.worker.js", import.meta.url).href;
if (!existsSync(resolve(import.meta.dirname, "parser.worker.js"))) {
    worker_path = new URL("./parser.worker.ts", import.meta.url).href;
}
this.worker = new Worker(worker_path);

bun build --compile does not automatically bundle Web Worker entry points into the compiled binary. The parser.worker.js file is not embedded in the output executable.

When the compiled binary runs:

  1. import.meta.dirname resolves to the binary's directory (e.g., /opt/homebrew/Cellar/huba/0.0.15/bin/)
  2. existsSync check for parser.worker.js next to the binary → fails (only the binary is installed)
  3. Falls back to parser.worker.ts URL → also doesn't exist
  4. new Worker(url) fails with: BuildMessage: ModuleNotFound resolving "/$bunfs/root/parser.worker.ts" (entry point)
  5. The worker error is caught, TreeSitterClient never initializes
  6. MarkdownRenderable receives no highlights → renders as plain unformatted text

Why It Appears to Work Locally

  • bun run dev: Runs from source. parser.worker.js exists in node_modules/@opentui/core/ and is resolved normally.
  • Compiled binary in build directory: The /$bunfs/root/ virtual filesystem paths may still resolve on the same machine where compilation happened, or there's a cached tree-sitter state in ~/.local/share/opentui/ from previous dev runs.

Reproduction

Minimal reproduction of the Bun limitation:

// main.ts
const worker = new Worker(new URL("./worker.ts", import.meta.url).href);
worker.onmessage = (e) => console.log("OK:", e.data);
worker.onerror = (e) => console.error("FAIL:", e.message);
// worker.ts
declare var self: { postMessage: (data: any) => void };
self.postMessage("hello");
bun build --compile main.ts --outfile ./test-binary
./test-binary          # may work (same location)
cp ./test-binary /tmp/
/tmp/test-binary       # FAIL: ModuleNotFound resolving "/$bunfs/root/worker.ts"

Impact

Any application using @opentui/core's MarkdownRenderable, CodeRenderable, or DiffRenderable (anything relying on TreeSitterClient) will silently lose syntax highlighting when distributed as a compiled Bun binary.

The failure is silent — no visible error to the user, just plain text output instead of highlighted markdown/code.

Environment

  • Bun: 1.3.2
  • Platform: macOS (darwin-arm64), also affects Linux
  • Distribution method: Homebrew (standalone binary)

Suggested Solutions

  1. Inline the worker code — Use new Worker(new Blob([workerSource])) or Bun's inline worker support to avoid the need for a separate file entry point.

  2. Main-thread fallback — Detect the compiled binary environment (e.g., import.meta.url contains /$bunfs/) and fall back to running tree-sitter parsing on the main thread when Worker creation fails.

  3. Graceful degradation with clear warning — If the worker fails to spawn, log a visible warning and fall back to unhighlighted rendering, so consumers know what's happening rather than silently degrading.

  4. Document the limitation — At minimum, document that bun build --compile consumers need to ship parser.worker.js alongside their binary and set OTUI_TREE_SITTER_WORKER_PATH env var.

Originally created by @sergeyzwezdin on GitHub (Mar 12, 2026). Original GitHub issue: https://github.com/anomalyco/opentui/issues/807 I'm experiencing a problem with my opentui app. It works perfectly when I run it locally, with markdown rendering functioning as expected. However, when I use bun to compile it and then publish it through homebrew, it fails without any error messages: <img width="968" height="1302" alt="Image" src="https://github.com/user-attachments/assets/c8211af8-99be-4e45-88fe-0ce25dfac996" /> I've mentioned this issue in a related post [here](https://github.com/anomalyco/opencode/issues/3312#issuecomment-4049554056), but I wanted to bring it to your attention as well. I spent some time with Claude and it finds the problem in how tree-sitter loads. Check below. --- MarkdownRenderable (tree-sitter) broken in Bun compiled binaries — Worker entry point not bundled. `MarkdownRenderable` (and any tree-sitter-based syntax highlighting) renders as plain text when the application is distributed as a Bun compiled binary (`bun build --compile`). It works correctly in dev mode (`bun run dev`) and appears to work when running the compiled binary from the build directory, but breaks when the binary is installed to a different location (e.g., via Homebrew). ## Root Cause `TreeSitterClient.startWorker()` spawns a Web Worker using: ```ts worker_path = new URL("./parser.worker.js", import.meta.url).href; if (!existsSync(resolve(import.meta.dirname, "parser.worker.js"))) { worker_path = new URL("./parser.worker.ts", import.meta.url).href; } this.worker = new Worker(worker_path); ``` **`bun build --compile` does not automatically bundle Web Worker entry points into the compiled binary.** The `parser.worker.js` file is not embedded in the output executable. When the compiled binary runs: 1. `import.meta.dirname` resolves to the binary's directory (e.g., `/opt/homebrew/Cellar/huba/0.0.15/bin/`) 2. `existsSync` check for `parser.worker.js` next to the binary → fails (only the binary is installed) 3. Falls back to `parser.worker.ts` URL → also doesn't exist 4. `new Worker(url)` fails with: `BuildMessage: ModuleNotFound resolving "/$bunfs/root/parser.worker.ts" (entry point)` 5. The worker error is caught, `TreeSitterClient` never initializes 6. `MarkdownRenderable` receives no highlights → renders as plain unformatted text ## Why It Appears to Work Locally - **`bun run dev`**: Runs from source. `parser.worker.js` exists in `node_modules/@opentui/core/` and is resolved normally. - **Compiled binary in build directory**: The `/$bunfs/root/` virtual filesystem paths may still resolve on the same machine where compilation happened, or there's a cached tree-sitter state in `~/.local/share/opentui/` from previous dev runs. ## Reproduction Minimal reproduction of the Bun limitation: ```ts // main.ts const worker = new Worker(new URL("./worker.ts", import.meta.url).href); worker.onmessage = (e) => console.log("OK:", e.data); worker.onerror = (e) => console.error("FAIL:", e.message); ``` ```ts // worker.ts declare var self: { postMessage: (data: any) => void }; self.postMessage("hello"); ``` ```bash bun build --compile main.ts --outfile ./test-binary ./test-binary # may work (same location) cp ./test-binary /tmp/ /tmp/test-binary # FAIL: ModuleNotFound resolving "/$bunfs/root/worker.ts" ``` ## Impact Any application using `@opentui/core`'s `MarkdownRenderable`, `CodeRenderable`, or `DiffRenderable` (anything relying on `TreeSitterClient`) will silently lose syntax highlighting when distributed as a compiled Bun binary. The failure is silent — no visible error to the user, just plain text output instead of highlighted markdown/code. ## Environment - Bun: 1.3.2 - Platform: macOS (darwin-arm64), also affects Linux - Distribution method: Homebrew (standalone binary) ## Suggested Solutions 1. **Inline the worker code** — Use `new Worker(new Blob([workerSource]))` or Bun's inline worker support to avoid the need for a separate file entry point. 2. **Main-thread fallback** — Detect the compiled binary environment (e.g., `import.meta.url` contains `/$bunfs/`) and fall back to running tree-sitter parsing on the main thread when Worker creation fails. 3. **Graceful degradation with clear warning** — If the worker fails to spawn, log a visible warning and fall back to unhighlighted rendering, so consumers know what's happening rather than silently degrading. 4. **Document the limitation** — At minimum, document that `bun build --compile` consumers need to ship `parser.worker.js` alongside their binary and set `OTUI_TREE_SITTER_WORKER_PATH` env var.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/opentui#989
No description provided.