Skip to content

perf: hook chain fast path for map[string]interface{} payloads #1188

Description

@zeroedin

Description

When hooks chain (hook 1's result feeds hook 2), the payload for hook 2 is map[string]interface{} — not a typed struct like HookRenderedPayload. Without a dedicated case, this falls through to the default: branch in CallHook's type switch, which JSON-serializes the entire map including ~800KB of HTML.

The RHDS site has two onPageRendered hooks (01-transforms.js and 02-lit-ssr.js). Hook 1 goes through the fast path and returns map[string]interface{}{"html": "..."}. Hook 2 receives that map, hits the default: case, and re-serializes 800KB of HTML through the full JSON round-trip — undoing the fast path's work.

Same issue affects the Node IPC path: EncodeMessage only has split-body cases for typed structs, not for map[string]interface{} chained results.

Fix

QuickJS path

Add case map[string]interface{}: to the fast path switch in CallHook, guarded by key presence:

case map[string]interface{}:
    if _, hasHTML := v["html"]; hasHTML {
        return r.callHookMapPayload(name, v)
    }
    if _, hasContent := v["content"]; hasContent {
        return r.callHookMapPayload(name, v)
    }
    // fall through to default JSON path for non-page maps

The guard is critical — map[string]interface{} is also used for onDataFetched, onBeforeValidation, onAfterValidation payloads. Only maps with html or content keys (the signature of a chained page hook result) should use the fast path.

callHookMapPayload builds a JS object with string values as native strings and non-string values via JSON. Selects the extractor based on which content field is present.

Node IPC path

Add case map[string]interface{}: to EncodeMessage's hook type switch with the same html/content key guard. Strip the large field and send raw bytes.

Spec test needs

  • Two QuickJS onPageRendered hooks chain: second hook receives html from first hook's result
  • Two hooks chain, second modifies html: pipeline applies second hook's modification
  • Non-page map payload (onDataFetched): NOT caught by the map fast path, full JSON round-trip preserved
  • Node IPC: chained map with html key uses split-body framing
  • Node IPC: chained map without html/content key uses standard JSON framing

Refs #1185, #1180

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions