feat(cli-build): preconnect and gate modulepreload of the CDN sanity module#1402
Open
jordanl17 wants to merge 1 commit into
Open
feat(cli-build): preconnect and gate modulepreload of the CDN sanity module#1402jordanl17 wants to merge 1 commit into
jordanl17 wants to merge 1 commit into
Conversation
Contributor
📦 Bundle Stats —
|
| Metric | Value | vs main (e29d4bb) |
|---|---|---|
| Internal (raw) | 2.7 KB | - |
| Internal (gzip) | 1.0 KB | - |
| Bundled (raw) | 11.16 MB | - |
| Bundled (gzip) | 2.10 MB | - |
| Import time | 896ms | +11ms, +1.2% |
bin:sanity
| Metric | Value | vs main (e29d4bb) |
|---|---|---|
| Internal (raw) | 782 B | - |
| Internal (gzip) | 423 B | - |
| Bundled (raw) | 9.87 MB | - |
| Bundled (gzip) | 1.78 MB | - |
| Import time | 2.27s | -61ms, -2.6% |
🗺️ View treemap · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
📦 Bundle Stats — @sanity/cli-core
Compared against main (e29d4bbd)
| Metric | Value | vs main (e29d4bb) |
|---|---|---|
| Internal (raw) | 106.7 KB | - |
| Internal (gzip) | 26.7 KB | - |
| Bundled (raw) | 21.72 MB | - |
| Bundled (gzip) | 3.46 MB | - |
| Import time | 782ms | +2ms, +0.2% |
🗺️ View treemap · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
📦 Bundle Stats — create-sanity
Compared against main (e29d4bbd)
| Metric | Value | vs main (e29d4bb) |
|---|---|---|
| Internal (raw) | 908 B | - |
| Internal (gzip) | 483 B | - |
| Bundled (raw) | 931 B | - |
| Bundled (gzip) | 491 B | - |
| Import time | ❌ ChildProcess denied: node | - |
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
Contributor
Coverage Delta
Comparing 1 changed file against main @ Overall Coverage
|
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.
Description
Re-introduces the
preconnect+modulepreloadresource hints for the CDNsanitymodule in the auto-update studio HTML, originally added in #1276 and reverted in #1400. This time the risky hint is gated so it cannot break Safari.#1276 was reverted because it blanked auto-update studios in Safari. The studio loads the
sanitymodule fromsanity-cdn.com, which responds with a cross-origin redirect to the bucket that actually holds the bytes (modules.sanity-cdn.com). That redirect is the auto-update mechanism: it resolves a version range to a specific pinned build. When amodulepreloadfollows a cross-origin redirect, WebKit (Safari) forces the request into credentialed CORS mode even though the hint iscrossorigin="anonymous"- a WebKit bug. The bytes are served straight from storage withAccess-Control-Allow-Origin: *and no credentials header, so the credentialed check fails, the preload errors, and the studio renders blank. Chromium and Firefox follow the same redirect correctly, which is why #1276 passed review and only Safari broke.This PR re-lands the hints with the unsafe one gated behind a browser allowlist (default-deny):
preconnectonly opens a connection; it never follows the redirect or downloads the module, so it is safe everywhere and runs unconditionally.modulepreloadis emitted only for engines confirmed to handle the cross-origin redirect (Chromium or Gecko, with all iOS devices excluded). Any other or unrecognised engine falls back to the normal import-map load - a missed download, never a blank studio. A denylist was rejected on purpose: a missed Safari user-agent would fail dangerous, whereas default-deny fails safe.Why this is not fixed in the server layer instead: the proper fix has to change what the module origin serves, and that is infrastructure work owned outside the CLI, not a code change here. The module bytes come from a storage/CDN bucket that our API gateway never sees, so we cannot patch the response headers there. A real fix is one of: serve the module bytes from the same origin as the redirect (so there is no cross-origin hop and the bug never triggers), or front the bucket with an edge that returns a specific
Access-Control-Allow-OriginplusAccess-Control-Allow-Credentials: true(the storage bucket alone cannot send either). Both are larger, riskier changes than this CLI gate, and they extend the optimization to Safari rather than block it. This PR recovers the win now for the browsers that can already use it, safely; the infra fix can follow.What to review
packages/@sanity/cli-build/src/actions/build/renderDocumentWorker/addTimestampImportMapScriptToHtml.ts- the injected runtime script. Focus on the gating predicateisKnownSafeEngine(!/\b(iPad|iPhone|iPod)\b/.test(ua) && /Chrom(e|ium)|Firefox/.test(ua)):preconnectruns unconditionally,modulepreloadonly when it passes.modulepreloadhref reusesreplaceTimestamp(...)so it matches the import-map entry exactly; a mismatched timestamp would download the largest chunk twice. Both hints usecrossorigin="anonymous"so the warmed connection is reused for the module fetch.Testing
Unit tests in
__tests__/addTimestampImportMapScriptToHtml.test.tsrun the injected script in JSDOM with a stubbednavigator.userAgent:preconnectis emitted withcrossorigin="anonymous"across every engine (desktop Chrome, desktop Safari, iOS Safari, iOS webview with aChrometoken, unrecognised engine).modulepreloadis emitted only for the allowlisted engine, with a timestamp equal to the import-map entry (guards against the double download).modulepreloadis withheld for desktop Safari, iOS Safari, an iOS user-agent carrying aChrometoken (proving the iOS guard is load-bearing), and an unrecognised engine, while the safepreconnectand the import-map and stylesheet rewrites still happen.sanityresolves to a non-CDN host, or when no import is asanity-cdnhost.Note
Medium Risk
Changes production HTML for auto-update studios and browser-specific loading behavior; gating is default-deny so WebKit should not regress, but UA heuristics could misclassify edge clients (missed preload only).
Overview
Re-lands CDN resource hints in the auto-update studio HTML injector (
addTimestampImportMapScriptToHtml.ts), after they were removed in #1400 becausemodulepreloadblanked WebKit when following the CDN’s cross-origin redirect.For import maps that resolve to
sanity-cdnhosts, the runtime script now always injectslink rel="preconnect"(withcrossorigin="anonymous") to warm the CDN connection. It injectslink rel="modulepreload"for thesanityentry only whennavigator.userAgentpasses a default-deny allowlist: not an iOS device (iPad|iPhone|iPod) and Chromium or Firefox. Safari, iOS (including Chrome-branded UAs), and unknown engines still get the import map and preconnect but no modulepreload, avoiding the WebKit CORS/redirect bug at the cost of a possible extra fetch. Hints are skipped when nothing points at a sanity-cdn host; the preload URL reuses the same timestamp rewrite as the import map to avoid double-downloading the main chunk.Tests exercise the injected script in JSDOM with stubbed
userAgent, covering preconnect everywhere, preload only on allowlisted UAs, graceful fallback when preload is withheld, and no hints for non-CDN maps. AGENTS.md documents automatic changesets; a minor changeset is included for@sanity/cli-build.Reviewed by Cursor Bugbot for commit bc94fe3. Bugbot is set up for automated code reviews on this repo. Configure here.