Hooked on Performance
01/ 11

Hooked on
Performance

Taming LiveView's JavaScript hooks at scale

02/ 11

A fast response, a busy browser

Page startup sequence
Browser busy with the bundle
ServerResponseHTML arrives quickly Server-rendered HTML can already be visible
BrowserDownloadThe application bundle __PKG__app.js
BrowserParseAll imported JavaScript
BrowserExecuteApp code and its imports
LiveViewConnectSocket joins, hooks mount The live connection and hook behavior wait until here
Jump has 1.5M+ lines of Elixir, 150K+ lines of JavaScript/TypeScript
03/ 11

The eager hook registry

Every import lands in app.js
__PKG__app.js DatePicker EditorHook ReactThing
the shared mega-bundle
04/ 11

lazy-hook

__PKG__app.js DatePickerReactThing
__CHUNK__hook chunk EditorHook
Loads when LiveView mounts the placeholder hook
05/ 11

Setup

1Installnpm install @jump-app/lazy-hook
2Splitesbuild: --format=esm --splitting __PKG__app.js __CHUNK__hook chunk
3Load<script type="module" src={~p"/assets/app.js"}>
Requires ViewHook subclasses LiveView ^1.2.11
06/ 11

Before and after

Before: hook code inside app.js
After: separate hook chunks
Chrome DevTools performance trace before lazy-hook: one long app.js download bar dominates the network track.
Chrome DevTools performance trace after lazy-hook: a shorter app.js download followed by many separate hook chunk requests.
Production Jump application with network and CPU throttling enabled Different time scales: Before axis ≈ 56 s wide · After axis ≈ 16 s wide
07/ 11

Loading across a lifecycle

1

Hook mounts and begins importing

2

User navigates away; the hook is destroyed

3

Import resolves

hook element
mounted × initialize widget?
import()
pending
Handling it inside the hook 4 × isDestroyed guards every callback async module-level import cache
08/ 11

The placeholder handoff

With lazy-hook, the whole change
The hook implementation
stays as it is
LiveViewRegisters and mounts the placeholder hookhooks: { EditorHook }
sync
mount
Placeholder hook · ViewHookStarts the importimport("./hooks/editor")
module
arrives
Real hookReplaces the placeholder hook in the registrymounted()
Destroyed while loadingskip initialization
Updates while loadingone pending updated()
09/ 11

Choosing hooks

lazyHook(() => import(...))Good candidates import ... from ...Usually keep eager
Large dependencies on occasional pagesSmall hooks used throughout the app
Editors, charts, specialized pickersHooks requiring immediate setup
The first mount waits for a chunk request.
10/ 11

A cleaner integration

Type shim removed
Thank you, Steffen and the LiveView team
phoenixframework/phoenix_live_view#4324 · types re-exposed in LiveView 1.2.9
11/ 11

Remember lazy-hook

@jump-app/lazy-hook
github.com/Jump-App/lazy-hook
Scan for the repository Thank you · Questions