Skip to content

🍕 vite: preload view-mode startup graph + on-page component chunks#255

Open
jjpaulino wants to merge 1 commit into
masterfrom
jordan/vite-modulepreload
Open

🍕 vite: preload view-mode startup graph + on-page component chunks#255
jjpaulino wants to merge 1 commit into
masterfrom
jordan/vite-modulepreload

Conversation

@jjpaulino

Copy link
Copy Markdown
Member

What does this PR do?

Makes the Vite view-mode pipeline emit the manifest data the server needs to preload the JavaScript that gates first paint of interactive UI — most visibly the global nav — instead of discovering it via a runtime import() waterfall.

TL;DR — for reviewers short on time

Review in this order (foundational → consumer-facing → mechanical):

  1. Manifest now keys dynamic entriesbuildManifest / isManifestChunk in lib/cmd/vite/scripts.js. This is the core change: components/<name>/client and layouts/<name>/client chunks are now discoverable in _manifest.json, not just the bootstrap/kiln entries.
  2. Startup-graph preloadgetViteModulePreloads in lib/cmd/vite/index.js now returns the bootstrap plus its static imports (globals-init, env-init, shared chunks).
  3. Per-component preload helpergetComponentPreloads in lib/cmd/vite/index.js (new export) returns each on-page component's client chunk + static imports, de-duped, for the server to merge into media.modulePreloads.
  4. Small shared helpers entryUrls / makeRebase (mechanical — extracted to keep the two resolvers under the complexity budget).
  5. Docs + tests (mechanical): CLAY-VITE.md §4d, index.test.js, scripts.test.js.

Risk: low. Additive — new manifest keys + a new export. resolveModuleScripts/existing callers are unchanged, and getComponentPreloads is guarded on the consumer side. No behavior change for Browserify sites.

If you only review one thing: isManifestChunk / buildManifest — confirm only entry + dynamic-entry chunks get keyed and shared/vendor chunks stay reachable via imports[].

Why are we doing this? Any context or related work?

On Vite sites the global nav presents noticeably slower than on Browserify sites. Root cause is a module load waterfall: the view bootstrap is deferred, and it mounts components by scanning the DOM and import()-ing each component's client.js at runtime. Those dynamic chunks are referenced by no tag in the server-rendered HTML, so the browser can't begin fetching global-nav/client.js (or its deps — auth, gtm, shared chunks) until the bootstrap has downloaded, parsed, executed, and called import(). Browserify, by contrast, ships that code in large eagerly-executed bundles, so the nav is wired up as soon as the bundle runs.

amphora-html already supports <link rel="modulepreload"> (and nymag/sites enables it), but today ClayCLI only ever preloads the bootstrap file itself — not its static imports, and not the per-page component chunks. This PR closes that gap on the ClayCLI/manifest side.

Stacked with the nymag/sites consumer PR, which merges getComponentPreloads(...) output into media.modulePreloads in resolve-media.js (guarded so older ClayCLI versions without the helper are a no-op). Cross-link added there.

Where should a reviewer start?

buildManifest in lib/cmd/vite/scripts.js — everything else builds on the enriched manifest.

Manual testing steps?
  1. clay compile --vite (or the split view/edit build) on a site and open .clay/public/js/_manifest.json.
  2. Confirm it now contains components/<name>/client keys (e.g. components/global-nav/client) each with a file + imports[], in addition to vite-bootstrap and the kiln entry.
  3. In a consuming app, confirm getComponentPreloads(['global-nav'], assetPath) returns the nav client chunk + its static imports, and getViteModulePreloads(assetPath) returns the bootstrap + globals-init/env-init/shared chunks.
  4. Render a page and verify <link rel="modulepreload"> tags appear in <head> for those URLs and the nav paints without waiting on a cold import() round-trip.
Screenshots

n/a (build/runtime manifest change).

Additional Context

  • Docs: CLAY-VITE.md §4d "Module preloading & the on-demand component waterfall".
  • Tests: lib/cmd/vite/index.test.js (getViteModulePreloads startup graph + full getComponentPreloads suite), lib/cmd/vite/scripts.test.js (dynamic entry keyed, shared chunk not keyed). Full lib/cmd/vite jest suite green; repo npm run lint clean.

Made with Cursor

The Vite view bootstrap mounts components by scanning the DOM and import()-ing
each component's client.js at runtime. Those dynamic chunks aren't referenced by
any tag in the server-rendered HTML, so the browser can't start fetching a
component's client (nav, header, …) or its deps (auth, gtm, shared chunks) until
the deferred bootstrap has downloaded, executed, and called import() — a load
waterfall that visibly delays JS-gated UI like the global nav.

- buildManifest now keys dynamic entries (components/<name>/client,
  layouts/<name>/client), not just the bootstrap/kiln entries, so the server can
  look up a page's component chunks.
- getViteModulePreloads now returns the bootstrap PLUS its static import chunks
  (globals-init, env-init, shared chunks) so the whole synchronous startup graph
  is preloaded during HTML parse.
- new getComponentPreloads(names, assetPath) returns each component's client
  chunk + static imports (de-duped) for the server to merge into
  media.modulePreloads.

Consumed by nymag/sites resolve-media.js (stacked PR). Docs: CLAY-VITE.md §4d.
Tests: lib/cmd/vite/index.test.js, lib/cmd/vite/scripts.test.js.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coveralls

Copy link
Copy Markdown

Coverage Status

coverage: 87.13% (+0.05%) from 87.084% — jordan/vite-modulepreload into master

@jjpaulino

Copy link
Copy Markdown
Member Author

Paired consumer PR (the sites side that merges getComponentPreloads(...) into media.modulePreloads in resolve-media.js, guarded so it's a no-op until this claycli bump): nymag/sites#13445.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants