fix(core/bundler): inline fonts and images so a lone bundle renders - #3590
Draft
miguel-heygen wants to merge 1 commit into
Draft
fix(core/bundler): inline fonts and images so a lone bundle renders#3590miguel-heygen wants to merge 1 commit into
miguel-heygen wants to merge 1 commit into
Conversation
`bundleToSingleHtml` documented itself as producing "a single self-contained HTML file", but `INLINE_MIME` covered only `.svg`, `.json`, `.txt`, `.cube` and `.xml`. Every font and raster image stayed a live project-relative reference. That is invisible to every consumer in this repo, because each one serves the bundled string from a server rooted at the project directory, so the relative paths resolve. It breaks the moment the bundle is stored on its own, with no sibling asset directory: the font 404s and the page silently reflows in a fallback face, which is worse than a visible failure. Widen the inline set to fonts (woff2/woff/ttf/otf) and raster images (png/jpg/jpeg/gif/webp/avif), behind a 2 MiB per-asset cap. The cap is measured against this repo's own assets rather than guessed: the largest of 164 tracked `.woff2` files is 105 KB, and the largest of 284 tracked raster images is 2.00 MB, so everything in-tree inlines while a video-sized file cannot. Oversized assets keep their relative URL and warn, reusing the existing "may not be self-contained" wording. Audio and video stay external on purpose: they are large, streamed rather than laid out, and their absence is obvious rather than silent. Scripts already had a better path (`script[src]` is folded in as source), so they are deliberately not added to the MIME table. The five rebasing tests that asserted a relative path survived now assert the data URL's decoded content instead. That is a stronger check: resolving from the wrong base directory finds no file, so nothing inlines and the assertion fails.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The gap
bundleToSingleHtmldocuments itself as producing "a single self-contained HTML file", andBundleOptions.runtime: "inline"is described as right for "any ship a single .html file use case".INLINE_MIMEcovered only.svg,.json,.txt,.cubeand.xml, so every font and raster image stayed a live project-relative reference.This is invisible to every consumer in this repo, because each one serves the bundled string from an HTTP server rooted at the project directory (
serveStaticProjectHtml, the producer file server), so the relative paths resolve. It breaks as soon as the bundle is stored on its own with no sibling asset directory. A missing image is obvious; a missing font is not, because the page silently reflows into a fallback face and still looks plausible.Measured, with the real bundler and real binary assets
Each bundle loaded alone in an empty directory, which is what a single-object consumer actually has:
"Brand"font usable<img>loadedicon.png,Brand.woff2,gsap.min.js)falsefalsegsap.min.js)truetrueThe change
Widen the inline set to fonts (
woff2/woff/ttf/otf) and raster images (png/jpg/jpeg/gif/webp/avif), behind a 2 MiB per-asset cap.The cap is measured, not guessed. Against this repo's own tracked assets: the largest of 164
.woff2files is 105 KB (p90 75 KB), and the largest of 284 raster images is 2.00 MB (p90 437 KB). So every font and effectively every image in-tree inlines, while a video-sized file cannot. Base64 costs ~33%, so an unbounded rule would let one careless asset produce a bundle nothing should be asked to parse. Oversized assets keep their relative URL and warn, reusing the existing "may not be self-contained" wording.Deliberately not included:
script[src]is folded in as source by the existing "Inline local JS" pass. Adding.jsto the MIME table would only catchtype="module"scripts, which are skipped on purpose because adata:URL changes their import base URL.Known limitation
A
<script src>written from inside adocument.writestring is invisible to any static rewriter, so a GSAP tag injected that way still 404s. That is the one remaining failed request in the table above. Handling it would mean executing author JS at bundle time; naming it beats pretending it is covered.Tests
The new fixture is the point: the previous asset tests could not contain this bug, because they asserted that a relative path survived. Added a fixture with a font, four image formats, a
srcset, aposterand a script, asserting no reference intoassets/survives.The five rebasing tests that asserted a surviving relative path now assert the data URL's decoded content. That is a stronger check: resolving from the wrong base directory finds no file at all, so nothing inlines and the assertion fails.
Non-vacuousness proven by reverting the implementation: the new test fails on
expected … to contain 'data:font/woff2;base64,Zm9udC1ieXRlcw…'.Verification
packages/corefull suite: 2589 passed (125 files), green onmainbeforehand as wellbundleWithLocalizedFonts,validate,inspect,layout,registryBlocks): 37 passedtsc --noEmitclean,oxlint/oxfmt/fallowcleanWorth a reviewer's opinion
publishalready solves self-containment a different way — it zips the whole project tree, assets included. If the intended contract is that a composition always travels with its directory, then a consumer storing one key is the thing to fix, not the bundler. This change is the right one only if a bundle must be able to stand alone, which is what the function's own name and docstring already promise.