build(desktop): the renderer ships as ES modules, and its bundle shrinks
What
The desktop renderer's entry now builds as ES modules (format: "esm", splitting: true) instead of one IIFE, and the three documents that load it (index.html, voice.html, call.html) load it with <script type="module">. Features and lazy loading are unchanged: the whiteboard, file tree, plan editor, and session recorder bundles stay IIFEs, loaded as before.
Bytes loaded up front (gzip level 9): 764,016 ā 757,948, which is 6,068 smaller.
| before (IIFE) | after (ESM) | |
|---|---|---|
renderer.js | 764,016 | 596,817 |
chunk-Z2BTE7WG.js (static import) | n/a | 161,131 |
| up front | 764,016 | 757,948 |
The saving comes from dropping the IIFE wrapper and from freer minification of module scope. esbuild also emits two tiny chunks for dynamic import()s that were already in a dependency's code (highlighted-body-*.js, 649 B, and mermaid-*.js, 129 B). The IIFE build inlined those behind a Promise. Now they're fetched the first time that code runs. That's still asynchronous, as before, so nothing new is lazy-loaded on purpose.
Details
- Chunk names: esbuild's default
[name]-[hash], a content hash. Every shared chunk is namedchunk, so a stable[name]would collide. The windows readfile://, so caching isn't a factor.dist/is wiped on every build, so stale chunks can't pile up. The entry keeps its name (renderer.js) through the object form ofentryPoints. - CSP:
script-src 'self'already admits the module entry and its sibling chunks; no policy changed. - Loading: windows call
loadFilewithsandbox,contextIsolation, andwebSecurityon. Electron'sfile://scheme serves module scripts and their relative imports, and no Electron fuse in the build turns that off. On Linux I loaded the builtindex.htmlin Electron 43.3.0 with those web preferences, from the plaindist/and again from inside anapp.asar. In both runsindex.html,styles.css,renderer.js, andchunk-*.jsloaded with status 200. The renderer code ran and stopped where it reads the preload bridge, which that bare harness doesn't provide. - Deferred execution: the script tag was already the last element of
<body>, and nothing in the renderer readsdocument.currentScript, waits onDOMContentLoaded, or checksreadyState. Module scripts still run beforeDOMContentLoadedandload, so main'sdid-finish-loadordering doesn't change. - Session replay's path scrubbing (
session-replay.ts) matches the directory the page was loaded from. The chunks sit besiderenderer.jsin that directory, so their stack frames are covered by the same rule.
Bundle budget
scripts/build.mjsreads the renderer build's metafile and measures a bundle as itself plus every chunk it reaches through static imports, at any depth, each gzipped on its own. Chunks reached only by a dynamicimport()are left out. Bundles the metafile doesn't describe (the IIFEs) are still one file.- The build now fails if any of the three documents loads
renderer.jswithouttype="module". A classic script would hit a syntax error on the entry's firstimportand leave the window blank. - Baseline change, a deliberate build change, not drift:
renderer/renderer.jsgoes from 762,061 to 757,948. This lowers the baseline: the new up-front figure is below both the old recorded baseline and whatmainmeasures today (764,016).LUKE_UPDATE_BUNDLE_BUDGET=1rewrites every row, so I kept only the renderer's line. The other bundles have drifted since their baselines were recorded, and this PR doesn't touch their baselines. - Checked both ways: with the renderer's baseline set to 600,000 the build fails at 757,948, so the chunk is counted (the entry alone, 596,817, would have passed). With
call.htmlput back to a classic script, the build fails on the new check.
Verification
pnpm check:affected: exit 0./scripts/check.sh: exit 0- CI could not verify this. It builds nothing for the Mac and runs no window. This change affects how every window loads its script, so a
./scripts/verify.shrun on a Mac is still owed: open the panel, the voice window, and the call window, and confirm each one draws. The owner chose to merge on green and fix anything it turns up afterwards; until that run happens, the release rehearsal on a tag is the first Mac gate this change meets.
š¤ Generated with Claude Code
Summary by cubic
The desktop renderer now builds and loads as ES modules instead of one IIFE, shrinking the gzipped bytes loaded up front from 764,016 to 757,948. The index.html, voice.html, and call.html documents load the entry with <script type="module">; the whiteboard, file tree, plan editor, and session recorder bundles are unchanged.
Build and budget
- The renderer build now emits a module entry plus content-hashed chunks, which the windows load via relative imports. No CSP or Electron fuse changes were needed.
- The bundle budget now measures a bundle as itself plus every chunk it reaches through static imports, each gzipped on its own; dynamically imported chunks are excluded.
- The renderer baseline drops to 757,948, and the build fails if any of the three documents loads
renderer.jswithouttype="module", since a classic script would hit a syntax error on the entry's firstimport.