Skip to content

bundleToSingleHtml throws "RangeError: Invalid string length" when one image is referenced many times #6133

Closed

Description

@cauyxy

Describe the bug

bundleToSingleHtml inlines every relative image/font under 2 MB as a base64 data URL, once per reference. The 2 MB limit is per asset, so nothing bounds the bundle as a whole: a 1.4 MB image referenced 400 times writes ~750M characters, past V8's max string length (~2^29). Under Node, check, snapshot and render then fail with RangeError: Invalid string length. (Bun has a higher limit, so this only shows on Node.)

We hit this in production on lessons that repeat a counting unit (e.g. 63 copies of a 1.1 MB chair image).

Minimal reproduction

npx hyperframes init repro --non-interactive --example blank && cd repro
node -e "require('fs').writeFileSync('unit.png', Buffer.alloc(1_400_000))"
# add 400 × <img src="unit.png"> inside the composition root of index.html

Steps to reproduce

  1. Set up the reproduction above
  2. Run npx hyperframes check (Node 22/24)
  3. See RangeError: Invalid string length

Expected behavior

The project bundles; repeated assets do not blow up the bundle size.

Actual behavior

RangeError: Invalid string length from htmlBundler.ts. 250 references (≈468M chars) still pass; 400 throw.

Environment

Reproduced on Node v24.16.0 (darwin arm64) against main @ 2ed882c; also seen in production on Linux x64 with Node 24.

Additional context

main already caches the file read (createAssetInliner), but still writes the full data URL for each reference.

Activity

  1. cauyxy commented on Oct 23, 2026

    @cauyxy
    ContributorAuthor

    #6134 only stops the crash: it caps the total inlined size and leaves bundles under the cap unchanged. The underlying waste is still there: an asset referenced N times is written N times.

    Would you be open to deduplicating inlined assets by default? HTML has no way to share one payload across <img src>, srcset, CSS url() and @font-face, so the options I see are:

    1. Runtime rehydration: emit each asset once (e.g. in a <script type="application/octet-stream">), turn it into a blob URL at load, and rewrite every reference. This covers every reference type. Trade-offs:
      • images only get a src after the script runs, so render/seek would need to wait for rehydration before the first frame
      • the bundle no longer works without JS
      • srcset, <picture>, inline styles, stylesheets and fonts each need their own rewrite
    2. CSS custom properties: one --asset-N: url(data:…) per asset. No JS, but it only covers background-image, not <img> or fonts.

    If dedup by default sounds right to you, I'm happy to implement option 1 behind whatever readiness hook render already uses. Otherwise the cap in #6134 is enough for our case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions