P0a: ES Module Execution — NameTooLong Fix + Dependency Resolution #74

Closed
opened 2026-06-18 17:17:54 +00:00 by Artur · 3 comments
Owner

P0a: ES Module Execution — NameTooLong Bug + Dependency Resolution

Priority: CRITICAL
Betrifft: vuejs.org, solidjs.com, qwik.dev, angular.io, web.dev — alle VitePress/Vite-Sites
Impact: Framework-Seiten laden nur statisches HTML. App bootstrapped nie.

Problembeschreibung

executeModule() in execution-realm.ts lädt Module via data:text/javascript,${encodeURIComponent(code)}.
Zwei fundamentale Probleme:

Problem 1: NameTooLong
Bun's import() wirft NameTooLong für Vite-Bundles >~500KB (ein typischer VuePress/Vite-Bundle).
encodeURIComponent() erzeugt extrem lange data:-URLs → Bun's URL-Parser bricht ab.

Problem 2: Relative Import Resolution
Module mit import { D } from "./chunks/runtime-core.esm-bundler.js" schlagen fehl, weil data: URLs keinen Base-Path haben. Relative Imports werden nicht aufgelöst.

Aktuelles Verhalten

// execution-realm.ts:257-259
const encoded = encodeURIComponent(sandboxedCode);
const moduleUrl = `data:text/javascript,${encoded}`;
const mod = await import(moduleUrl);
// → NameTooLong für große Bundles
// → Relative Imports (./chunks/...) schlagen fehl

Lösungsansätze

Option A: File-Write statt data: URL (EMPFEHLUNG)

const tmpFile = `${tmpDir}/${hash}.mjs`;
await Bun.write(tmpFile, sandboxedCode);
const mod = await import(tmpFile);
await Bun.spawn(["rm", tmpFile]); // cleanup

Vorteile:

  • Kein NameTooLong (File-Pfade sind kurz)
  • Relative Imports funktionieren (File kann Importe auflösen)
  • Base-Path ist der File-Pfad → Module können Sub-Module finden

Nachteile:

  • File-System-Ärger (tmp-Files)
  • Nicht-Module-Importe müssen gelöst werden (file: statt http: base)

Option B: Base64-Encoding

const b64 = Buffer.from(sandboxedCode).toString('base64');
const mod = await import(`data:text/javascript;base64,${b64}`);

Vorteile: Kürzer, kein NameTooLong mehr
Nachteile: Relative Imports funktionieren immer noch nicht

Option C: Bun Plugin für Module Resolution

Bun.plugin({
  name: 'thb-module-loader',
  setup(build) {
    build.onResolve({ filter: /^thb:/ }, (args) => {
      return { path: args.path, namespace: 'thb' };
    });
    build.onLoad({ filter: /.*/, namespace: 'thb' }, async (args) => {
      // Custom module loading logic
    });
  }
});

Entscheidung

Option A + B kombiniert:

  • Für Bundles <1MB: Base64 (schneller, kein FS-I/O)
  • Für Bundles >1MB: File-Write + import(filePath)

Akzeptanzkriterien

  • VitePress-Seite (vuejs.org) lädt ohne NameTooLong Error
  • Module mit import/export Syntax werden ausgeführt
  • Relative Imports (./chunks/...) werden resolved
  • Module-Exports sind in _win sichtbar (Export-Merging)
  • Unit-Test: 1MB+ Module lädt via File-Write
  • Unit-Test: Module mit Sub-Import resolved korrekt

Betroffene Dateien

Datei Änderung
src/js/execution-realm.ts executeModule — File-Write + Base64 Fallback
tests/unit/sprint16-es-module.test.ts 10+ Tests

Dependencies

  • Sprint 15 (Issue #71): Export-Merging
  • Keine weiteren

Performance-Impact

  • File-Write + import(filePath): ~5ms Overhead pro Bundle
  • Base64: ~1ms Overhead (vernachlässigbar)
## P0a: ES Module Execution — NameTooLong Bug + Dependency Resolution **Priority:** CRITICAL **Betrifft:** vuejs.org, solidjs.com, qwik.dev, angular.io, web.dev — alle VitePress/Vite-Sites **Impact:** Framework-Seiten laden nur statisches HTML. App bootstrapped nie. ### Problembeschreibung `executeModule()` in `execution-realm.ts` lädt Module via `data:text/javascript,${encodeURIComponent(code)}`. Zwei fundamentale Probleme: **Problem 1: NameTooLong** Bun's `import()` wirft `NameTooLong` für Vite-Bundles >~500KB (ein typischer VuePress/Vite-Bundle). `encodeURIComponent()` erzeugt extrem lange data:-URLs → Bun's URL-Parser bricht ab. **Problem 2: Relative Import Resolution** Module mit `import { D } from "./chunks/runtime-core.esm-bundler.js"` schlagen fehl, weil `data:` URLs keinen Base-Path haben. Relative Imports werden nicht aufgelöst. ### Aktuelles Verhalten ```typescript // execution-realm.ts:257-259 const encoded = encodeURIComponent(sandboxedCode); const moduleUrl = `data:text/javascript,${encoded}`; const mod = await import(moduleUrl); // → NameTooLong für große Bundles // → Relative Imports (./chunks/...) schlagen fehl ``` ### Lösungsansätze #### Option A: File-Write statt data: URL (EMPFEHLUNG) ```typescript const tmpFile = `${tmpDir}/${hash}.mjs`; await Bun.write(tmpFile, sandboxedCode); const mod = await import(tmpFile); await Bun.spawn(["rm", tmpFile]); // cleanup ``` **Vorteile:** - Kein NameTooLong (File-Pfade sind kurz) - Relative Imports funktionieren (File kann Importe auflösen) - Base-Path ist der File-Pfad → Module können Sub-Module finden **Nachteile:** - File-System-Ärger (tmp-Files) - Nicht-Module-Importe müssen gelöst werden (file: statt http: base) #### Option B: Base64-Encoding ```typescript const b64 = Buffer.from(sandboxedCode).toString('base64'); const mod = await import(`data:text/javascript;base64,${b64}`); ``` **Vorteile:** Kürzer, kein NameTooLong mehr **Nachteile:** Relative Imports funktionieren immer noch nicht #### Option C: Bun Plugin für Module Resolution ```typescript Bun.plugin({ name: 'thb-module-loader', setup(build) { build.onResolve({ filter: /^thb:/ }, (args) => { return { path: args.path, namespace: 'thb' }; }); build.onLoad({ filter: /.*/, namespace: 'thb' }, async (args) => { // Custom module loading logic }); } }); ``` ### Entscheidung **Option A + B kombiniert:** - Für Bundles <1MB: Base64 (schneller, kein FS-I/O) - Für Bundles >1MB: File-Write + import(filePath) ### Akzeptanzkriterien - [ ] VitePress-Seite (vuejs.org) lädt ohne NameTooLong Error - [ ] Module mit import/export Syntax werden ausgeführt - [ ] Relative Imports (./chunks/...) werden resolved - [ ] Module-Exports sind in _win sichtbar (Export-Merging) - [ ] Unit-Test: 1MB+ Module lädt via File-Write - [ ] Unit-Test: Module mit Sub-Import resolved korrekt ### Betroffene Dateien | Datei | Änderung | |-------|----------| | `src/js/execution-realm.ts` | executeModule — File-Write + Base64 Fallback | | `tests/unit/sprint16-es-module.test.ts` | 10+ Tests | ### Dependencies - Sprint 15 (Issue #71): Export-Merging ✅ - Keine weiteren ### Performance-Impact - File-Write + import(filePath): ~5ms Overhead pro Bundle - Base64: ~1ms Overhead (vernachlässigbar)
Artur closed this issue 2026-06-18 17:27:07 +00:00
Author
Owner

Sprint 16 implementiert — ES Module NameTooLong Fix

Was wurde gemacht:

  • executeModule(): Base64 statt encodeURIComponent() für data:-URLs (kürzer, kein NameTooLong)
  • File-Write-Fallback: Bei NameTooLong → /tmp/thb-modules/<hash>.mjs schreiben + import(filePath)
  • ensureModuleDir() Helper für /tmp/thb-modules/
  • Gleicher Fix in executeModuleStatic()

Verbleibend (separates Issue):

  • ./chunks/... relative Imports können nicht aufgelöst werden (data:/file: URLs haben keinen Base-Path)
  • Braucht URL-base Resolution: Module von CDN fetchen + in temp dir speichern + von dort importieren

Commit: eca1765

✅ **Sprint 16 implementiert** — ES Module NameTooLong Fix **Was wurde gemacht:** - `executeModule()`: Base64 statt `encodeURIComponent()` für data:-URLs (kürzer, kein NameTooLong) - File-Write-Fallback: Bei NameTooLong → `/tmp/thb-modules/<hash>.mjs` schreiben + `import(filePath)` - `ensureModuleDir()` Helper für `/tmp/thb-modules/` - Gleicher Fix in `executeModuleStatic()` **Verbleibend (separates Issue):** - `./chunks/...` relative Imports können nicht aufgelöst werden (data:/file: URLs haben keinen Base-Path) - Braucht URL-base Resolution: Module von CDN fetchen + in temp dir speichern + von dort importieren **Commit:** `eca1765`
Author
Owner

ES Module Dependency Resolution implementiert!

Neue Datei: src/js/module-resolver.ts

Ansatz: Bun Plugin der import()-Aufrufe für ES Module interceptet:

  1. onResolve (thb-module: Namespace + ./relative/path):

    • Relative Pfade werden gegen die URL des importierenden Moduls aufgelöst
    • ./chunks/foo.jshttps://vuejs.org/assets/chunks/foo.js
  2. onLoad (fetch vom CDN + Sandbox-Blockers):

    • fetch(url) → Security Check → Content-Type Prüfung
    • Prepended: var Bun = void 0; var process = void 0; ...
    • Module Cache + In-Flight Dedup
  3. Fallback-Strategie (3-stufig):

    • Primary: URL-basiert via Bun Plugin → Dependency Resolution
    • Secondary: Base64 data: URL (kleine Module, inline)
    • Tertiary: File-Write + import(filePath) (NameTooLong-Fallback)

Ablauf:

  1. script-loader.ts:executeRaw(type="module")realm.executeModule(code, url)
  2. executeModule(url)loadModuleFromUrl(url)import("thb-module:https://...")
  3. Bun Plugin: onResolve → onLoad → fetch vom CDN → Sandbox → return Contents
  4. Sub-Module mit import("./chunks/foo.js") → onResolve resolved absolut → onLoad lädt

Commit: 0204d41

✅ **ES Module Dependency Resolution implementiert!** **Neue Datei:** `src/js/module-resolver.ts` **Ansatz:** Bun Plugin der `import()`-Aufrufe für ES Module interceptet: 1. **onResolve** (`thb-module:` Namespace + `./relative/path`): - Relative Pfade werden gegen die URL des importierenden Moduls aufgelöst - `./chunks/foo.js` → `https://vuejs.org/assets/chunks/foo.js` 2. **onLoad** (fetch vom CDN + Sandbox-Blockers): - `fetch(url)` → Security Check → Content-Type Prüfung - Prepended: `var Bun = void 0; var process = void 0; ...` - Module Cache + In-Flight Dedup 3. **Fallback-Strategie** (3-stufig): - Primary: URL-basiert via Bun Plugin → Dependency Resolution ✅ - Secondary: Base64 data: URL (kleine Module, inline) - Tertiary: File-Write + import(filePath) (NameTooLong-Fallback) **Ablauf:** 1. `script-loader.ts:executeRaw(type="module")` → `realm.executeModule(code, url)` 2. `executeModule(url)` → `loadModuleFromUrl(url)` → `import("thb-module:https://...")` 3. Bun Plugin: onResolve → onLoad → fetch vom CDN → Sandbox → return Contents 4. Sub-Module mit `import("./chunks/foo.js")` → onResolve resolved absolut → onLoad lädt **Commit:** `0204d41`
Author
Owner

Finale Implementierung: Source-Level Import Rewriting

Bun.plugin() interceptiert KEINE runtime import()-Aufrufe auf Bun 1.3.14.
→ Alternative: Source-Code-Rewriting vor dem Base64-Encoding.

Was jetzt passiert:

  1. executeModule(code, url) mit HTTP(S)-URL → _loadUrlModule(url)
  2. Fetch original source vom CDN
  3. rewriteRelativeImports(): import { X } from "./chunks/foo.js"import { X } from "https://cdn/assets/chunks/foo.js"
  4. Blockers prependen: var Bun = void 0; var process = void 0; ...
  5. Base64-encoden + data:text/javascript;base64,... — keine Imports im Code mehr
  6. Sub-Module (die chunks) werden von Bun nativ von CDN geladen → haben meist keine weiteren relativen imports

Module loading hierarchy:

  • Primary: URL-basiert mit Import-Rewriting (fetch + rewrite + base64)
  • Secondary: Inline Base64 (keine URL bekannt)
  • Tertiary: Temp-File (NameTooLong-Fallback)

Commit: 397e769

✅ **Finale Implementierung: Source-Level Import Rewriting** `Bun.plugin()` interceptiert KEINE runtime `import()`-Aufrufe auf Bun 1.3.14. → Alternative: Source-Code-Rewriting vor dem Base64-Encoding. **Was jetzt passiert:** 1. `executeModule(code, url)` mit HTTP(S)-URL → `_loadUrlModule(url)` 2. Fetch original source vom CDN 3. `rewriteRelativeImports()`: `import { X } from "./chunks/foo.js"` → `import { X } from "https://cdn/assets/chunks/foo.js"` 4. Blockers prependen: `var Bun = void 0; var process = void 0; ...` 5. Base64-encoden + `data:text/javascript;base64,...` — keine Imports im Code mehr 6. Sub-Module (die chunks) werden von Bun nativ von CDN geladen → haben meist keine weiteren relativen imports **Module loading hierarchy:** - Primary: URL-basiert mit Import-Rewriting ✅ (fetch + rewrite + base64) - Secondary: Inline Base64 (keine URL bekannt) - Tertiary: Temp-File (NameTooLong-Fallback) **Commit:** `397e769`
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
glow-all/true-headless-browser#74
No description provided.